Skip to content

功能规格说明:打印模式 ​

创建日期:2026-06-09

用户场景与测试 (必填) ​

用户故事:干净的仅响应输出(优先级:P0) ​

作为运行 wave -p 'message' 的用户,我希望只看到主 agent 的响应文本,以便输出适合管道传递给其他命令和脚本。

为什么是这个优先级:这是打印模式的基本目的——机器可读的干净输出。没有它,-p 无法用于脚本。

独立测试:运行 wave -p 'hi' 并验证只有助手的响应文本被打印到 stdout,没有子 agent 提示、文件列表或内部管理输出。

验收场景:

  1. 假设通过 wave -p 'hi' 的打印模式,当 agent 响应时,则只有主 agent 的内容被写入 stdout。
  2. 假设打印模式,当主响应后自动记忆提取子 agent 运行时,则没有子 agent 输出(系统提示、文件清单、推理或内容)出现在 stdout 上。
  3. 假设打印模式,当 agent 使用 Agent 工具时,则不打印子 agent 的用户消息或助手流式输出——只有主 agent 的工具块指示器和最终响应。

用户故事:流式进度指示器(优先级:P2) ​

作为在终端中运行 wave -p 的用户,我希望看到轻量级的进度指示器(推理、工具调用),以便我知道 agent 正在工作,即使子 agent 内部被抑制。

为什么是这个优先级:增强用户体验,但不是核心功能所必需的。

独立测试:运行 wave -p 'do something complex' 并验证推理标题、工具块名称和主 agent 的响应内容被显示。

验收场景:

  1. 假设打印模式带有复杂提示,当主 agent 在响应前推理时,则打印 💭 Reasoning: 标题后跟推理文本。
  2. 假设打印模式,当主 agent 调用工具时,则打印 🔧 <tool_name> <compactParams> 行。
  3. 假设打印模式,当错误块被添加时,则打印 ❌ Error: <message> 行。

用户故事:与 Claude Code 行为兼容(优先级:P1) ​

作为从 Claude Code 迁移的用户,我希望 wave -p 的行为与 claude -p 一致,以便现有脚本和期望无需更改即可工作。

为什么是这个优先级:与参考实现的一致性确保平滑的迁移路径。

独立测试:比较 claude -p 'hi' 和 wave -p 'hi' 的输出——两者都应该只显示主 agent 的响应文本。

验收场景:

  1. 假设 claude -p 和 wave -p 两者,当使用相同提示时,则两者都不打印子 agent 内部输出(用户消息、系统提示、文件清单)。
  2. 假设两个工具,当分叉/后台 agent(如记忆提取)运行时,则其输出完全静默——永远不会发送到 stdout。
  3. 假设两个工具,当常规 Agent 工具子 agent 运行时,则子 agent 的结果作为 tool_result 返回给主 agent 并纳入主 agent 的最终响应,不单独打印。

用户故事:等待后台工作及主 agent 收尾完成后再退出(优先级:P0) ​

作为在打印模式下由主 agent 启动后台子 agent 的用户,我希望 wave -p 等待所有后台子 agent 完成、并且主 agent 处理完所有完成通知(产出最终响应)之后才退出,以便最终结果不会丢失。

为什么是这个优先级:后台子 agent 的结果必须通过主 agent 的收尾 turn 纳入最终响应;过早退出会销毁主 agent 进行中的 turn 并丢失结果,等同于功能不可用。

独立测试:运行 wave -p 让主 agent 以后台方式启动多个子 agent,验证所有子 agent 完成、主 agent 输出包含各子 agent 结果的最终响应后进程才退出。

验收场景:

  1. 假设打印模式下主 agent 启动多个后台子 agent 并结束当前 turn,当所有子 agent 陆续完成时,则主 agent 处理完全部完成通知并输出最终响应后进程才退出。
  2. 假设最后一个后台任务状态已变为完成、但主 agent 仍在处理通知的 turn 中(isLoading),当退出等待循环检查时,则不得退出也不得 destroy agent。
  3. 假设消息队列中仍有待处理的通知(主 agent 空闲但通知未消费),当退出等待循环检查时,则不得退出。
  4. 假设打印模式下启动单个后台子 agent,当子 agent 完成时,则主 agent 的收尾 turn 完整输出后才退出,不被 500ms 轮询窗口截断。

用户故事:结构化输出(优先级:P0) ​

作为在 CI 流水线里跑 wave -p 的用户,我希望能拿到机器可读的结果(最终文本、用量、工具调用序列、失败与否),以便按「发现数量 / 严重度」判定门禁,而不是正则解析带 emoji 的人读输出。

为什么是这个优先级:-p 的纯文本输出是给人看的,退出码只有 0/1,CI 里任何判定都只能靠脆弱的文本匹配;自动化流水线集成的所有后续能力(状态上报、审查门禁、质量报告)都依赖这一层。

独立测试:wave -p '列出当前目录' --output-format json | jq -r '.result' 得到助手响应的完整文本;把同一提示换成 --output-format text,输出与改动前逐字节一致。

