@thincoder/core 0.9.4 → 0.10.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 (221) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +1 -1
  3. package/abort-provenance.mjs +10 -10
  4. package/advisor/citations.mjs +5 -5
  5. package/advisor/compaction.mjs +16 -15
  6. package/advisor/loop.mjs +14 -14
  7. package/advisor/messages.mjs +6 -6
  8. package/advisor/project-context.mjs +5 -5
  9. package/advisor/repos.mjs +3 -3
  10. package/advisor/run.mjs +23 -13
  11. package/advisor.mjs +4 -4
  12. package/agent/assemble.mjs +111 -0
  13. package/agent/chat-call.mjs +68 -0
  14. package/agent/child-marks.mjs +2 -2
  15. package/agent/completion.mjs +9 -2
  16. package/agent/continue-decision.mjs +25 -0
  17. package/agent/dispatch-gates.mjs +140 -0
  18. package/agent/dispatch-run.mjs +167 -0
  19. package/agent/dispatch.mjs +55 -294
  20. package/agent/family-tools.mjs +11 -11
  21. package/agent/helpers.mjs +63 -3
  22. package/agent/live-beat.mjs +49 -0
  23. package/agent/post-turn.mjs +4 -8
  24. package/agent/record-results.mjs +23 -20
  25. package/agent/relay-prefix.mjs +2 -2
  26. package/agent/run-stages.mjs +19 -15
  27. package/agent/run-start.mjs +111 -0
  28. package/agent/setup-reminders.mjs +9 -9
  29. package/agent/setup.mjs +32 -4
  30. package/agent/spawn-child.mjs +9 -9
  31. package/agent/suspension.mjs +90 -30
  32. package/agent/timers.mjs +113 -0
  33. package/agent/turn-loop.mjs +244 -0
  34. package/agent/write-gate.mjs +103 -6
  35. package/agent-tools/advisor-async.mjs +11 -11
  36. package/agent-tools/advisor-settle.mjs +17 -17
  37. package/agent-tools/advisor.mjs +38 -43
  38. package/agent-tools/async-discard.mjs +44 -1
  39. package/agent-tools/async-settle.mjs +23 -20
  40. package/agent-tools/audit-block.mjs +7 -7
  41. package/agent-tools/bash-async.mjs +281 -0
  42. package/agent-tools/batch-lifecycle.mjs +65 -34
  43. package/agent-tools/batch-paths.mjs +186 -0
  44. package/agent-tools/batch-skeleton.mjs +19 -1
  45. package/agent-tools/batch.mjs +22 -119
  46. package/agent-tools/checkpoint.mjs +121 -0
  47. package/agent-tools/child-permission.mjs +5 -3
  48. package/agent-tools/consult.mjs +58 -58
  49. package/agent-tools/context.mjs +4 -20
  50. package/agent-tools/design-token.mjs +3 -3
  51. package/agent-tools/digest-budget.mjs +1 -1
  52. package/agent-tools/eng.mjs +5 -5
  53. package/agent-tools/escalate-async.mjs +18 -13
  54. package/agent-tools/goal.mjs +3 -10
  55. package/agent-tools/panel-blocks.mjs +3 -3
  56. package/agent-tools/parent-channel.mjs +3 -13
  57. package/agent-tools/plan.mjs +3 -4
  58. package/agent-tools/read-history.mjs +12 -28
  59. package/agent-tools/recent-changes.mjs +4 -5
  60. package/agent-tools/review-facts.mjs +1 -1
  61. package/agent-tools/settings.mjs +47 -20
  62. package/agent-tools/skill.mjs +3 -5
  63. package/agent-tools/spawn-gates.mjs +3 -3
  64. package/agent-tools/subagent-actions-query.mjs +270 -0
  65. package/agent-tools/subagent-actions.mjs +29 -273
  66. package/agent-tools/subagent-async.mjs +36 -31
  67. package/agent-tools/subagent-panel.mjs +38 -9
  68. package/agent-tools/subagent-run.mjs +44 -29
  69. package/agent-tools/subagent-scheduler.mjs +29 -28
  70. package/agent-tools/subagent-spawn.mjs +57 -25
  71. package/agent-tools/subagent.mjs +46 -66
  72. package/agent-tools/task.mjs +3 -9
  73. package/agent-tools/timer.mjs +12 -12
  74. package/agent-tools/verify.mjs +14 -13
  75. package/agent-tools.mjs +0 -1
  76. package/agent.mjs +20 -351
  77. package/attachments.mjs +125 -0
  78. package/auto-think.mjs +1 -1
  79. package/config-presets.mjs +12 -7
  80. package/config-watch.mjs +83 -0
  81. package/config.mjs +17 -4
  82. package/context-degrade.mjs +81 -0
  83. package/context-echo.mjs +88 -0
  84. package/context-push.mjs +51 -0
  85. package/context.mjs +8 -187
  86. package/conventions.mjs +110 -161
  87. package/declaration.mjs +194 -0
  88. package/embedding.mjs +3 -1
  89. package/env-ref.mjs +81 -0
  90. package/explore-distill.mjs +2 -3
  91. package/file-links.mjs +53 -0
  92. package/file-refs.mjs +103 -0
  93. package/generate-title.mjs +28 -4
  94. package/git/checkpoint.mjs +4 -8
  95. package/history-window.mjs +17 -2
  96. package/i18n.mjs +2 -0
  97. package/index-discover.mjs +7 -7
  98. package/ledger-cmd.mjs +34 -126
  99. package/ledger-db.mjs +35 -7
  100. package/ledger-migrate.mjs +299 -0
  101. package/ledger-tools.mjs +188 -0
  102. package/ledger.mjs +11 -5
  103. package/log.mjs +12 -7
  104. package/manifest-discovery.mjs +131 -0
  105. package/manifest-schema.mjs +166 -0
  106. package/manifest.mjs +18 -217
  107. package/mcp.mjs +4 -1
  108. package/memory/code-search.mjs +149 -0
  109. package/memory/code-sync.mjs +89 -230
  110. package/memory/core.mjs +47 -32
  111. package/memory/docs.mjs +63 -262
  112. package/memory/file-list.mjs +89 -0
  113. package/memory/file-walk.mjs +7 -4
  114. package/memory/memory-tool.mjs +229 -0
  115. package/memory/schema.mjs +18 -3
  116. package/memory/sweep.mjs +289 -0
  117. package/memory/sync-tail.mjs +81 -0
  118. package/memory-status.mjs +69 -0
  119. package/memory.mjs +3 -0
  120. package/model-specs.mjs +131 -66
  121. package/notify-policy.mjs +51 -0
  122. package/package.json +1 -1
  123. package/peer-claims.mjs +263 -0
  124. package/peer-domains.mjs +100 -71
  125. package/peer-instances.mjs +9 -10
  126. package/process-probe-exec.mjs +187 -0
  127. package/process-probe.mjs +27 -175
  128. package/prompt-overlays.mjs +11 -10
  129. package/prompts/advisor-design.md +2 -2
  130. package/prompts/advisor-round2.md +1 -1
  131. package/prompts/advisor-round3.md +1 -1
  132. package/prompts/common.md +19 -7
  133. package/prompts/discipline-engineering.md +57 -16
  134. package/prompts/discipline-normal.md +6 -3
  135. package/prompts/persona-coder.md +0 -1
  136. package/prompts/persona-eng-coder.md +1 -5
  137. package/prompts/persona-eng-designer.md +1 -2
  138. package/prompts/persona-engineering.md +41 -11
  139. package/prompts/persona-explore.md +0 -1
  140. package/prompts/persona-normal.md +10 -1
  141. package/prompts/persona-plan.md +3 -4
  142. package/prompts/subagent-base.md +14 -0
  143. package/provider/core.mjs +11 -48
  144. package/provider/list-models.mjs +3 -0
  145. package/provider/responses-request.mjs +237 -0
  146. package/provider/responses.mjs +6 -228
  147. package/provider-flows.mjs +248 -0
  148. package/queued.mjs +90 -0
  149. package/rules.mjs +209 -2
  150. package/session-gc.mjs +67 -6
  151. package/session-guard.mjs +27 -7
  152. package/session-lifecycle.mjs +90 -18
  153. package/session-rename.mjs +5 -1
  154. package/session-slot-claims.mjs +230 -0
  155. package/session-slot-scan.mjs +299 -0
  156. package/session-slot-verify.mjs +304 -0
  157. package/session-slot-write.mjs +74 -8
  158. package/session-slots-manifest.mjs +163 -236
  159. package/session-slots.mjs +99 -71
  160. package/session.mjs +30 -10
  161. package/shell-candidates.mjs +78 -0
  162. package/text-budget.mjs +2 -2
  163. package/think-off.mjs +49 -0
  164. package/token-ttl.mjs +15 -4
  165. package/tool-args.mjs +73 -0
  166. package/tool-docs/advisor.md +9 -0
  167. package/tool-docs/batch.md +1 -0
  168. package/tool-docs/code_search.md +1 -0
  169. package/tool-docs/consult_start.md +5 -0
  170. package/tool-docs/consult_stop.md +3 -0
  171. package/tool-docs/context.md +7 -0
  172. package/tool-docs/doc_search.md +1 -0
  173. package/tool-docs/edit.md +2 -13
  174. package/tool-docs/eng.md +1 -0
  175. package/tool-docs/execute.md +1 -9
  176. package/tool-docs/git.md +25 -51
  177. package/tool-docs/goal.md +6 -0
  178. package/tool-docs/ledger_add.md +1 -0
  179. package/tool-docs/ledger_close.md +1 -0
  180. package/tool-docs/ledger_count.md +1 -0
  181. package/tool-docs/ledger_query.md +1 -0
  182. package/tool-docs/ledger_update.md +1 -0
  183. package/tool-docs/main_history.md +1 -0
  184. package/tool-docs/memory.md +10 -0
  185. package/tool-docs/notify_parent.md +9 -0
  186. package/tool-docs/peer_instances.md +1 -0
  187. package/tool-docs/plan.md +5 -0
  188. package/tool-docs/process.md +7 -3
  189. package/tool-docs/question.md +1 -0
  190. package/tool-docs/read_history.md +7 -0
  191. package/tool-docs/recent_changes.md +1 -0
  192. package/tool-docs/repo_outline.md +1 -0
  193. package/tool-docs/settings.md +6 -0
  194. package/tool-docs/skill.md +5 -0
  195. package/tool-docs/subagent.md +31 -0
  196. package/tool-docs/task.md +7 -0
  197. package/tool-docs/timer.md +6 -0
  198. package/tool-docs/verify.md +1 -0
  199. package/tool-docs/wait_for.md +2 -1
  200. package/tools/bash.mjs +28 -4
  201. package/tools/edit-batch.mjs +10 -11
  202. package/tools/edit-diff.mjs +33 -25
  203. package/tools/exec-run.mjs +102 -4
  204. package/tools/execute.mjs +5 -2
  205. package/tools/file.mjs +11 -12
  206. package/tools/git-run.mjs +1 -1
  207. package/tools/git.mjs +0 -24
  208. package/tools/glob-dialect.mjs +3 -3
  209. package/tools/index.mjs +2 -1
  210. package/tools/linter.mjs +2 -1
  211. package/tools/node-child.mjs +21 -0
  212. package/tools/ops.mjs +50 -13
  213. package/tools/patch.mjs +4 -2
  214. package/tools/repomap.mjs +20 -9
  215. package/tools/search.mjs +2 -2
  216. package/tools/shared.mjs +9 -7
  217. package/tools/web.mjs +5 -2
  218. package/tools/write-path.mjs +1 -1
  219. package/traces/trace-store.mjs +17 -14
  220. package/vision-reader.mjs +76 -0
  221. package/agent-tools/batch-segment.mjs +0 -18
