@thincoder/core 0.10.3 → 0.10.5

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 (172) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +1 -1
  3. package/advisor/citations.mjs +29 -3
  4. package/advisor/compaction.mjs +31 -7
  5. package/advisor/history.mjs +8 -6
  6. package/advisor/loop.mjs +29 -27
  7. package/advisor/messages.mjs +2 -1
  8. package/advisor/run.mjs +9 -5
  9. package/advisor/timeline.mjs +26 -0
  10. package/advisor.mjs +4 -2
  11. package/agent/assemble.mjs +1 -1
  12. package/agent/chat-call.mjs +1 -1
  13. package/agent/child-marks.mjs +2 -2
  14. package/agent/completion.mjs +3 -1
  15. package/agent/digest-account.mjs +189 -0
  16. package/agent/dispatch-gates.mjs +8 -0
  17. package/agent/dispatch.mjs +39 -18
  18. package/agent/family-tools.mjs +27 -16
  19. package/agent/helpers.mjs +12 -3
  20. package/agent/light-round.mjs +66 -0
  21. package/agent/post-turn.mjs +8 -4
  22. package/agent/record-results.mjs +4 -0
  23. package/agent/run-stages.mjs +32 -7
  24. package/agent/run-start.mjs +50 -3
  25. package/agent/setup-reminders.mjs +1 -1
  26. package/agent/setup.mjs +18 -3
  27. package/agent/subagent-event.mjs +1 -1
  28. package/agent/suspension.mjs +37 -12
  29. package/agent/timers.mjs +56 -5
  30. package/agent/turn-loop.mjs +30 -5
  31. package/agent/write-gate.mjs +20 -32
  32. package/agent-tools/advisor-async.mjs +15 -7
  33. package/agent-tools/advisor-settle.mjs +2 -2
  34. package/agent-tools/advisor.mjs +34 -33
  35. package/agent-tools/async-discard.mjs +34 -0
  36. package/agent-tools/async-settle.mjs +32 -7
  37. package/agent-tools/audit-block.mjs +1 -1
  38. package/agent-tools/bash-async.mjs +1 -1
  39. package/agent-tools/batch-lifecycle.mjs +59 -17
  40. package/agent-tools/batch-paths.mjs +24 -4
  41. package/agent-tools/batch-skeleton.mjs +59 -43
  42. package/agent-tools/batch.mjs +39 -18
  43. package/agent-tools/browser-async.mjs +198 -0
  44. package/agent-tools/checkpoint.mjs +1 -1
  45. package/agent-tools/design-slots.mjs +232 -0
  46. package/agent-tools/design-token.mjs +20 -0
  47. package/agent-tools/digest-budget.mjs +1 -1
  48. package/agent-tools/escalate-async.mjs +4 -2
  49. package/agent-tools/manifest.mjs +193 -0
  50. package/agent-tools/parent-channel.mjs +7 -4
  51. package/agent-tools/review-facts.mjs +143 -16
  52. package/agent-tools/settings.mjs +1 -1
  53. package/agent-tools/skill.mjs +1 -1
  54. package/agent-tools/subagent-actions-query.mjs +13 -6
  55. package/agent-tools/subagent-async.mjs +31 -15
  56. package/agent-tools/subagent-panel.mjs +2 -1
  57. package/agent-tools/subagent-scheduler.mjs +8 -5
  58. package/agent-tools/subagent-spawn.mjs +30 -56
  59. package/agent-tools/subagent.mjs +21 -8
  60. package/agent-tools/timer.mjs +186 -12
  61. package/agent-tools/zero-write-watch.mjs +77 -0
  62. package/agent-tools.mjs +2 -1
  63. package/agent.mjs +2 -2
  64. package/attachments.mjs +1 -1
  65. package/config-io.mjs +18 -13
  66. package/config-migrate.mjs +13 -29
  67. package/config-presets.mjs +33 -29
  68. package/config.mjs +68 -25
  69. package/context-degrade.mjs +1 -1
  70. package/context-echo.mjs +1 -1
  71. package/context-push.mjs +2 -2
  72. package/context.mjs +1 -1
  73. package/conventions.mjs +28 -8
  74. package/declaration.mjs +18 -2
  75. package/escape.mjs +1 -1
  76. package/file-links.mjs +1 -1
  77. package/file-refs.mjs +1 -1
  78. package/history-window.mjs +1 -1
  79. package/i18n.mjs +7 -0
  80. package/ledger-cmd.mjs +62 -16
  81. package/ledger-db.mjs +6 -0
  82. package/ledger-surface.mjs +49 -5
  83. package/ledger-tools.mjs +158 -87
  84. package/ledger-variant-notice.mjs +47 -0
  85. package/ledger.mjs +11 -18
  86. package/log.mjs +1 -1
  87. package/manifest-discovery.mjs +1 -1
  88. package/manifest-schema.mjs +7 -5
  89. package/manifest.mjs +5 -3
  90. package/mcp/transport-stdio.mjs +1 -1
  91. package/mcp.mjs +1 -1
  92. package/memory/code-index.mjs +1 -1
  93. package/memory/code-search.mjs +1 -1
  94. package/memory/code-sync.mjs +1 -1
  95. package/memory/docs.mjs +6 -2
  96. package/memory/file-list.mjs +1 -1
  97. package/memory/file-walk.mjs +17 -5
  98. package/memory/maintain.mjs +410 -0
  99. package/memory/memory-tool.mjs +21 -6
  100. package/memory/schema.mjs +19 -7
  101. package/memory/sweep.mjs +8 -5
  102. package/memory/sync-tail.mjs +16 -10
  103. package/model-ref.mjs +16 -13
  104. package/model-specs.mjs +21 -9
  105. package/package.json +1 -1
  106. package/peer-instances.mjs +1 -1
  107. package/permission.mjs +33 -0
  108. package/process-probe.mjs +2 -2
  109. package/prompts/advisor-design.md +4 -2
  110. package/prompts/advisor-round1.md +1 -1
  111. package/prompts/advisor-round2.md +1 -1
  112. package/prompts/advisor-round3.md +1 -1
  113. package/prompts/common.md +38 -4
  114. package/prompts/consult-base.md +1 -1
  115. package/prompts/discipline-engineering.md +32 -15
  116. package/prompts/discipline-normal.md +46 -2
  117. package/prompts/persona-eng-designer.md +6 -6
  118. package/prompts/persona-engineering.md +18 -9
  119. package/prompts/persona-normal.md +2 -0
  120. package/prompts/subagent-base.md +3 -0
  121. package/provider/anthropic.mjs +14 -2
  122. package/provider/core.mjs +6 -0
  123. package/provider/errors.mjs +28 -3
  124. package/provider/google.mjs +22 -10
  125. package/provider/list-models.mjs +3 -2
  126. package/provider/responses-request.mjs +3 -1
  127. package/provider/responses.mjs +38 -4
  128. package/provider/sse.mjs +3 -0
  129. package/provider-flows.mjs +14 -15
  130. package/proxy-chunked.mjs +102 -0
  131. package/proxy-target.mjs +54 -0
  132. package/proxy-transport.mjs +298 -0
  133. package/proxy.mjs +17 -248
  134. package/session-gc.mjs +2 -2
  135. package/session-lifecycle.mjs +18 -14
  136. package/session-slot-claims.mjs +2 -2
  137. package/session-slots-manifest.mjs +1 -1
  138. package/session-slots.mjs +3 -3
  139. package/shell-identity.mjs +107 -0
  140. package/team-ledger.mjs +282 -0
  141. package/team.mjs +223 -0
  142. package/tool-docs/advisor.md +1 -1
  143. package/tool-docs/bash.md +2 -1
  144. package/tool-docs/batch.md +1 -1
  145. package/tool-docs/browser.md +48 -0
  146. package/tool-docs/delete.md +7 -5
  147. package/tool-docs/ledger.md +6 -0
  148. package/tool-docs/manifest.md +16 -0
  149. package/tool-docs/memory.md +1 -0
  150. package/tool-docs/notify_parent.md +5 -0
  151. package/tool-docs/process.md +4 -4
  152. package/tool-docs/question.md +1 -0
  153. package/tool-docs/subagent.md +14 -3
  154. package/tool-docs/team_ledger.md +9 -0
  155. package/tool-docs/timer.md +6 -0
  156. package/tool-docs/wait_for.md +1 -0
  157. package/tools/bash.mjs +9 -4
  158. package/tools/browser.mjs +161 -0
  159. package/tools/git.mjs +1 -1
  160. package/tools/index.mjs +4 -4
  161. package/tools/ops.mjs +26 -10
  162. package/tools/patch.mjs +8 -2
  163. package/tools/repomap.mjs +1 -1
  164. package/tools/write-path.mjs +8 -6
  165. package/traces/trace-cleanup.mjs +1 -1
  166. package/traces/trace-store.mjs +1 -1
  167. package/vision-reader.mjs +10 -16
  168. package/tool-docs/ledger_add.md +0 -1
  169. package/tool-docs/ledger_close.md +0 -1
  170. package/tool-docs/ledger_count.md +0 -1
  171. package/tool-docs/ledger_query.md +0 -1
  172. package/tool-docs/ledger_update.md +0 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,68 @@
