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:
@@ -75,6 +75,21 @@ function resolveCheckoutRequest(
|
||||
};
|
||||
}
|
||||
|
||||
function buildFirstAgentContext(input: {
|
||||
prompt: string;
|
||||
attachments: AgentAttachment[];
|
||||
}): { prompt?: string; attachments?: AgentAttachment[] } | undefined {
|
||||
const trimmedPrompt = input.prompt.trim();
|
||||
if (!trimmedPrompt && input.attachments.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
|
||||
attachments: input.attachments,
|
||||
};
|
||||
}
|
||||
|
||||
interface NewWorkspaceScreenProps {
|
||||
serverId: string;
|
||||
sourceDirectory?: string;
|
||||
@@ -779,6 +794,8 @@ async function createMultiplicityWorkspace(input: {
|
||||
selectedItem: PickerItem | null;
|
||||
currentBranch: string | null;
|
||||
withInitialAgent: boolean;
|
||||
prompt: string;
|
||||
attachments: AgentAttachment[];
|
||||
mergeWorkspaces: (
|
||||
serverId: string,
|
||||
workspaces: ReturnType<typeof normalizeWorkspaceDescriptor>[],
|
||||
@@ -790,6 +807,10 @@ async function createMultiplicityWorkspace(input: {
|
||||
const baseBranch = isWorktree
|
||||
? (resolveCheckoutRequest(input.selectedItem, input.currentBranch)?.refName ?? undefined)
|
||||
: undefined;
|
||||
const firstAgentContext = buildFirstAgentContext({
|
||||
prompt: input.prompt,
|
||||
attachments: input.attachments,
|
||||
});
|
||||
const payload = await input.client.createWorkspace({
|
||||
source: isWorktree
|
||||
? {
|
||||
@@ -804,6 +825,7 @@ async function createMultiplicityWorkspace(input: {
|
||||
path: input.project.iconWorkingDir,
|
||||
projectId: input.project.projectKey,
|
||||
},
|
||||
...(firstAgentContext ? { firstAgentContext } : {}),
|
||||
});
|
||||
if (payload.error || !payload.workspace) {
|
||||
throw new Error(payload.error ?? input.createFailedMessage);
|
||||
@@ -1292,21 +1314,13 @@ export function NewWorkspaceScreen({
|
||||
throw new Error("Choose a project");
|
||||
}
|
||||
const checkoutRequest = resolveCheckoutRequest(selectedItem, currentBranch);
|
||||
const trimmedPrompt = input.prompt.trim();
|
||||
const hasFirstAgentContext = trimmedPrompt.length > 0 || input.attachments.length > 0;
|
||||
const firstAgentContext = buildFirstAgentContext(input);
|
||||
|
||||
return {
|
||||
cwd: selectedProject.iconWorkingDir,
|
||||
projectId: selectedProject.projectKey,
|
||||
worktreeSlug: createNameId(),
|
||||
...(hasFirstAgentContext
|
||||
? {
|
||||
firstAgentContext: {
|
||||
...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
|
||||
...(input.attachments.length > 0 ? { attachments: input.attachments } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(firstAgentContext ? { firstAgentContext } : {}),
|
||||
...checkoutRequest,
|
||||
};
|
||||
},
|
||||
@@ -1334,6 +1348,8 @@ export function NewWorkspaceScreen({
|
||||
selectedItem,
|
||||
currentBranch,
|
||||
withInitialAgent: input.withInitialAgent,
|
||||
prompt: input.prompt,
|
||||
attachments: input.attachments,
|
||||
mergeWorkspaces,
|
||||
serverId,
|
||||
createFailedMessage: t("newWorkspace.errors.createWorktreeFailed"),
|
||||
|
||||
@@ -186,17 +186,18 @@ export function useWorkspaceTerminals(input: UseWorkspaceTerminalsInput) {
|
||||
const paneWorkspaceId = normalizedWorkspaceId || undefined;
|
||||
|
||||
const unsubscribeChanged = client.on("terminals_changed", (message) => {
|
||||
// The terminal subscription is keyed by cwd at the protocol level, so this
|
||||
// gate only routes the event to the pane watching that cwd; it is not an
|
||||
// ownership decision.
|
||||
if (message.payload.cwd !== workspaceDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Two workspaces can share a cwd, so the push can carry terminals from a
|
||||
// sibling workspace. Keep only the ones whose workspaceId matches this
|
||||
// pane; terminals without a workspaceId predate Model B and belong to
|
||||
// whichever pane is watching the cwd.
|
||||
// sibling workspace. A terminal belongs to a workspace only by its
|
||||
// workspaceId, so keep only the ones whose workspaceId matches this pane.
|
||||
const matchingTerminals = message.payload.terminals.filter(
|
||||
(terminal) =>
|
||||
terminal.workspaceId === undefined || terminal.workspaceId === paneWorkspaceId,
|
||||
(terminal) => terminal.workspaceId === paneWorkspaceId,
|
||||
);
|
||||
|
||||
queryClient.setQueryData<ListTerminalsPayload>(queryKey, (current) => ({
|
||||
|
||||
@@ -1791,7 +1791,6 @@ function WorkspaceScreenContent({
|
||||
sessionAgents: state.sessions[normalizedServerId]?.agents,
|
||||
agentDetails: state.sessions[normalizedServerId]?.agentDetails,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
workspaceDirectory,
|
||||
}),
|
||||
workspaceAgentVisibilityEqual,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user