package/tools/ops.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  /**
2
- * ops.mjs — operational tools: file_ops (move/copy/rename), process (list),
2
+ * ops.mjs — operational tools: file_ops (move/copy/rename), process (list/kill),
3
3
  * get_current_time, wait_for (condition wait). Each exists so the model reaches
4
4
  * for a dedicated tool instead of shelling out to `bash` for the same operation
5
5
  * (parity with thinworker).
6
+ * #9:process 收编 `kill`(靶 = pid ∥ 后台 bash 任务 id——欠面收正:原须绕 bash taskkill);
7
+ * wait_for 条件族 +`bash id:N done`。list ∥ 既有条件面零变。
6
8
  */
7
9
  import { DESC, resolveInCwd, truncate } from "./shared.mjs"
8
10
  // 单一写路径点(编辑器编辑径注入缝——CORE-UNIFICATION §2.13.5):路径操作也经它落盘
@@ -51,11 +53,21 @@ export const processTool = {
51
53
  parameters: {
52
54
  type: "object",
53
55
  properties: {
54
- name: { type: "string", description: "Optional name substring filter (case-insensitive)" },
56
+ action: { type: "string", enum: ["list", "kill"], description: "list (default) — show running processes; kill — terminate a process tree (target: pid) or a background bash task (target: id)" },
57
+ name: { type: "string", description: "Optional name substring filter for list (case-insensitive)" },
58
+ pid: { type: "number", description: "kill target: process id — the whole tree is terminated (taskkill /T /F on win32; POSIX group kill)" },
59
+ id: { type: "number", description: "kill target: background bash task id (bash#N from the bash async ack)" },
55
60
  },
56
61
  },
57
- readonly: true,
58
- async execute({ name }, ctx) {
62
+ // #9:动作分级——list 保持只读分类(dispatch-gates `isSubagentReadonlyAction`:planMode 放行/
63
+ // 免审批);kill 是破坏性动作(审批门)。`parallel` 保既有调度形(list 恒可批并行)。
64
+ readonly: false,
65
+ parallel: true,
66
+ async execute(args = {}, ctx = {}) {
67
+ const action = args?.action ?? "list"
68
+ if (action === "kill") return await executeKill(args, ctx)
69
+ if (action !== "list") return `Error: action must be list | kill (got "${action}")`
70
+ const { name } = args
59
71
  const filter = typeof name === "string" && name.trim() ? name.trim().toLowerCase() : null
60
72
  let rows
61
73
  try {
@@ -69,6 +81,24 @@ export const processTool = {
69
81
  },
70
82
  }
71
83
 
84
+ /** #9 kill 执行体(靶 = pid ∥ 后台任务 id 二选一;两靶皆树杀)。动态 import(W8 契约②:
85
+ * agent-tools 链静态达 node:sqlite——端壳静态闭包须保持零命中)。 */
86
+ async function executeKill({ pid, id }, ctx) {
87
+ const hasPid = pid !== undefined && pid !== null && String(pid).trim() !== ""
88
+ const hasId = id !== undefined && id !== null && String(id).trim() !== ""
89
+ if (hasPid === hasId) {
90
+ return "Error: kill requires exactly one target — pid (process id) or id (background bash task id)"
91
+ }
92
+ const { killBgTask, killBgPid } = await import("../agent-tools/bash-async.mjs")
93
+ if (hasPid) {
94
+ if (!killBgPid(pid)) return `Error: kill pid: "${String(pid)}" is not a valid process id`
95
+ return `killed process tree ${Number(pid)} (taskkill /T /F on win32; POSIX group kill)`
96
+ }
97
+ const res = killBgTask(ctx?.agent, id)
98
+ if (res.status === "error") return `Error: ${res.error}`
99
+ return `killed background bash#${res.id} — its process tree was terminated; no digest will arrive${res.logPath ? ` — log so far: ${res.logPath}` : ""}`
100
+ }
101
+
72
102
  function listWindows() {
73
103
  // tasklist /FO CSV /NH → lines: "name.exe","1234","Console","1","12,345 K"
74
104
  const out = execFileSync("tasklist", ["/FO", "CSV", "/NH"], { encoding: "utf8", timeout: 10000 })
@@ -112,7 +142,7 @@ export const getCurrentTimeTool = {
112
142
 
113
143
  // ─── wait_for ───────────────────────────────────────────────────
114
144
 
115
- /** wait_for bounds (TOOLS.md §16): a BUILT-IN ceiling replaces the unbounded
145
+ /** wait_for bounds: a BUILT-IN ceiling replaces the unbounded
116
146
  * sleep-then-wait — timeout_ms defaults to 30s (config.json agent.waitForTimeoutMs
117
147
  * overrides), capped at WAIT_FOR_MAX_TIMEOUT_MS (600s — the execute-tool timeout
118
148
  * convention, Math.min(t, 600_000)); interval_ms defaults to 1s with a 100ms
@@ -122,7 +152,7 @@ export const WAIT_FOR_MAX_TIMEOUT_MS = 600_000
122
152
  export const WAIT_FOR_DEFAULT_INTERVAL_MS = 1_000
123
153
  export const WAIT_FOR_MIN_INTERVAL_MS = 100
124
154
 
125
- // Condition-source seam (§16 评审 #4): production uses the real evaluator
155
+ // Condition-source seam (评审 #4): production uses the real evaluator
126
156
  // (evaluateWaitForCondition); tests inject a deterministic fake source via
127
157
  // setWaitForConditionSource (default null — production path unchanged).
128
158
  let injectedConditionSource = null
@@ -139,10 +169,11 @@ export function parseWaitForCondition(condition) {
139
169
  if (c === "advisor settled") return { kind: "advisor", arg: null }
140
170
  if (c === "consult done") return { kind: "consult", arg: null }
141
171
  if ((m = c.match(/^subagent id:\s*(\d+) done$/))) return { kind: "subagent", arg: String(Number(m[1])) }
172
+ if ((m = c.match(/^bash id:\s*(\d+) done$/))) return { kind: "bash", arg: String(Number(m[1])) }
142
173
  if ((m = c.match(/^file exists:\s*(.+)$/))) return { kind: "file", arg: m[1].trim() }
143
174
  if ((m = c.match(/^port open:\s*(\d+)$/))) return { kind: "port", arg: Number(m[1]) }
144
175
  throw new Error(
145
- `wait_for: unsupported condition "${c}" — supported: "advisor settled", "subagent id:N done", "consult done", "file exists:path", "port open:N"`,
176
+ `wait_for: unsupported condition "${c}" — supported: "advisor settled", "subagent id:N done", "consult done", "bash id:N done", "file exists:path", "port open:N"`,
146
177
  )
147
178
  }
148
179
 
@@ -170,7 +201,7 @@ function asyncEntryDone(agent, id) {
170
201
  return entry.done === true || entry.status === "done"
171
202
  }
172
203
 
173
- /** 评审池双载体(第 10 批 §18.3 #2):agent ∪ history——未初始化的载体不进集合。 */
204
+ /** 评审池双载体(第 10 批 AGENT-LOOP-ASYNC-POOL.md §6.11 #2):agent ∪ history——未初始化的载体不进集合。 */
174
205
  function advisorPools(agent) {
175
206
  return [agent?._asyncAdvisors, agent?.history?._asyncAdvisors].filter((m) => m instanceof Map)
176
207
  }
@@ -183,8 +214,8 @@ function hasRunningIn(pool, pred) {
183
214
  return false
184
215
  }
185
216
 
186
- /** "advisor settled" 真判据(第 10 批 §18.3 #2 修正):**评审池真实态**——双载体
187
- * (agent._asyncAdvisors ∪ history._asyncAdvisors)无 running/queued 条目(与 §11.2 的
217
+ /** "advisor settled" 真判据(第 10 批 AGENT-LOOP-ASYNC-POOL.md §6.11 #2 修正):**评审池真实态**——双载体
218
+ * (agent._asyncAdvisors ∪ history._asyncAdvisors)无 running/queued 条目(与 AGENT-LOOP-ASYNC-POOL.md §6.10 的
188
219
  * 未决评审判定 advisorReviewPending 同源语义)。修前读**子代理池**的 role==="advisor"
189
220
  * 条目——评审条目只在 _asyncAdvisors → 恒无命中 → 恒真 0ms 秒过(用户实证)。 */
190
221
  function advisorSettled(agent) {
@@ -228,12 +259,18 @@ export async function evaluateWaitForCondition(condition, ctx) {
228
259
  const agent = ctx?.agent
229
260
  switch (parsed.kind) {
230
261
  case "advisor":
231
- // 第 10 批修正(§18.3 #2——修前恒真 0ms 秒过):判据 = 评审池真实态(双载体)——
232
- // 不再读子代理池的 role==="advisor" 条目(该池永无此类条目);语义与 §11.2 未决
262
+ // 第 10 批修正(AGENT-LOOP-ASYNC-POOL.md §6.11 #2——修前恒真 0ms 秒过):判据 = 评审池真实态(双载体)——
263
+ // 不再读子代理池的 role==="advisor" 条目(该池永无此类条目);语义与 §6.10 未决
233
264
  // 评审判定同源(advisorReviewPending)。
234
265
  return advisorSettled(agent)
235
266
  case "subagent":
236
267
  return asyncEntryDone(agent, parsed.arg)
268
+ case "bash": {
269
+ // #9:后台 bash 任务判据(池内 done ∥ 出池即 done——`bash-async.mjs` 单点)。
270
+ // 动态 import(W8 契约②:agent-tools 链静态达 node:sqlite——端壳静态闭包零命中)。
271
+ const { bgTaskDone } = await import("../agent-tools/bash-async.mjs")
272
+ return bgTaskDone(agent, parsed.arg)
273
+ }
237
274
  case "consult":
238
275
  return consultAllDone(agent)
239
276
  case "file":
@@ -256,7 +293,7 @@ export const waitForTool = {
256
293
  parameters: {
257
294
  type: "object",
258
295
  properties: {
259
- condition: { type: "string", description: 'Condition expression — "advisor settled", "subagent id:N done", "consult done", "file exists:path", "port open:N"' },
296
+ condition: { type: "string", description: 'Condition expression — "advisor settled", "subagent id:N done", "consult done", "bash id:N done", "file exists:path", "port open:N"' },
260
297
  interval_ms: { type: "integer", description: "Poll interval in ms (default 1000, floor 100)" },
261
298
  timeout_ms: { type: "integer", description: "Overall timeout in ms (default 30000, cap 600000; config.json agent.waitForTimeoutMs overrides the default)" },
262
299
  },
package/tools/patch.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  majorityEol
8
8
  } from "./shared.mjs";
9
9
  import { writeThroughPath, TMP_SUFFIX, markDirty } from "./write-path.mjs";
10
- import { execFileSync } from "node:child_process";
10
+ import { spawnGit } from "./git-run.mjs";
11
11
  import { mkdir, readFile, lstat, unlink } from "node:fs/promises";
12
12
  import { existsSync } from "node:fs";
13
13
  import { relative, dirname } from "node:path";
@@ -275,7 +275,9 @@ export const deleteTool = {
275
275
  const rel = relative(ctx.cwd, abs).replace(/\\/g, "/")
276
276
  let tracked = false
277
277
  try {
278
- execFileSync("git", ["ls-files", "--error-unmatch", "--", rel], { cwd: ctx.cwd, stdio: "ignore" })
278
+ // #208①(2026-09-25):表外同步 spawn 收口——探针转 `spawnGit` 单点(GIT_ENV 加固 + 超时 + 树杀);
279
+ // 失败语义零变(未跟踪 / 非仓 ⇒ tracked 保持 false)。
280
+ await spawnGit(ctx.cwd, ["ls-files", "--error-unmatch", "--", rel])
279
281
  tracked = true
280
282
  } catch {
281
283
  // untracked / non-git repo
package/tools/repomap.mjs CHANGED
@@ -6,7 +6,8 @@
6
6
  import { existsSync } from "node:fs"
7
7
  import { readFile, stat } from "node:fs/promises"
8
8
  import { join } from "node:path"
9
- import { normalizeEOL } from "./shared.mjs"
9
+ import { DESC, normalizeEOL } from "./shared.mjs" // #15 描述外置:文本单点 = tool-docs/repo_outline.md(DESC 单解析面)
10
+ import { INDEX_ORIGIN_ROW_WARN } from "../memory/schema.mjs" // §6.14 B4 文件数上界(WARN 值复用)
10
11
 
11
12
  /** Extract JS/TS file import paths (normalize by stripping .ts/.js/.mjs suffixes) */
12
13
  function parseImports(lines, ext) {
@@ -111,13 +112,22 @@ function normalizeExt(p) {
111
112
  return p.replace(/\.(m?js|jsx|tsx?)$/i, "")
112
113
  }
113
114
 
115
+ /** B4 越界提示行(跳过大纲构建;指路 = 声明 ∕ 剪枝)——两入口(buildSummary ∕ buildOutline)同文;
116
+ * `origin` 给出时才缀「for this origin」(repo_outline 工具缝不传 origin——跨 origin 行不假称本 origin)。 */
117
+ function outlineOverLimit(origin = null) {
118
+ return `(code index too large for an outline: > ${INDEX_ORIGIN_ROW_WARN} files${origin ? " for this origin" : ""} — declare index.excludePaths in PROJECT-MANIFEST.json, or prune: thincoder memory sweep --origin <o> --path <sub>)`
119
+ }
120
+
114
121
  /**
115
122
  * Internal: scan all files, build forward dependency graph + reverse reference graph.
116
123
  * Returns { deps, importers, fileCount } shared by buildOutline / buildSummary.
117
124
  */
118
- async function _buildDepGraph(db, cwd) {
119
- const allFiles = db.prepare(`SELECT DISTINCT path FROM code_chunks ORDER BY path`).all().map((r) => r.path)
125
+ async function _buildDepGraph(db, cwd, { origin = null } = {}) {
126
+ // §6.14 B4:origin 限定 + 文件数上界(文件行 `LIMIT = WARN+1` 形——越界不构建大纲,返回信号)
127
+ const limit = INDEX_ORIGIN_ROW_WARN + 1
128
+ const allFiles = db.prepare(`SELECT DISTINCT path FROM code_chunks ${origin ? "WHERE origin = ? " : ""}ORDER BY path LIMIT ${limit}`).all(...(origin ? [origin] : [])).map((r) => r.path)
120
129
  if (allFiles.length === 0) return null
130
+ if (allFiles.length >= limit) return { overLimit: true, fileCount: allFiles.length }
121
131
 
122
132
  const deps = new Map() // path → { imports: Set, exports: Set, size: number, dir: string }
123
133
  const importers = new Map() // importee → Set<importer>
@@ -182,8 +192,9 @@ async function _buildDepGraph(db, cwd) {
182
192
  * 3. Entry points (files with no importers — startup/top-level entry points)
183
193
  * Output is naturally bounded (~1000-2000 chars), no more OUTLINE_INJECT_MAX hard truncation.
184
194
  */
185
- export async function buildSummary(db, cwd) {
186
- const graph = await _buildDepGraph(db, cwd)
195
+ export async function buildSummary(db, cwd, { origin = null } = {}) {
196
+ const graph = await _buildDepGraph(db, cwd, { origin })
197
+ if (graph?.overLimit) return outlineOverLimit(origin)
187
198
  if (!graph) return "(no indexed source files; run codeSync or /reindex first)"
188
199
  const { deps, importers, fileCount } = graph
189
200
 
@@ -257,8 +268,9 @@ export async function buildSummary(db, cwd) {
257
268
  }
258
269
 
259
270
  /** Get known file list from code_chunks (reuse index), parse by path to generate outline text */
260
- export async function buildOutline(db, cwd, focusPath) {
261
- const graph = await _buildDepGraph(db, cwd)
271
+ export async function buildOutline(db, cwd, focusPath, { origin = null } = {}) {
272
+ const graph = await _buildDepGraph(db, cwd, { origin })
273
+ if (graph?.overLimit) return outlineOverLimit(origin)
262
274
  if (!graph) return "(no indexed source files; run codeSync or /reindex first)"
263
275
  const { deps, importers } = graph
264
276
 
@@ -296,8 +308,7 @@ export async function buildOutline(db, cwd, focusPath) {
296
308
  export function repoOutlineTool(db, cwd) {
297
309
  return {
298
310
  name: "repo_outline",
299
- description:
300
- "Show the project's file dependency outline: which files import/export from which, and what symbols they export. Use when you need to understand the project structure, find where a function is defined, or see what files depend on a module. Pass a path to focus on a single file's relationships. Find code by keyword or snippet with code_search.",
311
+ description: DESC("repo_outline"), // #15 外置:文本单点 = tool-docs/repo_outline.md(工具名逐字对核)
301
312
  parameters: {
302
313
  type: "object",
303
314
  properties: {
package/tools/search.mjs CHANGED
@@ -32,7 +32,7 @@ export const globTool = {
32
32
  readonly: true,
33
33
  async execute(args, ctx) {
34
34
  const base = resolveInCwd(ctx, args.path ?? ".")
35
- // §17 调用侧拆分(评审 #5 职责分层):空格分隔 include !exclude 多模式在这里拆;
35
+ // TOOLS.md §6.7 调用侧拆分(评审 #5 职责分层):空格分隔 include !exclude 多模式在这里拆;
36
36
  // globToRegex 只收单个模式(数组由 compileGlobMatchers 收)。
37
37
  let match
38
38
  try {
@@ -115,7 +115,7 @@ export const grepTool = {
115
115
  } catch {
116
116
  return `grep error: path not found: ${base}`
117
117
  }
118
- // §17 调用侧拆分:glob 参数支持空格分隔 include !exclude 多模式(include/exclude 求交)。
118
+ // TOOLS.md §6.7 调用侧拆分:glob 参数支持空格分隔 include !exclude 多模式(include/exclude 求交)。
119
119
  let fileTest = null
120
120
  if (args.glob) {
121
121
  try {
package/tools/shared.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  * Imported by tools/file.mjs / system.mjs / web.mjs / git.mjs
4
4
  */
5
5
 
6
- import { spawn, execFileSync, execFile } from "node:child_process"
6
+ import { execFile } from "node:child_process"
7
7
  import { readFileSync, existsSync, realpathSync, readdirSync, statSync, openSync, readSync, closeSync } from "node:fs"
8
8
  import { dirname, join, resolve } from "node:path"
9
9
  import { fileURLToPath } from "node:url"
@@ -217,12 +217,14 @@ export function makeDecoder() {
217
217
  }
218
218
  }
219
219
 
220
- /** Single-file git diff. Silently returns empty on failure. Large diffs that exceed maxBuffer are truncated rather than swallowed. */
221
- export function gitDiffOne(cwd, abs) {
220
+ /** Single-file git diff. Silently returns empty on failure. Large diffs that exceed maxBuffer are truncated rather than swallowed.
221
+ * #208①(2026-09-25):体转 async 薄壳委托 `spawnGit` 单点(GIT_ENV 加固 + 两档超时 + 树杀 + 非阻塞);
222
+ * 形保真 = trim / 200 行截断 / 10MB 溢出取部分输出 / 失败静默回 `""`(调用面 5 处 `await` 化)。 */
223
+ export async function gitDiffOne(cwd, abs) {
222
224
  try {
223
- const diff = execFileSync("git", ["--no-pager", "diff", "--no-color", "--", abs], {
224
- cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 10 * 1024 * 1024,
225
- }).trim()
225
+ const diff = (await spawnGit(cwd, ["--no-pager", "diff", "--no-color", "--", abs], {
226
+ maxBuffer: 10 * 1024 * 1024,
227
+ })).trim()
226
228
  if (!diff) return ""
227
229
  const lines = diff.split("\n")
228
230
  if (lines.length <= 200) return diff
@@ -373,7 +375,7 @@ export function detectDanger(command) {
373
375
  }
374
376
 
375
377
 
376
- /** Glob dialect (TOOLS.md §17) lives in glob-dialect.mjs — single authority for
378
+ /** Glob dialect (TOOLS.md §6.7) lives in glob-dialect.mjs — single authority for
377
379
  * brace expansion / exclusion / explicit syntax errors (extracted 2026-09-06 —
378
380
  * advisor #5: shared.mjs exceeded 500 lines). Re-exported so existing importers
379
381
  * (system.mjs tools, ls filter, tests) keep their import paths unchanged. */
package/tools/web.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  import { DESC, truncate, stripTags, htmlToText, isPrivateHost } from "./shared.mjs";
2
2
  import { URL } from "node:url";
3
3
  import { proxyFetch } from "../proxy.mjs";
4
+ import { resolveEnvRefs } from "../env-ref.mjs";
4
5
 
5
6
  export const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
6
7
  const FETCH_TIMEOUT = 15_000
7
- // §14 D-TF3:网络失败错误文本追加 proxy 提示——纯文本提示,不自动路由(2026-08-31 裁定:proxy 显式传不自动应用)
8
+ // D-TF3:网络失败错误文本追加 proxy 提示——纯文本提示,不自动路由(2026-08-31 裁定:proxy 显式传不自动应用)
8
9
  const PROXY_HINT = "network failure — retry with proxy: 'http://host:port' if the target is blocked"
9
10
 
10
11
  // ── Web search (Bing; direct by default, through proxy when configured and web toggle on) ──
@@ -46,7 +47,9 @@ const ENGINE_NAMES = ENGINES.map(e => e.name)
46
47
  * { engine, results } or null to fall back to Bing HTML scraping.
47
48
  * proxyUri: explicit per-call proxy (args.proxy) — never the config.json one. */
48
49
  async function fetchTavily(query, limit, ctx, proxyUri) {
49
- const apiKey = ctx?.agent?.config?.websearch?.apiKey
50
+ // #57(provider-config-family 批):值位 `${env:VAR}` 消费侧解析(CONFIG.md §6.3)——未设 ∕ 空串 ∕
51
+ // 畸形 ⇒ 抛错(不静默字面透传);未配置 key 仍走 Bing 回落。
52
+ const apiKey = resolveEnvRefs(ctx?.agent?.config?.websearch?.apiKey)
50
53
  if (!apiKey) return null
51
54
  const ctrl = new AbortController()
52
55
  const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT)
@@ -6,7 +6,7 @@
6
6
  * 写入又与用户随后的保存互相竞写(split-brain 数据丢失)。核内原先写盘直调
7
7
  * `node:fs/promises`、散在 5 个档的 8 个写点上 ⇒ **没有可注入的一跳** ⇒ 端侧接核后会
8
8
  * 丢掉编辑器径。本模块就是那一跳:核内**工具面的 8 个编辑写点**全改调 `writeThroughPath`
9
- * (模型面直写 `agent-tools/batch-segment.mjs` = 设计登记的 S2 前置门——不在本缝覆盖面;
9
+ * (模型面直写 `agent-tools/batch.mjs` = 设计登记的 S2 前置门——不在本缝覆盖面;
10
10
  * 口径与 `CORE-UNIFICATION.md` §2.13.5 落地收正同源)。
11
11
  *
12
12
  * 默认径 = CLI 语义(**零行为变**):`writeFile` + 记账。端侧
@@ -1,5 +1,5 @@
1
1
  /**
2
- * trace-store.mjs — §18.6 完整轨迹存档(AGENT-LOOP.md §18.6 D-TR1..TR8 权威规格)。
2
+ * trace-store.mjs — 完整轨迹存档(TRACES.md §6 机制 + §7 决策表 D-TR1–4 / D-TR6 / D-TR10–13 权威规格)。
3
3
  *
4
4
  * 完整请求-响应轨迹落盘:每次 chat() 调用一个 JSONL 文件到
5
5
  * ~/.thincoder/traces/YYYY-MM-DD/<sessionKey>-<seq>.jsonl。采集点唯一 = core.mjs
@@ -11,9 +11,8 @@
11
11
  * 写盘异步(node:fs/promises——不 await);落盘失败静默降级(不抛错、不阻塞
12
12
  * chat() 返回)。recordChatTrace 返回落盘 promise——仅供测试/显式消费方 await。
13
13
  * - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
14
- * 轨迹目录(既有测试跑真实 agent 管线会产生数百次 chat 调用);显式设置
15
- * THINCODER_TRACES_DIR 强制写入该目录(traces.test.mjs 用它隔离临时目录——
16
- * 与 log.mjs 的 THINCODER_LOG_DIR 同惯例)。
14
+ * 轨迹目录(既有测试跑真实 agent 管线会产生数百次 chat 调用);显式测试缝
15
+ * (`_setTracesRootForTest`)强制写入该目录(与 log.mjs 同惯例——CONFIG.md §6.2)。
17
16
  * - 脱敏(D-TR2):复用 log.mjs 字段名黑名单(apikey/designtoken/password/secret/
18
17
  * token/authorization/proxyuri/proxy)+ SECRET_FORM 形态扫描(redactSecret)——对
19
18
  * messages/content/reasoning/toolCalls/error 全字段应用;不发明新遮蔽模式。
@@ -24,7 +23,7 @@
24
23
  * 与写盘同在 fire-and-forget 异步体内)。2026-09-21(TRACES.md §6.4):判据改**目录级三段梯**
25
24
  * + 每写 prune **节流**;清理实现外提 `trace-cleanup.mjs`(本档包装保既有 import 面)。
26
25
  * - seq = 当日目录内最大已有 seq + 1(D-TR3——跨会话/进程重启不覆写既有旧轨迹——
27
- * 18.6.1 评审 #3);**进程内 seqCache(§23.3.2——消除逐调用同步扫目录)**:首次
26
+ * 18.6.1 评审 #3);**进程内 seqCache(TRACES.md §6.2——消除逐调用同步扫目录)**:首次
28
27
  * readdirSync 后进程内递增预留,目录被清理(retention)后取下界重扫一次(防御)。
29
28
  * 多进程语义(修正轮 #10——登记):同 cwd 多实例各自进程内缓存——seq 可撞、同
30
29
  * sessionKey 记录并入同名文件(append 不覆写);**可容忍**(本机 traces = 诊断面、
@@ -44,16 +43,20 @@ import { configDir, loadConfig } from "../config.mjs"
44
43
  import { redactSecret, errText, classifyErr } from "../log.mjs"
45
44
  import { normalizeCwd } from "../session-slots.mjs"
46
45
 
47
- /** 轨迹根目录:THINCODER_TRACES_DIR(测试隔离/override——同 THINCODER_LOG_DIR
48
- * 惯例)> ~/.thincoder/traces(configDir——D-TR3——与 sessions/ 同域)。 */
46
+ /** Test seam: override the traces root (process-internal seam — CONFIG.md §6.2 ③ 通道). */
47
+ let _tracesRoot = null
48
+ export function _setTracesRootForTest(p) { _tracesRoot = p }
49
+ export function _resetTracesRootForTest() { _tracesRoot = null }
50
+
51
+ /** 轨迹根目录:显式测试缝 > ~/.thincoder/traces(configDir——D-TR3——与 sessions/ 同域)。 */
49
52
  export function tracesRoot() {
50
- return process.env.THINCODER_TRACES_DIR ?? join(configDir, "traces")
53
+ return _tracesRoot ?? join(configDir, "traces")
51
54
  }
52
55
 
53
56
  /** 写门(测试隔离):test runner 进程(NODE_TEST_CONTEXT)默认跳过——除显式
54
- * THINCODER_TRACES_DIR override(traces.test.mjs 隔离临时目录)。 */
57
+ * 测试缝 override(轨迹测试隔离临时目录)。 */
55
58
  function writeEnabled() {
56
- if (process.env.NODE_TEST_CONTEXT && !process.env.THINCODER_TRACES_DIR) return false
59
+ if (process.env.NODE_TEST_CONTEXT && _tracesRoot === null) return false
57
60
  return true
58
61
  }
59
62
 
@@ -93,7 +96,7 @@ export function tracesDirFor(dateStr) {
93
96
  // 完整落盘:不截断、不丢行(无单消息/单记录额度,无在途丢弃);清理 = 每写一次 prune
94
97
  // (写盘成功后顺带清理——与写盘同在 fire-and-forget 异步体内,永不阻塞 chat)。
95
98
 
96
- // 进程内 seq 缓存(§23.3.2:dir → 已预留 max;首次读盘后递增预留,目录被清理后取下界重扫一次)。
99
+ // 进程内 seq 缓存(TRACES.md §6.2:dir → 已预留 max;首次读盘后递增预留,目录被清理后取下界重扫一次)。
97
100
  const _seqCache = new Map()
98
101
  // 每写 prune 节流(D-TR12 · §6.4):窗 = 10 分钟 + 在飞合并(同窗 3 连写 ⇒ 扫描 ≤1 次)。
99
102
  export const PRUNE_THROTTLE_MS = 10 * 60_000
@@ -132,7 +135,7 @@ function scanMaxSeq(dir) {
132
135
 
133
136
  /** seq = max(当日目录最大已有 seq, 进程内已预留) + 1(D-TR3——跨会话/进程重启
134
137
  * 不覆写——T-TR8/T-TR10;同步预留 = 异步写盘启动前的原子号位分配)。
135
- * §23.3.2:seqCache 进程内递增——不逐调用 readdirSync(目录被清理/缺失 → 重扫一次)。 */
138
+ * TRACES.md §6.2:seqCache 进程内递增——不逐调用 readdirSync(目录被清理/缺失 → 重扫一次)。 */
136
139
  export function nextTraceSeq(dateStr) {
137
140
  const dir = tracesDirFor(dateStr)
138
141
  let max = _seqCache.get(dir)
@@ -152,7 +155,7 @@ export function tracesEnabled(logCtx) {
152
155
  return logCtx?.traces !== false
153
156
  }
154
157
 
155
- // ─── 单遍序列化(§23.3.2 D-TR1——脱敏内联、零复制图、字段名/次序同构)────────
158
+ // ─── 单遍序列化(TRACES.md §6.2 D-TR1——脱敏内联、零复制图、字段名/次序同构)────────
156
159
 
157
160
  const OMIT = Symbol("omit") // JSON.stringify 语义:对象属性省略(数组元素 → null)
158
161
 
@@ -292,7 +295,7 @@ export function recordChatTrace(provider, opts = {}, result = null, error = null
292
295
  }
293
296
 
294
297
  /** D-TR10 清理面(D-TR11 目录级三段梯)——实现外提 `traces/trace-cleanup.mjs`;此处包装保
295
- * 既有 import 面与缺省 `dir`(`tracesRoot()` 单源——测试经 `THINCODER_TRACES_DIR` 隔离)。
298
+ * 既有 import 面与缺省 `dir`(`tracesRoot()` 单源——测试经 `_setTracesRootForTest` 隔离)。
296
299
  * 调用点 = 每写 prune(经 `maybePruneTraces`)+ 启动清理(壳侧);返回删除文件数。 */
297
300
  export function cleanupTraces({ dir = tracesRoot(), retentionHours = 24 } = {}) {
298
301
  return cleanupTracesImpl({ dir, retentionHours })
@@ -0,0 +1,76 @@
1
+ /**
2
+ * vision-reader.mjs — 非多模态降级读图跑者(上提核件 —— parity-b4 W0 · 2026-09-29)。
3
+ *
4
+ * 上提源 = VSC `thincoder-vscode/src/extension/vision-channel.mjs:14`(`findVisionChannel` 逐字)
5
+ * + `image-handler.mjs:70-95`(`runVisionReader` 逐字);VSC 自持跑者 ∕ 渠道副本已随迁移轮(parity-b4)删。
6
+ * 消费面 = VSC `image-handler.mjs`(已改指本档)· 桌面 `src/main/attachments.mjs`
7
+ * (降级适配器缺省 = 本档 —— `visionReader` 缝的核默认实现)。
8
+ *
9
+ * `findVisionChannel`(纯函数):渠道表 ⇒ 视觉读图渠道 —— 「优先同名(当前主)渠道的视觉默认模型,
10
+ * else 全渠道首个视觉默认模型者」;判据 = `specForModel(p.model).multimodal`
11
+ * (与 `read_image` 注册门 ∕ `isNonVisionModel` 同源)。
12
+ *
13
+ * `runVisionReader`:一次性 depth-1 只读 explore 子代理读图返回文本描述。
14
+ * 渠道 = 视觉渠道(`resolveProviders()` 现读);provider = 核 `resolveChildProvider`(`<渠道>:<模型>`);
15
+ * 工具 = 按**该渠道模型**装配的只读族(`read_image` 随 multimodal 注册 —— 主模型非视觉时其工具表
16
+ * 本无该件,故按子代理模型重装,不沿用主代理工具表);`config ∕ memory` = 主代理同源。
17
+ * 失败径全 `null`(原样兜底,不静默丢):无渠道 ∕ 渠道未知 ∕ 渠道无 key(spawn 期失败回落)∕
18
+ * spawn 失败 ∕ 超时 ∕ 空返;外部 `signal` 已停 ⇒ 快速失败,否则桥接内部控制器。
19
+ *
20
+ * W8 契约②(2026-09-29 断链):`agent.mjs` ∕ `agent-tools/subagent-async.mjs` 静态链达 `node:sqlite`
21
+ * (`agent.mjs` → `agent/setup.mjs` → `memory.mjs` → `memory/schema.mjs`)⇒ 两端一律**函数体内动态 import**
22
+ * (约定源 = `ledger.mjs:16`;实例 = `tools/index.mjs:63`);本档静态边只留闭包净件(`config-io.mjs` ∕
23
+ * `model-specs.mjs` ∕ `tools/index.mjs`)。
24
+ */
25
+ import { resolveProviders } from "./config-io.mjs"
26
+ import { specForModel } from "./model-specs.mjs"
27
+ import { assembleBuiltinTools } from "./tools/index.mjs"
28
+
29
+ /** 读图墙钟上限(VSC `image-handler.mjs:70` 同值——一次性子代理的成功径上限)。 */
30
+ export const VISION_READ_TIMEOUT_MS = 60_000
31
+
32
+ /** 找可跑视觉读图的渠道:`{ provider, model }` ∥ `null`——优先同名(当前主)渠道的视觉默认模型,
33
+ * else 全渠道首个视觉默认模型者("优先同名渠道视觉模型 → 首视觉渠道")。 */
34
+ export function findVisionChannel(providers, currentName = "") {
35
+ const first = []
36
+ for (const p of providers ?? []) {
37
+ const model = typeof p?.model === "string" && p.model && specForModel(p.model).multimodal ? p.model : ""
38
+ if (!model) continue
39
+ if (p.name === currentName) return { provider: p.name, model }
40
+ first.push({ provider: p.name, model })
41
+ }
42
+ return first[0] ?? null
43
+ }
44
+
45
+ /** 降级读图跑者(缝契约 = `visionReader({ paths, signal }) ⇒ { ok: true, description }` ∥ `null`
46
+ * ——见 `attachments.mjs` `downgradeNonVisionImages`)。 */
47
+ export async function runVisionReader({ paths, providerName, cwd, parentAgent, signal }) {
48
+ // 取消缝(VSC A12 同款):已停 ⇒ 快速失败(不复用 60s 超时路径);否则桥接内部 controller。
49
+ if (signal?.aborted) return null
50
+ let providers = []
51
+ try { providers = resolveProviders().providers } catch { return null }
52
+ const vc = findVisionChannel(providers, providerName)
53
+ if (!vc) return null
54
+ let provider = null
55
+ try {
56
+ const { resolveChildProvider } = await import("./agent-tools/subagent-async.mjs") // 动态 import(该链静态达 node:sqlite——W8 契约②)
57
+ provider = resolveChildProvider(parentAgent, `${vc.provider}:${vc.model}`)
58
+ } catch { return null }
59
+ const ac = new AbortController()
60
+ const timer = setTimeout(() => ac.abort(), VISION_READ_TIMEOUT_MS)
61
+ const onAbort = () => ac.abort()
62
+ if (signal) signal.addEventListener("abort", onAbort, { once: true })
63
+ try {
64
+ const { createAgent, runAgent, readonlyToolNames, excludeSubagentTools } = await import("./agent.mjs") // 动态 import(agent 链静态达 node:sqlite——W8 契约②)
65
+ const builtins = await assembleBuiltinTools({ memory: parentAgent?.memory, cwd, model: vc.model })
66
+ const allowed = readonlyToolNames(builtins)
67
+ const tools = excludeSubagentTools(builtins.filter((t) => allowed.has(t.name)))
68
+ const child = createAgent({ provider, tools, config: parentAgent?.config, cwd, memory: parentAgent?.memory, role: "explore" })
69
+ const task = paths.map((f) => `用 read_image 读 ${f} 返回图像内容描述`).join("\n")
70
+ const desc = await runAgent(child, task, {}, { depth: 1, maxTurns: 10, signal: ac.signal })
71
+ return typeof desc === "string" && desc.trim() ? { ok: true, description: desc.trim() } : null
72
+ } catch { return null } finally {
73
+ clearTimeout(timer)
74
+ if (signal) signal.removeEventListener("abort", onAbort)
75
+ }
76
+ }
@@ -1,18 +0,0 @@
1
- /**
2
- * agent-tools/batch-segment.mjs — **过渡 shim**(batch 工具单名化收口,BATCH-RECORD §4.14):
3
- * 本档仅 re-export `batch.mjs` 主档导出面(旧 import 面 = VSC `setup-tooltable.mjs` 经此 import
4
- * `configureBatchSegment` / 旧测试消费——导出全超集,行为零变)。权威实现 = batch.mjs
5
- * (batchTool 四 action + append 迁移面 + 过渡别名 batchSegmentTool);撤除判据 = §4.14
6
- * (消费面清零后删本档——先例债:删档需同步 tooltable 装配 import,随批登记)。
7
- */
8
- export {
9
- MAX_TEXT_CHARS,
10
- SEGMENT_BY_ROLE,
11
- batchDocBases,
12
- batchDocForReview,
13
- batchSegmentTool,
14
- batchTool,
15
- configureBatchSegment,
16
- resetBatchSegment,
17
- resolveBatchDocPath,
18
- } from "./batch.mjs"