Key workspace status and ownership by ID, not directory
Multiple workspaces can share one checkout directory. Agent and terminal status, sidebar tabs, and ownership now attribute strictly by workspace ID, so a running or blocked agent in one workspace no longer shows up on its same-directory siblings. A one-time startup migration backfills workspace IDs onto legacy agents, and every agent and terminal is created with one thereafter. Directory lookups remain only for genuinely folder-derived git facts (branch, diff) and the archive-by-path adapter.
This commit is contained in:
@@ -28,11 +28,10 @@ function workspace(input?: Partial<WorkspaceDescriptor>): WorkspaceDescriptor {
|
||||
}
|
||||
|
||||
describe("workspace archive pending suppression", () => {
|
||||
it("tracks a locally pending workspace archive by id and directory", () => {
|
||||
it("tracks a locally pending workspace archive by id", () => {
|
||||
markWorkspaceArchivePending({
|
||||
serverId: "server-1",
|
||||
workspaceId: "/repo/worktree",
|
||||
workspaceDirectory: "/repo/worktree",
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -41,12 +40,6 @@ describe("workspace archive pending suppression", () => {
|
||||
workspaceId: "/repo/worktree",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isWorkspaceArchivePending({
|
||||
serverId: "server-1",
|
||||
workspaceDirectory: "/repo/worktree",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldSuppressWorkspaceForLocalArchive({
|
||||
serverId: "server-1",
|
||||
@@ -68,19 +61,34 @@ describe("workspace archive pending suppression", () => {
|
||||
markWorkspaceArchivePending({
|
||||
serverId: "server-1",
|
||||
workspaceId: "/repo/worktree",
|
||||
workspaceDirectory: "/repo/worktree",
|
||||
});
|
||||
|
||||
expect(
|
||||
shouldSuppressWorkspaceForLocalArchive({
|
||||
serverId: "server-1",
|
||||
workspace: workspace({ workspaceDirectory: "/repo/worktree" }),
|
||||
workspace: workspace({ archivingAt: null }),
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
clearWorkspaceArchivePending({ serverId: "server-1", workspaceId: "/repo/worktree" });
|
||||
});
|
||||
|
||||
it("does not suppress a same-cwd sibling whose id is not the one archived", () => {
|
||||
markWorkspaceArchivePending({
|
||||
serverId: "server-1",
|
||||
workspaceId: "/repo/worktree",
|
||||
});
|
||||
|
||||
expect(
|
||||
shouldSuppressWorkspaceForLocalArchive({
|
||||
serverId: "server-1",
|
||||
workspace: workspace({ id: "sibling", workspaceDirectory: "/repo/worktree" }),
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
clearWorkspaceArchivePending({ serverId: "server-1", workspaceId: "/repo/worktree" });
|
||||
});
|
||||
|
||||
it("allows upserts when this client did not start the archive", () => {
|
||||
expect(
|
||||
shouldSuppressWorkspaceForLocalArchive({
|
||||
@@ -94,7 +102,6 @@ describe("workspace archive pending suppression", () => {
|
||||
markWorkspaceArchivePending({
|
||||
serverId: "server-1",
|
||||
workspaceId: "/repo/worktree",
|
||||
workspaceDirectory: "/repo/worktree",
|
||||
});
|
||||
|
||||
expect(
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import type { WorkspaceDescriptor } from "@/stores/session-store";
|
||||
import { normalizeWorkspaceOpaqueId, normalizeWorkspacePath } from "@/utils/workspace-identity";
|
||||
import { normalizeWorkspaceOpaqueId } from "@/utils/workspace-identity";
|
||||
|
||||
interface PendingWorkspaceArchive {
|
||||
workspaceId: string;
|
||||
workspaceDirectory: string | null;
|
||||
}
|
||||
|
||||
const pendingWorkspaceArchivesByServer = new Map<string, Map<string, PendingWorkspaceArchive>>();
|
||||
const pendingWorkspaceArchivesByServer = new Map<string, Set<string>>();
|
||||
|
||||
function pendingArchiveKey(input: { serverId: string; workspaceId: string }): string {
|
||||
return `${input.serverId.trim()}::${input.workspaceId.trim()}`;
|
||||
@@ -15,7 +10,6 @@ function pendingArchiveKey(input: { serverId: string; workspaceId: string }): st
|
||||
export function markWorkspaceArchivePending(input: {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
workspaceDirectory?: string | null;
|
||||
}): void {
|
||||
const serverId = input.serverId.trim();
|
||||
const workspaceId = normalizeWorkspaceOpaqueId(input.workspaceId);
|
||||
@@ -23,11 +17,8 @@ export function markWorkspaceArchivePending(input: {
|
||||
return;
|
||||
}
|
||||
|
||||
const archives = pendingWorkspaceArchivesByServer.get(serverId) ?? new Map();
|
||||
archives.set(pendingArchiveKey({ serverId, workspaceId }), {
|
||||
workspaceId,
|
||||
workspaceDirectory: normalizeWorkspacePath(input.workspaceDirectory),
|
||||
});
|
||||
const archives = pendingWorkspaceArchivesByServer.get(serverId) ?? new Set<string>();
|
||||
archives.add(pendingArchiveKey({ serverId, workspaceId }));
|
||||
pendingWorkspaceArchivesByServer.set(serverId, archives);
|
||||
}
|
||||
|
||||
@@ -54,34 +45,15 @@ export function clearWorkspaceArchivePending(input: {
|
||||
export function isWorkspaceArchivePending(input: {
|
||||
serverId: string;
|
||||
workspaceId?: string | null;
|
||||
workspaceDirectory?: string | null;
|
||||
}): boolean {
|
||||
const serverId = input.serverId.trim();
|
||||
if (!serverId) {
|
||||
const workspaceId = normalizeWorkspaceOpaqueId(input.workspaceId);
|
||||
if (!serverId || !workspaceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const archives = pendingWorkspaceArchivesByServer.get(serverId);
|
||||
if (!archives) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const workspaceId = normalizeWorkspaceOpaqueId(input.workspaceId);
|
||||
if (workspaceId && archives.has(pendingArchiveKey({ serverId, workspaceId }))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const workspaceDirectory = normalizeWorkspacePath(input.workspaceDirectory);
|
||||
if (!workspaceDirectory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const archive of archives.values()) {
|
||||
if (archive.workspaceDirectory === workspaceDirectory) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return archives?.has(pendingArchiveKey({ serverId, workspaceId })) ?? false;
|
||||
}
|
||||
|
||||
export function shouldSuppressWorkspaceForLocalArchive(input: {
|
||||
@@ -91,6 +63,5 @@ export function shouldSuppressWorkspaceForLocalArchive(input: {
|
||||
return isWorkspaceArchivePending({
|
||||
serverId: input.serverId,
|
||||
workspaceId: input.workspace.id,
|
||||
workspaceDirectory: input.workspace.workspaceDirectory,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user