Skip to content

feat(session): issue #46 runtime alignment — P0 result visibility, P1 recovery & prompts, P2 search & background shell - #60

Open
yixiao5428 wants to merge 85 commits into
devfrom
issue-46-runtime-alignment
Open

yixiao5428 wants to merge 85 commits into
devfrom
issue-46-runtime-alignment

Conversation

@yixiao5428

@yixiao5428 yixiao5428 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Base branch: dev. The default branch main only takes promotion PRs from dev.

Issue for this PR

Closes #46

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

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 in docs/fixes/, and the decisions behind them in docs/research/runtime-alignment-research.md (§7 decision record).

Result visibility (three fixes):

  • shell exit codes now reach the model. A shared helper projects non-zero exits into the replayed tool result and into compaction summaries; ! direct commands record their exit code too. Before this, a silent exit 7 was indistinguishable from success.
  • MCP tool results: structuredContent is appended to the output when content is non-empty (it used to be dropped entirely), resource_link/audio degrade to explicit placeholders instead of vanishing, and error messages carry structuredContent details instead of a generic fallback.
  • compaction summaries keep the head (1,400 chars) and the tail (600 chars) of every tool result plus the exit line. The old head-only 2,000-char cut dropped the full-output pointer and tail diagnostics; both runtimes (SessionPrompt loop and the core runner) are fixed.

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:

  • a five-rule working agreement is appended to all ten model templates (completion criteria, mid-task steering, verify-and-report-truthfully, delivery, comment density).
  • the hard "fewer than 4 lines / never explain changes / no comments ever" rules in default and trinity are removed. Delegation timing has no separate text asset: the review round withdrew the "When to delegate" tool-description line (PR feat(tui): Agents list with folds, running line and shared subagent view; subagent lifecycle in the agent tools #59's principle — descriptions state mechanics, not workflow); the async machinery notes stay in the templates and descriptions. Other templates keep their soft variants for now (rationale in the design doc).

Search and background shells:

  • grep gains output modes (content / files_with_matches / count), context lines, case-insensitive search and head_limit/offset pagination, with explicit distinction between more-available, exact-end and offset-beyond-results.
  • bash gains run_in_background: the tool returns a task id immediately (in metadata as well as text), shell_output reads status — including the real exit code after the task settles — and output tail, shell_kill stops 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?

  • 70+ new tests. Beyond per-fix unit tests, there are wire-level end-to-end tests that drive a loop session with a scripted LLM and capture what actually reaches the model: exit-code visibility, MCP results, grep pagination outcomes, background completion notifications, and the shared prompt agreement (including that an explicit agent prompt still replaces it on the wire). Property-based tests (fast-check, fixed seeds) cover the four invariants the review flagged: summary head/tail retention and bounds, pagination windows without loss or duplication, restoration budget/file caps and idempotence, and exactly-once structured-content projection.
  • full suite: core 1102/1102 green; opencode 3983 pass with 16 failures, each verified as environment, not code (this shared machine's proxy intercepts real-listener PTY traffic — Bun captures proxy config at process start — and load-flakes 5s-timeout tests; the same tests fail on a stashed dev baseline). The 2026-10-13 isolation round documented the same class — see docs/devlog/2026-10-13-test-env-isolation.md and docs/devlog/2026-10-20-pr60-review-fixes.md (§ Verification, per-failure attribution).
  • typecheck clean in both packages. The wire-schema snapshot was updated for the new tool parameters (grep +7, bash +1).

Screenshots / recordings

No UI change in this PR; the grep and background-shell features are tool-level (text output consumed by the model).

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

yixiao5428 and others added 19 commits October 9, 2026 14:37
- 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>
@github-actions

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

The following comment was made by an LLM, it may be inaccurate:

@yixiao5428
yixiao5428 marked this pull request as draft October 12, 2026 08:41
yixiao5428 and others added 6 commits October 12, 2026 16:43
…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>
@lihaokun

Copy link
Copy Markdown
Owner

@yixiao5428 你在这个 PR 里处理压缩恢复,顺带请看一下 #77,是 fermat 上一次长 run 里查出的压缩缺陷,和这里的改动在同一片代码:

  1. 重试时摘要挂错消息,压缩不生效、反复触发。 session/prompt.ts 处理压缩任务时用的是 parentID: lastUser.id,摘要完成前只要进来新的用户消息(人、子 agent、结束报告、watchdog),摘要就挂到最新那条上,filterCompacted 认不到,下一步带着 87 万 token 的完整历史又超阈值。
  2. 与本 PR 的交互: 修订后的恢复逻辑在 compaction.process 返回 continue 后执行,而 restoration.ts 取的是"最新的压缩标记",不检查是否有已完成的摘要指向它。遇到第 1 条的情况,压缩没生效,恢复块仍会注入,叠在完整历史上。建议恢复只在标记确有已完成摘要时进行。
  3. 失败的摘要留在历史里。 被取消的那次摘要留下 21 万字符推理,仍在模型上下文里;session/compaction.ts 的序列化还会以 [Assistant reasoning] 把它写进下一次摘要的输入,和本 PR 改的 summarizeToolText 在同一个序列化函数里。
  4. 不设压缩总超时是维护者的决定(慢不等于卡死,真卡死由 chunkTimeout 兜底),修的时候不要加。
  5. TUI 压缩刚完成时把摘要请求的大小当成当前上下文显示。

建议 #77 另开 PR 做,不要再扩大本 PR 的范围;第 2 条如果本 PR 改起来顺手,可以在这里先加上"标记必须有已完成摘要"的判断。

另外看到你的修改回复了,我这边会再复审一轮。

yixiao5428 and others added 2 commits October 21, 2026 10:48
…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>
@yixiao5428

Copy link
Copy Markdown
Collaborator Author

#77 的交互防御已加(eef5174bb1):

  • restoration.ts 的标记扫描现在要求标记已结算——谓词与 filterCompacted/completedCompactions 同款(assistant summary && finish && !error 且 parentID 指向标记消息)。摘要挂在别的消息上、失败或在途时标记不触发恢复,恢复块不再叠加在未生效的完整历史上。
  • 实施中踩了一版错的:第一版用"tail_start_id 缺失即未结算"判断,被 GAP-1 三压缩测试当场抓红——全量摘要回退路径的 tail_start_id 本来就是 undefined(没有 tail 可保留),那是合法结算形态。改为完成摘要谓词后两种结算形态(尾保留/全量摘要)都正确覆盖。
  • 测试:seed 补齐 summary assistant;新增负例(无完成摘要 → 不注入)+ 正例(补上完成摘要 → 注入)。
  • expectations §5 与 compaction-recovery 设计文档已同步。

#77 其余三项(摘要 parentID 锚定标记消息、失败摘要从模型上下文与摘要序列化输入排除、TUI 上下文读数跳过 summary 消息)按你的划界另开 PR,从 dev 切分支,与 Windows 退出码那个一起排。

等你的复审。

yixiao5428 and others added 3 commits October 21, 2026 14:22
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>
@yixiao5428

Copy link
Copy Markdown
Collaborator Author

CI windows 分片(2/3、3/3)的 4 个失败已核实,全部是同一个确定性根因——就是你划为 E 项的 PowerShell 退出码折叠缺陷,且这轮是它第一次真正暴露:

根因链:四个测试都跑 bun -e "process.exit(7)" 并断言退出码 7 到达投影。测试用 withSh 设 SHELL=/bin/sh,在 windows runner 上 windowsPath('/bin/sh') 的 drive-letter 正则把 /b 误写成 B:/,resolve 失败回落到 win()[0] = runner 上的 pwsh 7;core/shell.ts 的 ps 分支用 -Command 传命令,pwsh 把最后一个原生命令的非零退出折叠为 1(若失败后跟成功语句甚至折叠为 0)。退出码 7 → 1,四处断言同红。

为什么现在才暴露:折叠缺陷在 Windows 上一直存在,但本 PR 之前退出码从不对外可见(这正是 P0-1 修的盲区)——新的投影 + 新测试把它照了出来。dev 上次 windows 全绿是因为特性不存在,不是没有缺陷。Linux 三分片、双端 e2e、typecheck 全绿;本机全套 16 个环境性失败与本项无关(已逐个归因过)。

处置:按你的划界,不在本 PR 修。已从 dev 切 fix/windows-powershell-exit-code 修好并发 PR:#94(exit $LASTEXITCODE 包装 + 全 shell 矩阵回归测试 + §7.1 修正方案文档)。#94 合入后本 PR 的 windows 分片应复绿——这 4 个测试正是 #94 的回归用例,两边 CI 会互相印证。

lihaokun commented Nov 2, 2026

Copy link
Copy Markdown
Owner

复审结论:需要修改后再审,当前不建议合并。

审核基准:HEAD 91a8d01174d26d55ecf81cd55897744637ce47a1;按 docs/workflow.md §5–§6 完成四个新增子计划的独立契约审核、主审核调用链检查、实际实现反例及与当前 dev 的本地集成验证。提交本评论前确认 HEAD 未变。

之前 A1/B5/B6/B8 等修改已经纳入验证;下面是本轮确认仍存在的问题,不是要求重做已完成项。

一、主要问题

R1 · P1:已完成后台任务的输出绕过会话归属校验。

位置:shell.ts:825–845。

任务结束后 live shellTasks 条目被删除,shell_output 进入 !entry && job fallback,直接返回 registry 中的标题和输出;后面的 session owner 校验不会执行,该分支也没有验证 job.type === "shell"。

实际复现:同一实例内,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。

buildTailText 无条件拼入 completed tool.output;实际模型投影会将 state.time.compacted 的工具输出替换为 [Old tool result content cleared]。原文 substring 命中不能证明下一次请求仍含文件正文。

实际复现:写入真实 session/read/成功 summary/保留 tail 数据,read 已 compacted,磁盘正文与历史 output 一致;执行 SessionRestoration.apply → filterCompactedEffect → toModelMessagesEffect。最终只有清理占位符,文件正文和恢复块都缺失。验证的是实际消息投影,没有调用远程模型。

请使去重基线与实际过滤、投影的可见性一致,并补保留 tail / 完整压缩 / 连续压缩的下一次请求内容断言。

R4 · P2:恢复预算没有覆盖最终输出,引用说明可远超预算。

位置:restoration.ts:215–244。

used 只计算正文;路径、标题、围栏及超限/缺失引用说明不计入预算,说明条数也不受 max_files 限制。

实际调用生产 renderMaterials:1,000 个历史文件引用,budget_tokens=8000,最终得到 189,985 字符 / Token.estimate=47,496,约预算的 5.94 倍。这是项目估算器的值,不是 provider tokenizer 的计费测量。

细化设计 §5.4 确实写了“引用说明不计预算”的例外,但没有数量/长度上限,因此与架构 G3/I2、总预算配置及“输出 ≤ budget”的证明冲突。不是忽略了已有例外,而是例外使所声明的上界不成立。

请对最终文本计费,将省略项合并成有界摘要。另外,当前先 materialize 全部历史候选再应用 max_files/budget,也不能限制历史文件读取数量;建议及早停止或按预算惰性读取。当前证据不足以得出“没有性能劣化”。

二、其他已复现问题

  • R5 · P2,默认查询丢失已完成任务: 等待真实后台任务完成后,shell_output({}) 报“no background shell tasks”,显式 ID 却仍能读。默认选择只扫描已经清理的 live Map。应从本 session 的 shell jobs 中选择最近任务,包含终态。
  • R6 · P2,取消后尾部输出丢失: 子进程打印动态拼接的 marker,先确认输出可读,再 shell_kill。kill 响应仍含 marker,后续 shell_output 丢失。取消没有成功返回值供 BackgroundJob 保存,finally 又清理 buffer;fallback 还让调用方查看取消路径不会发送的 completion message。请对取消/失败也保留有界快照。
  • R7a · P2,超末页返回纯 context: 文件 hit\nnot-a-match\n,pattern="^hit$", after=1, offset=1, head_limit=1,应无匹配,却返回非命中行并显示 matches 2-2。原因是未选中任何 match 时仍追加 pending context(ripgrep.ts:449)。
  • R7b · P2,分页范围把 context 当匹配: 文件 hit\nafter\nhit\nafter\n,context=1, offset=1, head_limit=1,只有一个匹配却显示 matches 2-4。范围计算用了含 context 的 rows.length(grep.ts:205)。

其他边界也有复现,优先级低于上述主问题:

  • context=-1 被接受;before/after/context 应使用非负整数约束,再检查 ≤10。
  • 短 hit 紧邻 70,000 字符非命中行,无 context 成功,加 after=1 后因 64KiB 原始记录上限失败。应保留防御上限,并安全丢弃/截断附属 context,或明确修订契约。
  • POSIX 上一个含换行的文件名,在 files_with_matches 中被拆成两个虚构路径;需要无歧义的路径边界。count 模式同类问题目前仅静态分析,不写成已实测。

三、workflow 验证缺口需要一起修正

  1. grep PBT 没有测试生产实现。 pbt-grep-pagination.test.ts:16 自己实现 windowPage,只 import fast-check/bun:test。断言又过滤 context,既不能发现纯 context 空页,也不能验证真实 rg 的跨调用顺序。请遵循 AGENTS.md 的“测试实际实现、不复制逻辑”,用 production helper/service + 独立 oracle。

  2. 恢复 PBT 的 oracle 不够。 预算只加总 material.content,没有检查最终渲染文本;文件数检查是每个 ref 的出现次数,不是总文件数。请覆盖最终输出上界、所有 material 类型和零预算等边界。

  3. “连续三次压缩”实际只处理了第一次。 compaction.test.ts:2365 后两轮只调用 createSummaryCompaction,接着复用旧 captured;该 helper 调用的 SessionCompaction.create 只写 marker,并不 process。请实际执行三次处理,确认三次模型调用,再验证每轮恢复与下一次请求。另一个 restoration.test.ts 中的 full-summary 用例也未调用 unset("tail_start_id") 分支。

  4. “所有模板”矩阵只有 8 个实际 route。 9 个输入缺 generic GPT;k3 不带匹配 providerID,落到 default。生产 systemSections 无条件追加协议,默认/显式 agent prompt 的真实 wire 用例也通过,因此这里是覆盖缺口,不是声称对应模型已经丢提示词。补 route identity 与确切共享尾元素断言。

  5. 四子计划相关代码没有 Step Pn 标记;部分设计、证明、expectations 仍不一致,例如“无 tail_start_id 即未 settled”的旧文字。事后 expectations 已诚实标注,不追认事前时序;本轮应闭合实际不一致。按 §6.5–§6.6 留存 audit-report/decisions,缺失的机械检查按规定说明并跟踪,不能将 unresolved 直接标为通过。

四、验证结果与集成边界

本地 Linux / Bun 1.3.14,包目录执行;各批使用独立 TMPDIR 避免 preload 临时目录碰撞。

验证批次 结果
restoration/MCP/PBT/system/grep/shell-background/registry,11 文件 82 pass,0 fail
相关 prompt/compaction/message-v2/shell wire 与投影用例 16 pass,234 filtered,0 fail
message-v2/compaction/shell/catalog/code-mode/参数 snapshot,6 文件 243 pass,1 skip,0 fail;16 snapshots
core session-compaction 7 pass,0 fail
原 HEAD 的 opencode、core typecheck 均通过
本轮主要生产实现反例 7 个失败断言;预算另 1 个;边界另 3 个,均保留为缺陷证据
与 dev 本地解决冲突后的 8 文件集成验证 73 pass,0 fail;opencode typecheck 通过

批次有重叠,不应相加为独立测试总数;没有声称跑过整个 monorepo 全量 suite 或本地 Windows。

与 dev 7bd8532a755ca131f35751a2012c2717dad12a80 实际合并时,session/tools.ts 存在 MCP 投影 helper 与 #97 refuseRepeats 的冲突。本地隔离 worktree 保留两组 helper 后完成上述集成验证,作者分支仍需同步解决。当前两个 Windows unit 分片失败沿用此前 PowerShell 退出码归因,#94 已进入 dev;同步后需要在新 HEAD 重跑 CI,不把这个旧问题当成本轮新发现。

维持已有决定:提示词继续留在本 PR,不要求拆 PR;实模行为按 beta 观察;#77/#86 其他压缩问题单独处理,不追加总 compaction timeout。未证实的通知 race、权限撤销/符号链接恢复等不作为已确认漏洞。

请优先修复 R1–R7,并用实际实现的测试闭合上述性质。当前未达到 workflow §6.6 的“0 critical / unresolved”退出标准。

yixiao5428 and others added 7 commits November 5, 2026 12:07
…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>
@yixiao5428

Copy link
Copy Markdown
Collaborator Author

二轮审核的 R1–R7 与 workflow 缺口已全部修复,CI 全绿(14/14,含 windows 三分片——E 项修复经 #94 合入 dev 后本 PR 同步,原 4 个红测试已复绿)。逐项:

主要问题

  • R1:shell_output/shell_kill 全部读路径(live/settled/kill-settled)先验 type === "shell" && metadata.sessionId === ctx.sessionID;跨会话回归覆盖 completed 形态(cancelled/error 路径同款检查,kill settled 分支补了缺失的 type 校验)。
  • R2:三模式 rg args 加 --sort=path(你的复现场景本机重现:160 文件 3 轮 3 哈希;修复后七页 165 全唯一)。代价按你要求评估并记入 design doc:rg 放弃并行遍历,rowBudget 已界定单次工作量。
  • R3:buildTailText 跳过 time.compacted 的 tool parts——dedup 基线对齐 message-v2 投影("[Old tool result content cleared]"),不再按模型看不见的存储原文误判 duplicate。
  • R4:Not restored 节封顶 20 条 + 溢出单行汇总;materializeCandidates 预算耗尽即停读(最陈旧尾部先弃);budget_tokens ≤ 0 → 不恢复。你给的 1,000 引用场景现在整块 ≤ budget + 固定框架。expectations G3 与 design doc §5.3/5.4 已按新口径修订(旧"引用行不计预算"例外废弃)。
  • R5:默认 task_id 解析改为优先取本 session 最近 shell job(含 settled),live registry 兜底。
  • R6:BackgroundJob.cancel 加可选 output 快照(cancel 临界区内写入 Info.output),shell_kill 传入 stopped\n<buffer>——取消后 shell_output 从 job 读到真实尾部,不再指向永不发送的 completion message。core 单测 2 个。
  • R7a/R7b:窗口逻辑提取为导出纯函数 windowContentRows;越末页 trailing context 仅在本页确有 match 时入页;Showing 范围只数 match 行。

边界三件:context 负数 → NonNegativeInt schema 拒绝(快照已更新);64KiB 超限 context 行降级丢弃(match 行仍整体失败);换行文件名记为已知限制(JSON 迁移为再评估条件)。

workflow 缺口

  1. grep PBT 改 import 生产 windowContentRows(不再复制实现)+ R7a 性质;跨调用顺序由真实目录树测试覆盖。
  2. restoration PBT oracle 改对最终文本断言(Files 节内 heading ≤ max_files、整块估算 ≤ budget+框架、零预算性质、skill/todo kind)——强化后当场抓出两个实现缺陷(todo 复用 /f/ ref 混入计数;budget=0 时 1 字符内容免费内联),均已修。
  3. 三压缩测试 2-3 轮改真跑 compaction.process(原 createSummaryCompaction 只写 marker、stub 永不消费——你的判断完全正确,断言的是第一轮陈旧捕获);现在断言每轮捕获不同 + 每轮恢复。
  4. system 矩阵逐条断言确切模板;gpt-5.5 补裸 PROMPT_GPT 路线;k3 带 providerID;restoration 补 tail_start_id undefined 全量摘要正例。
  5. 四子计划补 // Step Pn: 标记(全部 grep 可验);audit-report/decisions 回填(诚实声明事后时序);路径 A 标 N/A(fork 无自备检查器);"无 tail_start_id 即未 settled"旧口径已改为完成摘要谓词。

集成边界:dev 已合并(9 commits)。#97 refuseRepeats 与本分支 projectMcpToolResult 共存;processor 内嵌 DoomLoop 让位;codex 路由按 issue-74 判定删除(dev 删文件后 import 残留由测试抓出)。tools.ts/prompt.ts 冲突已解,无待同步项。

验证:全套 4161 pass / 1 fail(concurrent loop callers 负载贴限,单跑 3/3 绿,10-20 轮已归因环境类);core 全绿;CI 14/14。devlog:docs/devlog/2026-11-05-pr60-second-review-round.md。

等你的复审。

yixiao5428 and others added 9 commits November 5, 2026 14:24
…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>
@yixiao5428

Copy link
Copy Markdown
Collaborator Author

二轮修复(R1–R7 + III.1–III.5)完成后,按审核标准对本 PR 又做了一轮对抗性自审(两个独立视角按「归属/顺序/有界/终态/可见性」五个不变量维度扫全部改动面),确认了 6 个真实缺陷并已修复,全部带 RED 验证的回归测试。当前 HEAD aeb88cd9ec,CI 14/14 绿。

本轮修复内容

P2 级(3 项)

  1. bun.lock 缺行(5fa184392c):PBT 测试新增的 fast-check@4.8.0 devDependency 从未写入 lock 的 workspace block——CI 的非 frozen 安装第一次跑就会弄脏 lock。frozen install 之所以能过,是因为 effect 的传递依赖恰好提供了该包(「意外满足」)。已单独补入该行并用 frozen install 验证。

  2. 恢复块跨压缩堆积,违反 G4(62e02f0c01):触发判定只比较消息 index、字节去重只看最新 user message——当第二次压缩的保留尾仍含第一个恢复块的载体消息时,两个满血「Context restored」块(各至多 budget_tokens=8000)同时模型可见,都自称当前恢复。修复:写入新块前,把 transcript 上所有旧 sentinel 原地翻 ignored: true(updatePart 同 id upsert,是标志位翻转不是追加行——transcript 保留审计轨迹;投影与压缩 serializer 本就跳过 ignored text part)。触发扫描与字节去重同样跳过 ignored 块。回归测试构造双压缩 + 保留尾锚定第一块载体消息的形态,回滚修复即红(投影可见块=2)。

  3. 级联取消丢 shell 尾部输出(0a9a156a6d + 3f45585998):R6 只修了 shell_kill 直杀路径;run-state.ts(agent_stop / interrupt)与 session.ts(会话删除)两条级联路径仍裸调 background.cancel(job.id)——被级联打断的 run fiber 同样到不了 settle,buffer 照丢,而「打断整个会话」比单独杀任务更常见。修复:shell 任务注册表与 cancelSnapshot(taskId) 拆到叶子模块 tool/shell/tasks.ts(status\nbuffer 与自然 settle 同形),两条级联均传入。会话级取消的既有测试同步加强:断言尾部输出存活,不再只断言 status。

    这项修复初版把 accessor 放在 tool/shell.ts 里,session → tool/shell → plugin → session 的值导入环让 provider LayerNode 的 deps 数组在模块初始化期 TDZ 崩溃——三个 linux 分片全红(typecheck 双向都绿,既有 import 是 type-only)。本地用 bun run ./src/index.ts generate 逐 commit 二分定位后拆叶子模块修复。已记入仓库已知限制。

P3 级核心(2 项)

  1. Not-restored 引用行只封条数不封长度(92dbdc7900):ref 是原始绝对路径(PATH_MAX 4096),20 行上限在最坏情况 ≈ 21k token 对 8000 预算——刚压缩完释放的上下文又被灌爆。修复:MAX_REF_CHARS=160 截断,覆盖全部 ref 出现点(引用行、两条降级行、### ref 节标题)。原 PBT generator 硬编码短 ref(等于把结论假设进 generator),新增 PATH_MAX 级生成器 + 确定性最坏锚点(PBT 对长字符串欠采样)。

  2. todo 候选排序颠倒 + 误导文案(458905e065):todo 的 lastUseSeq(seq+1)是全场最高却在 sort 之后追加——最便宜的材料反而最后处理、最先被预算截断,整份当前 todo list 降级为一条引用行。修复:排到最前(与它的 seq 一致)。降级文案按 kind 分流:文件继续指向 read 工具、skill 指向 skill 工具、todo 如实说明「未重新内联」——原来对 todo 说「用 read 工具读取」是指一条死路(不存在 todo 读工具)。

已知限制(自审确认、本轮明确不修)

  • shellTasks 注册表在启动失败/中断时可能残留条目:Effect.ensuring 覆盖 run fiber 的全部退出路径,但 background.start 本身失败(极端)时 set 先于 start 的窗口仍在。进程生命周期级泄漏,量级极小。
  • deliverAsync 用 Effect.ignore 只吞 error 不吞 defect:完成通知投递通道若发生 defect 仍会中断 run fiber。与「通知失败不影响任务状态」的契约在 defect 一侧不完全一致。
  • skill 候选在读盘前无 stat 预检:与 files 的 stat-guard 不对称——超限 skill 会先全文读入再判断。浪费 IO 但输出仍受 cap 约束。
  • grep files/count 模式的 total 截断文案(上轮划界保持不变):has-more 页面的 prose 头部显示的是截断计数而非真实总数。
  • 换行文件名(上轮已知限制保持):rg JSON 按行分割,文件名内换行无法无歧义表达,记为 JSON 协议迁移的再评估条件。
  • SDK types.gen.ts 无 PR 时再生门槛:restoration 配置块由真实生成器产出(943f3fea51),但仓库只有 push-to-dev 的自动再生 workflow,feature 分支上的漂移要等 dev bot 合并后才可见。

验证

  • 每项修复均做 falsification:回滚修复 → 新测试必红。
  • 定向套件全绿(restoration 15 + PBT 4 + shell-background 14 + run-state 3 + session/shell 26 + prompt/compaction 261)+ 双包 typecheck + generate。
  • CI 14/14(windows 3/3 首跑 tsc cache-miss 后无错误日志退出,同 commit 重跑绿——load-class flake,与本 PR 改动无关)。

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants