feat(agent): 选定方案即批准,服务端绑定计划(AGUI-3)

- agent.propose_options 的方案可带 action(capabilityId + planId)。提出方案时服务端按计划当前的
  payload hash + revision 绑定,并用计划本身生成动作要点(对象、大小 / 文件数)发给界面,不用模型的说法。
- 控制台管理员会话选定绑定了计划的方案时,建一份 scope.via=option 的已批准单次审批(审计 approval.approved);
  执行时授权器 optionApproval 核对能力、计划类型 + ID、payload hash、revision、有效期后放行,用后 consumed,
  不再出第二张审批卡。API Key 作答、超时自动选、计划已变化都不算批准;移动 / 替换文件的整理与发布策略
  (删除、覆盖类)选定后仍单独确认。会话停止时撤销没用掉的这类审批。
- contracts:CAPABILITY_ACTIVITY 给每个能力加 verb / noun(会话进度行的人话计数);agent.plan /
  agent.propose_options 标为 hidden。
- e12 补测试:管理员选定 → 无 approval_requested、审批绑定同一计划并 consumed、换 hash 不放行;
  API Key 选定 → 仍出审批卡。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 22:40:57 +08:00
1 parent c9c6505d71
commit 4e0e6712da
8 files changed
+507 -33

No files matched your search

+27 -1
View File
@@ -131,6 +131,18 @@ export const AgentPlanStep = z.object({
});
export type AgentPlanStep = z.infer<typeof AgentPlanStep>;
/**
* 方案绑定的待审批动作(AGUI-3):模型只给能力与计划 ID;服务端在提出方案时按计划当前的 payload hash + revision
* 绑定,并用计划本身生成动作要点(对象、大小)。管理员会话选定这个方案 = 批准这一份计划,执行时服务端再核对绑定。
*/
export const AgentOptionActionCapability = z.enum(["download.submit", "organize.execute", "policy.apply"]);
export const AgentOptionAction = z.object({
capabilityId: AgentOptionActionCapability,
/** download.submit / organize.execute 的 planId;policy.apply 的 policyRevisionId */
planId: Id,
});
export type AgentOptionAction = z.infer<typeof AgentOptionAction>;
/** 给用户选的一个方案 */
export const AgentOptionItem = z.object({
id: z.string().trim().min(1).max(40),
@@ -139,9 +151,23 @@ export const AgentOptionItem = z.object({
risks: z.array(z.string().max(300)).max(8).default([]),
/** 估算代价(体积 / 时长 / 费用),模型给出的文本,未经核查 */
estimatedCost: z.string().max(200).nullable().default(null),
/** 选定后要执行的需审批动作(可选) */
action: AgentOptionAction.nullable().optional(),
});
export type AgentOptionItem = z.infer<typeof AgentOptionItem>;
/** 事件里的方案动作:服务端从计划生成的要点;approves = 选定即批准(删除、覆盖类动作为 false,仍单独确认) */
export const AgentOptionActionView = AgentOptionAction.extend({
/** 动作名,如「提交下载」 */
label: z.string(),
/** 动作要点:对象与大小 / 数量,如「《沙丘:第二部》· 12.0 GB」 */
detail: z.string().nullable(),
approves: z.boolean(),
});
export type AgentOptionActionView = z.infer<typeof AgentOptionActionView>;
export const AgentOptionView = AgentOptionItem.extend({ action: AgentOptionActionView.nullable().optional() });
export type AgentOptionView = z.infer<typeof AgentOptionView>;
/** 超时未选时的处理:recommended = 自动选推荐项;stop = 不选,模型收尾 */
export const AgentOptionsTimeoutAction = z.enum(["recommended", "stop"]);
export type AgentOptionsTimeoutAction = z.infer<typeof AgentOptionsTimeoutAction>;
@@ -159,7 +185,7 @@ const OptionsProposed = z.object({
/** 对应的待处理事项(用户也可以在待处理中心选) */
decisionId: Id,
question: z.string(),
options: z.array(AgentOptionItem),
options: z.array(AgentOptionView),
recommendedId: z.string().nullable(),
/** 出现在哪一步(提出时正在进行的步骤) */
stepId: z.string().nullable(),