@sema-ai/core 1.276.0 → 1.277.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +2 -5
  2. package/dist/bench/metrics.js +1 -1
  3. package/dist/bench/metrics.js.map +1 -1
  4. package/dist/core/auto-mode.d.ts.map +1 -1
  5. package/dist/core/auto-mode.js +14 -1
  6. package/dist/core/auto-mode.js.map +1 -1
  7. package/dist/core/cache-break-detector.js +0 -0
  8. package/dist/core/cache-break-detector.js.map +1 -1
  9. package/dist/core/task-registry.d.ts +6 -0
  10. package/dist/core/task-registry.d.ts.map +1 -1
  11. package/dist/core/task-registry.js +30 -0
  12. package/dist/core/task-registry.js.map +1 -1
  13. package/dist/tools/fs/index.d.ts.map +1 -1
  14. package/dist/tools/fs/index.js +5 -2
  15. package/dist/tools/fs/index.js.map +1 -1
  16. package/dist/tools/gitea-issue.d.ts +1 -1
  17. package/dist/tools/gitea-issue.d.ts.map +1 -1
  18. package/dist/tools/scheduler-tools.d.ts.map +1 -1
  19. package/dist/tools/scheduler-tools.js +8 -6
  20. package/dist/tools/scheduler-tools.js.map +1 -1
  21. package/package.json +3 -5
  22. package/CHANGELOG.md +0 -5444
  23. package/docs/A1-ATTRIBUTION-ROUND1-2026-07-09.md +0 -181
  24. package/docs/AB-ORACLE-GATE-VERDICT-2026-07-10.md +0 -100
  25. package/docs/ANCHOR-GAP-INVENTORY-2026-07-09.md +0 -143
  26. package/docs/ARCHITECTURE.md +0 -125
  27. package/docs/BUGHUNT-2026-07-05-NIGHT.md +0 -98
  28. package/docs/BUILD.md +0 -63
  29. package/docs/CANCEL-ASYNC-ATTRIBUTION-2026-07-09.md +0 -98
  30. package/docs/CC-198-VS-201-DRIFT-2026-07-09.md +0 -70
  31. package/docs/CC-BASELINE-FAKE-ZERO-AUDIT-2026-07-09.md +0 -51
  32. package/docs/CC-DRIFT-198-206-AGENT-TYPES-2026-07-10.md +0 -82
  33. package/docs/CC-DRIFT-198-206-CHANGELOG-2026-07-10.md +0 -82
  34. package/docs/CC-DRIFT-198-206-REMINDERS-2026-07-10.md +0 -96
  35. package/docs/CC-DRIFT-198-206-ROADMAP-2026-07-10.md +0 -110
  36. package/docs/CC-DRIFT-198-206-TOOL-INVENTORY-2026-07-10.md +0 -53
  37. package/docs/CC-DRIFT-198-206-USAGE-CONSTANTS-2026-07-10.md +0 -64
  38. package/docs/CC-DRIFT-198-206-WORKFLOW-2026-07-10.md +0 -83
  39. package/docs/CC-PARITY-CHECKLIST.md +0 -220
  40. package/docs/CC-PARITY-DEEP-SWEEP-2026-07-07.md +0 -149
  41. package/docs/CC-SUBAGENT-PROMPT-STUDY-2026-07-09.md +0 -219
  42. package/docs/CC-TASK-REMINDER-CADENCE-STUDY-2026-07-09.md +0 -396
  43. package/docs/CC-TOOL-PARITY-SWEEP-2026-07.md +0 -56
  44. package/docs/CC-TOOL-PROMPT-DEEP-DIFF-2026-07-08.md +0 -274
  45. package/docs/CC-TOOLSURFACE-DEEP-DIVE-2026-07-07.md +0 -197
  46. package/docs/CC206-OBSERVER-ANCHORS-2026-07-11.md +0 -463
  47. package/docs/CC207-CORE-SWEEP-BACKLOG-2026-07-12.md +0 -155
  48. package/docs/CENTER-CONTROL-PLANE-SEAM.md +0 -86
  49. package/docs/CLEANUP-CONTRACT-ANCHOR-2026-07-09.md +0 -83
  50. package/docs/COLD-START-RSI-2026-07-09.md +0 -72
  51. package/docs/COMPACTION-LIVE-AUDIT-2026-07-09.md +0 -114
  52. package/docs/CONTEXT-lifecycle-extension-points-and-stop-attribution.md +0 -71
  53. package/docs/COREWARS-POLYGLOT-TRIAGE-2026-07-09.md +0 -85
  54. package/docs/DEADTARGET-OVERFULL-HBOX-2026-07-10.md +0 -92
  55. package/docs/DEADTARGET-PATH-TRACING-REVERSE-2026-07-10.md +0 -87
  56. package/docs/DEADTARGET-WINNING-AVG-COREWARS-2026-07-10.md +0 -96
  57. package/docs/DEEPSEEK-API-LIMITS-PROBE-2026-07-09.md +0 -118
  58. package/docs/DESIGN-140-MOCK-VERIFY-2026-07-11.md +0 -79
  59. package/docs/DESIGN-140-VERIFICATION-2026-07-11.md +0 -43
  60. package/docs/DESIGN-DRAFT-workflow-collab-2026-07-11.md +0 -56
  61. package/docs/DESIGN-ORACLE-GROUNDING-GATE-2026-07-10.md +0 -167
  62. package/docs/DESIGN-TFRAG-RECONCILE-SCAFFOLD-2026-07-09.md +0 -102
  63. package/docs/EXP-PA2-PE-DESIGN-2026-07-11.md +0 -220
  64. package/docs/EXT-DOGFOOD-2026-07-12.md +0 -50
  65. package/docs/FINAL-VERIFY-SURFACE-AUDIT-2026-07-09.md +0 -44
  66. package/docs/FRAG-RECON-INVESTIGATION-2026-07-09.md +0 -183
  67. package/docs/HANDOFF.md +0 -121
  68. package/docs/KNOWN-ISSUES.md +0 -96
  69. package/docs/LEGW-WINDOW-TAX-2026-07-10.md +0 -82
  70. package/docs/LIVE-PROBE-BATCH-2026-07-11.md +0 -207
  71. package/docs/M2-CERT-ATTRIBUTION-2026-07-10.md +0 -61
  72. package/docs/M25-LEG-VARIANCE-ATTRIBUTION-2026-07-10.md +0 -81
  73. package/docs/MAILMAN-TIMEOUT-ATTRIBUTION-2026-07-10.md +0 -88
  74. package/docs/MEMORY-BACKEND-DIALECT-NOTES.md +0 -69
  75. package/docs/MTEB-ESCAPE-HATCH-ATTRIBUTION-2026-07-09.md +0 -57
  76. package/docs/MTEB-LEADERBOARD-ATTRIBUTION-2026-07-10.md +0 -134
  77. package/docs/ORACLE-GATE-NEGATIVE-SAMPLE-2026-07-11.md +0 -28
  78. package/docs/PARITY-CONTEXT-TRANSACTIONS-2026-07-10.md +0 -67
  79. package/docs/PARITY-PROGRESS-MATRIX-2026-07-10.md +0 -37
  80. package/docs/PARITY-SPOT-AGENT-TYPES-2026-07-10.md +0 -70
  81. package/docs/PARITY-SPOT-REMINDERS-2026-07-10.md +0 -144
  82. package/docs/PARITY-SPOT-TOOL-INVENTORY-2026-07-10.md +0 -61
  83. package/docs/PARITY-SPOT-USAGE-CONSTANTS-2026-07-10.md +0 -95
  84. package/docs/PARITY-SPOT-WORKFLOW-2026-07-10.md +0 -93
  85. package/docs/PASSWORD-RECOVERY-PANEL-VERDICT-2026-07-10.md +0 -62
  86. package/docs/R8-R9-FINALIZE-COORDINATION-2026-07-09.md +0 -124
  87. package/docs/REPL-CODE-MODE-EVALUATION-2026-07-10.md +0 -187
  88. package/docs/REPORT-1249-TARGETED.md +0 -48
  89. package/docs/RESEARCH-3P-MEMORY-SWAP-2026-07-12.md +0 -273
  90. package/docs/ROADMAP.md +0 -111
  91. package/docs/RSI-CLOUD-DEPLOY-2026-07-06.md +0 -289
  92. package/docs/RSI-PROCESS-AI-COLDSTART-2026-07-11.md +0 -68
  93. package/docs/S18-THOROUGHNESS-AUDIT-2026-07-09.md +0 -113
  94. package/docs/SERVICE-INTEGRATION-GUIDE.md +0 -298
  95. package/docs/SUBAGENT-STREAM-RESILIENCE-PARITY-2026-07-10.md +0 -118
  96. package/docs/SWEBENCH-PRO-RECON-2026-07-11.md +0 -31
  97. package/docs/TB-EFFICIENCY-TELEMETRY-2026-07-08.md +0 -114
  98. package/docs/TB-FAILURE-AUTOPSY-2026-07-05-RERUN.md +0 -67
  99. package/docs/TB-FAIRNESS-AUDIT-2026-07-08.md +0 -141
  100. package/docs/TB-FULL-1257-2026-07-08.md +0 -77
  101. package/docs/TB-LEG-GAP-ATTRIBUTION-1261-1263-2026-07-11.md +0 -154
  102. package/docs/TB-REGRESSION-DAEMON-KILL-2026-07-08.md +0 -58
  103. package/docs/TB-RSI-CROSSCHECK-2026-07-06.md +0 -68
  104. package/docs/TB-RSI-LEDGER.md +0 -30
  105. package/docs/TB-STABLE-SET-2026-07-08.md +0 -127
  106. package/docs/TB21-RECON-2026-07-11.md +0 -28
  107. package/docs/TEST-DESIGN-BATCH-1-2-3.md +0 -97
  108. package/docs/THIRD-PARTY-INTEGRATION.md +0 -45
  109. package/docs/TOKEN-ESTIMATE-CALIBRATION-LIVE-2026-07-09.md +0 -102
  110. package/docs/audit-2026-06-10-/347/254/254/344/272/214/350/275/256-/347/251/272/347/231/275/345/214/272/344/270/216/345/277/230/346/216/245/347/272/277.md +0 -147
  111. package/docs/audit-2026-06-10-/350/256/241/347/256/227bug/346/211/253/346/217/217.md +0 -163
  112. package/docs/audit-2026-06-11-/344/277/256/345/244/215/345/244/215/345/256/241/344/270/216/345/233/236/345/275/222/344/270/223/346/211/253.md +0 -95
  113. package/docs/audit-2026-06-12-design71-P1/346/275/234/344/274/217/351/235/242/350/243/201/345/206/263/344/270/216/345/220/236/351/224/231/345/256/241/350/256/241.md +0 -45
  114. package/docs/audit-2026-06-12-/345/205/254/345/274/200/346/227/213/351/222/256/346/264/273/346/200/247/344/270/216vendored/346/255/273/350/267/257.md +0 -63
  115. package/docs/audit-2026-06-15/ADDENDUM-second-pass.md +0 -88
  116. package/docs/audit-2026-06-15/ARCHITECTURE-AND-ROADMAP.md +0 -187
  117. package/docs/audit-2026-06-15/DOC-CORRECTIONS.md +0 -105
  118. package/docs/audit-2026-06-15/INDEX.md +0 -49
  119. package/docs/audit-2026-06-15/ORCHESTRATION-STATUS.md +0 -98
  120. package/docs/audit-2026-06-15/PRODUCTION-ISSUES.md +0 -189
  121. package/docs/audit-2026-06-15/REFERENCES-literature.md +0 -139
  122. package/docs/audit-2026-06-15/VENDOR-REFACTOR-PLAN.md +0 -96
  123. package/docs/cc-probe-198/README.md +0 -72
  124. package/docs/cc-probe-198/STABILITY-AUDIT.md +0 -118
  125. package/docs/cc-probe-198/live-schema-monitor-worktree-2026-07-09.json +0 -56
  126. package/docs/cc-probe-198/mount-variants.json +0 -536
  127. package/docs/cc-probe-198/plan-mode-run.json +0 -1
  128. package/docs/cc-probe-198/probe-bash-bg.json +0 -224
  129. package/docs/cc-probe-198/probe-edit-mismatch.json +0 -120
  130. package/docs/cc-probe-198/probe-read-missing.json +0 -62
  131. package/docs/cc-probe-198/probe-search-empty.json +0 -127
  132. package/docs/cc-probe-198/probe-task-family.json +0 -227
  133. package/docs/cc-probe-198/probes.status +0 -6
  134. package/docs/cc-probe-198/raw-capture-interactive.json +0 -948
  135. package/docs/cc-probe-198/raw-capture-print.json +0 -787
  136. package/docs/cc-probe-198/raw-capture2-interactive.json +0 -948
  137. package/docs/cc-probe-198/raw-capture2-print.json +0 -787
  138. package/docs/cc-probe-198/system-prompt.txt +0 -135
  139. package/docs/cc-probe-198/tools-array.json +0 -962
  140. package/docs/durability-boundary.md +0 -63
  141. package/docs/fix-2026-06-10-/350/256/241/347/256/227bug/344/277/256/345/244/215-search/346/211/271.md +0 -75
  142. package/docs/fix-2026-06-11-/347/254/254/344/272/214/350/275/256-/344/277/256/345/244/215-search/346/211/271.md +0 -62
  143. package/docs/parity-sweep/REPORT-DYNAMIC.md +0 -97
  144. package/docs/parity-sweep/REPORT-STATIC-GGB.md +0 -127
  145. package/docs/parity-sweep/REPORT-STATIC-RWE.md +0 -133
  146. package/docs/vendor-history.md +0 -385
  147. package/docs/vendored-upstream-diff-2026-07.md +0 -83
