Restore an archived workspace when its agent is reopened
Reopening an archived agent previously dead-ended at "workspace not found": the agent record was un-archived but its workspace stayed archived and was never sent to the client. The daemon now un-archives the owning workspace alongside the agent when its backing directory still exists, and the app auto-fires the restore ahead of the missing-workspace gate so the user lands back in the workspace rather than a dead end. Worktrees whose directory was deleted on archive are not yet restorable (recreating them from their branch is a follow-up); the workspace record now persists its base branch as groundwork for that.
This commit is contained in:
@@ -16,16 +16,25 @@ interface RecordedHostNav {
|
||||
|
||||
interface RecordedTabNav extends NavigateToPreparedWorkspaceTabInput {}
|
||||
|
||||
interface RecordedRestore {
|
||||
serverId: string;
|
||||
agentId: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
function createFakeNavigators(target: AgentNavTarget): {
|
||||
deps: NavigateToAgentDeps;
|
||||
hostNavigations: RecordedHostNav[];
|
||||
tabNavigations: RecordedTabNav[];
|
||||
restores: RecordedRestore[];
|
||||
} {
|
||||
const hostNavigations: RecordedHostNav[] = [];
|
||||
const tabNavigations: RecordedTabNav[] = [];
|
||||
const restores: RecordedRestore[] = [];
|
||||
return {
|
||||
hostNavigations,
|
||||
tabNavigations,
|
||||
restores,
|
||||
deps: {
|
||||
readAgentNavTarget: () => target,
|
||||
navigateToHostAgent: (route) => {
|
||||
@@ -35,6 +44,9 @@ function createFakeNavigators(target: AgentNavTarget): {
|
||||
tabNavigations.push(input);
|
||||
return `/h/${input.serverId}/workspace/${input.workspaceId}`;
|
||||
},
|
||||
restoreArchivedWorkspace: (input) => {
|
||||
restores.push(input);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -63,6 +75,55 @@ describe("resolveNavigateToAgent", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("delegates a restore attempt whenever a workspaceId resolves", () => {
|
||||
const { deps, restores, tabNavigations } = createFakeNavigators({
|
||||
agentWorkspaceId: WORKSPACE_ID,
|
||||
});
|
||||
|
||||
resolveNavigateToAgent({ serverId: SERVER_ID, agentId: AGENT_ID, pin: true }, deps);
|
||||
|
||||
expect(restores).toEqual([
|
||||
{ serverId: SERVER_ID, agentId: AGENT_ID, workspaceId: WORKSPACE_ID },
|
||||
]);
|
||||
expect(tabNavigations).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("uses the input workspaceId without reading the nav target", () => {
|
||||
const readTargets: { serverId: string; agentId: string }[] = [];
|
||||
const { deps, tabNavigations } = createFakeNavigators({ agentWorkspaceId: null });
|
||||
deps.readAgentNavTarget = (input) => {
|
||||
readTargets.push(input);
|
||||
return { agentWorkspaceId: null };
|
||||
};
|
||||
|
||||
resolveNavigateToAgent(
|
||||
{ serverId: SERVER_ID, agentId: AGENT_ID, workspaceId: WORKSPACE_ID },
|
||||
deps,
|
||||
);
|
||||
|
||||
expect(readTargets).toEqual([]);
|
||||
expect(tabNavigations).toEqual([
|
||||
{
|
||||
serverId: SERVER_ID,
|
||||
workspaceId: WORKSPACE_ID,
|
||||
target: { kind: "agent", agentId: AGENT_ID },
|
||||
currentPathname: undefined,
|
||||
pin: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not trigger a restore when no workspaceId resolves", () => {
|
||||
const { deps, restores, hostNavigations } = createFakeNavigators({
|
||||
agentWorkspaceId: null,
|
||||
});
|
||||
|
||||
resolveNavigateToAgent({ serverId: SERVER_ID, agentId: AGENT_ID }, deps);
|
||||
|
||||
expect(restores).toEqual([]);
|
||||
expect(hostNavigations).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("falls back to the host agent route when the agent has no workspaceId", () => {
|
||||
const { deps, hostNavigations, tabNavigations } = createFakeNavigators({
|
||||
agentWorkspaceId: null,
|
||||
|
||||
Reference in New Issue
Block a user