feat(agent): budget reminders, usage while waiting, budget-exceeded status and dedupe

- tool results carry a budget reminder near limits; text before the last tool
  call is dropped as report when cut by budget/abort
- usage persisted when a run suspends, so waiting sessions show spent tokens
- task page shows budget_exceeded via business.outcome instead of completed
- budget-exceeded decisions merge by root cause (decision / schedule / goal
  template); new summaries added to investigate allowlist

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 01:58:28 +08:00
1 parent 4cd9ec8390
commit a00a9eaf5d
14 files changed
+205 -21

No files matched your search

+28
View File
@@ -270,6 +270,34 @@ describe("PiAgentRuntime + FakeModelProvider", () => {
expect(fx.audits.filter((a) => a.capabilityId === "inventory.query")).toHaveLength(2);
});
it("预算将尽:工具结果追加【预算提醒】;被预算截断时中间过程文字不作为汇报", async () => {
const fx = makeFixture();
const loop: FakeScript = {
match: "Dune",
steps: Array.from({ length: 10 }, () => ({
text: "Let me check the inventory again.",
toolCalls: [{ capability: "inventory.query", input: { title: "Dune" } }],
})),
finalText: "不该到这里",
};
const { run, fake } = await start(fx, [loop], { settings: { maxToolCalls: 4 } });
const out = await run.prompt();
expect(out).toMatchObject({ status: "failed", reason: "budget", budget: "tool_calls", finalText: "" });
expect(fake.lastRequest?.transcript).toContain("【预算提醒】工具调用只剩 2 次");
});
it("预算:收尾文字在最后一次工具调用之后 → 被截断时仍保留为汇报", async () => {
const fx = makeFixture();
const script: FakeScript = {
match: "Dune",
steps: [{ toolCalls: [{ capability: "inventory.query", input: { title: "Dune" } }] }],
finalText: "小结:库里没有 Dune。",
};
const { run } = await start(fx, [script], { settings: { maxToolCalls: 5 } });
const out = await run.prompt();
expect(out).toMatchObject({ status: "completed", finalText: "小结:库里没有 Dune。" });
});
it("预算:运行时长超限 → run_failed(budget/duration)", async () => {
const fx = makeFixture({ slowMs: 5_000 });
fx.grants.set("user:1", new Set(["download.submit"]));