mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
fix(workflow): agent dead 带 reason/detail + prompt 加压 StructuredOutput
12 agent audit workflow 8 个 dead,journal 只记 {kind:"dead"} 无信息,
事后无法区分 "agent 没产 StructuredOutput" vs "runAgent 抛错"。
证据指向主因:sonnet 长 tool chain 后忘记调 StructuredOutput,
extractStructuredOutput 返回 null 即降级 dead。
- types.ts: AgentRunResult.dead 加可选 reason/detail 字段
(no-structured-output / runagent-threw / worktree-failed / unknown)
兼容旧 journal(均 optional)。
- claudeCodeBackend.ts: 三处 dead 填 reason + detail;
no-structured-output 把 finalized 文本前 200 字符做 detail,
让日志/面板能立刻看到 agent 最后说了什么。
- claudeCodeBackend.ts: schema 模式 prompt 首尾各放一次
StructuredOutput 强制要求,针对 sonnet 长 tool chain 后忘记收尾。
- hooks.ts: retry 日志带 reason;retry 仍 throw 时降级 dead 也填
reason=runagent-threw + detail。
- types.test.ts: 加 reason JSON 往返 + 旧 journal 兼容测试。
Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
@@ -19,6 +19,28 @@ test('AgentRunResult skipped/dead 分支可 JSON 往返', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// dead 携带可选 reason/detail:journal 持久化后能保留死因,事后审计/面板展示用。
|
||||
test('AgentRunResult dead 带 reason/detail 可 JSON 往返', () => {
|
||||
const dead = {
|
||||
kind: 'dead' as const,
|
||||
reason: 'no-structured-output' as const,
|
||||
detail: 'finalize content has no StructuredOutput tool_use or JSON text',
|
||||
}
|
||||
const round = JSON.parse(JSON.stringify(dead))
|
||||
expect(round).toEqual(dead)
|
||||
expect(round.kind).toBe('dead')
|
||||
expect(round.reason).toBe('no-structured-output')
|
||||
})
|
||||
|
||||
// 兼容旧 journal:reason/detail 都可选,缺失时仍是合法 dead。
|
||||
test('AgentRunResult dead 无 reason 仍合法(兼容旧 journal)', () => {
|
||||
const legacy = { kind: 'dead' as const }
|
||||
const round = JSON.parse(JSON.stringify(legacy))
|
||||
expect(round.kind).toBe('dead')
|
||||
expect(round.reason).toBeUndefined()
|
||||
expect(round.detail).toBeUndefined()
|
||||
})
|
||||
|
||||
test('JournalEntry 形状稳定', () => {
|
||||
const entry = {
|
||||
key: 'abc123',
|
||||
|
||||
@@ -166,12 +166,16 @@ export function makeHooks(
|
||||
// 都给一次重试机会;WorkflowAbortedError(kill)不重试——是用户意图。
|
||||
// 重试仍失败:dead 保持 dead;throw 降级为 dead(不让一个 agent 击穿 workflow)。
|
||||
// budget 不重复扣:dead 不 addOutputTokens;重试 ok 才扣一次(最终 ok 时)。
|
||||
// dead.reason 透传到日志(审计 8/12 dead 都是 no-structured-output 时直接可见)。
|
||||
let result: AgentRunResult
|
||||
try {
|
||||
result = await invokeBackend()
|
||||
if (result.kind === 'dead') {
|
||||
ctx.ports.logger.warn?.(
|
||||
`agent "${label ?? `#${agentId}`}" returned dead; retrying once`,
|
||||
`agent "${label ?? `#${agentId}`}" returned dead` +
|
||||
(result.reason ? ` (${result.reason})` : '') +
|
||||
(result.detail ? `: ${result.detail.slice(0, 150)}` : '') +
|
||||
'; retrying once',
|
||||
)
|
||||
result = await invokeBackend()
|
||||
}
|
||||
@@ -185,7 +189,11 @@ export function makeHooks(
|
||||
} catch (e2) {
|
||||
if (e2 instanceof WorkflowAbortedError) throw e2
|
||||
// 重试仍抛:降级 dead(保持 workflow 继续;hooks.agent 返 null)
|
||||
result = { kind: 'dead' }
|
||||
result = {
|
||||
kind: 'dead',
|
||||
reason: 'runagent-threw',
|
||||
detail: (e2 as Error).message,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.kind === 'ok') {
|
||||
|
||||
@@ -33,7 +33,13 @@ export type AgentProgressUpdate = {
|
||||
toolCount: number
|
||||
}
|
||||
|
||||
/** AgentRunner 返回。ok 变体携带 model/toolCount 供面板展示(可选,独立后端可不填)。 */
|
||||
/**
|
||||
* AgentRunner 返回。ok 变体携带 model/toolCount 供面板展示(可选,独立后端可不填)。
|
||||
*
|
||||
* dead 携带可选 reason/detail:journal 历史只记 `{kind:"dead"}` 无信息,
|
||||
* 调试时无法区分"agent 跑完没产 StructuredOutput"还是"runAgent 抛错"。
|
||||
* reason 让 hooks 重试日志、面板、事后审计能立刻看到死因。
|
||||
*/
|
||||
export type AgentRunResult =
|
||||
| {
|
||||
kind: 'ok'
|
||||
@@ -47,7 +53,23 @@ export type AgentRunResult =
|
||||
tokenCount?: number
|
||||
}
|
||||
| { kind: 'skipped' }
|
||||
| { kind: 'dead' }
|
||||
| {
|
||||
kind: 'dead'
|
||||
/**
|
||||
* 死因分类,方便日志聚合 / 事后审计。可选以兼容旧 journal。
|
||||
* - no-structured-output:agent 完成但 finalize content 无 StructuredOutput(既没调工具也没在文本里产 JSON)
|
||||
* - runagent-threw:runAgent 抛非 abort 错误(API 故障 / context 溢出 / runtime 错误)
|
||||
* - worktree-failed:isolation:'worktree' 创建失败(fail-closed 退化)
|
||||
* - unknown:未分类(兼容旧 backend / 第三方 adapter)
|
||||
*/
|
||||
reason?:
|
||||
| 'no-structured-output'
|
||||
| 'runagent-threw'
|
||||
| 'worktree-failed'
|
||||
| 'unknown'
|
||||
/** 详细信息(错误 message / 文本预览),用于日志,不展示给最终用户。 */
|
||||
detail?: string
|
||||
}
|
||||
|
||||
/** journal 中单条记录。seq = agent() 调用序号,read() 据此重排以稳定 resume。 */
|
||||
export type JournalEntry = {
|
||||
|
||||
Reference in New Issue
Block a user