feat(agent): automation-first sessions with step plan, option choice and inline approval

- agent.plan emits plan_updated; system prompt asks for a plan first and updates while executing
- agent.propose_options registers a decision and suspends the run (waitForFact, no worker slot);
  the answer or timeout (recommended/stop) resumes it as the tool result
- console-session runs get inline approvals for plan-bound capabilities: approve replays the call
  with the approval, reject fails it with the reason and records it on the decision
- proposeFirst blocks execute capabilities until an option is chosen; E12 covers all paths

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 21:07:01 +08:00
1 parent 74c2ed45aa
commit cf2c4ff0e6
14 files changed
+1360 -26

No files matched your search

+9 -2
View File
@@ -4,6 +4,7 @@ import { defineTask, type OutboxRouteFn, type TaskRegistry } from "@umt/tasks";
import { z } from "zod";
import type { ServerContext } from "../types.ts";
import { retryTask } from "./capabilities.ts";
import { interactionReady, markWaitingForUser } from "./interaction.ts";
import {
annotateRunSummary,
applyRecoveryOutcome,
@@ -90,11 +91,17 @@ export function registerAgentTasks(registry: TaskRegistry, env: () => AgentEnv)
break;
}
const waitMs = Math.max(0, Date.parse(sus.deadline) - Date.now());
// 等用户(方案选择 / 审批)与等子会话共用挂起机制;步骤名保持 subagent-await-N 以兼容已有运行的步骤历史
const user = sus.kind === "options" || sus.kind === "approval";
const fact = `subagent-await-${n}`;
const engineRunId = t.runId;
await t.step.run(`${fact}-arm`, () => setAwaitWake(e.ctx, runId, { engineRunId, fact }));
await t.step.run(`${fact}-arm`, async () => {
await setAwaitWake(e.ctx, runId, { engineRunId, fact });
if (user) await markWaitingForUser(e.ctx, runId, sus);
return null;
});
await t.step.waitForFact<true>(fact, {
check: () => awaitReady(e.ctx, runId, sus),
check: () => (user ? interactionReady(e.ctx, runId, sus) : awaitReady(e.ctx, runId, sus)),
timeout: `${AWAIT_POLL_SEC}s`,
maxRounds: Math.max(1, Math.ceil(waitMs / 1000 / AWAIT_POLL_SEC) + 1),
});