Files
hypothesis-machine/src/run-store.ts
T
Emil 029ce743bb feat: /agent opens a subagent's full chat (OpenCode-style), /back returns
- /agent [filter] lists the run's agents (glyphs, elapsed, task) via the
  native selector and switchSession()es to the agent's own session file
- run is restored in the agent session by deriving the run id from the
  session file path (<stateDir>/runs/<runId>/sessions/...)
- main session file recorded in the run manifest so /back works even after
  a reload while viewing an agent chat
- widget re-attach timer guard on session switch
2026-08-01 00:18:47 +03:00

57 lines
2.3 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import type { AgentRecord } from "./types.js";
export interface RunManifest {
version: 1;
runId: string;
goal: string;
status: "active" | "paused" | "stopped" | "completed";
rootAgentId: string;
createdAt: string;
updatedAt: string;
agents: Record<string, AgentRecord>;
/** Session file of the main chat, used by `/back` to return from an agent chat. */
mainSessionFile?: string;
}
export class RunStore {
constructor(readonly stateDir: string) {}
runDir(runId: string): string { return resolve(this.stateDir, "runs", runId); }
manifestPath(runId: string): string { return resolve(this.runDir(runId), "manifest.json"); }
agentDir(runId: string): string { return resolve(this.runDir(runId), "agents"); }
sessionDir(runId: string): string { return resolve(this.runDir(runId), "sessions"); }
create(runId: string, goal: string, root: AgentRecord): RunManifest {
const now = new Date().toISOString();
const manifest: RunManifest = { version: 1, runId, goal, status: "active", rootAgentId: root.id, createdAt: now, updatedAt: now, agents: { [root.id]: root } };
this.save(manifest);
return manifest;
}
load(runId: string): RunManifest {
const data = JSON.parse(readFileSync(this.manifestPath(runId), "utf8")) as RunManifest;
if (data.version !== 1 || data.runId !== runId || typeof data.agents !== "object") throw new Error(`Invalid run manifest: ${runId}`);
return data;
}
mainSessionFileOf(runId: string): string | undefined { return this.load(runId).mainSessionFile; }
setMainSessionFile(runId: string, sessionFile: string): void {
const manifest = this.load(runId);
manifest.mainSessionFile = sessionFile;
this.save(manifest);
}
exists(runId: string): boolean { return existsSync(this.manifestPath(runId)); }
save(manifest: RunManifest): void {
manifest.updatedAt = new Date().toISOString();
const path = this.manifestPath(manifest.runId);
mkdirSync(dirname(path), { recursive: true });
const temporary = `${path}.${process.pid}.tmp`;
writeFileSync(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
renameSync(temporary, path);
}
}