mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 17:34:51 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
313abd9736 | ||
|
|
818d2ee9f3 | ||
|
|
f90aad44b7 | ||
|
|
c64cdd863d |
@@ -2,7 +2,15 @@ import { mkdtempSync, realpathSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { test, expect } from "../support/fixtures";
|
||||
import { openSessions } from "../support/helpers/archive-tab";
|
||||
import type { Page } from "@playwright/test";
|
||||
import {
|
||||
clickSessionRow,
|
||||
closeWorkspaceAgentTab,
|
||||
expectSessionRowArchived,
|
||||
expectSessionRowNotArchived,
|
||||
openSessions,
|
||||
} from "../support/helpers/archive-tab";
|
||||
import { expectComposerEditable } from "../support/helpers/composer";
|
||||
import {
|
||||
assertChatTranscript,
|
||||
cleanupRewindFlow,
|
||||
@@ -31,6 +39,23 @@ async function historyContainsAgent(client: SeedDaemonClient, agentId: string):
|
||||
return history.entries.some((entry) => entry.agent.id === agentId);
|
||||
}
|
||||
|
||||
async function closeAgentTab(page: Page, agentId: string): Promise<void> {
|
||||
await page
|
||||
.getByTestId(`workspace-tab-agent_${agentId}`)
|
||||
.filter({ visible: true })
|
||||
.first()
|
||||
.hover();
|
||||
await closeWorkspaceAgentTab(page, agentId);
|
||||
}
|
||||
|
||||
async function unarchiveOpenAgent(page: Page): Promise<void> {
|
||||
await page.getByRole("button", { name: "Unarchive", exact: true }).click();
|
||||
await expect(page.getByRole("button", { name: "Unarchive", exact: true })).toHaveCount(0, {
|
||||
timeout: 60_000,
|
||||
});
|
||||
await expectComposerEditable(page);
|
||||
}
|
||||
|
||||
test.describe("archived Codex agent recovery", () => {
|
||||
test.setTimeout(600_000);
|
||||
|
||||
@@ -112,4 +137,34 @@ test.describe("archived Codex agent recovery", () => {
|
||||
await cleanupRewindFlow({ handle, cwd });
|
||||
}
|
||||
});
|
||||
|
||||
test("refreshes cached History after unarchiving without restarting", async ({ page }) => {
|
||||
const cwd = realpathSync(mkdtempSync(path.join(tmpdir(), "paseo-unarchive-history-")));
|
||||
let handle: AgentHandle | undefined;
|
||||
try {
|
||||
handle = await launchAgent({
|
||||
page,
|
||||
provider: "codex",
|
||||
providerConfig: { model: "gpt-5.6-sol" },
|
||||
cwd,
|
||||
mode: "full-access",
|
||||
});
|
||||
await sendMessage(handle, INITIAL_PROMPT);
|
||||
await assertChatTranscript(handle, [
|
||||
{ role: "user", text: INITIAL_PROMPT },
|
||||
{ role: "assistant", text: INITIAL_REPLY },
|
||||
]);
|
||||
await closeAgentTab(page, handle.agentId);
|
||||
await openSessions(page);
|
||||
await expectSessionRowArchived(page, "rewind-flow-codex-");
|
||||
await clickSessionRow(page, "rewind-flow-codex-");
|
||||
await unarchiveOpenAgent(page);
|
||||
await openSessions(page);
|
||||
await expectSessionRowNotArchived(page, "rewind-flow-codex-");
|
||||
await clickSessionRow(page, "rewind-flow-codex-");
|
||||
await expectComposerEditable(page);
|
||||
} finally {
|
||||
await cleanupRewindFlow({ handle, cwd });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -296,6 +296,7 @@ export function useAgentHistory(options: {
|
||||
queryKey,
|
||||
enabled: Boolean(enabled && targetHosts.length > 0),
|
||||
staleTime: 30_000,
|
||||
refetchOnMount: "always",
|
||||
initialPageParam: null,
|
||||
getNextPageParam: getNextAgentHistoryPageParam,
|
||||
queryFn: async ({ pageParam }) => {
|
||||
|
||||
@@ -9047,6 +9047,52 @@ test("unarchiveSnapshot unarchives native provider storage before clearing archi
|
||||
expect(stored?.labels).toEqual({ retained: "yes", source: "reimport" });
|
||||
});
|
||||
|
||||
test("unarchiveSnapshot publishes the stored state for a closed agent", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-unarchive-state-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
const storage = new AgentStorage(storagePath, logger);
|
||||
const client = new NativeArchiveRecordingClient();
|
||||
const manager = new AgentManager({
|
||||
clients: { codex: client },
|
||||
registry: storage,
|
||||
logger,
|
||||
});
|
||||
|
||||
try {
|
||||
const agent = await manager.createAgent(
|
||||
{
|
||||
provider: "codex",
|
||||
cwd: workdir,
|
||||
title: "Unarchive state target",
|
||||
},
|
||||
undefined,
|
||||
{ workspaceId: undefined },
|
||||
);
|
||||
await manager.closeAgent(agent.id);
|
||||
await manager.archiveSnapshot(agent.id, new Date().toISOString());
|
||||
|
||||
const states: string[] = [];
|
||||
const unsubscribe = manager.subscribe(
|
||||
(event) => {
|
||||
if (event.type === "agent_state" && event.agent.id === agent.id) {
|
||||
states.push(event.agent.lifecycle);
|
||||
}
|
||||
},
|
||||
{ replayState: false },
|
||||
);
|
||||
|
||||
const unarchived = await manager.unarchiveSnapshot(agent.id);
|
||||
unsubscribe();
|
||||
|
||||
expect(unarchived).toBe(true);
|
||||
expect((await storage.get(agent.id))?.archivedAt).toBeNull();
|
||||
expect(states).toEqual(["closed"]);
|
||||
} finally {
|
||||
for (const agent of manager.listAgents()) await manager.closeAgent(agent.id);
|
||||
rmSync(workdir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("unarchiveSnapshotByHandle unarchives native provider storage for the matched snapshot", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-native-unarchive-handle-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
|
||||
@@ -2497,16 +2497,22 @@ export class AgentManager {
|
||||
if (this.agents.has(agentId)) await this.closeAgentRuntime(agentId);
|
||||
await this.syncNativeArchiveState(record.provider, record.persistence, "restore");
|
||||
|
||||
await registry.upsert({
|
||||
const unarchivedRecord: StoredAgentRecord = {
|
||||
...record,
|
||||
...(updates?.workspaceId ? { workspaceId: updates.workspaceId } : {}),
|
||||
...(updates?.labels ? { labels: applyLabelPatch(record.labels, updates.labels) } : {}),
|
||||
archivedAt: null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
};
|
||||
await registry.upsert(unarchivedRecord);
|
||||
|
||||
if (this.getAgent(agentId)) {
|
||||
this.notifyAgentState(agentId);
|
||||
} else if (!unarchivedRecord.internal) {
|
||||
// An archived agent has no live runtime, so `notifyAgentState` is a no-op.
|
||||
// Publish the stored snapshot the same way `markRecordArchived` does, or
|
||||
// clients keep rendering the archived row until they refetch.
|
||||
this.dispatchStoredAgentState(unarchivedRecord);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user