61 lines
3.3 KiB
TypeScript
61 lines
3.3 KiB
TypeScript
// 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);
|