feat(agent): 运行时内部错误上报到日志(onReport)

- PiAgentRuntimeOptions.onReport:本次运行扩展的系统提示 section、能力工具、beforeTool / beforeRequest 钩子抛错
  先上报(runId + 位置 + 脱敏信息)再原样抛出;pi-durable 的 Harness onReport 也接进来(source=harness)
- server runner / 视觉子流程用 runtimeReporter 写 warn 日志
- 测试:钩子抛错时上报带 runId 与位置、信息已脱敏,调用照常被 block

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-08 13:58:55 +08:00
1 parent 003815591c
commit 7c5215663d
5 files changed
+184 -62

No files matched your search

+40
View File
@@ -11,6 +11,7 @@ import {
createFakeRuntime,
type FakeScript,
LegacySessionError,
type RuntimeReport,
} from "../src/index.ts";
import { crashScript, RUN_ID } from "./fixtures/crash-script.ts";
import { type Fixture, makeFixture, settings, tempDir, types } from "./helpers.ts";
@@ -462,4 +463,43 @@ describe("PiAgentRuntime + FakeModelProvider", () => {
expect(err).toBeInstanceOf(LegacySessionError);
expect((err as Error).message).toContain("旧版本");
});
it("扩展钩子抛错:上报 onReport(带 runId 与位置、已脱敏),pi-durable 照常按 block 处理", async () => {
const fx = makeFixture();
const reports: RuntimeReport[] = [];
const { runtime } = createFakeRuntime({
registry: fx.registry,
scripts: [
{
match: "Dune",
steps: [{ toolCalls: [{ capability: "inventory.query", input: { title: "Dune" } }] }],
finalText: (r) => `结果:${r.at(-1)?.isError}`,
},
],
onReport: (r) => reports.push(r),
});
const run = await runtime.createRun({
runId: "run-report",
goal: "Dune",
systemPrompt: "x",
capabilities: ["inventory.query"],
settings: settings(),
sessionDir: await tempDir(),
actor: { actorId: "user:1", taskId: "t", scopeIds: [] },
externalUsage: () => {
throw new Error("子会话用量读取失败 passkey=abcdef123456");
},
});
opened.push(run);
const out = await run.prompt();
expect(out).toMatchObject({ status: "completed", finalText: "结果:true" });
expect(reports).toEqual([
expect.objectContaining({
runId: "run-report",
source: "hook:beforeTool",
message: expect.stringContaining("子会话用量读取失败"),
}),
]);
expect(reports[0]?.message).not.toContain("abcdef123456");
});
});