Repository navigation
[PR] fix(pi-subagents): watchdog timeout no longer falsely reports completed; empty reply diagnostics include upstream termin | fix(pi-subagents): watchdog 超时不再误报 completed;空回复诊断带上游终态 - #4411
Conversation
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.7200-pr.4411.5ea79e8This exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr4411.tgz
npm install -g packageInstall instructions are refreshed on each push. |
…; empty-reply diagnostics carry upstream terminal state (#4359) Two defects in the async delegate lifecycle: 1. Status misclassification. When the watchdog killed a non-settled child (code===null, run.timedOut set), partial progress text or stderr alone coerced effectiveExitCode to 0 and the run was marked "completed" — the parent got a success notification with no deliverable. New pure rule classifyDelegateOutcome: "completed" requires a successful effective exit AND positive completion evidence — a real non-null exit code without a watchdog timeout, OR agent_settled observed. The settled exception is kept (delivered output whose process hung in teardown/EOF still completes); with agentSettled=false the rule projects exactly onto the sync ternary, so async/sync semantics are aligned. Behavior change: watchdog kills, external null-code closes, and spawn errors of non-settled runs now report FAILED (artifacts kept for inspection) instead of completed when partial output or stderr exists. 2. Empty-reply diagnostics. Exit-0 empty replies previously got one generic "likely a thinking-only stop" message. The event applier now captures the last assistant message_end terminal state (stopReason/rawStopReason/ errorMessage + text/thinking block census + that turn's usage); the failure body, result-file backfill, and warn log carry an "upstream terminal state:" line separating upstream error/incomplete from completed-but-empty. Unobserved renders as unknown; print mode says unavailable. NO_FINAL_OUTPUT_BODY no longer asserts a cause itself. pi-subagents 0.2.2 -> 0.2.3 (+ root lockfile line). Regression coverage: terminal-state matrix incl. the two misclassified rows, async/sync parity projection, message_end extraction on issue A/B/C shapes, applier last-wins capture, formatter rendering. README: timeout-is-failure semantics plus a nested inner/outer budget-conflict hint.
…-tool.test.ts (found while working #4359) Six latent type errors the test file carried before this PR (test files are not strict-typechecked by CI, so they stayed invisible): - injectedWaitMessage's run parameter typed result?: { file: string } while the runtime reads run.result?.file truthy-safely and the test named "tolerates a missing file" deliberately passes result: {} — loosen to result?: { file?: string } (type-only, backward compatible). - Four local run literals in the buildWaitResult/buildCancelResult usage tests lacked usageReported, which the tests then assert flips to true — seed usageReported: false. - The wait-timeout integration test called cancelTool.execute with 2 args against the 5-arg Tool.execute signature — pass the same trailing (undefined, undefined, ctx) its sibling wait call passes. Strict tsc on the file now exits 0; all 108 delegate-tool tests pass.
ddc9d57 to
0572473
Compare
…uires (#4599) The #5140 recovery test proves forkSnapshot reconstructs a non-retained parent's raw tail from the UNIFIED index — but it never pinned its own storage mode. Under the CI storage baseline (preload-storage-baseline.mjs sets BILI_STORAGE_UNIFIED=legacy for the broad corpus) no unified rows exist, so the reconstruction has no source and forkSnapshot fails closed with 409 SNAPSHOT_UNAVAILABLE. Deterministic, not a flake: reproduces 100% on master and any merged branch with BILI_STORAGE_UNIFIED=legacy, passes without it (default shadow). Pin full like the sibling persist tests and restore the ambient value afterwards.
…d was a stale-base artifact, see #4411)
…ocol: carries #5140 storage-mode pin)
(ai: vllm-qwen/qwen3.8-27b) 查重: 不重复。 watchdog 误报 completed / 空回复诊断两条签名在 open+closed issue、PR 中均无同机制条目;最接近的 #2832(已关,preflight 摘要探针路径)与 #3745(acp_delegate_wait 展示层)子系统不同。本 PR 即 #4359 的修复 PR(floor 2 有完整分析记录)。 标签: bug / pi / scope/wiring / p2——pi 车道子代理缺陷,协议接线层,有绕行(结果文件仍可人工检查)。 现状核查(本 PR 为此前 agent 工作,未重做,只核分支与 CI):
kernel-guard 红 = 陈旧基线假阳性,不是本 PR 改了内核:取证(job 日志 2026-10-26T08:17Z)显示触发时 master 还落后于分支经 #4599 ci-fixes sweep 带进的三处内核变更( 正在做:把最新 master + ci-fixes 合进分支推送重触发 CI(master 自身目前在若干 test 分片上红的在档继承家族见 #4599;合入 ci-fixes 会带入 #5140 存储模式钉,对应分片预期转绿,剩余 Windows 家族项与本 PR 无关)。完成后回帖八问结论。 |
(ai: vllm-qwen/qwen3.8-27b) 收工报告(八问)
行为变更披露(PR 正文已载,复核确认):未 settle 的 run 上,watchdog kill / 外部 null-code 关闭 / spawn 错误且带部分文本或 stderr 的情形,现在报 CI 状态:新 head 已触发(90 checks,其中 47 queued——舰队 runner 队列积压在档见 #4599,43 cancelled 为被取代的旧 run)。预期开跑后静态门禁全绿;test 分片若命中在档继承家族(#5140 已被带入的钉覆盖;Windows 家族项待 owner 裁决,与本 PR 无关)按 #4599 协议处置。已在 #4599 留一行同步记录。 判定:分支侧就绪(kernel-guard 假阳性已消除、本地两层测试全绿),待 CI 矩阵消化完队列确认绿后即可合并——合并动作在你手里:#4411 |
Closes #4359
修了什么
1. watchdog 超时被误报为 completed(状态误判)
旧行为:异步子进程被 watchdog 杀掉(
code===null、run.timedOut置位)时,只要还有任何非空进度文本或 stderr,effectiveExitCode就会强制为 0,随后run.status = effectiveCode === 0 && !noOutput ? "completed" : "failed"→ 部分输出 alone 就能把 run 标成completed,父 agent 收到成功通知但没有任何交付物。新行为:新增纯函数
classifyDelegateOutcome(code, timedOut, agentSettled, output, stderr)。completed要求有效退出码成功且有正向完成证据:真实非 null 退出码且无 watchdog 超时,或观察到agent_settled。watchdog kill 只在 agent 未 settle 时记timedOut(onKill/onEofGrace由agentSettled门控),因此 timeout + 未 settle = 一律 FAILED,与有无部分文本/stderr 无关。settled 例外保留:已交付输出但进程在 teardown/EOF 挂起(
code===null、agentSettled=true、timedOut未置位)仍算 completed。agentSettled=false时该规则与同步路径三元式(timedOut → failed)逐行投影一致,async/sync 语义对齐。行为变更披露:未 settle 的 run 上,watchdog kill / 外部 null-code 关闭 / spawn 错误且带有部分文本或 stderr 的情形,现在报告
FAILED ⚠️(旧值:completed)。结果文件与活动日志不变,仍可人工检查。这正是 issue 报告的误判场景。2. 空回复诊断区分上游 error/incomplete 与 completed-empty
事件 applier 捕获最后一条 assistant
message_end的终态:stopReason/rawStopReason/errorMessage(含error字段回退)+ text/thinking 块计数 + 该轮 usage。失败正文、结果文件回填、warn 日志各追加一行upstream terminal state: stopReason=…[, rawStopReason=…][, errorMessage=…] , text_blocks=N, thinking_blocks=N[, , last_turn_usage(in=A, out=B)];未观测字段渲染unknown,print 模式(无事件流)渲染unavailable。NO_FINAL_OUTPUT_BODY不再断言 "likely a thinking-only stop"(那只是可能原因之一),原因判断交给诊断行。响亮失败的行为本身不变。测试
tests/delegate-no-output.test.ts:11 行终态矩阵(含两行此前误判)+ async/sync 一致性投影断言。tests/events.test.ts:issue 中 A/B/C 三种 message_end 形状提取、terminal-only 事件、非 assistant 跳过;顺手把该文件 4 处既有坏类型断言换成收窄断言。tests/delegate-event-applier.test.ts:applier last-wins 捕获、terminal-only 事件不触发 onUsage。tests/delegate-tool.test.ts:formatter 渲染 + 基础文案回归。第二个 commit(顺手小修,独立标注)
做 #4359 时发现
tests/delegate-tool.test.ts存在 6 处既有 strict-tsc 错误(测试文件从不被 CI strict typecheck,所以一直不可见):injectedWaitMessage参数result?: { file: string }过窄(运行时按 truthy 安全读取,测试名就叫 "tolerates a missing file")→ 放宽为file?: string(仅类型、向后兼容);4 个本地 run 字面量缺usageReported→ 补false;wait-timeout 集成测试以 2 参调用 cancelTool.execute → 对齐兄弟调用补(undefined, undefined, ctx)。该文件 strict tsc 现退出 0,108 个 delegate-tool 测试全过。版本
pi-subagents 0.2.2 → 0.2.3(Pi-Subagents Boundary 要求);根 package-lock.json 连带一行。
范围外(另行处理)