@@ -1,63 +0,0 @@
1
- # Durability boundary — what `sema-core`'s durable suspend/resume does and does not guarantee
2
-
3
- > Honest scope statement (core [R45] P2-a). The durable checkpoint / suspend / resume machinery
4
- > (design/45/48/49) is an **approval-driven human-in-the-loop suspend/resume with exactly-once
5
- > continuation** — NOT a general-purpose, deterministic-replay workflow engine. This page states the
6
- > boundary precisely so you can rely on it correctly and not over-trust it.
7
-
8
- ## What it guarantees
9
-
10
- - **Durable suspend at an approval gate.** When a tool call hits a `policy: "ask"` gate and the task is run
11
- with `durableApproval` + a `CheckpointStore`, the task suspends and its resumable state is persisted. The
12
- suspend point is the **gate boundary** — the gated call has *not* executed yet.
13
- - **Cross-replica resume.** A *different* process/replica can resume the same task from the persistent
14
- checkpoint (the checkpoint carries the per-task correctness state + the remote `WorkspaceHandle`); resume
15
- is not pinned to the process that suspended.
16
- - **Exactly-once continuation.** The pending approved action is applied **at most once**: resume resolves the
17
- checkpoint via a single-token compare-and-set (`resolve`, mutually exclusive with `expire`), so two racing
18
- resumes can't both execute the gated call. Once-only is the load-bearing guarantee.
19
- - **Correct state restoration.** The per-task correctness fields needed to continue in the *same* state space
20
- (file read-state, recorded refs, active tools, output ref, nested stats, consolidation notes, …) are
21
- restored from an explicit whitelist; a field that can't be serialized is a fail-fast on suspend, never a
22
- silent drop on resume (a compile-time guard enforces the whitelist is covered).
23
- - **Remote workspace recovery.** For a remote `ExecutionEnv`, the workspace is restored (`resumeVM`) before
24
- any env operation, and post-resume consistency (`postResumeInit`: refs/caches/secrets) runs before work
25
- continues. A failed restore/consistency step is surfaced, not silently run on an empty workspace.
26
-
27
- ## What it does NOT guarantee
28
-
29
- - **Not deterministic replay.** There is no event-sourcing / deterministic re-execution of the whole task.
30
- This is conversation-level resume at gate boundaries, not a Temporal-style replay engine. Do not assume a
31
- task can be re-run from the beginning and reproduce identical intermediate steps.
32
- - **Not arbitrary-point durability.** Durable suspend happens only at DEFINED points — a `policy: "ask"` gate
33
- (HITL approval / design/80 supervisor) AND a `resource_limit` resource-slice boundary (design/74, landed: a
34
- long autonomous run suspends when a $/turn/walltime slice is exhausted, then resumes). A task is NOT
35
- checkpointed after every step; you cannot resume from an arbitrary instruction.
36
- - **Not mid-tool-execution replay.** A tool interrupted *mid-execution* (e.g. a crash while a side-effecting
37
- call was in flight) is reconciled by **effect class** and marked `[INTERRUPTED]` — it is **never
38
- automatically re-done**. An unknown-effect interrupted call is treated as possibly-applied, not replayed.
39
- - **Not a general workflow engine.** No DAG scheduling, no automatic retries of arbitrary steps, no
40
- timer/saga semantics. The mechanism exists for **human-approval-driven** pause/continue, and that is the
41
- use case it is correct for.
42
-
43
- ## Resume authorization (single-factor token)
44
-
45
- The resume token is a **branded bearer credential** (token-as-auth). The v1 trust model is that the **resume
46
- caller is the trusted control plane** — whoever holds the token may resume. There is no second factor in
47
- core.
48
-
49
- - For higher-risk deployments, wrap your own authorization **around** `resume` (bind the token to a
50
- principal / scope / identity in your control plane before calling). Core gives you the once-only resume
51
- primitive; identity binding is a deployment concern by design.
52
- - Treat a persisted token as a secret: a checkpoint row that stores a `WorkspaceHandle.sessionToken` is
53
- "whoever reads it can resume into the sandbox" — encrypt at rest, bind a TTL to token rotation, and audit
54
- read paths (deployment-side).
55
-
56
- ## One-line summary
57
-
58
- > Approval-boundary durable human-in-the-loop suspend/resume with exactly-once continuation and cross-replica
59
- > recovery — **not** a general deterministic-replay workflow engine, and the resume token is a single-factor
60
- > bearer credential you should wrap with your own authorization for high-risk use.
61
-
62
- ---
63
- *活文档(入口 [`HANDOFF.md`](HANDOFF.md))。最后核实 2026-06-18。design/74 已落地 `resource_limit` 第二种 durable suspend 点(资源切片,长自治跑),已并入正文。**如何重验**:`grep -n 'reopen\|resolve' src/core/checkpoint-store.ts`(exactly-once CAS + 补偿)。*
@@ -1,75 +0,0 @@
1
- # 计算 bug 修复 — search 批(直接修,clay 2026-06-10 授权)
2
-
3
- > 输入证据:`docs/audit-2026-06-10-计算bug扫描.md`(专门 AI 扫)+ 我的独立去相关核验(design/64 §28)。
4
- > 边界:**我直接修 5 个 core 层、改动清晰、可完整验证的**(S-1/A-3/A-4/A-6/A-7);**A-1/A-2/A-5/A-8 给 core 二次复核+修**(vendored 高风险 或 brain/新逻辑需深验,见团队/给core-AI.md)。
5
- > 基线:commit `cbdcd9f`(1.91.0)。每条:根因 → 修法步骤 → 单元测试 → 验证状态。
6
-
7
- ---
8
-
9
- ## S-1 — edit_file 单次替换走 JS `$` 替换模式,静默腐坏文件
10
-
11
- - **文件**:`src/tools/fs/index.ts:198`
12
- - **根因**:`content.replace(old_string, new_string)`,searchValue 为字符串时 replacement 仍解释 `$$`/`$&`/`` $` ``/`$'`。模型编辑含这些序列的内容(Makefile `$$`、bash `$'\n'`、正则 `$&`)→ 写回内容 ≠ 模型给的 `new_string`,且 `state.set` 用腐坏内容 hash 记账、staleness 抓不到、工具报 "Edited" 成功。`replace_all` 路径用 split/join(安全)= 两路不对称即铁证。
13
- - **修法**:单次替换改函数式 replacement `content.replace(old_string, () => new_string)` —— 函数返回值不被解释为替换模式,与 split/join 语义等价。
14
- - **单测**(hands-fs.test.ts,真机 NodeExecutionEnv):read→edit 一个文件,`new_string` 含 `$&`/`$$`/`` $` ``;断言写回内容**逐字等于** new_string(非被 `$` 模式展开)。回归:普通 new_string 仍正常。
15
- - **状态**:✅ 已改 + 单测绿。
16
-
17
- ---
18
-
19
- ## A-3 — anthropic brain `totalTokens` 漏算 cache token → 压缩锚低估、压缩不触发
20
-
21
- - **文件**:`src/brain/anthropic.ts:189`
22
- - **根因**:`totalTokens: input + output`,而 cacheRead/cacheWrite 上面已算出(:183-184)却没加进。下游 `calculateContextTokens = usage.totalTokens || (input+output+cacheRead+cacheWrite)` 非零短路取 totalTokens → cache token 全丢。Anthropic `input_tokens` 不含 cache(API 语义)、默认开 cacheBreakpoints → 长任务绝大多数 prompt 是 cacheRead → 锚严重低估 → auto-compaction 不触发 → provider context overflow 硬拒。
23
- - **修法**:`totalTokens: input + output + cacheRead + cacheWrite`(cacheRead/cacheWrite 也占上下文窗口,必须计入上下文占用)。
24
- - **单测**(anthropic.test.ts):构造含 cache_read/cache_creation 的 usage → 断言返回的 `totalTokens === input+output+cacheRead+cacheWrite`。
25
- - **状态**:✅ 已改 + 单测绿。
26
-
27
- ---
28
-
29
- ## A-4 — pricing 缺 cache 价时缓存 token 计为免费,与文档"按全价"相反 → 成本少算、预算钳失效
30
-
31
- - **文件**:`src/core/pricing.ts:46-56`(computeCostMicroUsd)+ 文档 `:12`
32
- - **根因**:`fullInput = totalInput − cacheRead − cacheWrite − cacheWriteLong`(无条件扣 cache 子集)+ `per1M(cacheReadTokens, pricing.cacheReadPer1M)`,而 `modelCostToPricing` 在 cost 表无 cacheRead 时传 `undefined` → `per1M(_, undefined)` = 0 → cache token 既不按 input 价(被扣了)也不按 cache 价(0)= **免费**。文档承诺"缺失=全价 input"(保守多算),实现是"缺失=免费"(激进少算)方向相反。暖循环 80-95% 输入是 cacheRead → 成本少算到 ~1/5、`overBudget`/cascade/verify 的 costCeiling 全失效。
33
- - **修法**:`computeCostMicroUsd` 里,cache 价为 `undefined`(未配)时该子集**不从 fullInput 扣**(落回全价 input);显式 `0` 仍表示免费。`modelCostToPricing` 已保留 undefined 区分(`cost?.cacheRead`),无需动。
34
- - **单测**(observability.test.ts):①pricing 只配 {input,output}(cache 价 undefined)+ counts 含 cacheRead → 断言成本 = 全部 token 按 input 价(cacheRead 不免费);②显式 cacheReadPer1M:0 → cacheRead 免费(保留显式免费语义);③显式 cacheReadPer1M>0 → 按 cache 价。
35
- - **状态**:✅ 已改 + 单测绿。
36
-
37
- ---
38
-
39
- ## A-6 — grep/glob 的 `path` 参数无 root 包含检查 → verifier readOnly 模式文件边界逃逸
40
-
41
- - **文件**:`src/tools/fs/index.ts`(makeGrepTool :255 / makeGlobTool :287 调用点)
42
- - **根因**:read/edit/write 全走 `resolveKey`(canonicalize+within),但 grep/glob 的 `path` 直接拼接传给 search(search.ts:170 `start = path.startsWith("/") ? path : rootPrefix+path`),`/etc` 或 `../../..` 直接走出 root,`rel()` 对 root 外路径原样返回 → 内容倒出。普通模式 bash 本就不沙箱、增量有限;但 **verifier readOnly 模式(只挂 read/grep/glob、无 bash)下是声明边界的真实逃逸**。
43
- - **修法**:grep/glob 工具 execute 里,若给了 `path`,先 `resolveKey(env, rootCanonical, path, signal)` → 越界(ok:false)返 violation 拒绝 → 通过则用 canonical key 替换传给 search(与 read/edit/write 同一道门)。落调用点、不改 search.ts 签名。
44
- - **单测**(hands-fs.test.ts,真机):grep/glob `path="/etc"` 或 `path="../.."` → 返 violation、不倒出 root 外内容;`path="子目录"`(合法)→ 正常搜索。
45
- - **状态**:✅ 已改 + 单测绿。
46
-
47
- ---
48
-
49
- ## A-7 — teacher finalize 累计 stats 漏权威成本字段 → 同对象内部自相矛盾
50
-
51
- - **文件**:`src/agents/teacher.ts:544-553`(finalize)+ addStudent :318 / teacherStats
52
- - **根因**:`stats: { ...result.stats, tokens: studentTokens, turns: studentTurns, costUsd: studentCost }` —— 展开保留的 `costMicroUsd`(authoritative)/`promptTokens`/`cachedTokens`/`outputTokens`/`nested` 全是**末次** runTask 值,只覆盖 tokens/turns/costUsd(累计)。types 标 costMicroUsd authoritative、costUsd @deprecated → 按推荐字段读成本的消费者在每次 escalation 拿到末次成本+累计 token=矛盾。附:addStudent 不计 nested(学生用 subagent 时漏委派成本)、addTeacher 计=不对称。
53
- - **修法**:累计并回写 `costMicroUsd/promptTokens/cachedTokens/outputTokens`(与 tokens/turns 同累计);addStudent 补 nested 累计;teacherStats 补 costMicroUsd。
54
- - **单测**(teacher.test.ts):mock 多次学生 run(含 escalation)→ 断言 finalize 后 stats 的 costMicroUsd === 累计值(非末次)、与 tokens 口径一致。
55
- - **状态**:✅ 已改 + 单测绿。
56
-
57
- ---
58
-
59
- ## 给 core 的 4 条(团队/给core-AI.md,存疑/高风险二次复核)
60
-
61
- - **A-1**(interrupt 对账污染会话=严格 provider 非法序列):vendored agent-loop + harness emitRunFailure + core reconcile 四层链、高爆炸半径、修法多选(不持久化 abort 空 assistant / reconcile 前剥尾部 failure / 回卷 leaf)。**+ 我 INT-D 的盲区**(没验打断后 session 序列对严格 provider 合法)。
62
- - **A-2**(findCutPoint 回落最早切点→幻觉摘要→压缩永久关闭):vendored compaction、修法需确认 cutIndex 语义(≤i 最大切点)+ 非 split 分支补空守卫。压缩主线核心。
63
- - **A-5**(openai thinking 不进 finalContent→DeepSeek 回放死):finalize 存 thinking 块只是一半,需同时验 toOpenAIMessages 对非 DeepSeek provider 跳过 thinking 的回放交互(误改会让 thinking 发给不支持 provider 报错)。
64
- - **A-8**(jsGrep 静默忽略 glob/multiline):实现 glob 过滤+整文件多行匹配+行号映射=新逻辑、易错、建议配 rg-parity 测试一起做。
65
-
66
- ---
67
-
68
- ## B-5 — cascade/team/verify stats 跨层不一致 + 漏 nested 委派成本(clay:值得修的 B 也修)
69
-
70
- - **文件**:`src/agents/cascade.ts`(rungCost + finalize ×2)、`team.ts:228`、`verify.ts:249/286`
71
- - **根因**:与 A-7 镜像同族。① cascade finalize 只覆盖 costMicroUsd=跨梯级总和、tokens/turns/costUsd 残末级 rung = 不一致;rungCost 不含 nested → ceiling/attempt 漏委派成本。② team `tokens/turns += res` 不含 nested。③ verify `spend += costMicroUsd` 不含 nested → cost ceiling 可被委派的 verifier 静默超。
72
- - **修法**:cascade 加 totalTokens/totalTurns/totalCostUsd 累计(含 nested)+ rungCost 含 nested + 两处 finalize 用累计;team tokens/turns 含 nested;verify cost/spend 含 nested。
73
- - **单测**(cascade.test.ts):2-rung escalate → `r.stats.turns===2`(累计、修复前末级=1)。**抓捕力验过**(stash 回退→`expected 1 to be 2` 红)。
74
- - **状态**:✅ 已改 + 单测绿 + 抓捕力验证。
75
- - **B-1/B-9 不在此批**:压缩主线(auto-compaction/runtask、core 正动 + 与 A-2/§26.4 估算修复耦合)→ 给 core 随压缩 council 一起,避免共享工作树撞车。其余 B(B-2/3/4/6/7/8/10~18)= 边界/触发苛刻/可观测,clay 定不修。
@@ -1,62 +0,0 @@
1
- # 第二轮 bug 修复 — search 批(直接修,clay 授权"修所有尽可能确定能修的、不确定给 core")
2
-
3
- > 输入:`docs/audit-2026-06-10-第二轮-空白区与忘接线.md`(专门 AI 扫)+ 我的独立去相关核验。
4
- > 边界:我直接修 **core 层、改动清晰、可独立验证、低风险** 的;**vendored / 方向决策 / 修法破坏现有语义** 的给 core。
5
- > 基线:core 已 push eae0f09(第一轮 A-1/A-2/A-5/A-8-jsGrep)+ f24f02d(第一轮 B-1/B-6/B-9),我的第二轮建在其上。
6
- > 验证:全套 944 测试,943 绿(唯一红 = F8,**core 的 f24f02d/B-9 引入的预存回归**,见末尾)。每条配单测 + 抓捕力说明。
7
-
8
- ## 我已修(10 条,core 层,进 council)
9
-
10
- ### A-1 — combinePolicies 丢弃 allow{updatedInput} 重写(1.65 runToolGate 孪生)
11
- - `src/core/tool-policy.ts` combinePolicies。fold 循环遇 `allow{updatedInput}` 既不短路也不记 → 返裸 ALLOW、重写丢失(脱敏/钳路径失效)+ 不级联给后续 policy。
12
- - 修:级联重写 args 到下一 policy(`current`)+ 带出最新 rewrite(`asked ?? rewrite ?? ALLOW`),deny>ask>allow 优先级不变。
13
- - 单测(security-fixes.test.ts):组合 redact+observe → updatedInput 带出 + 第二 policy 审脱敏后 args;deny 仍短路。
14
- - **边角给 core**:`ask` 类型不含 updatedInput,故 ask 越过后续 rewrite 时 rewrite 不随 ask 带出(需 ask-side carry 或 by-design — 已在代码注释 flag)。
15
-
16
- ### A-6 — delimitUntrusted 空路径不中和带属性的 system-reminder
17
- - `src/core/untrusted-text.ts` SYSTEM_REMINDER 正则。空 extraTags 路径用严格正则(不容忍属性),而 delimitUntrusted(web_fetch/MCP/verify)恰走空路径 → `<system-reminder importance="x">` 开标签逃逸。
18
- - 修:SYSTEM_REMINDER 正则加 `(?:\s[^>]*)?` 容忍属性,两路径统一(ZWSP 非 `\s`,幂等保持)。
19
- - 单测(untrusted-text.test.ts):sanitize/delimit 中和带属性标签 + 幂等 + 良性文本不动。
20
-
21
- ### A-7 — 记忆内容未围栏直接注入系统提示词(跨会话持久化注入)
22
- - `src/core/memory.ts` composeMemoryBlock + `memory-recall.ts` composeSelectiveMemoryBlock。note 含 `</user_memory>\n<system-reminder>` 逃出围栏伪造系统权威,每个后续同 scope 会话生效。
23
- - 修:payload / r.text / manifestText 过 `sanitizeUntrustedText(text, ["user_memory"])`(依赖 A-6)。
24
- - 单测(security-fixes.test.ts):composeMemoryBlock 中和 fence-escape、真 wrapper 仍框定。
25
-
26
- ### A-8 — subagent 子报告未围栏回注 leader(design/53 §2.A 遗漏)
27
- - `src/agents/subagent.ts`。child.result 原文拼进 [Sub-agent report] 回注 leader(结尾带可信指令行)→ 注入的子 agent 可伪造框架/标签污染 leader。design/53 已落 verify/team/web/mcp,唯独 subagent 漏。
28
- - 修:`delimitUntrusted("sub-agent result", child.result)`。**元数据行(core 生成)可信不动,只 fence worker 自由文本**。`details`(程序元数据、不进 LLM prompt)不是注入面。
29
- - 单测(multiagent.test.ts):端到端 child 注入 → leader 收到的 **content**(非 details)含 UNTRUSTED 围栏 + 中和;maxDepth:1 保 child leaf 确定(之前 flaky 根因 = child 再 delegate 的非确定性)。
30
-
31
- ### B-1 — hooks hook-ask 提升覆盖 policy allow{updatedInput}(A-1 同族第三处)
32
- - `src/core/hooks.ts` runToolGate。hook-ask 提升 allow→ask 时丢 policy 重写 → 批准后用未脱敏 args。
33
- - 修:promote 前记 policyRewrite、批准后 fallback 应用。
34
- - 单测(hooks.test.ts):hook-ask + policy redact + 批准 → updatedInput 仍 = 脱敏值。
35
-
36
- ### B-2 — validateSelectedIds off-by-one(maxSelected:0 返 1 条)
37
- - `src/core/memory-recall.ts`。`>= max` 在 push 后。修:检查移到 push 前。单测(security-fixes):max=0 返 []。
38
-
39
- ### B-3 — selective 选中正文无字节上限
40
- - `memory-recall.ts`。manifest/inject-all 有 cap,选中正文无 cap → 一条超大 note 无限进系统提示词。修:SELECTED_BODY_MAX_BYTES=50KB(UTF-8 边界安全)。
41
-
42
- ### B-4 — manifest 文案误导(recall by-id 死路)
43
- - `memory-recall.ts`。recall 是 lexical 关键词搜(非 by-id),文案"call recall to fetch by id"→ 模型照 id 调返空。修:文案改为"call recall with keywords from its description"。
44
-
45
- ### B-5 — selector 外层 signal 已 aborted 时不触发
46
- - `memory-recall.ts` selectAndComposeMemory。已 aborted 时 addEventListener 不触发 → selector 烧满超时。修:ac 创建后预查 `signal?.aborted` 立即 abort。
47
-
48
- ### W-3 — 删死代码 FILE_TOOL_NAMES
49
- - `src/tools/fs/index.ts`。根 index.ts 未 re-export、全仓零引用、HAND_TOOL_EFFECTS 已覆盖 → 删。
50
-
51
- ## 给 core(不确定 / 高风险 / vendored / 方向决策)
52
-
53
- - **A-2(SQL allowTables comma-join 绕过)**:真 bug、多租户 S 级(`FROM a, b` 第二表绕过 allowlist)。但修法是 SQL 解析方向决策(正则补丁易漏子查询/CTE、fail-closed 误拒、或加解析器依赖)—— 安全门错修更危险,给 core/clay 定方向。
54
- - **A-3(exec signal→exitCode 0)+ A-4(shell-output 满载竞态)**:vendored(nodejs.ts / shell-output.ts)、A-4 是异步竞态 —— 给 core。
55
- - **A-5(property-harness catch 吞结构违例)**:真假阴,但**我的修法(catch 记违例)破坏了现有测试** `totalNoThrow: caught only when totality is contracted` —— 外层 catch 无法区分"impl 抛(totalNoThrow 管)"vs"check 求值抛(结构违例)"。已回退。修法需在 iv.check 内部区分或重调 fn 探测,归 core(带分析,见代码注释)。
56
- - **B-6(selector runTask throw 时 release 不执行)**:现象真,但 runTask throw 时 sessionId 不可得、调用方无法 release —— 修法归 runTask 的 throw-清理契约,不在 selector。
57
- - **W-1(sessionTtlDays 无人读)/ W-2(RunnerDeps.agents 无人读)**:忘接线,"删字段+改 README" vs "真接线"是契约决策 —— 需 clay/service 确认 README 预期。
58
- - 其余 B(B-7~B-16):vendored / 触发苛刻 / 方向决策,给 core 顺模块修。
59
-
60
- ## 🔴 发现 core 的回归(F8,必须告知 core)
61
- - 全套跑唯一红 = `cleanups.test.ts > F8 — compaction failure is surfaced ... onError(phase=compaction)`(期望 `[{phase:'compaction'}...]`、实得 `[]`)。
62
- - **git stash 我的全部改动后 F8 仍红 = 不是我引入**。基线 HEAD = core 的 f24f02d,其 **B-9(压缩 LLM 调用入账)改了 compaction 调用路径** → 疑似该改动让 compaction summary throw 时不再发 onError(phase=compaction)。给 core 自查。
@@ -1,97 +0,0 @@
1
- # CC vs sema-core 工具面动态行为对照(ground truth,非源码推断)
2
-
3
- - 日期:2026-07-07
4
- - CC:`claude` CLI **2.1.187**(headless `claude -p --output-format stream-json --verbose --allowedTools Read,Edit,Write --permission-mode acceptEdits --model haiku`,每场景独立会话,场景间文件字节级再生)
5
- - sema:`@sema-ai/core` **1.246.0**(工作树 devendor@6258e6b),`createHandsToolkit(NodeExecutionEnv, new Map(), root, {includeShell:false})` 直调 Read/Edit/Write 的 `execute`,原始返回全文存 `sema-results.json`
6
- - 采集纪律:CC 侧只取 stream-json 里 `tool_use.input` / `tool_result.content` 原始 JSON(不信模型转述);每个写场景前后 `hexdump -C` 落盘字节;CC 原始件=`cc-*.jsonl`+`cc-extracted.json`,sema 原始件=`sema-results.json`
7
- - 测试文件生成:`gen-files.mjs`(确定性,场景间可字节级恢复)
8
-
9
- ## 对照总表
10
-
11
- | 场景 | CC 行为(原始摘录) | sema 行为(原始摘录) | 落盘字节差异 | 判定 |
12
- |---|---|---|---|---|
13
- | R1 Read utf16le-bom.txt | 成功,无 is_error。内容=按 UTF-8 硬解码的乱码:`"1\t??h e l l o …"`(U+FFFD×2=BOM 残骸,每字符后跟 NUL) | `Error (Read): "…utf16le-bom.txt" appears to be a binary file (non-text content); this tool reads UTF-8 text only.` | 两侧均无 | **分歧:CC 有损读(乱码进上下文,无警告);我们拒读(显式错误)。我们更严** |
14
- | R2 Read utf16le-nobom.txt | 成功,同样乱码(无 BOM 残骸) | 同 R1,binary 拒读 | 均无 | 同 R1,我们更严 |
15
- | R3 Read utf16be-bom.txt | 成功,乱码 | 同 R1,binary 拒读 | 均无 | 同 R1,我们更严 |
16
- | R4 Read utf8-bom.txt | 成功:`"1\thello 中文 world\n2\tline2\n3\t"` — **BOM 被静默剥离**(结果串无 U+FEFF,已核 repr) | 成功:U+FEFF **保留在第 1 行行首**(+防注入 system-reminder) | 均无 | **分歧:CC 剥 BOM(模型看不见→后续 Write 会丢 BOM);我们如实呈现** |
17
- | R5 Read crlf.txt | 成功:`"1\ta\n2\tb\n3\t"` — **CRLF 读出归一为 \n** | 成功:`"1\ta\r\n2\tb\r\n"` — \r 原样保留(details.file.content=`"a\r\nb\r"`) | 均无 | **分歧(呈现层):CC 归一显示、我们原样;落盘无影响(见 E3 等价)** |
18
- | R6 Read latin1.txt | 成功:`"1\tcaf? r?sum?\n2\t"`(0xE9→U+FFFD,有损无警告) | binary 拒读 | 均无 | **分歧:CC 有损读;我们拒。我们更严** |
19
- | R7 Read binary.txt(PNG 魔数,.txt 掩护) | 成功,无 is_error,二进制乱码直接进上下文 | binary 拒读 | 均无 | **分歧:CC 对掩护二进制不设防;我们内容嗅探拒读。我们更严** |
20
- | R8 Read empty.txt | 成功:`"<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>"` | 成功:`"[…/empty.txt: empty file]"` | 均无 | **一致(语义):都不报错、都给空文件信号;文案不同** |
21
- | E1 Edit utf16le-bom.txt hello→HELLO | Read=乱码(同 R1)但 Edit(old=`hello`)**成功**;落盘=`ff fe 48 00 45 00 4c 00 4c 00 4f 00 …` = **UTF-16LE+BOM 完整保留,真编码感知替换** | Read 拒(binary)→ Edit 被挡:`Error (Edit): File has not been read yet. Read it first before editing.` | CC:变(正确 UTF-16LE 编辑);sema:不变 | **分歧:CC 能力更强(BOM 检测→解码→替换→原编码回写);我们保守拒绝、零损坏。CC Read 乱码 vs Edit 成功 ⇒ CC Edit 层有独立编码检测** |
22
- | E2 Edit utf16le-nobom.txt hello→HELLO | Edit **失败** is_error=true:`<tool_use_error>String to replace not found in file.\nString: hello</tool_use_error>` | Read 拒 → Edit 被 read-first 挡 | 两侧均不变(无损) | **一致(结果):都失败且零损坏;路径不同(CC 编码感知只认 BOM,无 BOM 即失明;我们在读口就拒)** |
23
- | E3 Edit crlf.txt a→A | Read 显示 `a\n`;Edit 成功;落盘=`41 0d 0a 62 0d 0a` = **CRLF 保留** | Read 显示 `a\r\n`;Edit 成功(originalFile=`"a\r\nb\r\n"`);落盘同 | **两侧落盘字节完全相同**(A\r\nb\r\n) | **一致:双方都保 CRLF;仅呈现层不同** |
24
- | W1 Write 覆盖 utf16le-bom.txt 为 "rewritten 内容" | 模型先 Read(乱码)再 Write,成功;落盘=`72 00 65 00 … 85 51 b9 5b` = **回写 UTF-16LE(保编码)但 BOM 丢失** | Read 拒 → Write 拒:`Error (Write): "…" already exists and was not read this task — read it first to confirm before overwriting.`;补测 W1b(不先读直接写)同样被拒 | CC:变(UTF-16LE 无 BOM);sema:不变 | **分歧×2:① CC 保编码回写但丢 BOM(半保真);② 我们存在死锁面:Read 永拒的文件 read-first 永不可满足 → Write 永不能覆盖(CC 可以)** |
25
-
26
- ## 关键发现
27
-
28
- 1. **CC Read 无二进制/编码防线(2.1.187 实测)**:UTF-16 三种、latin-1、伪装 .txt 的 PNG 全部"成功"读出——乱码/NUL/U+FFFD 直接进模型上下文,is_error 恒空,无任何警告。sema 全部内容嗅探拒读(显式 Error)。上下文卫生我们严格更优;可用性上 CC 允许模型"看一眼"任何文件。
29
- 2. **CC Edit 是编码感知的,且与 Read 不同层**:E1 中 Read 呈现乱码,但 Edit 用纯 ASCII `old_string:"hello"` 命中并**以 UTF-16LE+BOM 原编码回写**(落盘字节完美)。检测依赖 BOM:无 BOM 的 E2 直接 not-found。sema 无此能力(读口即拒)。
30
- 3. **CC Write 半保真**:对 UTF-16LE+BOM 文件覆写保留 UTF-16LE 编码,但**丢 BOM**(FF FE 消失)。与发现 4 连读:Read 剥 BOM → 模型看不见 → 回写自然不带。
31
- 4. **UTF-8 BOM 呈现分歧**:CC 在 Read 输出中静默剥离 U+FEFF(已核 tool_result repr);sema 原样保留在第 1 行行首。
32
- 5. **CRLF 两侧落盘等价**:E3 后两侧文件字节逐一相同(`41 0d 0a 62 0d 0a`)。差异只在呈现层:CC 读出时 CRLF→LF 归一(写回恢复),sema 读出原样含 \r。
33
- 6. **sema 的 Write 死锁面(可改进项)**:凡 Read 拒读的文件(UTF-16/二进制/latin-1),read-first 不变量永不可满足,Write 覆盖被永久锁死(W1+W1b 双证)。CC 无此限制。刻意保守可接受,但意味着 sema agent 无法用 Write 修复一个坏编码文件。
34
- 7. **空文件**:语义一致(成功+空文件信号),文案不同(CC=offset 警告 system-reminder;sema=`[path: empty file]`)。
35
-
36
- ## 附录 A:原始文件 hexdump(gen-files.mjs 生成)
37
-
38
- ```
39
- === utf16le-bom.txt ===
40
- 00000000 ff fe 68 00 65 00 6c 00 6c 00 6f 00 20 00 2d 4e |..h.e.l.l.o. .-N|
41
- 00000010 87 65 20 00 77 00 6f 00 72 00 6c 00 64 00 0a 00 |.e .w.o.r.l.d...|
42
- 00000020 6c 00 69 00 6e 00 65 00 32 00 0a 00 |l.i.n.e.2...|
43
- === utf16le-nobom.txt ===
44
- 00000000 68 00 65 00 6c 00 6c 00 6f 00 20 00 2d 4e 87 65 |h.e.l.l.o. .-N.e|
45
- 00000010 20 00 77 00 6f 00 72 00 6c 00 64 00 0a 00 6c 00 | .w.o.r.l.d...l.|
46
- 00000020 69 00 6e 00 65 00 32 00 0a 00 |i.n.e.2...|
47
- === utf16be-bom.txt ===
48
- 00000000 fe ff 00 68 00 65 00 6c 00 6c 00 6f 00 20 4e 2d |...h.e.l.l.o. N-|
49
- 00000010 65 87 00 20 00 77 00 6f 00 72 00 6c 00 64 00 0a |e.. .w.o.r.l.d..|
50
- 00000020 00 6c 00 69 00 6e 00 65 00 32 00 0a |.l.i.n.e.2..|
51
- === utf8-bom.txt ===
52
- 00000000 ef bb bf 68 65 6c 6c 6f 20 e4 b8 ad e6 96 87 20 |...hello ...... |
53
- 00000010 77 6f 72 6c 64 0a 6c 69 6e 65 32 0a |world.line2.|
54
- === crlf.txt ===
55
- 00000000 61 0d 0a 62 0d 0a |a..b..|
56
- === latin1.txt ===
57
- 00000000 63 61 66 e9 20 72 e9 73 75 6d e9 0a |caf. r.sum..|
58
- === binary.txt ===
59
- 00000000 89 50 4e 47 0d 0a 1a 0a 1b 00 00 00 00 00 00 00 |.PNG............|
60
- 00000010 40 80 c0 00 00 00 00 00 00 40 00 00 00 00 00 00 |@........@......|
61
- 00000020 40 00 00 00 40 00 00 40 00 00 00 00 00 39 00 00 |@...@..@.....9..|
62
- 00000030 00 00 00 00 40 00 00 00 40 00 00 38 00 38 00 00 |....@...@..8.8..|
63
- === empty.txt === (0 字节)
64
- ```
65
-
66
- ## 附录 B:写场景落盘字节(after)
67
-
68
- ```
69
- --- CC E1 (utf16le-bom.txt, hello→HELLO) ---
70
- 00000000 ff fe 48 00 45 00 4c 00 4c 00 4f 00 20 00 2d 4e |..H.E.L.L.O. .-N|
71
- 00000010 87 65 20 00 77 00 6f 00 72 00 6c 00 64 00 0a 00 |.e .w.o.r.l.d...|
72
- 00000020 6c 00 69 00 6e 00 65 00 32 00 0a 00 |l.i.n.e.2...|
73
- → UTF-16LE+BOM 完整保留。sema E1:字节不变(编辑被拒)。
74
-
75
- --- CC E2 (utf16le-nobom.txt) --- 字节不变(not found)。sema E2:字节不变(拒读挡编辑)。
76
-
77
- --- CC E3 与 sema E3 (crlf.txt, a→A) —— 两侧字节相同 ---
78
- 00000000 41 0d 0a 62 0d 0a |A..b..|
79
-
80
- --- CC W1 (Write 覆盖 utf16le-bom.txt) ---
81
- 00000000 72 00 65 00 77 00 72 00 69 00 74 00 74 00 65 00 |r.e.w.r.i.t.t.e.|
82
- 00000010 6e 00 20 00 85 51 b9 5b |n. ..Q.[|
83
- → "rewritten 内容" 的 UTF-16LE(85 51=内, b9 5b=容),**无 BOM**(原文件有 FF FE)。
84
- 注:模型发送的 content 无尾部 \n(prompt 有,模型丢了——模型行为非工具行为)。
85
- sema W1/W1b:字节不变(Write 拒:已存在且本任务未读)。
86
-
87
- --- CC W1 tool_use 原始 input ---
88
- {"file_path": "./utf16le-bom.txt", "content": "rewritten 内容"}
89
- (相对路径被 CC 接受)
90
- ```
91
-
92
- ## 已知局限 / UNVERIFIED
93
-
94
- - CC 侧 tool 参数由 haiku 生成——参数形状(W1 丢尾 \n、W1 模型自发先 Read)含模型自由度;但 tool_result 与落盘字节是工具层 ground truth。
95
- - CC Edit 编码感知边界只测了 BOM 有/无两点;UTF-16BE Edit、latin-1 Edit 未测(UNVERIFIED)。
96
- - sema Write 对不存在文件的新建路径未测(本套件 W 场景全是覆盖已存在文件)。
97
- - CC 对真 .png 扩展名走图像读取路径,本实验用 .txt 掩护专测文本路径;扩展名分支行为 UNVERIFIED。
@@ -1,127 +0,0 @@
1
- # CC vs sema-core 静态机制对照 — 横扫静态腿 B(Grep/Glob/Bash/NotebookEdit)
2
-
3
- 日期:2026-07-07。素材:
4
- - CC 88 可读源:`collection-claude-code-source-code/original-source-code/src/tools/{GrepTool,GlobTool,BashTool,NotebookEditTool}/` + `utils/{glob,ripgrep,timeouts,shell/outputLimits,stringUtils}.ts`
5
- - CC 2.1.201 schema:`scratchpad/package/sdk-tools.d.ts`
6
- - 我们:`ai-agent-core/src/tools/fs/index.ts`(makeGrepTool :551 / makeGlobTool :642 / makeBashTool :984 / NotebookEdit :440)+ `fs/search.ts`(jsGrep/rgGrep/runGlobDetailed)
7
-
8
- 判定分级:**真缺口**(CC 有、我们缺、CC-trained 模型会踩)/**有意分歧**(我们文档化的 KEEP-OWN 决策)/**等价**(语义一致,实现路径可不同)/**无关**(CC-app 特有,core 不该有)。凡标 ⏱ = 行为可能随版本变或需真机跑,「需动态验证」。
9
-
10
- ---
11
-
12
- ## 1. Grep
13
-
14
- CC 88:`GrepTool/GrepTool.ts`;我们:`fs/index.ts:551` + `fs/search.ts`。
15
-
16
- | 维度 | CC 88 | 2.1.201 schema | 我们 | 判定 |
17
- |---|---|---|---|---|
18
- | pattern 语义 | **恒为 regex**(GrepTool.ts:35;`-` 开头走 `-e`,:380) | 同(恒 regex) | **默认 LITERAL 子串**,`regex:true` 才是 regex(search.ts:26,rgGrep `-F` :453) | **有意分歧**(design/115 KEEP-OWN,description 里明说)⚠️ 兼容风险:CC-trained 模型发 `a\|b`、`foo\(` 等 regex 语法会被按字面搜——静态可见的最大行为漂移 ⏱ |
19
- | output_mode 默认 | `files_with_matches`(:316) | 同 | **`content`**(search.ts:311) | **有意分歧**(description 声明)⏱ 模型不传 mode 时两边返回形状不同 |
20
- | -A/-B/-C/context | 有;-C/context 优先于 -B/-A(:362-376) | 同 | 有;同优先级(search.ts:333,rgGrep :459)+ 长名 `context/context_before/context_after` 双拼 | **等价**(index.ts:582-585 CC 键名收编) |
21
- | -i | 有 | 同 | `-i` + `ignore_case` 双名(index.ts:586-587) | **等价** |
22
- | -n | 默认 true,content mode 可关(:321) | 同 | 接受但**恒开**(no-op,index.ts:588) | 等价-(`-n:false` 无效;影响极小) |
23
- | **-o only-matching** | **无** | **有(新增)**:`rg -o`,一行一个匹配段 | **无** | **真缺口**(2.1.201 新参;小)|
24
- | type(--type) | 有(:387) | 同 | 有;rg 路径直传 --type,JS fallback 用 TYPE_GLOBS 映射,未知 type 诚实注记(search.ts:322-329) | **等价** |
25
- | glob 过滤 | 有;**逗号/空格拆成多个 --glob**(brace 保留,:391-409) | 同 | 单值直传(rgGrep :465;JS :315) | **真缺口**(小):模型发 `glob:"*.ts,*.tsx"`(非 brace 形)时 CC 拆开、我们整串→匹配不到 ⏱ |
26
- | head_limit | 默认 **250**,`0`=unlimited,truncation 才报 appliedLimit(:104-128) | 同(描述亦写 250) | 默认 **250**(GREP_DEFAULT_CAP,search.ts:14),`0`=unlimited;**但 schema 描述写"default 200"**(index.ts:592,GrepParams 注释 :47 同样写 200) | **等价** + 🐛 文档漂移:描述 200 ≠ 实际 250(自家 schema 说明该修) |
27
- | offset | 有,分页伙伴(:83) | 同 | 有(clamp 100k,search.ts:337) | **等价** |
28
- | multiline | `-U --multiline-dotall`(:341) | 同 | rg 同 flags(:455);JS fallback 全文 span 近似(multilineSpans) | **等价**(fallback 语义近似 ⏱) |
29
- | 行长上限 | `--max-columns 500`(:338)防 base64/minified 刷屏 | 同 | rg 路径**无** max-columns;JS fallback 每行 slice(0,400)(search.ts:370) | **真缺口**(小):rg 路径一条超长 minified 行可占大量输出(head_limit 只限行数不限行长)⏱ |
30
- | 隐藏文件 | `--hidden` + VCS 目录显式排除(.git/.svn/.hg/.bzr/.jj/.sl,:95-102) | ⏱ | rg 路径不传 --hidden(rg 默认跳隐藏);JS walk 跳全部 dotfile(search.ts:160) | **有意分歧**:CC 搜得到 `.github/`、`.eslintrc` 等隐藏内容,我们搜不到。对"配置文件里找东西"是可感知差距 ⏱ |
31
- | .gitignore | rg 默认尊重(未传 --no-ignore)+ permission-context ignore 模式 + plugin-cache 排除 | ⏱ | rg:尊重 + `--no-require-git`(非 git 树也生效,比 CC 更一致);JS:自研 gitignoreMatcher + DEFAULT_IGNORE_DIRS | **等价+**(--no-require-git 是我们更好的一侧) |
32
- | files_with_matches 排序 | **mtime 降序**(新→旧),文件名 tiebreak;测试环境按名(:529-553) | ⏱ | rg 输出序 / JS 按文件名排序(search.ts:308) | **有意分歧**(mtime 排序把"最近改的文件"排前,对 agent 找活跃文件有真实价值;可考虑收编)⏱ |
33
- | 相对路径化 | 输出转 relative 省 token(:456-465) | 同 | 输出 rel(相对 root)(search.ts:312) | **等价** |
34
- | 截断标记 | `[Showing results with pagination = limit: N, offset: M]`;仅真截断才标 | 同 | `…[capped at N of TOTAL]`(报真实总量)+ walk 诚实 caveat | **等价+**(我们报真实总数,CC 只报 limit) |
35
- | 无 rg 时 | **无 JS fallback**——rg 是 vendored/embedded 二进制(ripgrep.ts:31-60;`USE_BUILTIN_RIPGREP=0` 才用系统 rg),永远可用 | 同 | detectRipgrep(缓存)→ 无 rg 走 jsGrep(远程 E2B/SSH 场景);rg exit≥2 也回落 JS | **有意分歧**(架构:我们的 ExecutionEnv 可能是没有 rg 的远端;CC 只跑本机) |
36
- | 结果 size 上限 | maxResultSizeChars 20_000(persist 阈值,:164) | ⏱ | 无独立 grep 结果字节上限(靠 head_limit + 每行 400/--max-columns 缺席) | 小缺口,同"行长上限"条 |
37
- | path 校验 | 存在性检查 + cwd 建议 + UNC 跳过 | 同 | resolveKey 根 containment(A-6:防 verifier 越界) | **有意分歧**(我们多一层安全语义;不报"Did you mean") |
38
-
39
- ## 2. Glob
40
-
41
- CC 88:`GlobTool/GlobTool.ts` + `utils/glob.ts`;我们:`fs/index.ts:642` + `search.ts:495-530`。
42
-
43
- | 维度 | CC 88 | 2.1.201 schema | 我们 | 判定 |
44
- |---|---|---|---|---|
45
- | 参数面 | pattern + path(仅 2 个) | 同(逐字一致) | pattern + path + **max_results**(我们多一个) | **等价**(超集) |
46
- | 排序 | **`--sort=modified`(旧→新,最后一条最新)**(glob.ts:104);prompt 声称"sorted by modification time" | ⏱ | **按字母序**(search.ts:525),description 特意删了 CC 的 mtime 句以免误导 | **有意分歧**(文档化;CC 的"新文件在尾部"让模型天然看到最近改动)⏱ |
47
- | 上限/截断 | **100 条**(globLimits?.maxResults ?? 100,GlobTool.ts:157);尾注 "(Results are truncated. Consider using a more specific path or pattern.)" | ⏱ | **500 条**(opts.max ?? 500);`…[capped at N of M]` 报真实总量 | **有意分歧**(我们更宽 + 更诚实的总量) |
48
- | 隐藏文件 | **`--hidden` 默认开**(CLAUDE_CODE_GLOB_HIDDEN=false 关,glob.ts:99) | ⏱ | walk 跳全部 dotfile | **有意分歧/小缺口**:`Glob ".github/**"` 我们永远空 ⏱ |
49
- | .gitignore | **默认 `--no-ignore`(不尊重 gitignore!)**(CLAUDE_CODE_GLOB_NO_IGNORE 默认 true,glob.ts:98)→ CC Glob 能命中 node_modules(所以才 100 条上限) | ⏱ | 尊重 .gitignore + DEFAULT_IGNORE_DIRS(node_modules 等永不命中) | **有意分歧**(两边哲学相反:CC=全量+小上限,我们=过滤+大上限;模型想 glob node_modules 里的东西时我们做不到) |
50
- | 绝对路径 pattern | extractGlobBaseDirectory 拆 baseDir+相对 pattern(rg --glob 只吃相对)(glob.ts:17-84,含 Windows 盘根) | 同 | pattern 直接对 rel(f)/basename 匹配;绝对形 pattern 未特拆(`path` 参数另有绝对处理) | 小缺口 ⏱:模型发绝对路径 pattern(如 `/repo/src/**/*.ts`)CC 拆解、我们按 rel 匹配大概率空 |
51
- | `**/` 语义 | rg glob 引擎 | 同 | 自研 globTokenToRegExp(`**`→`.*`、`{a,b}` brace、`?`);还额外匹配 basename | **等价**(边缘语义差异 ⏱) |
52
- | 二进制文件 | 名字匹配即返回 | 同 | walk 的 nameOnly 通道保证二进制/超大文件可被 glob 命中(search.ts:521) | **等价** |
53
- | 引擎 | ripgrep --files(无 JS fallback) | 同 | 纯 JS walk(5000 文件/32 深度上限 + 诚实 caveat);**glob 从不走 rg** | **有意分歧**(超大 repo 我们 walk 上限先到;caveat 会说)⏱ |
54
-
55
- ## 3. Bash
56
-
57
- CC 88:`BashTool/BashTool.tsx` + prompt.ts + utils/timeouts.ts + utils/shell/outputLimits.ts;我们:`fs/index.ts:824(runShell)/984(makeBashTool)/1163(bash_readonly)`。
58
-
59
- | 维度 | CC 88 | 2.1.201 schema | 我们 | 判定 |
60
- |---|---|---|---|---|
61
- | timeout 默认/上限 | **120_000ms / 600_000ms**(timeouts.ts;BASH_DEFAULT_TIMEOUT_MS / BASH_MAX_TIMEOUT_MS env 可覆写) | max 600000(描述可见) | 120s/600s,模型面 **ms**,内部秒(msTimeoutToSec,index.ts:87);无 env 覆写;另有 design/130 execClamp 死线钳制 | **等价**(env 覆写缺席=无关紧要;execClamp 是我们独有加固) |
62
- | 输出截断 | **30_000 chars 默认**(BASH_MAX_OUTPUT_LENGTH env,上限 150k);EndTruncatingAccumulator=**保头砍尾**,`... [output truncated - NKB removed]`;超限完整输出**落盘** tool-results dir(≤64MB)+ `<persisted-output>` 预览消息给模型自取 | 同 ⏱ | **20_000 chars/流**;clipShellOutput=**保头+保尾、砍中间**,`…[N chars total, M omitted from the middle]…`(index.ts:73) | **有意分歧**(头尾双保 vs 保头+落盘全文取回)。cap 值 20k vs 30k 差异小 ⏱ |
63
- | stdout/stderr | **merged fd**(stderr 混进 stdout,:686-717),content = stdout(+error 注记) | 同 ⏱ | **分流**,框帧输出:`exit code: N\n--- stdout ---\n…\n--- stderr ---\n…`;details 里 CC-shaped 分字段 | **有意分歧** |
64
- | exit code 呈现 | 仅 error 时 append `Exit code N`(:697-699)并 throw ShellError→is_error;`returnCodeInterpretation` 对特殊码给语义(commandSemantics) | 同 ⏱ | **恒显示** `exit code: N` 首行;无 is_error 通道、无语义解读 | **有意分歧**(恒显式=模型永远看得到;缺 interpretCommandResult 的"grep exit 1=无匹配非错"类语义注记——小缺口)|
65
- | run_in_background | 有;后台 → LocalShellTask,输出落盘"Use Read to read";另有**超时自动后台化**、assistant-mode 15s 预算后台化、用户 Ctrl-B 后台化 | 有(描述简化) | 有;spawnBackground + TaskRegistry + TaskOutput/TaskStop + G2b 完成通知;**detach hub**(design/116,对应 Ctrl-B/超时后台化的 seam);无 15s 自动预算 | **等价**(机制对齐;auto-background-on-timeout 我们用 execClamp+detach 换形态)⏱ |
66
- | description 参数 | 有(CC 原文) | 同 | 有,**CC verbatim**(index.ts:1013) | **等价** |
67
- | cwd 持久化 | 进程内 persistent shell;`resetCwdIfOutsideProject`:cd 出 allowed working dirs → **重置回原目录**并 stderr 注记(utils.ts:170);子代理 preventCwdChanges | 同 ⏱ | CWD_SENTINEL 标记捕获最终 pwd → cwdRef(与 fs 工具共享);MSYS/Windows 防毒化([384]);**cd 出 root 不重置**(bash 不设沙箱,文档化) | **有意分歧**(重置守卫 vs 自由 cd;我们把 containment 放在 fs 工具+policy 层) |
68
- | 环境注入 | persistent shell + snapshot ⏱ | ⏱ | 每次 re-init,仅注 `PWD=`(逻辑 pwd);env var 不跨调用(description 明说) | **等价**(对模型的承诺一致:cwd 持久、env 不保证)⏱ |
69
- | sandbox 参数 | `dangerouslyDisableSandbox`(schema 内)+ shouldUseSandbox 启发式 + SandboxManager 违规注记;`_simulatedSedEdit` 内部字段特意从模型 schema omit(安全) | **有 `dangerouslyDisableSandbox`**(2.1.201 保留) | **无 sandbox 参数**——隔离=部署方 ExecutionEnv 的事;policy gate(design/37)+ shellGate 分类器为准 | **有意分歧(架构宪法)**:CC=本机自带沙箱可关;我们=core 零沙箱、部署注入。模型发 dangerouslyDisableSandbox 会被非严格校验静默忽略——无害 ⏱ |
70
- | sleep 阻断 | MONITOR_TOOL 特性门:`sleep N`(N≥2)开头即 Blocked,引导 Monitor/后台(:525-530) | ⏱ | 无(description 只有"don't sleep to wait"劝导) | **真缺口**(小,绑定 Monitor 工具缺席) |
71
- | 图片输出 | isImageOutput → image content block(+尺寸压缩 CC-304) | ⏱ | 无(Read 有 image 支持,Bash 无) | **真缺口**(小):`cat foo.png`/截图类命令 CC 直接给模型看图 |
72
- | 只读并行 | isReadOnly/isConcurrencySafe 由命令语义分析(BASH_SEARCH/READ/LIST_COMMANDS 等) | ⏱ | isConcurrencySafe = coarseReadonlyCheck 单命令白名单(design/120 P1.5,index.ts:1046) | **等价**(我们更保守:pipe/复合一律串行)⏱ |
73
- | bash_readonly | 无对应(CC 用 sandbox+permission 分层) | — | 独有:verifier 只读边界的白名单壳(index.ts:1163) | 我们独有,无关 |
74
-
75
- ## 4. NotebookEdit
76
-
77
- CC 88:`NotebookEditTool/NotebookEditTool.ts`;我们:`fs/index.ts:440`。**四工具里 parity 最高。**
78
-
79
- | 维度 | CC 88 | 2.1.201 schema | 我们 | 判定 |
80
- |---|---|---|---|---|
81
- | 参数面 | notebook_path/cell_id?/new_source/cell_type?/edit_mode?(replace\|insert\|delete) | 逐字相同 | 完全同名同枚举(index.ts:446-452) | **等价** |
82
- | cell 定位 | 先按 `id` 查,查不到再 parse `cell-N` 位置索引(:269-290) | 同 | 同:id → `/^cell-(\d+)$/` 位置 fallback(:493-499) | **等价** |
83
- | insert 语义 | 在 cell_id **之后**插;无 cell_id → 开头;insert 必须 cell_type | 同 | 同(:502,:468) | **等价** |
84
- | replace 越界 | replace 指到 length → 自动转 insert,cell_type 默认 code(:371-377) | 同 ⏱ | 同(:505-508) | **等价** |
85
- | cell id 生成 | nbformat>4 或 4.5+ 才生成;`Math.random().toString(36).substring(2,15)` | ⏱ | 同门槛;`randomUUID().slice(0,8)`;<4.5 时 id 被 stringify 丢弃 | **等价**(id 形态不同,语义一致) |
86
- | replace 副作用 | code cell → execution_count=null、outputs=[];cell_type 可换 | 同 ⏱ | 同(:523-527) | **等价** |
87
- | 写回格式 | JSON indent **1** + 保编码/行尾(writeTextContent) | ⏱ | JSON indent **1**(:529);行尾/编码保持无专门处理 ⏱ | **等价**(CRLF/编码保持需动态验证——非 LF 的 ipynb 会被我们规整)|
88
- | read-before-edit | readFileState 时戳 + mtime 陈旧检测(errorCode 9/10) | ⏱ | requireRead + sha256 staleness(:472-477) | **等价**(hash 比 mtime 更强) |
89
- | 错误呈现 | 错误装进 output.error → is_error tool_result | ⏱ | `Error (NotebookEdit): …` 字符串 | **等价**(通道不同) |
90
- | .ipynb 门 | validateInput extname 检查 + Edit/Write 拒 .ipynb 转推 | 同 | 同(:465;ipynbRedirect :94 反向门) | **等价** |
91
-
92
- ## 5. 顺带:2.1.201 schema 里 88 没有的全新工具
93
-
94
- (sdk-tools.d.ts 的 `*Input` 接口对照 88 `src/tools/` 目录)
95
-
96
- | 2.1.201 新工具 | 一句话 | 我们有无对应物 |
97
- |---|---|---|
98
- | Monitor | 流式看守:until 条件轮询/日志监视(Bash sleep 阻断的另一半) | 无(G2b 完成通知覆盖子集) |
99
- | ReportFindings | code-review 结构化 findings 提交(level/findings[≤32]) | 无(review 走 council/codex,core 外) |
100
- | Workflow | 工作流执行入口 | **有**(design/96 workflow 家族) |
101
- | CronCreate/CronDelete/CronList | 定时任务三件套(88 是单一 ScheduleCronTool) | **有**(scheduler-tools.ts) |
102
- | ScheduleWakeup | 定时自唤醒 | 部分(scheduler 近似) |
103
- | Artifact | artifact 产出通道 | 无 |
104
- | PushNotification | 推送通知 | 无(task-notification 是内向的) |
105
- | ReadMcpResourceDir | MCP 资源目录列举(88 只有 ReadMcpResource) | 无 |
106
- | ClaudeDesign / Projects / ShowOnboardingRolePicker | CC 产品面(设计稿/项目管理/onboarding UI) | 无关(app 特有) |
107
-
108
- 注:88 已有 EnterWorktree/ExitWorktree 目录,2.1.201 只是 schema 化,不算全新。sdk-tools.d.ts 只覆盖 SDK 暴露面(Skill/ToolSearch/SendMessage 等未列不代表移除)⏱。
109
-
110
- ## 6. 结论排序
111
-
112
- **真缺口(均为小件,建议排期):**
113
- 1. Grep `-o` only-matching(2.1.201 新参)。
114
- 2. Grep glob 逗号/空格多 pattern 拆分——`"*.ts,*.tsx"`(非 brace 形)现在整串传 rg 必空。
115
- 3. Grep rg 路径 `--max-columns 500` 缺席——minified 长行可刷输出。
116
- 4. Bash 图片输出 → image block。
117
- 5. Bash sleep 阻断(绑 Monitor 缺席)。
118
- 6. Glob 绝对路径 pattern 拆解(extractGlobBaseDirectory 对应物)。
119
- 7. Bash returnCodeInterpretation("grep exit 1=无匹配非错"类语义注记)。
120
-
121
- **文档 bug(自家):** Grep schema 描述写 "default 200",实际 GREP_DEFAULT_CAP=250(index.ts:592 / search.ts:47 注释同错)。
122
-
123
- **有意分歧但值得重议:** ① Grep 默认 literal vs CC 恒 regex——CC-trained 模型的 regex pattern 会被按字面搜,四工具最大静默行为差(动态腿应测发生率);② mtime 排序(Grep files 降序 / Glob 升序)vs 我们纯字母序——"活跃文件优先"对 agent 有信息价值。
124
-
125
- **等价面:** NotebookEdit 几乎逐条 parity;Bash timeout/描述/后台/并行安全对齐;Grep CC 键名(-i/-A/-B/-C/-n/type/head_limit/offset/multiline)已全收编(design/116 W1)。
126
-
127
- **架构分歧(不动):** sandbox 参数(部署方 ExecutionEnv 责任)、JS fallback 存在本身(远端无 rg;CC 是 vendored/embedded rg 永远在)、gitignore/hidden 哲学(CC Glob 默认 --no-ignore+--hidden 搜全量配 100 上限,我们过滤配 500 上限)、输出截断形态(我们头尾双保砍中间 vs CC 保头砍尾+全文落盘取回)。
@@ -1,133 +0,0 @@
1
- # 横扫静态腿 A:CC vs sema-core — Read/Edit/Write 三件套静态机制对照
2
-
3
- 日期:2026-07-07。素材:
4
- - CC 88 可读源(老版本 ground truth):`collection-claude-code-source-code/original-source-code/src/`
5
- - CC 2.1.201 schema:`scratchpad/package/sdk-tools.d.ts`(行号引用记为 sdk-tools)
6
- - 我们:`ai-agent-core/src/tools/fs/index.ts`(下称 index)+ `safety.ts`(下称 safety)+ `src/engine/execution-env/node-execution-env.ts`(下称 node-env)
7
-
8
- 诚实声明:CC 88 源较老;凡 2.1.201 行为可能已漂移处标注**需动态验证**。本报告只读未改任何项目文件。
9
-
10
- 分级图例:🔴真缺口 / 🟡有意分歧(我们故意不同,已论证)/ 🟢等价 / ⚪无关(CC 特有语境,对 sema 无意义)
11
-
12
- ---
13
-
14
- ## 维度 1:编码(detectEncoding 接线)
15
-
16
- CC 88 事实(比任务给的已知结论更细):
17
-
18
- | 接线点 | CC 88 行为 | 坐标 |
19
- |---|---|---|
20
- | **detectEncoding 本体** | 读前 4096 字节:`FF FE`→utf16le;`EF BB BF`→utf8;空文件→utf8(修过 emoji/CJK 写空文件损坏 bug);其余一律 utf8("utf8 是 ascii 超集") | fileRead.ts:20-49 |
21
- | **Read** | **完全不接 detectEncoding**。readFileInRange 固定 `readFile(..., 'utf8')` — CC 88 的 Read 读 UTF-16 文件同样出 mojibake | readFileInRange.ts:104(fast)/354-357(streaming) |
22
- | **Edit validateInput** | 自己内联 BOM 探测(仅 `FF FE`→utf16le,否则 utf8),`fileBuffer.toString(encoding).replaceAll('\r\n','\n')` | FileEditTool.ts:202-214 |
23
- | **Edit call(回写)** | `readFileSyncWithMetadata` 一趟拿 content+encoding+lineEndings;回写 `writeTextContent(path, updated, encoding, endings)` — **检测编码回写**(utf16le 文件编辑后仍是 utf16le) | FileEditTool.ts:442-449, 491;fileRead.ts:75-98 |
24
- | **Write call** | 同样 `readFileSyncWithMetadata`;`enc = meta?.encoding ?? 'utf8'`;新文件 utf8 | FileWriteTool.ts:268-277, 297-305 |
25
- | **readFileState 携带 encoding?** | **不携带**。FileState = {content, timestamp, offset, limit, isPartialView?} — 编码在每次 Edit/Write 时从磁盘重新探测,不进状态 | fileStateCache.ts:5-15 |
26
- | **UTF-16BE(FE FF)** | **不处理**,落到 utf8 → 乱码。ascii/latin1 也不单独处理(并入 utf8 默认)。任务问的"漏项"答案:CC 88 只认 utf16le 一种非 utf8 编码 | fileRead.ts:33-48 |
27
-
28
- 我们:utf8 固定(node-env:649-656 `readFile(..., 'utf8')`;写同理)。Read 有 `isBinaryContent` 内容嗅探(safety.ts:191-203)——UTF-16 文件 utf8 解码出 NUL → **干净拒绝**而非 mojibake(比 CC 88 的 Read 反而更诚实);但 Edit/Write 对 utf16le 文件完全无能力(CC 可以编辑并保持编码)。
29
-
30
- 2.1.201 佐证:sdk-tools 无 encoding 相关输入参数 → 编码仍是内部机制。**需动态验证**(2.1.201 是否已加 UTF-16BE/更全探测,88 源无法回答)。
31
-
32
- **判定:🔴真缺口(窄)** — utf16le 文件的 Edit/Write 往返(CC 有,我们无)。现实代码库里 utf16 罕见(Windows PowerShell/.reg/部分 .NET 资源文件),优先级低。Read 侧我们"拒绝"vs CC"乱码"记 🟡有意分歧(我们更好)。
33
-
34
- ## 维度 2:行尾(CRLF)
35
-
36
- | 接线点 | CC 88 | 我们 | 判定 |
37
- |---|---|---|---|
38
- | Read 展示 | 两条路径逐行剥 `\r`(CRLF→LF)+ 剥 BOM | **不剥**:`content.split("\n")`,行尾 `\r` 原样进模型上下文(不可见字符) | 🔴 |
39
- | Edit 匹配基线 | validateInput 与 readFileForEdit 都 `replaceAll('\r\n','\n')` — old_string 永远对 LF 化文本匹配 | 对原始 bytes 匹配:**跨行 old_string(含 `\n`)在 CRLF 文件上必 miss**,报"not found",模型无法自愈(它看不见 \r) | 🔴 |
40
- | Edit 回写 | `writeTextContent(..., endings)`:检测到 CRLF 则整文件重 join 为 `\r\n`(先归一防 `\r\r\n`)— **保留原行尾** | 无概念 | 🔴 |
41
- | Write 回写 | `writeTextContent(..., 'LF')` = **不做任何转换、模型给什么写什么**。FileWriteTool.ts:300-304 注释即任务说的历史决策:以前保留旧行尾/采样 repo,曾把 Linux bash 脚本写出 `\r` 而翻车,故意改成 as-is | 同样 as-is(node-env 直写) | 🟢 |
42
-
43
- CC 坐标:readFileInRange.ts:29,166-168,254-257;FileEditTool.ts:214,444-449;file.ts:84-98(writeTextContent);FileWriteTool.ts:297-305。我们:index.ts:205(split)、342(replace)、418(write);grep 证实 fs 工具零 `\r\n` 处理。
44
-
45
- **判定:Read/Edit 的 CRLF 归一化+回写保留 = 🔴真缺口(本维度最实的一条)**。触发面:任何 Windows 产/`.gitattributes crlf` 仓库上的多行 Edit。Write 🟢等价。BOM:CC Read 剥 BOM(readFileInRange.ts:138,227-229),我们不剥(模型看到 `1\t...`)— 🔴小缺口(substring 匹配通常仍中,主要是展示噪音)。
46
-
47
- ## 维度 3:部分读语义
48
-
49
- | 项 | CC 88 | 2.1.201 佐证 | 我们 | 判定 |
50
- |---|---|---|---|---|
51
- | offset 基 | 1-based;`offset=0` 视同 1(`offset===0?0:offset-1`,FileReadTool.ts:1020);schema nonnegative int | 描述同 88(sdk-tools:528-545) | 1-based,`Math.max(1,…)`(index:207) | 🟢 |
52
- | **默认 limit** | **代码无默认行数上限**:limit undefined → 整文件,只受 256KB 总大小/25K token 两闸;`MAX_LINES_TO_READ=2000` 只出现在 prompt 文案与 @-mention 附件截断(prompt.ts:10;attachments.ts:3149-3153)| — | **真 2000 行默认**(index:36,208)| 🟡有意分歧:我们把 CC 的"文案 2000"做成了实闸。效果:超 2000 行且 <256KB 的文件 CC 一次全给,我们分页 |
53
- | 大小闸 | maxSizeBytes=256KB,**闸总文件大小非切片**(limits.ts:8-9 自认 known mismatch);显式 limit 时**跳过字节闸**只留 token 闸(FileReadTool.ts:1026)| — | 256KB 总大小闸恒在(含带 offset/limit 的读,index:186-194)| 🟡:我们对显式 range 读也拒 >256KB 文件(CC 允许);"grep 大文件"提示是补偿 |
54
- | token 闸 | 25K tokens(粗估→API 精数),超 → **抛错无内容**(limits.ts:10-13 注释:实验过截断,回滚——错误 100B 比截断 25K tokens 便宜)| `truncatedByTokenCap?: boolean`(sdk-tools:205-208):**2.1.201 整文件超 token 闸改为自动分页给第一页**,非硬错。**需动态验证** | 100K chars(~25K tokens)超 → 硬错无内容,且在记 read-state 之前查(防盲编辑)(index:224-226)| 🟢对 88;对 2.1.201 是新行为线索(auto-paginate) |
55
- | 单行超长截 2000 chars | **无**(全源 grep 无 MAX_LINE/per-line 截断;那是更老版本的机制)| — | 无 | 🟢 |
56
- | cat -n 格式 | 紧凑 `N\t行`(compact 默认,GB killswitch 回退 `%6d→`)(file.ts:278-319)| — | `N\t行`(index:219)| 🟢 |
57
- | 超界 offset | 空 content + system-reminder "file exists but is shorter than the provided offset (N). The file has M lines."(FileReadTool.ts:707)| — | `[path: offset N is past the end of file (M lines)]`(index:212-214)| 🟢等价(文案不同) |
58
- | 空文件 | system-reminder "the file exists but the contents are empty"(FileReadTool.ts:706)| — | `[path: empty file]`(index:240)| 🟢 |
59
- | totalLines 口径 | 尾随换行的空片段**算一行**(`"a\nb\n"`→3 行,渲染幻影空行)(readFileInRange.ts:174-182)| — | 丢尾空片段(→2 行,cat -n 口径)(index:101-109,205)| 🟡(我们更对) |
60
- | 重复读 dedup | 同 offset+limit 且 mtime 未变 → `file_unchanged` stub(仅 Read 来源条目参与)(FileReadTool.ts:536-573)| `file_unchanged.source:"seeded"` 新增(sdk-tools:301-312):启动注入的 CLAUDE.md 也参与 dedup。**需动态验证** | 同窗口+全文 hash 未变 → stub(index:229-235)| 🟢机制异构等价(mtime vs hash) |
61
-
62
- ## 维度 4:staleness / readFileState
63
-
64
- | 项 | CC 88 | 我们 | 判定 |
65
- |---|---|---|---|
66
- | 状态形态 | FileState{content(所读文本), timestamp=⌊mtimeMs⌋, offset, limit, isPartialView?},**LRU 100 条/25MB**(fileStateCache.ts:17-40)——长会话可被逐出→已读文件被再次要求 Read | ReadEntry{hash(**全文** sha256), totalLines, truncated, view, lastReadAt},per-task Map 无逐出(safety.ts:30-51)| 🟡 |
67
- | 新旧判据 | **mtime 比较**(`lastWriteTime > readTimestamp.timestamp`),Windows mtime 假阳性时仅**全量读**允许 content 相等兜底;Edit/Write 在 validateInput 和 call 各查一遍(TOCTOU 双检)(FileEditTool.ts:289-311,451-468;FileWriteTool.ts:198-219,279-295)| **content-hash 比较**(编辑时重读文件全文比 sha256),mtime 完全不用——容器/NFS/跨副本 resume 安全(safety.ts:31-33,382-388;index:320-325)| 🟡有意分歧,我们的判据更强(CC 的 content 兜底只覆盖全量读,部分读遇 mtime 抖动只能重读;我们部分读也精确) |
68
- | 部分读后能否 Edit | **能**:offset/limit 条目不算 partial;isPartialView **只**标自动注入条目(CLAUDE.md 附件内容与磁盘不一致时)强制显式 Read(fileStateCache.ts:8-14;FileEditTool.ts:275-287;FileWriteTool.ts:199)| 能:部分读也记全文 hash,staleness 精确;截断读在 miss 时加"可能在未展示部分"提示(safety.ts:400-420)| 🟢 |
69
- | Edit/Write 后写回状态 | set{content=新全文, timestamp=新 mtime, offset:undefined, limit:undefined}(FileEditTool.ts:519-525;FileWriteTool.ts:331-337)| set{hash=新全文, truncated:false, …}(index:347,421)| 🟢 |
70
- | call 阶段错误 | `FILE_UNEXPECTEDLY_MODIFIED_ERROR` = "File has been unexpectedly modified. Read it again before attempting to write it."(FileEditTool/constants.ts:10-11)| 统一 stale 文案 | 🟢(见维度 7) |
71
-
72
- ## 维度 5:Write 前置条件
73
-
74
- | 项 | CC 88 | 我们 | 判定 |
75
- |---|---|---|---|
76
- | 已存在文件必须先读 | 是:"File has not been read yet. Read it first before writing to it."(errorCode 2)+ mtime staleness(FileWriteTool.ts:198-219)| 是:M3 守卫 + 重读 hash staleness(index:399-417)| 🟢 |
77
- | 新文件免读 | ENOENT → 直接放行(FileWriteTool.ts:186-196)| exists=false → 放行 | 🟢 |
78
- | **Edit 当 Create 用** | `old_string:""` + 文件不存在 = **合法建新文件**;`old_string:""` + 空文件 = 填充;非空文件报 "Cannot create new file - file already exists."(FileEditTool.ts:224-264;utils.ts:274-294,314-316)| checkEditMatch 拒空 old_string("old_string must not be empty",safety.ts:406-408),且 requireRead/读失败更早拦 | 🔴真缺口(窄):CC 训练的模型偶发用 Edit 建文件,我们报错(可自愈:换 Write),多烧一轮 |
79
- | 写入原子性 | temp+rename 原子写 + chmod 保留 + symlink 穿透写 + 失败回退非原子(file.ts:362-478)| `fs.writeFile` 直写(原地截断,天然保 mode/inode;**崩溃中途可留半文件**)(node-env:719-739)| 🟡小分歧:原子性缺口真实但极端条件才触发;symlink 穿透我们经 resolveKey canonical 化等效达成 |
80
- | mkdir 父目录 | 是(FileWriteTool.ts:249-254)| 是(node-env:730 recursive)| 🟢 |
81
-
82
- ## 维度 6:参数面(2.1.201 schema vs 我们)
83
-
84
- | 工具 | 2.1.201 input(sdk-tools:510-556) | 88 对照 | 我们(index) | 判定 |
85
- |---|---|---|---|---|
86
- | Read | file_path(必), offset?, limit?, pages? | **完全同 88**(zod:FileReadTool.ts:227-243)。**无新增输入参数** | file_path?/path?(runtime 二选一必), offset?, limit?;**无 pages** | pages=🟡有意分歧(我们拒 PDF);path 别名=🟡durable 兼容(index:59-67) |
87
- | Edit | file_path, old_string, new_string(均必), replace_all? | 同 | 同名,但 old/new schema-optional(durable 回放需要,runtime 强制,index:279-299)+ 隐藏 edits[](MultiEdit 别名回放);2.1.201 也无 MultiEdit | 🟢 |
88
- | Write | file_path, content(必) | 同 | 同 + path 别名 | 🟢 |
89
- | **新版行为线索(output 侧)** | ① `FileReadOutput.text.truncatedByTokenCap?`:超 token 闸自动分页(88 是硬错)② `file_unchanged.source:"seeded"`:启动注入条目也 dedup ③ `FileEditOutput.originalFile: string\|null`(88 输出必有 string) | — | ①我们仍硬错 ②我们无注入条目概念 | ①🟡候选改进,**需动态验证**具体分页语义 |
90
-
91
- ## 维度 7:错误/结果文案对照(关键路径)
92
-
93
- | 路径 | CC 88 原文 | 我们 | 判定 |
94
- |---|---|---|---|
95
- | 未读先编 | "File has not been read yet. Read it first before writing to it."(Edit ec6/Write ec2) | "File has not been read yet. Read it first before editing."(Edit,safety.ts:363-368);Write:"…already exists and was not read this task — read it first to confirm before overwriting."(index:407) | 🟢近似(CC 模型可识别) |
96
- | 读后被改 | validate:"File has been modified since read, either by the user or by a linter. Read it again before attempting to write it." / call:"File has been unexpectedly modified. …" | "File has been modified since it was read; read it again before editing."(safety.ts:385) | 🟢 |
97
- | 找不到 old_string | validate:"String to replace not found in file.\nString: ${old_string}"(FileEditTool.ts:321)/ call:"String not found in file. Failed to apply edit."(utils.ts:326) | "old_string not found in the file(+截断提示)"(safety.ts:411) | 🟢 |
98
- | 多处匹配 | "Found ${matches} matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.\nString: ${old_string}"(FileEditTool.ts:336) | "Found ${n} matches for old_string — provide more surrounding context to make it unique, or set replace_all.(+截断提示)"(safety.ts:413-418) | 🟢 |
99
- | no-op edit | "No changes to make: old_string and new_string are exactly the same."(FileEditTool.ts:148-153) | **逐字相同**(safety.ts:375-380,故意 verbatim) | 🟢 |
100
- | Edit 成功 | "The file X has been updated successfully." / replace_all:"…All occurrences were successfully replaced."(FileEditTool.ts:575-594) | "Edited X (N replacements)."(index:355-358) | 🟡文案分歧(信息量更高,CC 模型无碍) |
101
- | Write 成功 | "File created successfully at: X" / "The file X has been updated successfully."(FileWriteTool.ts:418-433) | "Wrote X (N lines)."(index:425) | 🟡同上 |
102
- | ENOENT 建议 | "File does not exist. Note: your current working directory is ${cwd}. Did you mean ${similar/cwd 修正}?"(FileReadTool.ts:639-647;file.ts:178-267 findSimilarFile+suggestPathUnderCwd) | 裸 "cannot read …: <fs error>"(index:196) | 🔴小缺口(DX):"丢 repo 目录段"路径自动纠错是 CC 高频自愈路径 |
103
- | 空 old_string 建新文件冲突 | "Cannot create new file - file already exists."(FileEditTool.ts:255) | (无此路径) | 见维度 5 |
104
-
105
- ## 附:七维度外同族机制(CC 88 有、值得记录)
106
-
107
- - **curly-quote 归一匹配**:old_string 直匹配失败 → 弯引号归一后再找(findActualString),并把弯引号风格回植 new_string(preserveQuoteStyle)(utils.ts:73-199)。我们无。🔴低优先(主要救老/弱模型输出)。
108
- - **desanitize 映射**(`<n>`→`<name>` 等 18 条,utils.ts:531-574;api.ts:627 入口归一):逆转 CC 自家 API 消毒。⚪无关(sema 无该消毒层)。
109
- - **new_string 尾空白剥离**(非 .md/.mdx,utils.ts:44-64,596-646)。我们无。🟡低优先。
110
- - **删行去尾换行**:new_string="" 且 old_string 不含尾 `\n` 时自动连 `\n` 一起删,不留空行(applyEditToFile,utils.ts:218-227)。我们的删除会留空行。🔴微缺口(输出洁癖级)。
111
- - **Edit 1GiB 大小闸**(MAX_EDIT_FILE_SIZE,FileEditTool.ts:80-84,防 OOM)。我们无显式闸,但 read-before-edit(Read≤256KB)间接兜底;Write 大 content 后再 Edit 是理论豁口。🟡。
112
- - isBinaryContent:CC 88 有(constants/files.ts:131-160,8KB 采样)但 **Read 不接**(只接 hasBinaryExtension);我们接在 Read(safety.ts:191-203,4KB 采样)。🟡有意分歧(我们增强,任务已知结论确认)。
113
- - 设备文件黑名单:两边同 12 条 + `/proc/*/fd/[0-2]`;我们另加 win32 保留名(CON/PRN/…)。🟢/我们略宽。
114
- - CYBER_RISK 提醒:CC 每次文本读后附(exempt 模型 opus-4-6 除外,FileReadTool.ts:729-738);我们附中性化版(index:47-49)。🟢。
115
- - macOS 截图 thin-space 路径重试(FileReadTool.ts:130-159):我们无。🟡边角。
116
-
117
- ---
118
-
119
- ## 结论:分级汇总
120
-
121
- **🔴真缺口(按触发概率排序)**
122
- 1. **CRLF 归一化**(Read 剥 `\r` / Edit 对 LF 基线匹配 / Edit 回写保留 CRLF)— 多行 Edit 在 CRLF 仓库必挂且模型不可自愈。唯一建议立案项。
123
- 2. Read 不剥 BOM(展示噪音,通常不阻断)。
124
- 3. ENOENT 相似文件/cwd 纠错建议(高频自愈路径,纯 DX)。
125
- 4. Edit-as-create(old_string="" 建新文件/填空文件)— 可自愈,烧一轮。
126
- 5. utf16le 文件 Edit/Write 往返(罕见文件类)。
127
- 6. 删行留空行 + curly-quote 归一(输出质量/弱模型救济级)。
128
-
129
- **🟡有意分歧(维持)**:2000 行默认实闸(CC 88 仅文案)、hash-staleness 替代 mtime(跨副本恢复设计前提)、range 读也受 256KB 文件闸、isBinaryContent 接 Read、totalLines cat -n 口径、结果文案信息量、pages/PDF 拒绝、path 别名、非原子直写(小)、win 保留名加严。
130
-
131
- **🟢等价**:offset/limit 1-based、`N\t` 行号格式、token 闸语义(对 88)、空文件/超界 offset、no-op/staleness/未读文案、Write 前置条件、Write 行尾 as-is、dedup stub(机制异构)、设备黑名单、无单行 2000-char 截断(88 已无此机制)。
132
-
133
- **需动态验证(2.1.201 可能已漂移)**:① 超 token 闸自动分页(truncatedByTokenCap)的具体行为与文案;② seeded dedup;③ 编码探测是否比 88 更全(UTF-16BE?);④ compact `N\t` 行号前缀是否已全量(88 里是 killswitch 实验)。