Restore archived agents from History (#2316)

* fix(app): restore archived agents from History

History navigation lost the explicit archived-agent intent during tab reconciliation, and workspace recovery stopped after restoring the workspace. Preserve the selected tab and recover its provider session as one action while serializing provider resume with timeline hydration.

* fix(app): preserve archived agent recovery invariants

* fix(server): preserve timeline hydration call shape

* fix(app): retain agent tabs through lookup

* fix(server): upgrade in-flight timeline broadcast
This commit is contained in:
Mohamed Boudra
2026-07-22 15:03:09 +02:00
committed by GitHub
parent 68993b7ab3
commit 42ee5a5949
22 changed files with 629 additions and 61 deletions
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { resolveWorkspaceRecoveryModel } from "./model";
import { recoverWorkspaceSelection, resolveWorkspaceRecoveryModel } from "./model";
describe("resolveWorkspaceRecoveryModel", () => {
it("keeps newer recovery actions visible but non-actionable", () => {
@@ -29,3 +29,24 @@ describe("resolveWorkspaceRecoveryModel", () => {
});
});
});
describe("recoverWorkspaceSelection", () => {
it("restores the workspace and selected archived agent as one recovery action", async () => {
const operations: string[] = [];
await recoverWorkspaceSelection({
workspaceId: "workspace-1",
agentId: "agent-1",
client: {
restoreWorkspace: async (workspaceId) => {
operations.push(`workspace:${workspaceId}`);
},
refreshAgent: async (agentId) => {
operations.push(`agent:${agentId}`);
},
},
});
expect(operations).toEqual(["workspace:workspace-1", "agent:agent-1"]);
});
});
@@ -34,6 +34,22 @@ export interface WorkspaceRecoveryController {
retryInspection: () => void;
}
export interface WorkspaceSelectionRecoveryClient {
restoreWorkspace: (workspaceId: string) => Promise<unknown>;
refreshAgent: (agentId: string) => Promise<unknown>;
}
export async function recoverWorkspaceSelection(input: {
client: WorkspaceSelectionRecoveryClient;
workspaceId: string;
agentId?: string | null;
}): Promise<void> {
await input.client.restoreWorkspace(input.workspaceId);
if (input.agentId) {
await input.client.refreshAgent(input.agentId);
}
}
function resolveRecoveryPhase(input: {
pending: boolean;
error: string | null;
@@ -4,7 +4,11 @@ import { useFetchQuery } from "@/data/query";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { useSessionStore } from "@/stores/session-store";
import { toErrorMessage } from "@/utils/error-messages";
import { resolveWorkspaceRecoveryModel, type WorkspaceRecoveryController } from "./model";
import {
recoverWorkspaceSelection,
resolveWorkspaceRecoveryModel,
type WorkspaceRecoveryController,
} from "./model";
export type { WorkspaceRecoveryController, WorkspaceRecoveryModel } from "./model";
@@ -28,6 +32,7 @@ function waitForMinimumRecoveryLoadingTime(): Promise<void> {
export function useWorkspaceRecovery(input: {
serverId: string;
workspaceId: string;
agentId?: string | null;
enabled: boolean;
}): WorkspaceRecoveryController {
const client = useHostRuntimeClient(input.serverId);
@@ -56,7 +61,11 @@ export function useWorkspaceRecovery(input: {
}
await waitForRecoveryLoadingPresentation();
await waitForMinimumRecoveryLoadingTime();
await client.restoreWorkspace(input.workspaceId);
await recoverWorkspaceSelection({
client,
workspaceId: input.workspaceId,
agentId: input.agentId,
});
},
});