Compare commits
4 commits
dev
...
kit/window
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aa7525da8 |
||
|
|
638a025ab4 | ||
|
|
31e92f1454 | ||
|
|
6aaaa4c807 |
8 changed files with 45 additions and 23 deletions
|
|
@ -5,7 +5,7 @@ import os from "node:os"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import { execSync } from "node:child_process"
|
import { execSync } from "node:child_process"
|
||||||
import { terminalAttr, type E2EWindow } from "../src/testing/terminal"
|
import { terminalAttr, type E2EWindow } from "../src/testing/terminal"
|
||||||
import { createSdk, modKey, resolveDirectory, serverUrl } from "./utils"
|
import { createSdk, modKey, resolveDirectory, serverUrl, workspaceKey } from "./utils"
|
||||||
import {
|
import {
|
||||||
dropdownMenuTriggerSelector,
|
dropdownMenuTriggerSelector,
|
||||||
dropdownMenuContentSelector,
|
dropdownMenuContentSelector,
|
||||||
|
|
@ -438,7 +438,7 @@ export async function resolveSlug(slug: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitDir(page: Page, directory: string) {
|
export async function waitDir(page: Page, directory: string) {
|
||||||
const target = await resolveDirectory(directory)
|
const target = workspaceKey(await resolveDirectory(directory))
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -446,7 +446,7 @@ export async function waitDir(page: Page, directory: string) {
|
||||||
const slug = slugFromUrl(page.url())
|
const slug = slugFromUrl(page.url())
|
||||||
if (!slug) return ""
|
if (!slug) return ""
|
||||||
return resolveSlug(slug)
|
return resolveSlug(slug)
|
||||||
.then((item) => item.directory)
|
.then((item) => workspaceKey(item.directory))
|
||||||
.catch(() => "")
|
.catch(() => "")
|
||||||
},
|
},
|
||||||
{ timeout: 45_000 },
|
{ timeout: 45_000 },
|
||||||
|
|
@ -456,7 +456,7 @@ export async function waitDir(page: Page, directory: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function waitSession(page: Page, input: { directory: string; sessionID?: string }) {
|
export async function waitSession(page: Page, input: { directory: string; sessionID?: string }) {
|
||||||
const target = await resolveDirectory(input.directory)
|
const target = workspaceKey(await resolveDirectory(input.directory))
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
|
|
@ -464,14 +464,14 @@ export async function waitSession(page: Page, input: { directory: string; sessio
|
||||||
const slug = slugFromUrl(page.url())
|
const slug = slugFromUrl(page.url())
|
||||||
if (!slug) return false
|
if (!slug) return false
|
||||||
const resolved = await resolveSlug(slug).catch(() => undefined)
|
const resolved = await resolveSlug(slug).catch(() => undefined)
|
||||||
if (!resolved || resolved.directory !== target) return false
|
if (!resolved || workspaceKey(resolved.directory) !== target) return false
|
||||||
if (input.sessionID && sessionIDFromUrl(page.url()) !== input.sessionID) return false
|
if (input.sessionID && sessionIDFromUrl(page.url()) !== input.sessionID) return false
|
||||||
|
|
||||||
const state = await probeSession(page)
|
const state = await probeSession(page)
|
||||||
if (input.sessionID && (!state || state.sessionID !== input.sessionID)) return false
|
if (input.sessionID && (!state || state.sessionID !== input.sessionID)) return false
|
||||||
if (state?.dir) {
|
if (state?.dir) {
|
||||||
const dir = await resolveDirectory(state.dir).catch(() => state.dir ?? "")
|
const dir = await resolveDirectory(state.dir).catch(() => state.dir ?? "")
|
||||||
if (dir !== target) return false
|
if (workspaceKey(dir) !== target) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return page
|
return page
|
||||||
|
|
@ -488,7 +488,7 @@ export async function waitSession(page: Page, input: { directory: string; sessio
|
||||||
|
|
||||||
export async function waitSessionSaved(directory: string, sessionID: string, timeout = 30_000) {
|
export async function waitSessionSaved(directory: string, sessionID: string, timeout = 30_000) {
|
||||||
const sdk = createSdk(directory)
|
const sdk = createSdk(directory)
|
||||||
const target = await resolveDirectory(directory)
|
const target = workspaceKey(await resolveDirectory(directory))
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
|
|
@ -498,7 +498,9 @@ export async function waitSessionSaved(directory: string, sessionID: string, tim
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
if (!data?.directory) return ""
|
if (!data?.directory) return ""
|
||||||
return resolveDirectory(data.directory).catch(() => data.directory)
|
return resolveDirectory(data.directory)
|
||||||
|
.then(workspaceKey)
|
||||||
|
.catch(() => workspaceKey(data.directory))
|
||||||
},
|
},
|
||||||
{ timeout },
|
{ timeout },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,14 @@ export async function resolveDirectory(directory: string) {
|
||||||
.then((x) => x.data?.directory ?? directory)
|
.then((x) => x.data?.directory ?? directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function workspaceKey(dir: string) {
|
||||||
|
const value = dir.replaceAll("\\", "/")
|
||||||
|
const drive = value.match(/^([A-Za-z]:)\/+$/)
|
||||||
|
if (drive) return `${drive[1]}/`
|
||||||
|
if (/^\/+$/i.test(value)) return "/"
|
||||||
|
return value.replace(/\/+$/, "")
|
||||||
|
}
|
||||||
|
|
||||||
export async function getWorktree() {
|
export async function getWorktree() {
|
||||||
const sdk = createSdk()
|
const sdk = createSdk()
|
||||||
const result = await sdk.path.get()
|
const result = await sdk.path.get()
|
||||||
|
|
@ -57,7 +65,8 @@ export function sessionPath(directory: string, sessionID?: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function workspacePersistKey(directory: string, key: string) {
|
export function workspacePersistKey(directory: string, key: string) {
|
||||||
const head = (directory.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
|
const dir = workspaceKey(directory)
|
||||||
const sum = checksum(directory) ?? "0"
|
const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||||
|
const sum = checksum(dir) ?? "0"
|
||||||
return `opencode.workspace.${head}.${sum}.dat:workspace:${key}`
|
return `opencode.workspace.${head}.${sum}.dat:workspace:${key}`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { useModels } from "@/context/models"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { modelEnabled, modelProbe } from "@/testing/model-selection"
|
import { modelEnabled, modelProbe } from "@/testing/model-selection"
|
||||||
import { Persist, persisted } from "@/utils/persist"
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
import { workspaceKey } from "@/utils/workspace"
|
||||||
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
||||||
import { useSDK } from "./sdk"
|
import { useSDK } from "./sdk"
|
||||||
import { useSync } from "./sync"
|
import { useSync } from "./sync"
|
||||||
|
|
@ -26,7 +27,7 @@ type Saved = {
|
||||||
const WORKSPACE_KEY = "__workspace__"
|
const WORKSPACE_KEY = "__workspace__"
|
||||||
const handoff = new Map<string, State>()
|
const handoff = new Map<string, State>()
|
||||||
|
|
||||||
const handoffKey = (dir: string, id: string) => `${dir}\n${id}`
|
const handoffKey = (dir: string, id: string) => `${workspaceKey(dir)}\n${id}`
|
||||||
|
|
||||||
const migrate = (value: unknown) => {
|
const migrate = (value: unknown) => {
|
||||||
if (!value || typeof value !== "object") return { session: {} }
|
if (!value || typeof value !== "object") return { session: {} }
|
||||||
|
|
@ -364,7 +365,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||||
const next = clone(snapshot())
|
const next = clone(snapshot())
|
||||||
if (!next) return
|
if (!next) return
|
||||||
|
|
||||||
if (dir === sdk.directory) {
|
if (workspaceKey(dir) === workspaceKey(sdk.directory)) {
|
||||||
setSaved("session", session, next)
|
setSaved("session", session, next)
|
||||||
setStore("draft", undefined)
|
setStore("draft", undefined)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
import { type Session } from "@opencode-ai/sdk/v2/client"
|
import { type Session } from "@opencode-ai/sdk/v2/client"
|
||||||
|
export { workspaceKey } from "@/utils/workspace"
|
||||||
|
import { workspaceKey } from "@/utils/workspace"
|
||||||
|
|
||||||
type SessionStore = {
|
type SessionStore = {
|
||||||
session?: Session[]
|
session?: Session[]
|
||||||
path: { directory: string }
|
path: { directory: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const workspaceKey = (directory: string) => {
|
|
||||||
const value = directory.replaceAll("\\", "/")
|
|
||||||
const drive = value.match(/^([A-Za-z]:)\/+$/)
|
|
||||||
if (drive) return `${drive[1]}/`
|
|
||||||
if (/^\/+$/i.test(value)) return "/"
|
|
||||||
return value.replace(/\/+$/, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortSessions(now: number) {
|
function sortSessions(now: number) {
|
||||||
const oneMinuteAgo = now - 60 * 1000
|
const oneMinuteAgo = now - 60 * 1000
|
||||||
return (a: Session, b: Session) => {
|
return (a: Session, b: Session) => {
|
||||||
|
|
|
||||||
|
|
@ -494,7 +494,7 @@ export default function Page() {
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => lastUserMessage()?.id,
|
() => [params.id, lastUserMessage()?.id] as const,
|
||||||
() => {
|
() => {
|
||||||
const msg = lastUserMessage()
|
const msg = lastUserMessage()
|
||||||
if (!msg) return
|
if (!msg) return
|
||||||
|
|
|
||||||
|
|
@ -112,4 +112,11 @@ describe("persist localStorage resilience", () => {
|
||||||
expect(result.endsWith(".dat")).toBeTrue()
|
expect(result.endsWith(".dat")).toBeTrue()
|
||||||
expect(/[:\\/]/.test(result)).toBeFalse()
|
expect(/[:\\/]/.test(result)).toBeFalse()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("workspace storage treats slash variants as the same workspace", () => {
|
||||||
|
const a = persistTesting.workspaceStorage("C:\\Users\\foo\\bar\\")
|
||||||
|
const b = persistTesting.workspaceStorage("C:/Users/foo/bar")
|
||||||
|
|
||||||
|
expect(a).toBe(b)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Platform, usePlatform } from "@/context/platform"
|
import { Platform, usePlatform } from "@/context/platform"
|
||||||
|
import { workspaceKey } from "@/utils/workspace"
|
||||||
import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage"
|
import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage"
|
||||||
import { checksum } from "@opencode-ai/util/encode"
|
import { checksum } from "@opencode-ai/util/encode"
|
||||||
import { createResource, type Accessor } from "solid-js"
|
import { createResource, type Accessor } from "solid-js"
|
||||||
|
|
@ -209,8 +210,9 @@ function normalize(defaults: unknown, raw: string, migrate?: (value: unknown) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
function workspaceStorage(dir: string) {
|
function workspaceStorage(dir: string) {
|
||||||
const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
|
const key = workspaceKey(dir)
|
||||||
const sum = checksum(dir) ?? "0"
|
const head = (key.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-")
|
||||||
|
const sum = checksum(key) ?? "0"
|
||||||
return `opencode.workspace.${head}.${sum}.dat`
|
return `opencode.workspace.${head}.${sum}.dat`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
7
packages/app/src/utils/workspace.ts
Normal file
7
packages/app/src/utils/workspace.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const workspaceKey = (dir: string) => {
|
||||||
|
const value = dir.replaceAll("\\", "/")
|
||||||
|
const drive = value.match(/^([A-Za-z]:)\/+$/)
|
||||||
|
if (drive) return `${drive[1]}/`
|
||||||
|
if (/^\/+$/i.test(value)) return "/"
|
||||||
|
return value.replace(/\/+$/, "")
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue