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:
1 parent
4cd9ec8390
commit
a00a9eaf5d
14 files changed
+205
-21
No files matched your search
@@ -59,6 +59,9 @@ export const INVESTIGATE_CAPABILITIES = [
|
||||
"release.inspect",
|
||||
"inventory.query",
|
||||
"task.inspect",
|
||||
"download.inspect",
|
||||
"decision.list",
|
||||
"subscription.list",
|
||||
"identity.resolve",
|
||||
] as const;
|
||||
/**
|
||||
@@ -266,7 +269,8 @@ function systemPromptFor(
|
||||
if (sub.canSpawn)
|
||||
constraints.push(
|
||||
`可以用 agent.spawn 把彼此独立的有界子目标交给子会话(同时最多 ${sub.maxConcurrent} 个,派生层数上限 ${sub.maxDepth});` +
|
||||
"子会话的能力、模式与预算都不会超过本会话,且会占用本会话预算;派生后用 agent.await 等结果再汇总,不要为小事派生。",
|
||||
"子会话的能力、模式与预算都不会超过本会话,且会占用本会话预算;budget 的每一项都必须小于本会话剩余预算(超过会被拒绝,不给则按剩余的一半划拨);" +
|
||||
"派生后用 agent.await 等结果再汇总,不要为小事派生。",
|
||||
);
|
||||
if (scope.capabilities.includes("web.search"))
|
||||
constraints.push(
|
||||
@@ -506,6 +510,8 @@ export async function drive(
|
||||
} finally {
|
||||
clearInterval(watch);
|
||||
signal.removeEventListener("abort", abortOnce);
|
||||
// 挂起等待(审批 / 选方案 / 子会话)时没有 run_completed 事件:把已用累计写回,等待期间界面照样显示 token / 费用
|
||||
await persistUsage(ctx, runId, handle.stats().usage).catch(() => undefined);
|
||||
await liveRun.stop();
|
||||
await handle.close();
|
||||
}
|
||||
@@ -543,6 +549,17 @@ export async function drive(
|
||||
}
|
||||
}
|
||||
|
||||
/** 用量累计写回 agent_run.usage:按字段取较大值(重开会话后运行时计数可能从 0 开始,不能倒退) */
|
||||
export async function persistUsage(ctx: ServerContext, runId: string, usage: Record<string, unknown>) {
|
||||
const nums = Object.fromEntries(Object.entries(usage).filter(([, v]) => typeof v === "number" && v > 0));
|
||||
if (Object.keys(nums).length === 0) return;
|
||||
await sql`
|
||||
update agent_run set usage = coalesce(usage, '{}'::jsonb) || (
|
||||
select coalesce(jsonb_object_agg(k, greatest(v::float8, coalesce((usage->>k)::float8, 0))), '{}'::jsonb)
|
||||
from jsonb_each_text(${JSON.stringify(nums)}::jsonb) as n(k, v))
|
||||
where id = ${runId}`.execute(ctx.db);
|
||||
}
|
||||
|
||||
/** 等待目标关联的下载/入库落定:返回报告(state≠waiting)或 null(继续等) */
|
||||
export async function goalSettled(env: AgentEnv, runId: string, modelText: string): Promise<AgentGoalReport | null> {
|
||||
const r = await buildGoalReport(env.ctx.db, runId, modelText);
|
||||
|
||||
Reference in new issue
Block a user