fix: preserve recovered workspace session state

This commit is contained in:
Mohamed Boudra
2026-07-16 15:16:51 +02:00
parent 1e06785c0a
commit 9b7f15828c
3 changed files with 46 additions and 5 deletions
+40 -1
View File
@@ -1,9 +1,16 @@
import { randomUUID } from "node:crypto";
import { execFileSync } from "node:child_process";
import { existsSync } from "node:fs";
import { rename } from "node:fs/promises";
import type { Page } from "@playwright/test";
import { buildHostWorkspaceRoute } from "@/utils/host-routes";
import { expect, test } from "./fixtures";
import { gotoAppShell } from "./helpers/app";
import {
expectWorkspaceBranch,
openChangesPanel,
switchBranchFromChangesPanel,
} from "./helpers/branch-switcher";
import {
createIdleAgent,
expectSessionRowNotArchived,
@@ -81,6 +88,19 @@ test.describe("Worktree restore", () => {
return seeded;
}
async function openArchivedAgentBeforeWorkspaceHydration(page: Page, prefix: string) {
const seeded = await createArchivedMissingWorktree(prefix);
const workspaceRoute = buildHostWorkspaceRoute(getServerId(), seeded.worktree.workspaceId);
const openAgent = encodeURIComponent(`agent:${seeded.agent.id}`);
await page.goto(`${workspaceRoute}?open=${openAgent}`);
await expect(page.getByText("Workspace archived", { exact: true })).toBeVisible({
timeout: 30_000,
});
await expect(page.getByTestId("workspace-recovery-action")).toHaveText("Restore");
return seeded;
}
test.afterEach(async () => {
for (const directory of createdWorktreeDirectories) {
await archiveWorkspaceFromDaemon(worktreeClient, directory).catch(() => undefined);
@@ -156,7 +176,10 @@ test.describe("Worktree restore", () => {
});
test("explicit Restore shows loading and opens the recreated workspace", async ({ page }) => {
const { agent, worktree } = await openArchivedWorkspaceFromHistory(page, "restore-success");
const { agent, worktree } = await openArchivedAgentBeforeWorkspaceHydration(
page,
"restore-success",
);
await page.getByTestId("workspace-recovery-action").click();
@@ -173,6 +196,22 @@ test.describe("Worktree restore", () => {
).toBeVisible({ timeout: 30_000 });
await expect(page.getByTestId("workspace-recovery-action")).toHaveCount(0);
expect(await fetchAgentArchivedAt(client, agent.id)).toBeNull();
const switchedBranch = `restored-live-${randomUUID().slice(0, 8)}`;
execFileSync("git", ["branch", switchedBranch], {
cwd: tempRepo.path,
stdio: "pipe",
});
await openChangesPanel(page);
await expectWorkspaceBranch(page, worktree.workspaceName);
await switchBranchFromChangesPanel(page, {
from: worktree.workspaceName,
to: switchedBranch,
});
await expectWorkspaceBranch(page, switchedBranch);
await expect(
page.getByTestId("workspace-header-title").filter({ visible: true }).first(),
).toHaveText(switchedBranch, { timeout: 30_000 });
});
test("restore failure stays visible and permits a successful retry", async ({ page }) => {
@@ -111,7 +111,7 @@ function HostWorkspaceRouteContent() {
const hasHydratedWorkspaces = useHasHydratedWorkspaces(serverId);
const workspaceExists = useWorkspaceExists(serverId, workspaceId);
const isOpenIntentWaitingForWorkspace = Boolean(
openValue && hasHydratedWorkspaces && !workspaceExists,
openValue && (!hasHydratedWorkspaces || !workspaceExists),
);
useEffect(() => {
if (!serverId || !workspaceId) {
+5 -3
View File
@@ -4012,9 +4012,11 @@ export class Session {
private async restoreWorkspaceAndEmit(workspaceId: string): Promise<void> {
await this.workspaceRecovery.restore(workspaceId);
await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], {
skipReconcile: true,
});
const workspace = await this.workspaceRegistry.get(workspaceId);
if (!workspace) {
throw new Error(`Recovered workspace record not found: ${workspaceId}`);
}
await this.workspaceGitObserver.warmGitData(workspace);
}
private async restoreOwningWorkspaceForLegacyAgentRefresh(agentId: string): Promise<void> {