3
3
  All notable changes to the core package are documented here.
4
4
  Format: Keep a Changelog · 中文 · 号在发布时定(CalVer——见 `docs/RELEASE.md` §4)。
5
5
 
6
+ ## [0.10.5] — 2026-10-13
7
+
8
+ > 0.10.4 → 0.10.5(月内 +1——发布时定号)
9
+
10
+ ### Added
11
+
12
+ - **浏览器工具输入域(#1045)**:`press` ∥ `hover` ∥ `wheel` ∥ `mouse` ∥ `drag` ∥ `touch` ∥ `insert` ∥ `clipboard` 八动作面(回执带来源标记)∥ 异步任务 + kill 缝 ∥ 动作预算与会话自愈。
13
+ - **`manifest` 工具(#1098)**:读 ∥ 初始化 ∥ 白名单点写(`docRoot`/`checkConfig`/`index` 子键;整对象写与表外键拒);设计门辖区 = manifest 树(#1104)。
14
+ - **索引自维护(#1096)**:幽灵行 GC ∥ 压实 ∥ 备份轮换 + `memory maintain` action;记忆索引行预算面向面收正(#1107)。
15
+ - **disk-root 启动门(#1080/#1081)**:磁盘根启动拦截 ∥ 索引排除名。
16
+ - **团队核件(B1)**:`teamVerify` 登录校验缝(三端共用);**团队台账核工具 `team_ledger`**(五 action——写三仅主会话 ∥ 子代理只读变体 ∥ 仓派生原文上送 ∥ 登录态门零 fetch)。
17
+ - **台账工具面深化**:证据语义(append 缺省 ∥ `replace` 旗)∥ 家族锚解析(`discoverFamily` anchor-local)。
18
+ - **提示词纪律线族**:每个配置项须有配置 UI(用户裁定)∥ 用户视角门(U1/U2 注册)∥ 平实写作 ∥ 禁以用户名义裁定 ∥ 工程判断 ∥ 任务书再派生(#969)∥ 上抛标记(#970)∥ 板-域-文件三层结构(#956)∥ 子代理报告四列表格 ∥ **上抛前先读档**(#1328——上抛须携已查证据行)。
19
+ - provider 配置一致性(核 + CLI 双舱);gemini-3.8/3.7 flash 规格行(#1198)。
20
+
21
+ ### Changed
22
+
23
+ - **provider 默认模型清退**(通道模型退役——五舱随动)。
24
+ - gemini 思考签名捕获/传递(#1205——工具环内保真);代理分块/传输拆分;会话槽面。
25
+ - 普通模式批档作者缝 + 工作管理说课(#1111)。
26
+ - 测试术语统一(unit / integration——单元测试件为开发期工具,永不入任何闸)。
27
+
28
+ ### Fixed
29
+
30
+ - 主会话异步交付两点(投递 ∥ 消费时点收正)。
31
+ - provider 探测随 per-provider 代理 ∥ loopback 恒直连(#1026)。
32
+ - 上下文预算 = 可用窗口推导(advisor ∥ agent 压实不再越界)。
33
+ - 陈旧注释 ∥ 字符串 ∥ 死导入清扫(#1131)。
34
+ - 台账状态行写后即时刷新(库变更 `fs.watch` 去抖 ⇒ 写后 ≤~0.5s 重扫)。
35
+
36
+ ## [0.10.4] — 2026-10-05
37
+
38
+ > 0.10.3 → 0.10.4(月内 +1——发布时定号)
39
+
40
+ ### Added
41
+
42
+ - **零落笔看门狗(#934)**:eng-coder ∥ eng-designer ∥ 评审面连续 50 轮零文本产出 ⇒ 自动上游提醒(阈值单源叶 `zero-write-watch.mjs`)。
43
+ - **工程模式状态档写通路(#941)**:`PROJECT-MANIFEST.json` ∥ `.thincoder/conventions.json` 合规写入面(`state` 第五值——「恰执行一次」语义;`src` 段仍拒)。
44
+ - **空目录清理通路(#943)**:`delete` 空目录臂(非空照拒 ∥ 嵌套空树自底向上——`delete` 描述面同拍)。
45
+ - **台账变体键首启提示(#935)**:盘符大小写变体旧库检测 + `migrate` 指引(不再静默分裂)。
46
+ - **工具面首用可发现性(#938)**:子代理派单六字段骨架进描述面 ∥ batch 头参 ∥ 占位域修正。
47
+ - **`ledger` 统一入口(#923)**:五工具合 1(action 分派;五旧名 = 弃壳零装配)。
48
+
49
+ ### Changed
50
+
51
+ - **消化账务语义(#930)**:投递-结算两分 ∥ 重试上限 + 升级 ∥ 未结可见。
52
+ - **模型可见面三笔(#950)**:派单六字段骨架 ∥ 消化签收纪律线 ∥ 停摆轮引用清理。
53
+ - **bash 执行器身份(#922)**:运行时 shell 探针 + 注入执行器行(描述与真实执行器不再相左)。
54
+ - **advisor 主循环拆分(#951)**:时间线记录 ∥ 压缩协作外提(行为零变——结构面)。
55
+
56
+ ### Fixed
57
+
58
+ - **消化账务取件窗(#939)**:多轮 run 首轮签收跨轮存活(假重投根治)。
59
+ - **设计令牌裁决闸(#940)**:评审回执无 `VERDICT: pass` 不签发(机械判 ∥ 回显径)。
60
+ - **评审串台面(#949)**:并发/排队评审按本评审实例定域 + 投递水印(不再借他场轮次)。
61
+ - **引文核验装饰剥离(#928)**:包裹引号 ∥ 行内注记并入捕获串不再误报失配(候选集 c1–c4;零模糊)。
62
+ - **评审 documents 域锚祖先链(#945)**:容器相对形在子仓锚下可解析(受理判据零放宽)。
63
+ - **批次档路径多落点歧义(#942)**:无仓前缀相对形不再静默嵌套 + 歧义显式拒。
64
+ - **docRoot 空值哨兵(#944)**:`null` = 本面无(不再被默认值补回)。
65
+ - **responses 适配面健壮性(#907)**:SSE error 帧双分支 ∥ 错误码映射 ∥ `max_output_tokens` 显式发送。
66
+ - **评审文档路径基面(#921)**:按候选项目根解析 + 诊断拒语 + 七处基面声明在位。
67
+
6
68
  ## [0.10.3] — 2026-10-04
7
69
 
8
70
  > 0.10.2 → 0.10.3(月内 +1——发布时定号)
package/README.md CHANGED
@@ -82,7 +82,7 @@ Per-release notes and artifacts: `CHANGELOG.md` in this package and the [GitHub
82
82
  The core lives in `thincoder-core/` of the ThinCoder repository; bug reports and questions go to the
83
83
  repository issue tracker. Two conventions shape every change: pure ESM `.mjs` with no build step, and
84
84
  zero third-party dependencies — if the Node.js standard library can do it, no third-party package is
85
- allowed. `npm test` runs the repo suite entry (`node test/run.mjs`); after the 2026-09-28 full reset the suite manifest is empty (zero tests = green — collection self-checks still run). Unit tests live as batch-local files under `docs/batches/` in the repository — current state: `docs/core/design/TESTING.md` §2.3. It runs again on release through `prepublishOnly`.
85
+ allowed. `npm test` runs the test entry (`node test/run.mjs`); after the 2026-09-28 full reset the test manifest is empty (zero tests = green — collection self-checks still run). Unit tests live as batch-local files under `docs/batches/` in the repository — current state: `docs/core/design/TESTING.md` §2.3. It runs again on release through `prepublishOnly`.
86
86
  Mechanism design documents live in the repository under `docs/core/design/`.
87
87
 
88
88
  ## License
@@ -7,8 +7,8 @@
7
7
  *
8
8
  * 第 11 批(C / F14 / `ADVISOR-GUARDS.md §3`):解析候选 = cwd + **评审对象声明范围派生根**(声明仓根 /
9
9
  * 声明文件目录 / 声明目录——纯路径派生,零扫描、零 git);命中判据三条件全中(围栏内 ∧
10
- * 可读 ∧ 目标行含引文内容)⇒ 零新增假命中。失败原因四类(file unreadable / content mismatch @ path /
11
- * path traversal / 非连续引文(省略号形——形状 vs 内容造假分列))——父侧不再人肉复核。
10
+ * 可读 ∧ 目标行含**任一内容候选**——c1 原捕获 / c2 剥外层装饰 / c3 首引号后段 / c4 首成对引号内层;
11
+ * 零模糊、零内容级归一(装饰剥离候选集 = `ADVISOR-GUARDS.md` §3)。失败原因四类(file unreadable / content mismatch @ path / path traversal / 非连续引文(省略号形——形状 vs 内容造假分列))——父侧不再人肉复核。
12
12
  */
13
13
  import { readFileSync, realpathSync } from "node:fs"
14
14
  import { resolve, relative, dirname, extname, sep, isAbsolute, join } from "node:path"
@@ -79,7 +79,7 @@ function resolveCitation(citation, roots, base) {
79
79
  } catch {
80
80
  continue // 存在但读不了(目录等)——按未命中处理,试下一候选
81
81
  }
82
- if (line.includes(citation.content)) {
82
+ if (citationCandidates(citation.content).some((c) => line.includes(c))) {
83
83
  return { matched: true, root, resolved: relative(base, real).split(sep).join("/") }
84
84
  }
85
85
  mismatch ??= real
@@ -147,3 +147,29 @@ export function appendCitationReport(text, cwd, opts = {}) {
147
147
  }
148
148
  return text + lines.join("\n")
149
149
  }
150
+
151
+ /** 装饰剥离候选集(`ADVISOR-GUARDS.md` §3 · 2026-10-05 批 · 台账 #928):评审引风常含包裹引号 ∥ 行内
152
+ * 注记(`file:line: "…引文…" — 注记`)——捕获串带装饰时字节包含判失败 ⇒ 误报 content mismatch。
153
+ * 候选(任一为所引行**连续子串**即命中——零模糊、零内容级归一):
154
+ * c1 原捕获(现行行为——零回归,不设下限)∥ c2 反复剥「引号族 + 空白」的先导/尾随 ∥ c3 首个引号
155
+ * 字符后段(剥前注记)∥ c4 首个成对引号段内层文本;派生候选下限 2 字符(空引号 ∥ 单字符垃圾形
156
+ * 不成候选)。 */
157
+ function citationCandidates(raw) {
158
+ const out = [raw] // c1——原捕获(零回归)
159
+ const add = (s) => { if (s.length >= 2 && !out.includes(s)) out.push(s) }
160
+ const DECOR = /^[\s"'“”「」『』]+|[\s"'“”「」『』]+$/g
161
+ let s = raw
162
+ for (;;) { const t = s.replace(DECOR, ""); if (t === s) break; s = t }
163
+ add(s) // c2——剥离外层装饰(反复至不动点)
164
+ const qi = raw.search(/["'“”「」『』]/)
165
+ if (qi >= 0) add(raw.slice(qi + 1).trim()) // c3——首个引号字符后段(剥前注记)
166
+ const spans = []
167
+ for (const [open, close] of [['"', '"'], ["“", "”"], ["「", "」"]]) {
168
+ const a = raw.indexOf(open)
169
+ if (a < 0) continue
170
+ const b = raw.indexOf(close, a + 1)
171
+ if (b > a + 1) spans.push({ a, inner: raw.slice(a + 1, b) })
172
+ }
173
+ if (spans.length) add(spans.sort((x, y) => x.a - y.a)[0].inner) // c4——首个成对引号段内层文本
174
+ return out
175
+ }
@@ -7,9 +7,10 @@
7
7
  * re-attach — F13/`ADVISOR-GUARDS.md §2` #3); renderTimeline moved verbatim too, so the tail
8
8
  * GENERATOR and the tail CLASSIFIER stay in one file with the assembler they
9
9
  * feed (`ADVISOR-GUARDS.md §1` 谓词 ↔ §4 结构化尾——同族单源)。拆分线 = 行数硬帽实测(见批次档 §5)。
10
+ * advisor-loop-split 批:loop 的压缩检查点归位本档(`compactContextIfNeeded`——`context_limit` 尾串生成点同迁;时间线面 ⇒ `advisor/timeline.mjs`)。
10
11
  */
11
12
 
12
- import { providerSpec } from "../config.mjs" // 第 25 批:预算派生(与 loop.mjs:11 同源导入)
13
+ import { providerSpec, outputReserve, usableWindow } from "../config.mjs" // 第 25 批:预算派生(与 loop.mjs:12 同源导入);`outputReserve`/`usableWindow` = 2026-10-06 预留裁定(#975)
13
14
  // B4(群 B 批,ADVISOR-GUARDS.md §9——F32):CJK 加权单源(provider/rate.mjs 叶子向无环)
14
15
  import { estimateText } from "../provider/rate.mjs"
15
16
 
@@ -21,14 +22,19 @@ export const MAX_ADVISOR_TURNS = 100
21
22
 
22
23
  // Context window limits
23
24
  // 上下文预算(第 25 批——120K 硬编码退场):预算跟随评审模型窗口(providerSpec:
24
- // 模型规格表 × provider 级 context 覆盖)。头寸用途 = chars/4 估算误差 + 响应/协议开销
25
- // (内存不构成约束——`ADVISOR-GUARDS.md §8`);判死线仍是宿主机自限线,服务端窗口约束不变。
26
- export const CONTEXT_LIMIT_RATIO = 0.8 // 判死线 = 窗口 × 0.8
25
+ // 模型规格表 × provider 级 context 覆盖)**扣完成预留**(2026-10-06 裁定——预留坐在窗口里 ⇒
26
+ // 压缩先行、不判死;基数 = 可用窗口,见 advisorContextBudget)。头寸用途 = chars/4 估算误差 +
27
+ // 响应/协议开销(内存不构成约束——`ADVISOR-GUARDS.md §8`);判死线仍是宿主机自限线,服务端窗口约束不变。
28
+ export const CONTEXT_LIMIT_RATIO = 0.8 // 判死线 = 可用窗口 × 0.8
27
29
  const COMPACT_TRIGGER_RATIO = 0.8 // 压缩触发 = 判死线 × 0.8(既有关系零改)
28
30
 
29
- /** 评审上下文预算(纯函数——两档阈值可机测;provider 为 null 时退化默认规格)。 */
31
+ /** 评审上下文预算(纯函数——两档阈值可机测;provider 为 null 时退化默认规格)。
32
+ * 基数 = **可用窗口 = 窗口 − 完成预留**(#975/用户 2026-10-06 裁定——预留坐在窗口里 ⇒
33
+ * 先压缩、不许 provider 先拒:provider 口径 = 输入 + max_tokens ≤ 窗口;预留 = `outputReserve`)。 */
30
34
  export function advisorContextBudget(provider) {
31
- const limit = Math.floor(providerSpec(provider).context * CONTEXT_LIMIT_RATIO)
35
+ const spec = providerSpec(provider)
36
+ const usable = usableWindow(spec.context, outputReserve(provider, spec))
37
+ const limit = Math.floor(usable * CONTEXT_LIMIT_RATIO)
32
38
  return { limit, compactAt: Math.floor(limit * COMPACT_TRIGGER_RATIO) }
33
39
  }
34
40
 
@@ -82,13 +88,31 @@ export function compactMessages(messages, pinned = null) {
82
88
  ...recent)
83
89
  }
84
90
 
91
+ /** 压缩检查点(自 advisor/loop.mjs 迁出——advisor-loop-split 批):超 `budget.compactAt` ⇒ `onText` 通知 +
92
+ * `compactMessages` 原地重写(F13 pinned 重挂随它);重写后仍超 `budget.limit` ⇒ 返回上下文超限尾串
93
+ * (调用点收尾——`context_limit` 生成点),否则 `null`。 */
94
+ export function compactContextIfNeeded(messages, budget, pinned, onText) {
95
+ // Check context window and compact if needed
96
+ const currentTokens = estimateTokens(messages)
97
+ if (currentTokens > budget.compactAt) {
98
+ onText(`\n[Context compacted: ${currentTokens} tokens → reducing to fit window]\n`)
99
+ compactMessages(messages, pinned)
100
+ if (estimateTokens(messages) > budget.limit) {
101
+ // Report the POST-compaction count — the pre-compaction currentTokens
102
+ // is stale by the time compaction has run.
103
+ return `Advisor: context window limit reached (${estimateTokens(messages)} tokens). Review incomplete — too many tool calls. Try a narrower scope.`
104
+ }
105
+ }
106
+ return null
107
+ }
108
+
85
109
  // ─────────────────────────────────────────────────────────────────────────────
86
110
  // 不完整判定族(A / F16 共用单谓词——`ADVISOR-GUARDS.md §1`;六 kind = 宿主尾族)
87
111
  // ─────────────────────────────────────────────────────────────────────────────
88
112
 
89
113
  /** 宿主尾族六 kind 的块首行逐字前缀(`ADVISOR-GUARDS.md §1` 表)。变量段(token 数 / 秒数 / 工具轮数)
90
114
  * 不入前缀——取各尾的固定字面部分;`review_failed` = run.mjs catch 的字符串 resolve
91
- * 形态(不 throw),其余五条 = renderTimeline 尾(loop.mjs)。 */
115
+ * 形态(不 throw);`context_limit` 生成点 = 本档 `compactContextIfNeeded`,其余四条 = renderTimeline 尾(loop.mjs)。 */
92
116
  const ADVISOR_INCOMPLETE_PREFIXES = [
93
117
  ["context_limit", "Advisor: context window limit"],
94
118
  ["turn_cap", "Advisor: stopped after"],
@@ -17,13 +17,15 @@ const DEFAULT_CRITERIA = `Review the code changes, focusing on:
17
17
  /**
18
18
  * Extract the agent's response table (| # | Action | Detail |) — the fix-claims
19
19
  * reference for convergence rounds.
20
- * Semantics (decision 2026-08-08): without sinceIdx, scan BACKWARD for the
21
- * MOST RECENT response table (no prior-table index is carried anymore — the
22
- * agent response is a focus aid only; format drift falls back to the
23
- * no-response text and never drives control flow). With sinceIdx, scan
24
- * FORWARD from it (legacy callers/tests).
20
+ * Semantics: with sinceIdx, scan FORWARD from it — the review instance passes
21
+ * its delivery watermark (`agent._advisorResponseAnchor`), so the table taken
22
+ * is the one written after THIS review's delivery. Without sinceIdx (legacy
23
+ * direct callers — no watermark), fall back to scanning BACKWARD for the most
24
+ * recent table. The agent response is a focus aid only — format drift falls
25
+ * back to the no-response text and never drives control flow (decision
26
+ * 2026-08-08).
25
27
  * @param {Array} history — message history
26
- * @param {number} [sinceIdx] — legacy: start scanning forward from this index
28
+ * @param {number} [sinceIdx] — delivery watermark: start scanning forward from this index
27
29
  * @returns {string|null} the response table content, or null
28
30
  */
29
31
  export function extractAgentResponseTable(history, sinceIdx) {
package/advisor/loop.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  * advisor/loop.mjs — advisor tool loop: chat → execute tools → repeat, plus the
3
3
  * review timeline (split out of advisor/run.mjs, 第 11 批 — run.mjs was 498/500
4
4
  * 硬帽;拆分保持既有 import 面:run.mjs 继续 re-export 本文件导出)。
5
+ * 自本档再拆(advisor-loop-split 批):时间线记录面 ⇒ `advisor/timeline.mjs`;压缩协作面 ⇒ `advisor/compaction.mjs`(`compactContextIfNeeded`)。
5
6
  *
6
7
  * 第 11 批(F15/`ADVISOR-GUARDS.md §4`):每次 chat 调用携带硬墙信号(`AbortSignal.any([signal,
7
8
  * AbortSignal.timeout(remaining)])`;墙判定绑信号状态——抛错 / partial 两形态同判),
@@ -13,10 +14,11 @@ import { toOpenAISchema } from "../tools/index.mjs"
13
14
  import { truncateAdvisorResult } from "./truncate.mjs"
14
15
  import { batchTool } from "../agent-tools/batch.mjs"
15
16
  import {
16
- estimateTokens, compactMessages, shouldBudgetNudge, budgetNudgeText, timeoutTail, renderTimeline,
17
+ shouldBudgetNudge, budgetNudgeText, timeoutTail, renderTimeline, compactContextIfNeeded,
17
18
  MAX_ADVISOR_TURNS, advisorContextBudget, TOOL_TIMEOUT_MS, REVIEW_TIMEOUT_MS, MAX_RESULT_CHARS,
18
19
  ADVISOR_THINKING_PLACEHOLDER,
19
20
  } from "./compaction.mjs"
21
+ import { createTimelineRecorder } from "./timeline.mjs"
20
22
 
21
23
  const { readTool, globTool, grepTool, lsTool } = await import("../tools/index.mjs")
22
24
  const { lspTool } = await import("../tools/lsp.mjs")
@@ -68,21 +70,7 @@ async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, c
68
70
  // 未注入 ⇒ 不发进度行(核内零端名分支——契约 5 / 10)。端在装配层传入自己的格式化器。
69
71
  const describeArgs = seams.describeArgs ?? (() => "")
70
72
  // 第 11 批硬墙 / 预算 / 尾:实现注解见下方各点;守卫函数与 renderTimeline 在 compaction.mjs。
71
- // Kind-tagged wrappers: the TUI panel colors reasoning / answer / tool progress differently.
72
- // Every chunk is ALSO recorded into an ordered timeline — the persisted record
73
- // must show the review process (thinking ↔ tool progress ↔ final text) at its
74
- // real positions, not a summary appended at the end. Same-kind consecutive
75
- // chunks merge (token streams); kind flips start a new entry.
76
- const timeline = []
77
- const record = (kind, text) => {
78
- const last = timeline.at(-1)
79
- if (last && last.kind === kind) last.text += text
80
- else timeline.push({ kind, text })
81
- }
82
- const emit = (kind) => (text) => { record(kind, text); onOutput?.({ kind, text }) }
83
- const onThink = emit("think")
84
- const onText = emit("text")
85
- const onTool = emit("tool")
73
+ const { timeline, onThink, onText, onTool } = createTimelineRecorder(onOutput)
86
74
  // toolsOverride = test seam (T-TS8/9): the real advisor tool set, or a mock
87
75
  // set with controllable timing/errors.
88
76
  const { schemas: toolSchemas, byName: toolByName } = toolsOverride ?? advisorToolsFor(agent, reviewType, batchDoc)
@@ -90,10 +78,20 @@ async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, c
90
78
  let toolCallCount = 0
91
79
  let reviewTextProduced = false
92
80
  let budgetNudged = false
81
+ // #934(零落笔看门狗——§6.32.2 ②):连续无文本产出轮计数(循环局部——单次评审运行内计数,
82
+ // 修复轮 = 新运行 ⇒ 重新计数)+ 单次运行通报闩(文本产出后不再触发——「连续无产出」条件不回头)
83
+ // + 本轮有无文本标记(轮顶结上一轮用)。
84
+ let silentRounds = 0
85
+ let stallAlerted = false
86
+ let roundHadText = false
93
87
  const startTime = now()
94
88
  // 第 25 批(`ADVISOR-GUARDS.md §8`):上下文预算跟随评审模型窗口——`providerSpec`(模型规格表 × provider 级
95
89
  // context 覆盖)派生;函数体内、while 轮次外一次性(provider 全场不变),两档消费见下守卫。
96
90
  const budget = advisorContextBudget(provider)
91
+ // #934(零落笔看门狗——AGENT-LOOP-UPSTREAM.md §6.32.2 ②):阈值单源(叶档 agent-tools/zero-write-watch.mjs)——
92
+ // 函数域动态 import:模块域会与既有环(叶 → parent-channel → async-settle ↔ advisor-async → 本档)
93
+ // 互锁(加载期 TLA 死锁);函数运行时图已就绪,模块缓存 ⇒ 每次评审一次代价。
94
+ const { ZERO_WRITE_ALERT_ROUNDS } = await import("../agent-tools/zero-write-watch.mjs")
97
95
 
98
96
  while (true) {
99
97
  // Interrupted (Ctrl+I) — stop immediately instead of spinning a fresh uncancellable signal
@@ -120,19 +118,23 @@ async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, c
120
118
  if (++turns > MAX_ADVISOR_TURNS) {
121
119
  return renderTimeline(timeline, "Advisor: stopped after " + MAX_ADVISOR_TURNS + " tool rounds — the review appears to be looping. You may retry with a narrower scope.")
122
120
  }
123
-
124
- // Check context window and compact if needed
125
- const currentTokens = estimateTokens(messages)
126
- if (currentTokens > budget.compactAt) {
127
- onText(`\n[Context compacted: ${currentTokens} tokens → reducing to fit window]\n`)
128
- compactMessages(messages, pinned)
129
- if (estimateTokens(messages) > budget.limit) {
130
- // Report the POST-compaction count — the pre-compaction currentTokens
131
- // is stale by the time compaction has run.
132
- return renderTimeline(timeline, `Advisor: context window limit reached (${estimateTokens(messages)} tokens). Review incomplete — too many tool calls. Try a narrower scope.`)
121
+
122
+ // #934(零落笔看门狗——§6.32.2 ② / §6.32.6 触发接线②):轮顶结上一轮(首轮无上一轮可结)——
123
+ // 上一轮无文本 ⇒ silentRounds += 1;越阈且未通报 ⇒ 恰一次通报(可选钩子——缺省零行为;
124
+ // 钩子恒零抛:抛错不得上抛为评审 failed 面——实现面兜底捕获兑现)。
125
+ if (turns > 1) {
126
+ if (!roundHadText) silentRounds += 1
127
+ roundHadText = false
128
+ if (!stallAlerted && silentRounds >= ZERO_WRITE_ALERT_ROUNDS) {
129
+ stallAlerted = true
130
+ try { seams.onStallRound?.(silentRounds) } catch { /* #934:恒零抛 */ }
133
131
  }
134
132
  }
135
133
 
134
+ // 压缩协作(迁出 compaction.mjs `compactContextIfNeeded`):超限尾 ⇒ 收尾
135
+ const contextTail = compactContextIfNeeded(messages, budget, pinned, onText)
136
+ if (contextTail) return renderTimeline(timeline, contextTail)
137
+
136
138
  // LLM generation silence: the reasoning phase produces no SSE bytes for
137
139
  // seconds to tens of seconds (server-side prefill on large contexts, per
138
140
  // tool-round LLM return). A placeholder keeps the panel visibly working.
@@ -155,7 +157,7 @@ async function runAdvisorToolLoop(provider, messages, onOutput, signal, agent, c
155
157
  messages,
156
158
  tools: toolSchemas,
157
159
  signal: callSignal,
158
- onToken: (t) => { if (String(t ?? "").trim()) reviewTextProduced = true; onText(t) },
160
+ onToken: (t) => { if (String(t ?? "").trim()) { reviewTextProduced = true; silentRounds = 0; roundHadText = true } onText(t) },
159
161
  onReasoning: onThink,
160
162
  // LOGGING(vscode advisor/run.mjs parity——按 stage 可 grep)
161
163
  // TRACES.md §6.1 D-TR4:轨迹元数据增补——kind=advisor(评审独立于子代理——T-TR2);role
@@ -53,6 +53,7 @@ export function buildDesignApprovalBlock(designToken, designId) {
53
53
  return [
54
54
  "## Approval Signal",
55
55
  echo,
56
+ "The verdict line is checked mechanically: without a `VERDICT: pass` line, or with a `VERDICT: changes-required` line, no token is issued even when the token is echoed.",
56
57
  "🟡 (Advisory) and 🔵 (Note) findings do NOT block approval — list them if present, but still include the token. If any 🔴 (Critical) issue remains unresolved, do NOT include the token. A reasoned non-fix accepted by the reviewer does not block (🔴 only — 🟡/🔵 never block approval); anything else stays unresolved.",
57
58
  ].join("\n")
58
59
  }
@@ -195,7 +196,7 @@ function buildAdvisorUserMessageInner(agent, prior, reviewType, designToken = nu
195
196
  // conversation can quote literal "\x"/"\u" sequences (server 400 risk).
196
197
  if (p && (agent._advisorRound || 0) > 0) {
197
198
  const scopeFiles = resolveScopeFiles(agent, paths)
198
- const response = extractAgentResponseTable(agent.history)
199
+ const response = extractAgentResponseTable(agent.history, agent._advisorResponseAnchor ?? undefined)
199
200
  || (scopeFiles?.length
200
201
  ? "(Agent did not provide a response table — perform a fresh review of: " + scopeFiles.slice(0, 10).join(", ") + ")"
201
202
  : "(Agent did not provide a response table — perform a fresh review of the files named in the system prompt context)")
package/advisor/run.mjs CHANGED
@@ -32,10 +32,10 @@ export function resolveAdvisorProvider(agent) {
32
32
  const provider = findProvider(agent.providers?.length ? agent.providers : agent.config?.providersList ?? [agent.provider], cfg.provider)
33
33
  // F-2a (MODEL-400-FIX):无 cfg.model 时渠道克隆须重派生 model——渠道裸克隆会丢 model 键
34
34
  // → 无 model 请求 → serde 400。
35
- // MODEL-SELECTION v2(M3④):无 cfg.model → 命中渠道自己的默认模型(`provider.model`
36
- // 单值);渠道无默认模型 → 父 provider 兜底(与 subagent F-2c 同构)——绝不产出静默
37
- // undefined-model 请求(最极端两者皆无 → chat 前 assertProviderModel fail-fast)。
38
- const result = cfg.model ? { ...provider, model: cfg.model } : { ...provider, model: provider.model ?? agent.provider?.model }
35
+ // 2026-10-09 清除批(与 CLI `cmd-advisor` 同族收正):模型面 = **父解析单档**——渠条目不携
36
+ // 模型(渠道单值模型退场)⇒ 无 cfg.model 取父运行模型 `agent.provider?.model`;两者皆无 →
37
+ // chat 前 `assertProviderModel` fail-fast(不静默)。
38
+ const result = cfg.model ? { ...provider, model: cfg.model } : { ...provider, model: agent.provider?.model }
39
39
  // 台账 #329 / `MODEL-SPECS.md §15.4-3`:off 形须**活着到达载荷层**——载荷层 off 门首款 = `provider.thinking === null`
40
40
  // (`thincoder-core/provider/core.mjs:213-219`);归一为 `undefined` 会让该门永不开(effort 族 advisor
41
41
  // 关思考静默失效)。显式 off 同清**继承档**(渠条目自带 / 主 provider 的 `reasoningEffort` 不得随行——
@@ -140,7 +140,11 @@ export async function runAdvisorReview(agent, reviewType, callbacks, designToken
140
140
  const pinned = buildPinnedBrief(reviewType, documents, object, designToken, designId)
141
141
 
142
142
  try {
143
- const result = await runAdvisorToolLoop(provider, messages, onOutput, signal, agent, advisorCwd, null, reviewType, boundBatchDoc, pinned)
143
+ // #934(零落笔看门狗——§6.32.6 触发接线②):评审族闸位钩子转发(callbacks 不供 `onStall` ⇒
144
+ // `onStallRound: undefined` ⇒ 环路零行为——同步径(advisor 工具)不供钩子)。
145
+ const result = await runAdvisorToolLoop(provider, messages, onOutput, signal, agent, advisorCwd, null, reviewType, boundBatchDoc, pinned, {
146
+ onStallRound: callbacks?.onStall,
147
+ })
144
148
 
145
149
  // Host-verified citations (decision d698434): mechanically check every
146
150
  // `file:line: content` reference in the review against the CURRENT file
@@ -0,0 +1,26 @@
1
+ /**
2
+ * advisor/timeline.mjs — 评审环路的有序时间线记录器(自 advisor/loop.mjs 迁出——advisor-loop-split 批):
3
+ * 记录序 / 同类合流 / 三 kind 包装一体;下游消费 = renderTimeline(compaction.mjs)。
4
+ * 零 import 叶(对照 agent/relay-prefix.mjs 先例家族)。
5
+ *
6
+ * @param {Function} [onOutput] — 实时 chunk 出口;缺省 ⇒ 只记录不出口。
7
+ * @returns {{ timeline: Array, onThink: Function, onText: Function, onTool: Function }}
8
+ */
9
+ export function createTimelineRecorder(onOutput) {
10
+ // Kind-tagged wrappers: the TUI panel colors reasoning / answer / tool progress differently.
11
+ // Every chunk is ALSO recorded into an ordered timeline — the persisted record
12
+ // must show the review process (thinking ↔ tool progress ↔ final text) at its
13
+ // real positions, not a summary appended at the end. Same-kind consecutive
14
+ // chunks merge (token streams); kind flips start a new entry.
15
+ const timeline = []
16
+ const record = (kind, text) => {
17
+ const last = timeline.at(-1)
18
+ if (last && last.kind === kind) last.text += text
19
+ else timeline.push({ kind, text })
20
+ }
21
+ const emit = (kind) => (text) => { record(kind, text); onOutput?.({ kind, text }) }
22
+ const onThink = emit("think")
23
+ const onText = emit("text")
24
+ const onTool = emit("tool")
25
+ return { timeline, onThink, onText, onTool }
26
+ }
package/advisor.mjs CHANGED
@@ -124,7 +124,9 @@ export function buildAdvisorSystemPrompt(agent, prior, reviewType) {
124
124
  * NOTE: the caller (prepareAdvisorMessages) applies escapeLiteralEscapes to
125
125
  * the return value — direct callers must do the same (the prior table and
126
126
  * agent response can quote literal "\x"/"\u" sequences).
127
- * @param {Object} agent — the parent agent (history used for the response table)
127
+ * @param {Object} agent — the parent agent (history used for the response table:
128
+ * scanned forward from `agent._advisorResponseAnchor` — this instance's delivery
129
+ * watermark; absent → legacy backward scan)
128
130
  * @param {Object|null} prior — prior issue table (extracted from history when null)
129
131
  * @param {string[]|null} [scopeFiles] — review surface for the no-response fallback (cwd-relative)
130
132
  * @param {Object|null} [object] — review-object declaration (AGENT-LOOP-ASYNC-POOL.md §6.18): mechanically
@@ -156,7 +158,7 @@ export function buildAdvisorFollowUp(agent, prior, scopeFiles = null, object = n
156
158
  const noResponseFallback = scopeFiles?.length
157
159
  ? "(Agent did not provide a response table — perform a fresh review of: " + scopeFiles.slice(0, 10).join(", ") + ")"
158
160
  : "(Agent did not provide a response table — perform a fresh full review; the review surface is unknown, ask the user for the file list)"
159
- const response = extractAgentResponseTable(agent.history) || noResponseFallback
161
+ const response = extractAgentResponseTable(agent.history, agent._advisorResponseAnchor ?? undefined) || noResponseFallback
160
162
  const round = (agent._advisorRound || 0) + 1
161
163
  // Review-object declaration FIRST (T-OA2 — round 2+ stays anchored, no re-archaeology).
162
164
  const declaration = buildObjectDeclarationBlock(object)
@@ -65,7 +65,7 @@ export async function assembleAgent({ cwd, deps = {}, toolsFinalize = null }) {
65
65
  const config = D.loadConfig()
66
66
  const provider = config.provider
67
67
  const providers = config.providersList
68
- // 代理注入(double opt-in: provider.proxy + config.proxy.model);config.provider 是 loadConfig 里的
68
+ // 代理注入(逐渠独立:provider.proxy ∧ `proxy.uri` 在案 ⇒ 该渠模型请求经代理——无全局闸,2026-10-08);config.provider 是 loadConfig 里的
69
69
  // 独立拷贝 —— 同步注入结果(proxyUri)。
70
70
  const injectProxy = D.injectProxy ?? (await import("../proxy.mjs")).injectProxy
71
71
  injectProxy(providers, config)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * agent/chat-call.mjs — 单回合模型调用(2026-09-29 P2 三拆:agent.mjs runAgent 段 4 逐字迁入 +
3
3
  * `streamOutputAllowed` 定义随迁——agent.mjs re-export 保 import 面)。
4
- * 接口 = callModelTurn(agent, ctx) → response;错误走原样:AbortError-中断臂处理完照旧 `throw e`,
4
+ * 接口 = callModelTurn(agent, ctx) → response;错误走原样:AbortError-中断分支处理完照旧 `throw e`,
5
5
  * 由 runAgent 的 catch/finally 收束。
6
6
  */
7
7
  import { chat } from "../provider/index.mjs"
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * 收编自 `spawn-child.mjs`(2026-09-20 端差·显示面消差批 §2.2 X6 实现轮):端壳侧需**静态**
5
5
  * 导入锚点做块头注记判定,而 `spawn-child.mjs` 静态链经核 agent 栈可达 `node:sqlite`
6
- * ⇒ 端壳静态闭包禁达(W8 契约②判据(现载体 = 批件 `docs/batches/2026-09-29-residuals-round2.test.mjs`;
7
- * 单测树重建时回迁端侧单测档):fail-closed 红)⇒ 锚点下沉**零依赖叶**(先例 = `relay-prefix.mjs` 同因下沉)。`spawn-child.mjs`
6
+ * ⇒ 端壳静态闭包禁达(W8 契约②判据(现载体 = 单元测试档 `docs/batches/2026-09-29-residuals-round2.test.mjs`;
7
+ * 单元测试树重建时回迁端侧单元测试档):fail-closed 红)⇒ 锚点下沉**零依赖叶**(先例 = `relay-prefix.mjs` 同因下沉)。`spawn-child.mjs`
8
8
  * 原样再导出 ⇒ 既有 import 面(核 agent-tools 族 / CLI `tool-events.mjs`)零改。
9
9
  *
10
10
  * 语义:消费端用 `includes()` 检出锚点 ⇒ 合成块头注记("work may be partial")——两端各一处:
@@ -28,10 +28,12 @@ const MAX_EMPTY_RETRIES = 2
28
28
  * @param {object} callbacks - { onTurnEnd, onSubTurnBreak }
29
29
  */
30
30
  export function handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks) {
31
- if (!response.content) {
31
+ if (String(response.content ?? "").trim() === "") {
32
32
  // Transient empty response (reasoning exhausted / output truncated): instead of
33
33
  // aborting the whole turn, inject a reminder and let the model respond again.
34
34
  // Bounded — after MAX_EMPTY_RETRIES consecutive empties, surface the original error.
35
+ // §6.31.8 F-DA6(#929 同族 · 批 digest-accounting):空判加宽为**空白判**(纯空白内容
36
+ // 原过判 truthy ⇒ 静默当正常收尾);既有重试(≤2)/ 抛错骨架零改。
35
37
  const retries = agent._emptyRetries ?? 0
36
38
  if (retries < MAX_EMPTY_RETRIES) {
37
39
  agent._emptyRetries = retries + 1