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:
@@ -77,6 +77,7 @@ describe("workspace navigation", () => {
|
||||
const agent = {
|
||||
id: "agent-1",
|
||||
cwd: "/repo/workspace-a",
|
||||
workspaceId: "workspace-a",
|
||||
requiresAttention: true,
|
||||
attentionReason: "permission",
|
||||
} as unknown as Agent;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
parseHostWorkspaceRouteFromPathname,
|
||||
} from "@/utils/host-routes";
|
||||
import {
|
||||
resolveWorkspaceIdByDirectory,
|
||||
normalizeWorkspaceOpaqueId,
|
||||
resolveWorkspaceMapKeyByIdentity,
|
||||
} from "@/utils/workspace-identity";
|
||||
import type { ActiveWorkspaceSelection } from "@/stores/last-workspace-selection";
|
||||
@@ -76,11 +76,7 @@ export function navigateToWorkspace(
|
||||
});
|
||||
const workspaceAgents = resolvedWorkspaceId
|
||||
? Array.from(deps.getSessionAgents(serverId)).filter(
|
||||
(agent) =>
|
||||
resolveWorkspaceIdByDirectory({
|
||||
workspaces: workspaces?.values(),
|
||||
workspaceDirectory: agent.cwd,
|
||||
}) === resolvedWorkspaceId,
|
||||
(agent) => normalizeWorkspaceOpaqueId(agent.workspaceId) === resolvedWorkspaceId,
|
||||
)
|
||||
: [];
|
||||
const attentionAgentId = pickAttentionAgent(workspaceAgents);
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
selectHasWorkspaces,
|
||||
selectProjectOrder,
|
||||
selectRecommendedProjectPaths,
|
||||
selectResolveWorkspaceIdByCwd,
|
||||
selectWorkspace,
|
||||
selectWorkspaceDirectory,
|
||||
selectWorkspaceExists,
|
||||
@@ -135,17 +134,6 @@ export function useHasWorkspaces(serverId: string | null): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function useResolveWorkspaceIdByCwd(
|
||||
serverId: string | null,
|
||||
cwd: string | null | undefined,
|
||||
): string | null {
|
||||
return useStoreWithEqualityFn(
|
||||
useSessionStore,
|
||||
(state) => selectResolveWorkspaceIdByCwd(state, serverId, cwd),
|
||||
workspaceEqualityFns.identity,
|
||||
);
|
||||
}
|
||||
|
||||
export function useWorkspaceStatusesForBadges(): DesktopBadgeWorkspaceStatus[] {
|
||||
return useStoreWithEqualityFn(
|
||||
useSessionStore,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
selectHasWorkspaces,
|
||||
selectProjectOrder,
|
||||
selectRecommendedProjectPaths,
|
||||
selectResolveWorkspaceIdByCwd,
|
||||
selectWorkspace,
|
||||
selectWorkspaceDirectory,
|
||||
selectWorkspaceFields,
|
||||
@@ -385,33 +384,6 @@ describe("selectHasWorkspaces", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectResolveWorkspaceIdByCwd", () => {
|
||||
it("resolves by cwd and stays stable under unrelated updates", () => {
|
||||
const workspaceA = createWorkspace({
|
||||
id: "workspace-a",
|
||||
workspaceDirectory: "/repo/a",
|
||||
});
|
||||
const workspaceB = createWorkspace({
|
||||
id: "workspace-b",
|
||||
workspaceDirectory: "/repo/b",
|
||||
});
|
||||
initializeWorkspaces([workspaceA, workspaceB]);
|
||||
|
||||
const tracked = trackSelector(
|
||||
useSessionStore,
|
||||
(state) => selectResolveWorkspaceIdByCwd(state, SERVER_ID, "/repo/a"),
|
||||
workspaceEqualityFns.identity,
|
||||
);
|
||||
const before = tracked.current;
|
||||
expect(before).toBe("workspace-a");
|
||||
|
||||
useSessionStore.getState().mergeWorkspaces(SERVER_ID, [{ ...workspaceB, status: "running" }]);
|
||||
expect(tracked.current).toBe(before);
|
||||
|
||||
tracked.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectWorkspaceStatusesForBadges", () => {
|
||||
it("tracks status changes without changing for no-ops or unrelated descriptor updates", () => {
|
||||
const workspaceA = createWorkspace({ id: "workspace-a", status: "done" });
|
||||
|
||||
@@ -5,10 +5,7 @@ import {
|
||||
type WorkspaceStructureProject,
|
||||
} from "@/projects/workspace-structure";
|
||||
import type { DesktopBadgeWorkspaceStatus } from "@/utils/desktop-badge-state";
|
||||
import {
|
||||
resolveWorkspaceIdByDirectory,
|
||||
resolveWorkspaceMapKeyByIdentity,
|
||||
} from "@/utils/workspace-identity";
|
||||
import { resolveWorkspaceMapKeyByIdentity } from "@/utils/workspace-identity";
|
||||
import type { EmptyProjectDescriptor, WorkspaceDescriptor } from "../session-store";
|
||||
|
||||
export type { DesktopBadgeWorkspaceStatus } from "@/utils/desktop-badge-state";
|
||||
@@ -250,21 +247,6 @@ export function selectHasWorkspaces(state: SessionsSnapshot, serverId: string |
|
||||
return (state.sessions[serverId]?.workspaces?.size ?? 0) > 0;
|
||||
}
|
||||
|
||||
export function selectResolveWorkspaceIdByCwd(
|
||||
state: SessionsSnapshot,
|
||||
serverId: string | null,
|
||||
cwd: string | null | undefined,
|
||||
): string | null {
|
||||
if (!serverId || !cwd) {
|
||||
return null;
|
||||
}
|
||||
const workspaces = state.sessions[serverId]?.workspaces;
|
||||
return resolveWorkspaceIdByDirectory({
|
||||
workspaces: workspaces?.values(),
|
||||
workspaceDirectory: cwd,
|
||||
});
|
||||
}
|
||||
|
||||
export function selectWorkspaceStatusesForBadges(
|
||||
state: SessionsSnapshot,
|
||||
): DesktopBadgeWorkspaceStatus[] {
|
||||
|
||||
@@ -56,6 +56,7 @@ const AGENT_DEFAULTS: Agent = {
|
||||
lastError: null,
|
||||
title: "Agent",
|
||||
cwd: WORKSPACE_DIRECTORY,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
model: null,
|
||||
features: undefined,
|
||||
thinkingOptionId: undefined,
|
||||
@@ -91,7 +92,7 @@ function deriveVisibilityFromSession(): WorkspaceAgentVisibility {
|
||||
const sessionAgents = useSessionStore.getState().sessions[SERVER_ID]?.agents ?? new Map();
|
||||
return deriveWorkspaceAgentVisibility({
|
||||
sessionAgents,
|
||||
workspaceDirectory: WORKSPACE_DIRECTORY,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user