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:
1 parent
74c2ed45aa
commit
cf2c4ff0e6
14 files changed
+1360
-26
No files matched your search
@@ -9,6 +9,7 @@ import {
|
||||
} from "@umt/agent";
|
||||
import {
|
||||
type AgentBudget,
|
||||
type AgentPlanStep,
|
||||
type AgentRunEvent,
|
||||
type AgentRunMode,
|
||||
type AgentRunStatus,
|
||||
@@ -84,14 +85,22 @@ export interface AwaitWake {
|
||||
fact: string;
|
||||
}
|
||||
|
||||
/** agent.await / agent.spawn{await:true} 挂起请求:runAgent 据此 waitForFact,落定后以 resumedAwaits 重开会话 */
|
||||
/**
|
||||
* 会话挂起请求:runAgent 据此 waitForFact,条件满足后以 resumedAwaits 重开会话并重放挂起的调用。
|
||||
* - subagents(缺省):agent.await / agent.spawn{await:true} 等子会话落定;
|
||||
* - options:agent.propose_options 等用户选方案(decisionId);
|
||||
* - approval:需审批的能力等管理员批准 / 拒绝(approvalId)。
|
||||
*/
|
||||
export interface AwaitSuspension {
|
||||
kind?: "subagents" | "options" | "approval";
|
||||
/** 挂起的工具调用(恢复时重放,同一 invocationId 不再挂起) */
|
||||
invocationId: string;
|
||||
/** 等待的子会话;null = 本会话全部子会话 */
|
||||
runIds: string[] | null;
|
||||
/** 超过这个时间(ISO)不再等,恢复后返回当前状态(未结束的标 pending) */
|
||||
/** 超过这个时间(ISO)不再等,恢复后返回当前状态(未结束的标 pending / 按超时处理) */
|
||||
deadline: string;
|
||||
decisionId?: string;
|
||||
approvalId?: string;
|
||||
}
|
||||
|
||||
export interface LiveRun {
|
||||
@@ -102,6 +111,8 @@ export interface LiveRun {
|
||||
pump(): Promise<void>;
|
||||
/** 子孙会话已用量(同步读缓存,pump 刷新) */
|
||||
externalUsage(): ExternalUsage;
|
||||
/** agent.plan 的当前步骤清单(首次用到时从事件流载入) */
|
||||
plan?: { steps: AgentPlanStep[]; revision: number } | null;
|
||||
}
|
||||
|
||||
const live = new Map<string, LiveRun>();
|
||||
@@ -638,17 +649,41 @@ export async function wakeParent(ctx: ServerContext, childRunId: string): Promis
|
||||
}
|
||||
}
|
||||
|
||||
/** 唤醒挂起中的会话自身(用户选了方案 / 审批已决定):按 await_wake 发引擎信号;没挂起时什么也不做 */
|
||||
export async function wakeRun(
|
||||
ctx: ServerContext,
|
||||
runId: string,
|
||||
payload: Record<string, string | boolean>,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const r = await ctx.db.selectFrom("agent_run").select("await_wake").where("id", "=", runId).executeTakeFirst();
|
||||
const w = r?.await_wake as AwaitWake | null | undefined;
|
||||
if (!w?.engineRunId || !w.fact) return false;
|
||||
await ctx.engine.signal(w.engineRunId, w.fact, payload);
|
||||
return true;
|
||||
} catch (err) {
|
||||
ctx.logger.warn({ runId, err: { message: (err as Error).message } }, "唤醒会话失败(会话会兜底轮询)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 子会话还没落定时请求挂起:记下等待条件并关闭会话(本调用在 JSONL 里保持未完成),drive 看到 requested 后返回
|
||||
* suspended。同一批里已有别的调用在挂起时不再重复登记。
|
||||
*/
|
||||
async function requestSuspension(live: LiveRun, invocationId: string, runIds: string[] | null, timeoutSec: number) {
|
||||
if (!live.suspend || live.suspend.requested) return;
|
||||
live.suspend.requested = {
|
||||
await suspendRun(live, {
|
||||
kind: "subagents",
|
||||
invocationId,
|
||||
runIds,
|
||||
deadline: new Date(Date.now() + timeoutSec * 1000).toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** 登记挂起条件并关闭会话(挂起的调用在 JSONL 里保持未完成);同一批里已有挂起时不再重复登记 */
|
||||
export async function suspendRun(live: LiveRun, sus: AwaitSuspension): Promise<void> {
|
||||
if (!live.suspend || live.suspend.requested) return;
|
||||
live.suspend.requested = sus;
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await live.handle.close();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user