// Modes: abort | crash | resume. Uses a file-backed JSONL session so a second process can recover. import { createModels, fauxProvider } from "@earendil-works/pi-ai"; import { AgentHarness, BACKGROUND_CONTEXT, JsonlSessionRepo } from "@earendil-works/pi-agent-core"; import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/harness/env/nodejs"; import { downloadSubmit, inventoryQuery, script, submitted } from "./common.ts"; const mode = process.argv[2]; const ctx = BACKGROUND_CONTEXT; const faux = fauxProvider({ provider: "umt-fake" }); const models = createModels(); models.setProvider(faux.provider); faux.setResponses(mode === "resume" ? [script[2]] : script); const root = new URL("./.sessions", import.meta.url).pathname; const fs = new NodeExecutionEnv({ cwd: root }); // used only as FileSystem for session storage; no shell tool is exposed const repo = new JsonlSessionRepo({ fileSystem: fs, sessionsRoot: root }); let session; if (mode === "resume") { const list = await repo.list({ cwd: root }, ctx); const meta = list.find((m) => m.id === "crash-demo"); console.log("found session", meta?.id, meta?.path); session = await repo.open(meta!, ctx); } else { session = await repo.create({ id: mode === "crash" ? "crash-demo" : `abort-${Date.now()}`, cwd: root }, ctx); } const { harness, open } = await AgentHarness.create({ session, models, model: faux.getModel(), systemPrompt: "媒体库助手", tools: [inventoryQuery, downloadSubmit], toolContext: { actorId: "user:1", grants: ["download.submit"] }, toolExecution: "sequential", }, ctx); console.log("open operations at attach:", JSON.stringify(open)); let lane = await harness.lane("main", ctx); for (const t of ["tool_start", "tool_end", "run_end", "operation_abort", "run_resume"] as const) harness.events.on(t as any, async (e: any) => { console.log(`[${e.type}]${e.recovery ? " (recovery)" : ""} ${e.toolName ?? ""} ${e.status ?? ""} ${e.result ? JSON.stringify(e.result.content[0]?.text).slice(0, 220) : ""}`); if (e.type === "tool_start" && e.toolName === "download__submit") { if (mode === "abort") setTimeout(async () => { console.log(">> lane.abort()"); console.log("abort result:", JSON.stringify(await lane.abort(ctx))); }, 300); if (mode === "crash") setTimeout(() => { console.log(">> SIGKILL self during download.submit"); process.kill(process.pid, "SIGKILL"); }, 300); } }); if (mode === "resume") { const r = await lane.resume(ctx); console.log("RESUME RESULT", JSON.stringify(r)); } else { const r = await lane.prompt("把《Dune》1080p 收进来", undefined, ctx); console.log("PROMPT RESULT", JSON.stringify(r)); } console.log("SUBMITTED in this process", JSON.stringify(submitted), "fake calls:", faux.state.callCount); const entries = (await lane.findEntries(undefined, ctx)).reverse(); for (const e of entries as any[]) { if (e.type !== "message") { console.log(" entry", e.type); continue; } const m = e.message; const text = m.role === "assistant" ? m.content.map((c: any) => c.type === "toolCall" ? `toolCall(${c.name})` : c.type === "text" ? c.text : `<${c.type}>`).join(" | ") : Array.isArray(m.content) ? m.content.map((c: any) => c.text).join("") : m.content; console.log(` ${m.role.padEnd(10)} ${m.toolName ?? ""} ${m.isError ? "[isError]" : ""} ${String(text).slice(0, 200)}`); } await harness.close(ctx);