diff --git a/apps/api/src/app/agent-runner.ts b/apps/api/src/app/agent-runner.ts index 4a71712..7dad669 100644 --- a/apps/api/src/app/agent-runner.ts +++ b/apps/api/src/app/agent-runner.ts @@ -70,15 +70,40 @@ export function createAgentRunner(deps: AgentRunnerDeps): { .join('\n'); const model = await deps.modelOf(taskId); - const result = await deps.gateway.generate({ - model, - prompt, - outputSchema: ACTION_SCHEMA, - taskId, - label: 'loop-turn', - }); - - return normalize(result.object, enabled); + try { + const result = await deps.gateway.generate({ + model, + prompt, + outputSchema: ACTION_SCHEMA, + taskId, + label: 'loop-turn', + }); + return normalize(result.object, enabled); + } catch (error) { + /* + * 模型这一轮问不出结果(端点不可达、鉴权失败、超时…)。 + * + * 这**不是**异常路径,是断点的一种:机器当下决断不了,就该调人 —— + * 「人在环是一种被调用的工具」。抛出去只会让任务挂着空转: + * 推进器每一拍都重试、每一拍都记一行错,预算照烧,负责人却看不到任何东西。 + * 世界一步都没碰,交回给人是安全且诚实的落点。 + */ + const detail = error instanceof Error ? error.message : String(error); + return { + kind: 'human', + itemId: enabled.entries[0]?.id ?? 'breakpoint', + workOrder: 'execution', + card: { + title: '模型这一轮未能决断,转人裁断', + reason: detail, + // 缺口是**依据**不是表单项:摆给人看,别让人逐条填 + gaps: enabled.gaps.join('、') || '(无)', + enabled: enabled.entries.map((e) => `${e.id} ${e.name}`).join('、') || '(空)', + ask: '请判断下一步:接着走哪一项,或补齐缺口后让环继续。', + fields: ['decision'], + }, + }; + } }, }; }