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

@@ -229,6 +229,8 @@ describe.skipIf(!hasPg)("E12 Agent 交互:步骤清单 / 方案选择 / 内联
"挂起等待方案",
);
expect(waiting.summary).toBe("等待选择方案");
// AG-3:挂起时没有 run_completed,已用累计照样写回(等待期间界面显示已用 token / 费用,而不是"未报告")
expect(Number((waiting.usage as Any).totalTokens ?? 0)).toBeGreaterThan(0);
const decision = await e.db
.selectFrom("decision")
.selectAll()
+8 -1
View File
@@ -427,7 +427,14 @@ describe.skipIf(!hasPg)("W9-I 进程与健康(真实 PG)", () => {
const list = await json(await admin("GET", "/engine/runs?from=2026-09-10T00:00:00Z&to=2026-09-11T00:00:00Z"));
const by = new Map(list.data.items.map((r: { id: string; business: unknown }) => [r.id, r.business]));
expect(by.get("b1")).toEqual({ kind: "feed", refId: feedId, title: "HDSky RSS", triggeredBy: "event" });
expect(by.get("b2")).toEqual({ kind: "agent", refId: agentId, title: "巡检:待处理", triggeredBy: "schedule" });
expect(by.get("b2")).toEqual({
kind: "agent",
refId: agentId,
title: "巡检:待处理",
triggeredBy: "schedule",
// AG-3:Agent 会话带业务结果(任务页据此显示超出预算 / 失败,而不是引擎的「完成」)
outcome: "queued",
});
expect(by.get("b3")).toEqual({
kind: "organization",
refId: "00000000-0000-0000-0000-0000000000aa",