Make workspace IDs opaque, independent of the filesystem path (#1503)

* Make workspace IDs opaque, independent of the filesystem path

Workspace IDs were the resolved checkout/worktree path, so code could
treat an ID as a path: prefix matching, deriving directories from it,
falling back to a path when a lookup missed. IDs are now opaque -
compared only by exact equality and never used as a path. Anything
path-shaped resolves the workspace record first and reads its cwd.

New workspaces get a generated `wks_` ID; existing path-shaped IDs are
read from disk and never regenerated, so there is no migration and no
change to the wire or persisted schemas. Groundwork for running
multiple workspaces in a single directory.

* Keep attachment scope key stable and align refetch test

The attachment scope key keeps `workspace=` instead of `wsid=` so existing
persisted drafts are not orphaned; the rename was cosmetic. The SDK refetch
test no longer asserts a filter, matching refetch fetching one page and
selecting by id client-side.

* Fix live agent updates in directories without a registered workspace

The opaque-ID work removed buildProjectPlacementForCwd's directory
fallback, so an agent running in a folder with no registered workspace
(e.g. a fresh non-git dir) produced a null placement. forwardAgentUpdate
then threw "Workspace not found", the error was swallowed by its catch,
and no agent_update was emitted — live model/thinking switches and status
updates silently stopped. Caught by the live-preferences e2e suite.

The fallback builds a directory-scoped project placement keyed by the
path. That key is a project grouping key (non-git projects group by
path), not a workspace id, so it stays within the opaque-id rule.

* Always run server worktree archive, even without a resolved workspace

Archiving bailed out entirely when the workspace was not found in the
client store, so a race or stale state could make "archive" do nothing
server-side with only a console.warn. The server archive is keyed by
worktreePath, which is always available, so it now runs regardless; only
the optimistic client-side updates (keyed by workspace id) are gated on
the workspace being resolved.

* Expect a directory-scoped placement for unregistered agent dirs

This unit test asserted the no-placement behavior reverted in the live
agent-update fix, which had broken live model/thinking switching. Update
it to expect the directory-scoped placement now emitted for an agent in a
directory with no registered workspace.

* Fix opaque workspace routing in app E2E

* Preserve workspace IDs during partial bootstrap

* Fix archive flows for opaque workspace IDs

* Stop treating opaque workspace IDs as filesystem paths

Workspace IDs are opaque (wks_<hex>), but several call sites still passed
the id where a directory was expected, which broke those flows for opaque
IDs. The branch switcher sent the id as a cwd to branch/stash/checkout git
operations, and server archive/reconcile cleanup keyed git-watch and
subscription teardown by id, leaking that state.

Git and filesystem operations now take the workspace directory; the opaque
id is used only for identity and cache keys. Archive/reconcile cleanup
routes through a single teardownArchivedWorkspace helper that keeps the key
split explicit: runtime store by id, git watch and subscription by cwd.
Path-derived grouping keys are renamed to directory keys, and the helpers
are split into workspace-identity and workspace-directory so the id-vs-path
boundary is obvious.
This commit is contained in:
Mohamed Boudra
2026-06-14 22:03:27 +08:00
committed by GitHub
parent f352072dac
commit 21deb08673
105 changed files with 1530 additions and 1045 deletions
@@ -490,8 +490,29 @@ function createSessionForWorkspaceTests(
notifyAgentState: () => {},
}),
agentStorage: asAgentStorage({
list: async () => [],
get: async () => null,
list: async () => [
createPersistedWorkspaceRecord({
workspaceId: "ws-repo-running",
projectId: "proj-repo-running",
cwd: REPO_CWD,
kind: "directory",
displayName: "repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
}),
],
get: async (workspaceId: string) =>
workspaceId === "ws-repo-running"
? createPersistedWorkspaceRecord({
workspaceId: "ws-repo-running",
projectId: "proj-repo-running",
cwd: REPO_CWD,
kind: "directory",
displayName: "repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
})
: null,
upsert: async () => {},
}),
projectRegistry: {
@@ -506,8 +527,29 @@ function createSessionForWorkspaceTests(
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => [
createPersistedWorkspaceRecord({
workspaceId: "ws-repo-running",
projectId: "proj-repo-running",
cwd: REPO_CWD,
kind: "directory",
displayName: "repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
}),
],
get: async (workspaceId: string) =>
workspaceId === "ws-repo-running"
? createPersistedWorkspaceRecord({
workspaceId: "ws-repo-running",
projectId: "proj-repo-running",
cwd: REPO_CWD,
kind: "directory",
displayName: "repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
})
: null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -853,10 +895,10 @@ test("agent_update placement does not refresh git snapshots", async () => {
});
});
test("agent_update emits fallback placement when no workspace is registered", async () => {
test("agent_update emits a directory-scoped placement when no workspace is registered", async () => {
const emitted: SessionOutboundMessage[] = [];
const getSnapshot = vi.fn(async () => {
throw new Error("getSnapshot should not be called for fallback agent_update placement");
throw new Error("getSnapshot should not be called for unregistered agent_update placement");
});
const session = asTestSession(
createSessionForWorkspaceTests({
@@ -886,17 +928,15 @@ test("agent_update emits fallback placement when no workspace is registered", as
);
expect(getSnapshot).not.toHaveBeenCalled();
expect(emitted.find((message) => message.type === "agent_update")?.payload).toMatchObject({
kind: "upsert",
project: {
projectKey: UNREGISTERED_CWD,
projectName: "unregistered",
checkout: {
cwd: UNREGISTERED_CWD,
isGit: false,
},
},
});
// An agent in a directory with no registered workspace must still emit an
// upsert (live model/thinking switches depend on this). It gets a directory-
// scoped placement keyed by the path — a project key, not a workspace id.
const update = emitted.find((message) => message.type === "agent_update");
if (update?.type !== "agent_update" || update.payload.kind !== "upsert") {
throw new Error("expected an agent_update upsert");
}
expect(update.payload.agent.id).toBe("agent-1");
expect(update.payload.project?.checkout.isGit).toBe(false);
});
test("archive emits an authoritative agent_update upsert for subscribed clients", async () => {
@@ -1158,7 +1198,7 @@ test("workspace clear attention can clear multiple workspaces in one request", a
const emitted: SessionOutboundMessage[] = [];
const workspaces = [
createPersistedWorkspaceRecord({
workspaceId: "/tmp/repo-a",
workspaceId: "ws-repo-a",
projectId: "/tmp/repo-a",
cwd: "/tmp/repo-a",
kind: "directory",
@@ -1167,7 +1207,7 @@ test("workspace clear attention can clear multiple workspaces in one request", a
updatedAt: "2026-03-30T15:00:00.000Z",
}),
createPersistedWorkspaceRecord({
workspaceId: "/tmp/repo-b",
workspaceId: "ws-repo-b",
projectId: "/tmp/repo-b",
cwd: "/tmp/repo-b",
kind: "directory",
@@ -2570,8 +2610,29 @@ test("workspace update stream keeps persisted workspace visible after agents sto
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => [],
get: async () => null,
list: async () => [
createPersistedWorkspaceRecord({
workspaceId: "ws-repo-running",
projectId: "proj-repo-running",
cwd: REPO_CWD,
kind: "directory",
displayName: "repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
}),
],
get: async (workspaceId: string) =>
workspaceId === "ws-repo-running"
? createPersistedWorkspaceRecord({
workspaceId: "ws-repo-running",
projectId: "proj-repo-running",
cwd: REPO_CWD,
kind: "directory",
displayName: "repo",
createdAt: "2026-03-01T12:00:00.000Z",
updatedAt: "2026-03-01T12:00:00.000Z",
})
: null,
upsert: async () => {},
archive: async () => {},
remove: async () => {},
@@ -2619,7 +2680,7 @@ test("workspace update stream keeps persisted workspace visible after agents sto
session.buildWorkspaceDescriptorMap = async () =>
new Map([
[
REPO_CWD,
"ws-repo-running",
{
id: "ws-repo-running",
projectId: "proj-repo-running",
@@ -2638,7 +2699,7 @@ test("workspace update stream keeps persisted workspace visible after agents sto
session.buildWorkspaceDescriptorMap = async () =>
new Map([
[
REPO_CWD,
"ws-repo-running",
{
id: "ws-repo-running",
projectId: "proj-repo-running",
@@ -2736,8 +2797,8 @@ test("create paseo worktree request returns a registered workspace descriptor",
const workspaces = new Map();
const projects = new Map();
session.paseoHome = paseoHome;
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.list = async () => Array.from(workspaces.values());
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
@@ -2775,7 +2836,8 @@ test("create paseo worktree request returns a registered workspace descriptor",
name: "worktree-123",
status: "done",
});
expect(response?.payload.workspace?.id).toContain(path.join("worktree-123"));
expect(response?.payload.workspace?.id).toMatch(/^wks_[0-9a-f]{16}$/);
expect(response?.payload.workspace?.workspaceDirectory).toContain(path.join("worktree-123"));
expect(workspaces.has(response?.payload.workspace?.id ?? "")).toBe(true);
expect(projects.has(response?.payload.workspace?.projectId ?? "")).toBe(true);
});
@@ -2875,8 +2937,8 @@ test("open_project_request registers a workspace before any agent exists", async
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -2904,10 +2966,13 @@ test("open_project_request registers a workspace before any agent exists", async
requestId: "req-open",
});
expect(workspaces.get(REPO_CWD)).toBeTruthy();
const registeredWorkspace = Array.from(workspaces.values()).find(
(workspace) => workspace.cwd === REPO_CWD,
);
expect(registeredWorkspace).toBeTruthy();
const response = findByType(emitted, "open_project_response");
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.id).toBe(REPO_CWD);
expect(response?.payload.workspace?.id).toBe(registeredWorkspace?.workspaceId);
});
test("import_agent_request registers a workspace for a never-seen cwd", async () => {
@@ -2927,8 +2992,8 @@ test("import_agent_request registers a workspace for a never-seen cwd", async ()
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -2971,15 +3036,22 @@ test("import_agent_request registers a workspace for a never-seen cwd", async ()
pendingUpdatesByWorkspaceId: new Map(),
lastEmittedByWorkspaceId: new Map(),
};
session.buildWorkspaceDescriptorMap = async () =>
new Map([
session.buildWorkspaceDescriptorMap = async () => {
const workspace = Array.from(workspaces.values()).find(
(candidate) => candidate.cwd === importedCwd,
);
if (!workspace) {
return new Map();
}
return new Map([
[
importedCwd,
workspace.workspaceId,
{
id: importedCwd,
projectId: importedCwd,
id: workspace.workspaceId,
projectId: workspace.projectId,
projectDisplayName: "imported-project",
projectRootPath: importedCwd,
workspaceDirectory: importedCwd,
projectKind: "non_git",
workspaceKind: "directory",
name: "imported-project",
@@ -2988,6 +3060,7 @@ test("import_agent_request registers a workspace for a never-seen cwd", async ()
},
],
]);
};
await session.handleMessage({
type: "import_agent_request",
@@ -2997,12 +3070,17 @@ test("import_agent_request registers a workspace for a never-seen cwd", async ()
cwd: importedCwd,
});
expect(workspaces.get(importedCwd)).toBeTruthy();
const importedWorkspace = Array.from(workspaces.values()).find(
(workspace) => workspace.cwd === importedCwd,
);
expect(importedWorkspace).toBeTruthy();
const workspaceUpdates = filterByType(emitted, "workspace_update");
expect(workspaceUpdates.length).toBeGreaterThan(0);
expect(
workspaceUpdates.some(
(update) => update.payload.kind === "upsert" && update.payload.workspace.id === importedCwd,
(update) =>
update.payload.kind === "upsert" &&
update.payload.workspace.workspaceDirectory === importedCwd,
),
).toBe(true);
});
@@ -3023,8 +3101,8 @@ test("open_project_response returns immediately even when the GitHub fetch is sl
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3062,7 +3140,8 @@ test("open_project_response returns immediately even when the GitHub fetch is sl
const response = findByType(emitted, "open_project_response");
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.id).toBe(cwd);
expect(response?.payload.workspace?.id).toMatch(/^wks_[0-9a-f]{16}$/);
expect(response?.payload.workspace?.workspaceDirectory).toBe(cwd);
expect(response?.payload.workspace?.gitRuntime).toBeUndefined();
expect(response?.payload.workspace?.githubRuntime).toBeUndefined();
@@ -3089,8 +3168,8 @@ test("open_project_request emits a workspace_update with githubRuntime once the
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3143,7 +3222,7 @@ test("open_project_request emits a workspace_update with githubRuntime once the
.map((update) => update.payload)
.filter(
(payload): payload is WorkspaceUpsertPayload =>
payload.kind === "upsert" && payload.workspace.id === cwd,
payload.kind === "upsert" && payload.workspace.workspaceDirectory === cwd,
)
.find((payload) => payload.workspace.githubRuntime?.pullRequest);
expect(upsertedWithGitHub?.workspace.githubRuntime?.pullRequest).toEqual(
@@ -3155,6 +3234,7 @@ interface WorkspaceUpsertPayload {
kind: "upsert";
workspace: {
id: string;
workspaceDirectory?: string;
githubRuntime?: {
pullRequest?: { url?: string } | null;
} | null;
@@ -3202,8 +3282,8 @@ test("open_project_request does not match a new child directory to an existing p
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3220,9 +3300,11 @@ test("open_project_request does not match a new child directory to an existing p
const response = findByType(emitted, "open_project_response");
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.id).toBe(worktree);
expect(response?.payload.workspace?.id).toMatch(/^wks_[0-9a-f]{16}$/);
expect(response?.payload.workspace?.workspaceDirectory).toBe(worktree);
expect(workspaces.get(worktree)).toBeTruthy();
expect(Array.from(workspaces.values()).some((workspace) => workspace.cwd === worktree)).toBe(
true,
);
});
test("open_project_request does not unarchive an archived parent workspace for a new child directory", async () => {
@@ -3269,8 +3351,8 @@ test("open_project_request does not unarchive an archived parent workspace for a
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3287,7 +3369,7 @@ test("open_project_request does not unarchive an archived parent workspace for a
const response = findByType(emitted, "open_project_response");
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.id).toBe(worktree);
expect(response?.payload.workspace?.id).toMatch(/^wks_[0-9a-f]{16}$/);
expect(response?.payload.workspace?.workspaceDirectory).toBe(worktree);
expect(workspaces.get(home)?.archivedAt).toBe(archivedAt);
expect(projects.get(home)?.archivedAt).toBe(archivedAt);
@@ -3308,6 +3390,7 @@ test("open_project_request reclassifies an archived directory workspace when git
);
const remoteProjectId = "remote:github.com/getpaseo/paseo";
const archivedAt = "2026-04-24T09:48:36.168Z";
const workspaceId = "ws-desktop-daemon-settings";
projects.set(
cwd,
@@ -3322,9 +3405,9 @@ test("open_project_request reclassifies an archived directory workspace when git
}),
);
workspaces.set(
cwd,
workspaceId,
createPersistedWorkspaceRecord({
workspaceId: cwd,
workspaceId,
projectId: cwd,
cwd,
kind: "directory",
@@ -3344,8 +3427,8 @@ test("open_project_request reclassifies an archived directory workspace when git
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3386,9 +3469,9 @@ test("open_project_request reclassifies an archived directory workspace when git
expect(response?.payload.workspace?.projectId).toBe(remoteProjectId);
expect(response?.payload.workspace?.workspaceKind).toBe("worktree");
expect(projects.get(remoteProjectId)?.kind).toBe("git");
expect(workspaces.get(cwd)?.projectId).toBe(remoteProjectId);
expect(workspaces.get(cwd)?.kind).toBe("worktree");
expect(workspaces.get(cwd)?.displayName).toBe("feature/desktop-daemon-settings");
expect(workspaces.get(workspaceId)?.projectId).toBe(remoteProjectId);
expect(workspaces.get(workspaceId)?.kind).toBe("worktree");
expect(workspaces.get(workspaceId)?.displayName).toBe("feature/desktop-daemon-settings");
});
test("open_project_request reclassifies an active directory workspace when git metadata becomes available", async () => {
@@ -3427,10 +3510,12 @@ test("open_project_request reclassifies an active directory workspace when git m
updatedAt: "2026-04-24T09:40:00.000Z",
}),
);
const workspaceId = "ws-desktop-daemon-settings-active";
const repoWorkspaceId = "ws-paseo-main";
workspaces.set(
cwd,
workspaceId,
createPersistedWorkspaceRecord({
workspaceId: cwd,
workspaceId,
projectId: cwd,
cwd,
kind: "directory",
@@ -3440,9 +3525,9 @@ test("open_project_request reclassifies an active directory workspace when git m
}),
);
workspaces.set(
repoRoot,
repoWorkspaceId,
createPersistedWorkspaceRecord({
workspaceId: repoRoot,
workspaceId: repoWorkspaceId,
projectId: repoRoot,
cwd: repoRoot,
kind: "local_checkout",
@@ -3461,8 +3546,8 @@ test("open_project_request reclassifies an active directory workspace when git m
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3502,9 +3587,9 @@ test("open_project_request reclassifies an active directory workspace when git m
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.projectId).toBe(repoRoot);
expect(response?.payload.workspace?.workspaceKind).toBe("worktree");
expect(workspaces.get(cwd)?.projectId).toBe(repoRoot);
expect(workspaces.get(cwd)?.kind).toBe("worktree");
expect(workspaces.get(cwd)?.displayName).toBe("feature/desktop-daemon-settings");
expect(workspaces.get(workspaceId)?.projectId).toBe(repoRoot);
expect(workspaces.get(workspaceId)?.kind).toBe("worktree");
expect(workspaces.get(workspaceId)?.displayName).toBe("feature/desktop-daemon-settings");
});
test("open_project_request groups a plain git worktree under an existing repo project", async () => {
@@ -3554,8 +3639,8 @@ test("open_project_request groups a plain git worktree under an existing repo pr
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3595,8 +3680,11 @@ test("open_project_request groups a plain git worktree under an existing repo pr
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.projectId).toBe(repoRoot);
expect(response?.payload.workspace?.workspaceKind).toBe("worktree");
expect(workspaces.get(cwd)?.projectId).toBe(repoRoot);
expect(workspaces.get(cwd)?.kind).toBe("worktree");
const worktreeWorkspace = Array.from(workspaces.values()).find(
(workspace) => workspace.cwd === cwd,
);
expect(worktreeWorkspace?.projectId).toBe(repoRoot);
expect(worktreeWorkspace?.kind).toBe("worktree");
});
test("open_project_request unarchives an existing archived workspace and project", async () => {
@@ -3606,6 +3694,7 @@ test("open_project_request unarchives an existing archived workspace and project
const workspaces = new Map<string, ReturnType<typeof createPersistedWorkspaceRecord>>();
const cwd = REPO_CWD;
const workspaceId = "ws-repo-archived";
projects.set(
cwd,
createPersistedProjectRecord({
@@ -3619,9 +3708,9 @@ test("open_project_request unarchives an existing archived workspace and project
}),
);
workspaces.set(
cwd,
workspaceId,
createPersistedWorkspaceRecord({
workspaceId: cwd,
workspaceId,
projectId: cwd,
cwd,
kind: "directory",
@@ -3641,8 +3730,8 @@ test("open_project_request unarchives an existing archived workspace and project
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3657,11 +3746,11 @@ test("open_project_request unarchives an existing archived workspace and project
requestId: "req-open-unarchive",
});
expect(workspaces.get(cwd)?.archivedAt).toBeNull();
expect(workspaces.get(workspaceId)?.archivedAt).toBeNull();
expect(projects.get(cwd)?.archivedAt).toBeNull();
const response = findByType(emitted, "open_project_response");
expect(response?.payload.error).toBeNull();
expect(response?.payload.workspace?.id).toBe(cwd);
expect(response?.payload.workspace?.id).toBe(workspaceId);
});
test.skip("open_project_request collapses a git subdirectory onto the repo root workspace", async () => {
@@ -3681,8 +3770,8 @@ test.skip("open_project_request collapses a git subdirectory onto the repo root
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
) => {
@@ -3842,8 +3931,8 @@ test.skip("opening a new worktree reconciles older local workspaces into the rem
if (!existing) return;
projects.set(projectId, { ...existing, archivedAt, updatedAt: archivedAt });
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.list = async () => Array.from(workspaces.values());
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
@@ -3948,8 +4037,8 @@ test.skip("fetch_workspaces_request reconciles remote URL changes for existing w
if (!existing) return;
projects.set(projectId, { ...existing, archivedAt, updatedAt: archivedAt });
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.list = async () => Array.from(workspaces.values());
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
@@ -4044,8 +4133,8 @@ test.skip("reconcile archives stale subdirectory workspace records when collapsi
) => {
projects.set(record.projectId, record);
};
session.workspaceRegistry.get = async (workspaceId: string) =>
workspaces.get(workspaceId) ?? null;
session.workspaceRegistry.get = async (lookupWorkspaceId: string) =>
workspaces.get(lookupWorkspaceId) ?? null;
session.workspaceRegistry.list = async () => Array.from(workspaces.values());
session.workspaceRegistry.upsert = async (
record: ReturnType<typeof createPersistedWorkspaceRecord>,
@@ -5040,7 +5129,7 @@ test("resolveRegisteredWorkspaceIdForCwd does not match home directory as a pref
const home = homedir();
const childCwd = path.join(home, "projects/new-app");
const homeWorkspace = createPersistedWorkspaceRecord({
workspaceId: home,
workspaceId: "ws-home",
projectId: "proj-home",
cwd: home,
kind: "directory",
@@ -5049,6 +5138,6 @@ test("resolveRegisteredWorkspaceIdForCwd does not match home directory as a pref
updatedAt: "2026-03-01T12:00:00.000Z",
});
expect(session.resolveRegisteredWorkspaceIdForCwd(childCwd, [homeWorkspace])).toBe(childCwd);
expect(session.resolveRegisteredWorkspaceIdForCwd(home, [homeWorkspace])).toBe(home);
expect(session.resolveRegisteredWorkspaceIdForCwd(childCwd, [homeWorkspace])).toBeNull();
expect(session.resolveRegisteredWorkspaceIdForCwd(home, [homeWorkspace])).toBe("ws-home");
});