Repository navigation
feat(session): issue #46 runtime alignment — P0 result visibility, P1 recovery & prompts, P2 search & background shell - #60
yixiao5428 wants to merge 85 commits into
Conversation
- research doc: P0 assertion verification (3/3 hold), five focused investigations (tool metadata surface, MCP projection prior art, core runner consumers, prompt assembly matrix, compaction recovery inventory), decision points with options/lean/open questions - devlog with metrics for the research round Co-Authored-By: Claude <noreply@anthropic.com>
Restructure for cold readability: system data flows first (tool result lifecycle, compaction pipeline, dual runtime), then trace each P0 issue station-by-station to locate where information is dropped, then derive decision options and leans from those facts. Dense inventories move to appendix; every recommendation now carries its reasoning chain. Co-Authored-By: Claude <noreply@anthropic.com>
- problem-level comparison: ~3/4 of fork fixes have no counterpart defect in ZCode (verified per-issue with file:line evidence); ZCode shares the bounded-cycle doom-loop defect and lacks content-filter classification - capability comparison across issue #46 dimensions plus fork-only assets (agent roster/list, native worktree, willOverflow preflight) - replacement analysis: governance-first argument, three paths, lean to keep fork with ZCode as reference implementation; honest list of conditions under which full replacement would win - cross-reference from runtime-alignment research doc Co-Authored-By: Claude <noreply@anthropic.com>
…ssment New section 7: itemized estimates for restoring fork-only assets in ZCode (sec 4.2, ~2-4 weeks + permanent private-patch tax + zero test coverage in the public drop) versus completing issue #46 in the fork (~4-8 weeks, trimmable, upstream-able). Includes newly verified fact: ZCode v3.14.3 public snapshot ships 4 test files (none for the core runtime) vs the fork's 675 files / ~174k lines. Co-Authored-By: Claude <noreply@anthropic.com>
New section 8, code-verified against v3.14.3: four external-transfer paths (chunked attachment upload to execution host, session share to zcode.z.ai with public-by-default import, user-initiated feedback object-storage upload, model requests) plus the official-endpoint gateway auto-redirect carrying auth headers without per-request confirmation. Negative findings documented with evidence: OTLP telemetry has no hardcoded endpoint (env-gated, default off) and no third-party analytics SDKs. Includes route-C usage precautions and re-verification pointers for future drops. Co-Authored-By: Claude <noreply@anthropic.com>
- research doc: section 7 becomes the decision record (2026-10-12, 11 items settled; C3/E1/E1' reverse the documented leans); section 5.1 shared-section analysis corrected (ternary replace semantics are the right opt-out, shared section goes inside provider()) - new fix doc session-fix-tool-exit-code-projection.md: P0-1 shell exit-code projection per decision B2/only-non-zero/success-semantics, with root cause, ZCode comparison, 3-file change plan, test list Co-Authored-By: Claude <noreply@anthropic.com>
A non-zero bash exit only ever lived in part metadata, which the replay projection never rendered, so the model could not tell a silent exit 7 from a silent success (issue #46 P0-1). - add core helper formatExitCodeLine (finite non-zero numbers only; 0 stays silent, null timeout/abort already has output prose); the compaction serialize will reuse it for summary-path consistency (P0-3) - message-v2 completed branch appends the line after truncated output; compacted parts are not annotated - the ! direct-command path now records handle.exitCode in part metadata so the same projection covers it Read-side projection per the decided B2/only-non-zero/success-semantics resolution; UI and persistence stay byte-identical when no exit line applies. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ath) Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…odel; carry sc into error messages On the default MCP path, structuredContent never reached the model when content was non-empty (the catalog only converts it when content is empty, by upstream design), and resource_link / audio content types vanished without even a placeholder. Error results whose details lived only in structuredContent collapsed to the generic fallback message. - extract projectMcpToolResult (pure, exported for tests): resource_link renders as 'name: uri', audio degrades to an explicit placeholder, and structuredContent is appended unless a content item already carries the same data (fast path keeps catalog's empty-content conversion byte-identical; semantic path via Bun.deepEquals catches pretty-printed or reordered echoes) - extract McpCatalog.callToolErrorMessage and route both the catalog and the code-mode mirror through it, so error details in structuredContent reach the model Issue #46 P0-2 per decisions C1 (append + semantic dedup) and C3 (error path merged into this PR). Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…ine) Co-Authored-By: Claude <noreply@anthropic.com>
…ary input The summary serializer cut each completed tool result to its first 2,000 chars and never read metadata, so generic tools lost the full-output path and read-more hint (fixed at the tail of the preview) while shell lost its trailing timeout/abort diagnostics — and a non-zero exit never reached the summary at all. - add summarizeToolText to the shared projection helper: results within budget pass through byte-identical; larger ones keep head 1,400 + omission marker + tail 600, so both anchor regions survive in either layout (issue #46 P0-3, decision D1) - both serializers (opencode SessionPrompt loop and the core runner) route through the same helper; exit lines attach at the V1 call site via formatExitCodeLine; error branches and the cleared-marker branch are unchanged - serializeToolContent itself is untouched, so the pinned media-format test still passes; the head-only local truncators and their constants are removed from both copies Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The P0 fixes were verified at both ends of each chain (real process -> persisted metadata; fixture -> projected model message) but no test drove a whole chain, so the issue acceptance bar -- what the model actually receives on the wire -- had no backing. - prompt loop e2e: fake LLM issues a real bash tool call, loop continues, the captured second request body carries '(no output)' plus '[exit code: 7]' (exit 0 negative stays silent) - compaction e2e: real processCompaction with a >2,000-char tool result (tail hint + exit 7); the captured summary payload keeps the head marker, omission marker with exact count, trailing full-output hint, and '[exit code: 7]' - default MCP path e2e: real InMemory-transport server -> catalog conversion -> SessionTools.resolve execute closure; the output text is text + serialized structuredContent with clean metadata/attachments Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: |
…n F1/F2) Co-Authored-By: Claude <noreply@anthropic.com>
…rchitecture Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
After a compaction the summary is all that stands in for compacted history, so evidence the task still needs — the current on-disk versions of files this session read or edited, loaded skill bodies, the todo list — was lost unless the model happened to re-read. Adds SessionRestoration (session/restoration.ts), wired one line after the agent roster at the turn boundary and modelled on it: edge-triggered by the position of the last completed compaction part vs the last restoration sentinel part, persisted once as a synthetic text part on the last user message, deduplicated byte-for-byte. - candidates come from read/edit/skill tool parts (display.path / filepath / name) plus the todo service; sorted by recency - file candidates are re-read from disk (current version, decision F1) with a stat-based size guard; content already intact in the retained tail is skipped; missing/oversized degrade to diagnosable reference lines instead of failing - budget: max_files 5, per-file 4k tokens, total 8k tokens, all configurable under compaction.restoration (enabled default true) - apply absorbs every error into a warning so the loop never breaks; plugin extension contract deferred per decision F2 (the compacted event and this hook point are its future seam) Tests: unit coverage for buildTailText / collectCandidates / materializeCandidates (inlined, tail-duplicate, missing, oversize reference) / renderMaterials (budget degradation, max_files, sentinel, empty-to-undefined), plus idempotency (two applies -> exactly one restoration part) and model-visibility of the restored part. Issue #46 P1, design in docs/design/compaction-recovery/. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…s E1/E1'/E2) Co-Authored-By: Claude <noreply@anthropic.com>
|
@yixiao5428 你在这个 PR 里处理压缩恢复,顺带请看一下 #77,是 fermat 上一次长 run 里查出的压缩缺陷,和这里的改动在同一片代码:
建议 #77 另开 PR 做,不要再扩大本 PR 的范围;第 2 条如果本 PR 改起来顺手,可以在这里先加上"标记必须有已完成摘要"的判断。 另外看到你的修改回复了,我这边会再复审一轮。 |
…nse) Reviewer follow-up on PR #60, interacting with issue #77: when a compaction summary is still in flight (or failed, or a retry parked it on a newer user message), SessionRestoration.apply treated the marker as triggerable and would stack the restoration block on top of a full, un-compacted history — compaction had not actually taken effect. The settled test is the same predicate filterCompacted and completedCompactions already use: an assistant with summary && finish && !error whose parentID is the marker's message. This covers both settled shapes — tail-retaining (tail_start_id set) and full-summary fallback (tail_start_id undefined because there is no tail to retain); the first cut keyed on tail_start_id alone and broke the full-summary path, caught by the GAP-1 three-compaction test before commit. The summary assistant is now part of the restoration test seeds, and a new negative case covers the unsettled marker (no completed summary → no injection). The rest of #77 (summary parentID anchoring, failed-summary exclusion from model context, TUI context readout) lands as a separate PR per the reviewer's scoping. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
|
#77 的交互防御已加(
#77 其余三项(摘要 parentID 锚定标记消息、失败摘要从模型上下文与摘要序列化输入排除、TUI 上下文读数跳过 summary 消息)按你的划界另开 PR,从 dev 切分支,与 Windows 退出码那个一起排。 等你的复审。 |
The test-sufficiency audit found the mechanism layer fully covered but three wire-level gaps: - MCP projection: projectMcpToolResult had unit + PBT coverage, but no test drove the real tool-call path. A stub MCP server exposing a tool with structuredContent now runs through the loop; the replayed tool message carries the text item and the appended structured JSON. The makeMcp/makeHttp test layers gain an mcpTools option. - B6 injection point: the compaction task arm's restoration call had no loop-driven test — GAP-1 calls apply directly. The provider-overflow wire test already drives the arm; it now also seeds a completed read tool part and asserts the restoration content reaches the third model request (the post-compaction one). - D14 stop semantics: hasLiveDescendant's positive scenario (a child whose own background shell is still running does not report to the parent until the shell settles) had no test — only the self-reference negative. The new wire test starts a background shell in the child, asserts the parent hears nothing while it runs, then waits for normal shell completion and asserts the delayed notice wakes the parent. Co-Authored-By: Claude <noreply@anthropic.com>
…ompaction request Completes the B6 injection-point coverage that 0f193fb's message claimed but did not contain. The overflow wire test now seeds a completed read on the replayed turn; after compaction settles, the task-arm injection must put the file's current on-disk body on the third request and attach exactly one restoration part to the replayed user message. Non-synthetic text parts must still equal the original turn's. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
|
CI windows 分片(2/3、3/3)的 4 个失败已核实,全部是同一个确定性根因——就是你划为 E 项的 PowerShell 退出码折叠缺陷,且这轮是它第一次真正暴露: 根因链:四个测试都跑 为什么现在才暴露:折叠缺陷在 Windows 上一直存在,但本 PR 之前退出码从不对外可见(这正是 P0-1 修的盲区)——新的投影 + 新测试把它照了出来。dev 上次 windows 全绿是因为特性不存在,不是没有缺陷。Linux 三分片、双端 e2e、typecheck 全绿;本机全套 16 个环境性失败与本项无关(已逐个归因过)。 处置:按你的划界,不在本 PR 修。已从 dev 切 |
|
复审结论:需要修改后再审,当前不建议合并。 审核基准:HEAD 之前 A1/B5/B6/B8 等修改已经纳入验证;下面是本轮确认仍存在的问题,不是要求重做已完成项。 一、主要问题R1 · P1:已完成后台任务的输出绕过会话归属校验。 位置:shell.ts:825–845。 任务结束后 live 实际复现:同一实例内,session A 启动真实后台 shell 并打印唯一 marker,等待任务完成;A 可以正常查询,session B 用同一个 task_id 也成功读到输出,预期应拒绝。这里的前提是调用方已知 task_id,不是在声称 ID 可枚举。 请对 live/snapshot 两条路径统一先校验 owner 和类型,补 active/completed/cancelled/error 的跨会话回归。 R2 · P2:grep 的 offset 分页没有稳定顺序,静态目录也会重复、遗漏结果。 位置:ripgrep.ts:382–395,files/count 参数分支也未约束顺序。 每一页启动独立 rg 进程,没有排序或稳定快照保证;offset 却要求各次调用遍历相同序列。旧搜索不依赖跨进程顺序,新增分页引入了这个前提。 实际复现:11 个子目录、160 个文件,每个一个匹配;文件树保持不变,每页 20,offset 依次 0/20/…/140。共收到 160 条,但两轮分别只有 96、104 个唯一文件。最后一轮为 104/160;具体数量随调度变化,性质失败是重复和漏项。 请建立稳定排序或快照/游标语义,三种模式均覆盖,并检查排序的性能影响。 R3 · P2:恢复去重把“存储中的旧输出”误当成“模型仍可见的内容”。 位置:restoration.ts:77–85、去重分支:187。
实际复现:写入真实 session/read/成功 summary/保留 tail 数据,read 已 compacted,磁盘正文与历史 output 一致;执行 请使去重基线与实际过滤、投影的可见性一致,并补保留 tail / 完整压缩 / 连续压缩的下一次请求内容断言。 R4 · P2:恢复预算没有覆盖最终输出,引用说明可远超预算。
实际调用生产 细化设计 §5.4 确实写了“引用说明不计预算”的例外,但没有数量/长度上限,因此与架构 G3/I2、总预算配置及“输出 ≤ budget”的证明冲突。不是忽略了已有例外,而是例外使所声明的上界不成立。 请对最终文本计费,将省略项合并成有界摘要。另外,当前先 materialize 全部历史候选再应用 max_files/budget,也不能限制历史文件读取数量;建议及早停止或按预算惰性读取。当前证据不足以得出“没有性能劣化”。 二、其他已复现问题
其他边界也有复现,优先级低于上述主问题:
三、workflow 验证缺口需要一起修正
四、验证结果与集成边界本地 Linux / Bun 1.3.14,包目录执行;各批使用独立 TMPDIR 避免 preload 临时目录碰撞。
批次有重叠,不应相加为独立测试总数;没有声称跑过整个 monorepo 全量 suite 或本地 Windows。 与 dev 维持已有决定:提示词继续留在本 PR,不要求拆 PR;实模行为按 beta 观察;#77/#86 其他压缩问题单独处理,不追加总 compaction timeout。未证实的通知 race、权限撤销/符号链接恢复等不作为已确认漏洞。 请优先修复 R1–R7,并用实际实现的测试闭合上述性质。当前未达到 workflow §6.6 的“0 critical / unresolved”退出标准。 |
…ment # Conflicts: # packages/opencode/src/session/system.ts # packages/opencode/src/session/tools.ts # packages/opencode/test/session/system.test.ts
…e-74, refuseRepeats + projectMcpToolResult coexist - system.ts: issue-74 (#79) deleted prompt/codex.txt on dev (apply_patch artifact per its archaeology); our P1 had re-added the CODEX route on the upstream-re-added file. Drop the route + import; codex ids now route to PROMPT_GPT as dev's tests assert. String-returning provider() (B7 refactor) kept; dev's new routing tests adapted to it. - tools.ts: both sides inserted helpers after resolve — keep dev's refuseRepeats (#97) and our projectMcpToolResult side by side. - processor-side DoomLoop: dev's #97 removal wins (verified 0 refs). Co-Authored-By: Claude <noreply@anthropic.com>
…, cancel keeps the tail (R1/R5/R6) R1: shell_output's settled fallback answered any caller holding the task_id — the owner check lived after the early return and the branch never validated job.type. Ownership + shell-type are now checked before any read on both live and settled paths (and in shell_kill's settled branch, which also lacked the type check). R5: the default task_id resolution scanned only the live registry, so a session whose only task had finished answered 'no background shell tasks' while an explicit id still worked. The default now prefers the session's most recent shell job (settled included) via background.list, falling back to the live registry. R6: killing a task returned the buffer tail only in the kill response — the interrupted fiber never settles, so a follow-up shell_output found no entry, no job output, and a pointer to a completion message a cancel never sends. BackgroundJob.cancel gains an optional output snapshot param (same status-line format settle produces); shell_kill hands over 'stopped\n<buffer>' inside the cancel critical section, making Info.output the single observable source for every terminal shape. Core cancel-snapshot unit tests included; the wrapper forwards options. Two legacy expectations updated to the new contract (kill renders 'stopped' + tail, not bare 'cancelled'). Co-Authored-By: Claude <noreply@anthropic.com>
…nly ranges (R2/R7) R2: every page was an independent rg invocation with no ordering guarantee — rg's walk order varies run to run, so offset windows duplicated and dropped rows across pages (reproduced: 11 dirs / 160 files, page size 20, unions of 96-104 unique files). All three modes now pass --sort=path; rg gives up its parallel walk for it, which the row budget already bounds — determinism wins. R7a: windowContentRows (extracted from the closure into an exported pure function) only admits trailing context when the page actually contains a match — an offset past the last match no longer surfaces the skipped match's context rows as phantom results. R7b: the 'Showing matches a-b' range counts match rows, not match+context rows. Edge cases: before/after/context tightened to NonNegativeInt (negative values used to pass validation and silently act as 0); the 64KiB JSON record cap still fails the search for oversized MATCH lines but now drops oversized CONTEXT lines instead of failing a page whose match lines are fine (oversizedRecord helper). Newline-in-filename splitting in files/count modes recorded as a known limitation in the design doc (line-delimited rg output cannot express them; JSON migration noted as the revisit condition). The pagination PBT now tests the production windowContentRows instead of a copied model, with a phantom-empty-page property for R7a. Co-Authored-By: Claude <noreply@anthropic.com>
…ut is bounded (R3/R4) R3: buildTailText joined completed tool outputs unconditionally, but the model projection renders parts with time.compacted as '[Old tool result content cleared]' — a file whose disk body matched only cleared history was wrongly deduped as 'already visible' and never restored. Compacted outputs are now excluded from the baseline, mirroring message-v2 and instruction.ts. R4: the budget only counted inlined content; the 'Not restored' section had neither count nor length caps (1,000 historical refs → 47.5k tokens, 5.9× budget). Now: materializeCandidates stops stat/read-ing once the budget is consumed (candidates are most-recent-first, so the dropped tail is the stalest); the not-restored list caps at 20 lines and collapses the remainder into one bounded summary line; budget_tokens <= 0 renders undefined (closes the 1-2-char-content-estimates-to-zero freebie). Output bound: budget + fixed framing. The strengthened PBT oracle (final-text based: file headings inside the Files section, whole-block token estimate, zero-budget property, skill/todo kinds in the generator) drove these fixes — the old input- summing oracle could not see them. Design doc and expectations updated to the new budget contract. Co-Authored-By: Claude <noreply@anthropic.com>
… (III.1-III.5) III.1 The grep pagination PBT now imports the production windowContentRows (extracted in the R7a commit) instead of re-implementing the window; new phantom-empty-page property covers R7a at the property level. Cross-invocation order (R2) is covered by the real-tree test in grep.test.ts. III.3 The 'three consecutive compactions' test now runs the REAL compaction.process in rounds 2-3 (the old version only wrote markers via create() and re-asserted round 1's stale capture — the queued stub replies were never consumed). Asserts each round's capture differs. III.4a The full-summary marker shape (tail_start_id undefined, completed summary present) is now exercised — the #77 test's unset('tail_start_id') branch was dead code. III.4b The system-template matrix asserts the DISTINCT template each model routes to (was: shared-tail-only, which let k3-without-providerID silently fall to DEFAULT); bare 'gpt-5.5' covers the plain PROMPT_GPT route no other entry reaches; k3 carries its providerID. III.5 Step Pn markers added to the four subplans' code paths (grepext P1-P9, bgshell P2-P9, comprec P1-P8, prompt-1 P1-P4), grep-verifiable; audit-report/decisions backfilled for all four with honest timing declarations; path-A machinery explicitly N/A (no fork checker yet); stale 'no tail_start_id = unsettled' wording in expectations corrected to the completed-summary predicate. Co-Authored-By: Claude <noreply@anthropic.com>
…ond review round - parameters snapshot: before/after/context now render as integer with minimum 0 (the NonNegativeInt tightening in the R7 batch); schema-layer parse assertions added for negative values. The full-file update also drops the stale apply_patch entry (tool removed by issue-74; the snapshot lingered because filtered runs never rewrote it). - devlog 2026-11-05-pr60-second-review-round.md with the round's metrics and lessons (helper-refactor semantics, oracle-before-implementation, merge auto-resolution blind spots, wrapper-hop interface changes). Co-Authored-By: Claude <noreply@anthropic.com>
|
二轮审核的 R1–R7 与 workflow 缺口已全部修复,CI 全绿(14/14,含 windows 三分片——E 项修复经 #94 合入 dev 后本 PR 同步,原 4 个红测试已复绿)。逐项: 主要问题
边界三件:context 负数 → workflow 缺口
集成边界:dev 已合并(9 commits)。#97 refuseRepeats 与本分支 projectMcpToolResult 共存;processor 内嵌 DoomLoop 让位;codex 路由按 issue-74 判定删除(dev 删文件后 import 残留由测试抓出)。tools.ts/prompt.ts 冲突已解,无待同步项。 验证:全套 4161 pass / 1 fail( 等你的复审。 |
…rojection when the only prior copy was compacted Reproduces the reviewer's exact chain (apply → filterCompactedEffect → toModelMessagesEffect): a read whose stored output equals the disk body byte-for-byte and was then marked compacted. The model must receive the body via the restoration block; the cleared read renders as the placeholder. The first version of this fixture seeded output='read <path>' instead of the body itself, so tailText.includes(content) could never hit and the test stayed green with the R3 fix reverted — a false regression guard. With output === disk body, reverting buildTailText's compacted-skip reddens the test (verified by reintroducing the bug, then restoring). Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The PBT suite added fast-check@4.8.0 to packages/opencode but the lock workspace block was never updated. CI's setup-bun installs in non-frozen mode, so the first install dirtied bun.lock with this line (plus ambient drift). Committed alone, verified with a frozen install. Found by the post-review-round self-audit (P2-a). Co-Authored-By: Claude <noreply@anthropic.com>
The re-trigger check compared only message indices, and the byte-dedup only looked at the latest user message — nothing ever retired an older sentinel living on a different message. A second compaction whose retained tail still held the first block's carrier message left TWO 'Context restored' blocks model-visible at once, each up to budget_tokens, both claiming to be current (violates the design's G4: at most one restoration block per context). Every prior sentinel is now flipped to ignored:true in place (same part id — updatePart upserts the row, so this is not an append) before the fresh block is written. The trigger scan and byte-dedup both skip ignored sentinels, so a transcript whose only blocks are retired re-fires instead of silently doing nothing. toModelMessagesEffect and the compaction serializer already skip ignored text parts. RED-verified: reverting the fix makes the new two-compaction test fail (two visible blocks); with it, exactly one reaches the projection. Found by the post-review-round self-audit (P2-b). Co-Authored-By: Claude <noreply@anthropic.com>
R6 fixed the direct shell_kill path but not the two cascade callers: run-state.ts (agent_stop / runState.cancel) and session.ts (session removal) called background.cancel(job.id) bare. The cascade interrupts the run fiber the same way, so the accumulated buffer was lost exactly when the user interrupts a whole session — the more common path. shell.ts now exports cancelSnapshot(taskId) (same 'status\nbuffer' shape the natural settle produces; undefined for foreign/settled/restart -lost tasks) and both cascades pass it. The existing session-cancel test now also asserts the tail survives, not just the status. RED-verified: reverting the fix makes the tail assertion fail. Found by the post-review-round self-audit (P2-c). Co-Authored-By: Claude <noreply@anthropic.com>
MAX_REFERENCE_LINES bounded the count of 'Not restored' lines but not their length: refs are raw absolute paths (PATH_MAX 4096) and reasons carry guidance prose, so 20 lines could admit ~21k tokens against the default 8000 budget — right after a compaction freed context. Every ref emission point (reference lines, the two inlined-degradation lines, and the '### ref' section headings) now clips to 160 chars. The PBT generator hardcoded short refs, so it could never catch this; the new property generates PATH_MAX-scale refs, plus a deterministic worst-case anchor (the PBT can under-sample long strings): a full set of 4096-char refs at the line cap. RED-verified: reverting the fix fails the worst-case anchor (21k > 10k). Found by the post-review-round self-audit (P3-a). Co-Authored-By: Claude <noreply@anthropic.com>
…read path collectCandidates appended the todo candidate AFTER the most-recent-first sort even though its lastUseSeq (seq + 1) makes it the most recent candidate — so the cheapest material was processed last and was the first to hit 'spent >= budget', degrading the entire current todo list to a reference line whenever files consumed the budget. The todo now rides at the top of the ordering, matching its seq. Its degradation copy also told the model to recover the todo list 'with the read tool' — there is no todo-reading tool (todo_write only). The materialize and renderMaterials fallback reasons are now kind-specific: files keep the read-tool guidance, skills point at the skill tool, todos state plainly that the list was not re-inlined. RED-verified: reverting the fix fails both the ordering assertion and the honest-reason assertion. Found by the post-review-round self-audit (P3-b). Co-Authored-By: Claude <noreply@anthropic.com>
The P2-c fix imported cancelSnapshot from tool/shell.ts into session/session.ts and session/run-state.ts. That import is a module cycle (session → tool/shell → plugin → session), and it deadlocks the provider LayerNode's deps array at module-init time — the CI generate step (bun run ./src/index.ts generate) died with 'ReferenceError: Cannot access node before initialization' at provider.ts:2107, failing all three linux unit shards. The live registry and cancelSnapshot now live in tool/shell/tasks.ts, a leaf module; tool/shell.ts and both cascades import from there. Generate verified locally (was failing before this commit), typecheck clean, shell-background/run-state/session suites green. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
|
二轮修复(R1–R7 + III.1–III.5)完成后,按审核标准对本 PR 又做了一轮对抗性自审(两个独立视角按「归属/顺序/有界/终态/可见性」五个不变量维度扫全部改动面),确认了 6 个真实缺陷并已修复,全部带 RED 验证的回归测试。当前 HEAD 本轮修复内容P2 级(3 项)
P3 级核心(2 项)
已知限制(自审确认、本轮明确不修)
验证
|
…scoping Conflict resolutions: - grep.ts: #107 hardened path handling (missing paths reject, non-file/dir shapes refused, single-file targets scope rg to that file with '-' basename guarded). Adopted verbatim on the stat/dispatch side; our multi-mode search/render kept, with File targets rendering back to the REQUESTED path in every mode (FSUtil.resolve realpaths, so rg reports the symlink's target — #107's symlink test now covers our branch too). - tools.ts: #107 dropped truncate.output's per-agent args; our projectMcpToolResult text keeps the same single-string call. - parameters snapshot: keep our extended grep schema, adopt #107's 'file or directory' path description. - grep.test.ts: keep all R2/R7/schema tests, add #107's symlink single-file scoping test. Co-Authored-By: Claude <noreply@anthropic.com>
Issue for this PR
Closes #46
Type of change
What does this PR do?
Implements issue #46 in three stages. The issue asked for three things: tool results must not silently lose information on their way to the model, compaction must preserve and restore what a task needs to continue, and the prompt/tool surface needed unifying and extending. Design docs are in
docs/design/, root-cause analyses for the fixes indocs/fixes/, and the decisions behind them indocs/research/runtime-alignment-research.md(§7 decision record).Result visibility (three fixes):
!direct commands record their exit code too. Before this, a silentexit 7was indistinguishable from success.structuredContentis appended to the output when content is non-empty (it used to be dropped entirely),resource_link/audiodegrade to explicit placeholders instead of vanishing, and error messages carry structuredContent details instead of a generic fallback.Compaction recovery:
SessionRestoration: after a compaction, files this session read or edited are re-read from disk (current version, not a cached copy), deduplicated against the retained tail, budget-capped, and injected as a single restoration block. Todos and loaded skill bodies ride along. Idempotent across repeated compactions — covered by a full-chain test that survives three consecutive compactions.Prompt unification:
Search and background shells:
run_in_background: the tool returns a task id immediately (in metadata as well as text),shell_outputreads status — including the real exit code after the task settles — and output tail,shell_killstops the task (idempotent against already-settled tasks), completion arrives as a session message through the same delivery path agent results use, session stop/interrupt cancels running background jobs, and the output buffer is a bounded tail cleaned up when the run settles. The synchronous path is unchanged.How did you verify your code works?
docs/devlog/2026-10-13-test-env-isolation.mdanddocs/devlog/2026-10-20-pr60-review-fixes.md(§ Verification, per-failure attribution).Screenshots / recordings
No UI change in this PR; the grep and background-shell features are tool-level (text output consumed by the model).
Checklist