验收场景:

  1. 假设 --output-format json,当 一次运行正常结束时,则 stdout 上是单条 JSON 对象,字段含 type: "result"、subtype、is_error、result(最后一次助手消息的纯文本)、session_id、num_turns、duration_ms、usage、model_usage、tool_calls,除此之外 stdout 上没有任何其他字符。
  2. 假设 --output-format json,当 运行中出现可恢复的错误块(工具 hook 报错、压缩历史失败等),且主 agent 照常产出了正文时,则 该错误进入 errors 数组(仅非空时出现),subtype 仍为 success、is_error 为 false、退出码 0——可恢复错误不等于本次运行失败;工具执行失败同理,它记在 tool_calls 条目的 success 上。
  3. 假设 --output-format json,当 运行本身失败(建 agent 或发送消息抛错)时,则 stdout 上仍输出一条 subtype: "error_during_execution"、is_error: true 的 result 对象——不带 result 字段,失败原因放在必填的 errors 里(与 Claude Code 的错误版一致),进程退出码为 1。
  4. 假设 任一格式,当 运行结束时错误块非空、且主 agent 一句话正文都没产出(典型来源:模型不可用、鉴权被拒——这类请求级错误不抛出,只落一个错误块)时,则 判为运行失败:subtype: "error_during_execution"、is_error: true、退出码 1(纯文本模式同样按此退出码收尾)。
  5. 假设 --output-format stream-json,当 运行时,则 stdout 每行一个 JSON 事件,首行为 system/init(含 session_id、model、cwd、permission_mode、tools),其后按发生顺序是 assistant/reasoning_delta、assistant/content_delta、tool/start、tool/end、error,最后一行为与场景 1 结构相同的 result 事件。
  6. 假设 --output-format stream-json,当 建 agent 阶段已经写出了一个事件(真机路径:-r 给了非法 session id,初始化在抛错前先落一个错误块)时,则 该事件不抢先写出——agent 建成功时它被押后到 system/init 之后、system/init 仍是首行;建 agent 直接失败、根本拿不到 session_id / model 时,流里没有 system/init,先出该事件、末行是 result。消费方不得把「读到 init」当作流已开始的唯一判据。
  7. 假设 --output-format stream-json,当 流式内容到达时,则 文本事件携带的是增量(delta),不是累积快照,消费方拼接即得完整文本。
  8. 假设 任一结构化格式,当 进程退出时,则 stdout 上已排队的 JSON 全部写完(管道消费方不会读到截断的末行)。
  9. 假设 任一结构化格式,当 有代码往 stdout 写了非 JSON 的行(依赖库的 console.log、遗漏的调试输出)时,则 该行被转到 stderr 并带标记前缀,stdout 上的 NDJSON 流不被打断。
  10. 假设 不指定 --output-format(默认 text),当 运行时,则 输出与既有纯文本行为完全一致:推理/响应标题、🔧 工具行、❌ 错误行、--show-stats 的用量与耗时汇总都照旧写在 stdout。
  11. 假设 结构化格式搭配 --show-stats,当 运行时,则 用量汇总与耗时写到 stderr,stdout 保持纯 JSON。
  12. 假设 交互模式(没有 -p)下给了 --output-format,当 解析参数时,则 报错并以退出码 1 结束(该选项只对打印模式有意义)。

边界情况 ​

  • 主 agent 的响应为空怎么办? 仅打印一个换行符;结构化模式下 result 为空字符串。
  • 子 agent 出错怎么办? 错误作为 tool_result 传播到主 agent;主 agent 决定如何在其响应中报告。不直接打印子 agent 错误。
  • 并发子 agent 怎么办? 无论并发如何,所有子 agent 输出都被抑制。
  • 如果自动记忆提取在打印模式等待后台工作时运行怎么办? hasRunningBackgroundWork 循环等待它,但不打印提取输出。
  • 多个后台子 agent 错峰完成怎么办? 等待条件必须覆盖主 agent 处理通知的收尾 turn 与队列中未消费的通知,最后一个子 agent 完成不得触发提前退出。
  • 一次运行里有多次助手消息怎么办? result 取最后一条助手消息的文本,num_turns 记录主 agent 的助手消息条数;被作废(流式降级)的半截消息不计入。
  • 子 agent 的输出会进 JSON 吗? 不会——与纯文本模式同一口径,只收主 agent 的推理/内容/工具调用。
  • tool_calls 里带工具输出吗? 不带,只有 {id, name, input, success, error?};完整输出在会话 transcript 里(stdout 不能被一次 Read 撑爆)。
  • 工具参数不是合法 JSON 怎么办? input 保留原始字符串。
  • 结构化格式下 worktree 保留/删除提示怎么写? 走 stderr,不进 stdout。
  • 与 Claude Code 的 JSON 字段完全一致吗? 信封骨架对齐(type / subtype / is_error / result / session_id / duration_ms / num_turns / 错误版用 errors 而不带 result),数值口径不对齐:usage 沿用本项目的 prompt_tokens / completion_tokens / total_tokens(OpenAI 口径,prompt_tokens 已含 cache_read),不等于 CC 的 input_tokens(不含 cache);total_cost_usd 与 modelUsage.costUSD 我们没有价目表,不输出,也不填 0 假装有值。
  • stream-json 的中间事件与 CC 同构吗? 不同构。CC 发的是 Anthropic 消息本体(assistant / user 包 BetaMessage、工具调用在 content block 里),我们的数据源是 SDK 回调而非 SSE 事件流,伪造那层结构会得到永远填不满的字段。只对齐两头:首事件 system/init 与末事件 result。