fix(server): publish the stored state when unarchiving a closed agent

This commit is contained in:
siyxo
2026-10-17 20:06:26 +08:00
parent 06cdc5a0b8
commit c64cdd863d
2 changed files with 54 additions and 2 deletions
@@ -8652,6 +8652,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");
@@ -2331,16 +2331,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;
}