@thincoder/core 0.9.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 (203) hide show
  1. package/abort-provenance.mjs +116 -0
  2. package/advisor/citations.mjs +139 -0
  3. package/advisor/compaction.mjs +174 -0
  4. package/advisor/convergence.mjs +80 -0
  5. package/advisor/history.mjs +77 -0
  6. package/advisor/loop.mjs +288 -0
  7. package/advisor/messages.mjs +299 -0
  8. package/advisor/notice.mjs +141 -0
  9. package/advisor/project-context.mjs +197 -0
  10. package/advisor/repos.mjs +150 -0
  11. package/advisor/run.mjs +190 -0
  12. package/advisor/truncate.mjs +57 -0
  13. package/advisor.mjs +281 -0
  14. package/agent/child-marks.mjs +24 -0
  15. package/agent/completion.mjs +145 -0
  16. package/agent/dispatch.mjs +493 -0
  17. package/agent/family-tools.mjs +174 -0
  18. package/agent/helpers.mjs +412 -0
  19. package/agent/post-turn.mjs +70 -0
  20. package/agent/record-results.mjs +174 -0
  21. package/agent/relay-prefix.mjs +39 -0
  22. package/agent/run-stages.mjs +244 -0
  23. package/agent/setup-reminders.mjs +199 -0
  24. package/agent/setup.mjs +234 -0
  25. package/agent/spawn-child.mjs +258 -0
  26. package/agent/suspension.mjs +240 -0
  27. package/agent/write-gate.mjs +87 -0
  28. package/agent-tools/advisor-async.mjs +481 -0
  29. package/agent-tools/advisor-settle.mjs +240 -0
  30. package/agent-tools/advisor.mjs +280 -0
  31. package/agent-tools/async-discard.mjs +143 -0
  32. package/agent-tools/async-settle.mjs +299 -0
  33. package/agent-tools/batch-segment.mjs +265 -0
  34. package/agent-tools/child-permission.mjs +45 -0
  35. package/agent-tools/consult.mjs +471 -0
  36. package/agent-tools/design-token.mjs +117 -0
  37. package/agent-tools/digest-budget.mjs +76 -0
  38. package/agent-tools/eng.mjs +102 -0
  39. package/agent-tools/escalate-async.mjs +302 -0
  40. package/agent-tools/goal.mjs +119 -0
  41. package/agent-tools/panel-blocks.mjs +24 -0
  42. package/agent-tools/parent-channel.mjs +231 -0
  43. package/agent-tools/plan.mjs +86 -0
  44. package/agent-tools/read-history.mjs +309 -0
  45. package/agent-tools/recent-changes.mjs +24 -0
  46. package/agent-tools/review-facts.mjs +31 -0
  47. package/agent-tools/settings.mjs +268 -0
  48. package/agent-tools/skill.mjs +63 -0
  49. package/agent-tools/spawn-gates.mjs +109 -0
  50. package/agent-tools/subagent-actions.mjs +496 -0
  51. package/agent-tools/subagent-async.mjs +456 -0
  52. package/agent-tools/subagent-panel.mjs +160 -0
  53. package/agent-tools/subagent-run.mjs +208 -0
  54. package/agent-tools/subagent-scheduler.mjs +446 -0
  55. package/agent-tools/subagent-spawn.mjs +478 -0
  56. package/agent-tools/subagent.mjs +419 -0
  57. package/agent-tools/task.mjs +87 -0
  58. package/agent-tools/timer.mjs +46 -0
  59. package/agent-tools/verify.mjs +295 -0
  60. package/agent-tools.mjs +23 -0
  61. package/agent.mjs +430 -0
  62. package/auto-think.mjs +115 -0
  63. package/compress-form.mjs +24 -0
  64. package/config-io.mjs +277 -0
  65. package/config-migrate.mjs +178 -0
  66. package/config-presets.mjs +49 -0
  67. package/config.mjs +419 -0
  68. package/context.mjs +495 -0
  69. package/conventions.mjs +223 -0
  70. package/embedding.mjs +120 -0
  71. package/escape.mjs +152 -0
  72. package/expand-home.mjs +16 -0
  73. package/explore-distill.mjs +152 -0
  74. package/generate-title.mjs +123 -0
  75. package/git/checkpoint.mjs +448 -0
  76. package/git/gitmem.mjs +100 -0
  77. package/history-window.mjs +179 -0
  78. package/hooks.mjs +108 -0
  79. package/i18n.mjs +106 -0
  80. package/index-bin.mjs +48 -0
  81. package/index-discover.mjs +176 -0
  82. package/ledger-cmd.mjs +209 -0
  83. package/ledger-db.mjs +86 -0
  84. package/ledger-surface.mjs +76 -0
  85. package/ledger.mjs +202 -0
  86. package/log.mjs +195 -0
  87. package/manifest.mjs +338 -0
  88. package/markdown.mjs +106 -0
  89. package/mcp/helpers.mjs +51 -0
  90. package/mcp/transport-http.mjs +248 -0
  91. package/mcp/transport-stdio.mjs +140 -0
  92. package/mcp/transport-ws.mjs +122 -0
  93. package/mcp.mjs +295 -0
  94. package/memory/code-index.mjs +219 -0
  95. package/memory/code-sync.mjs +427 -0
  96. package/memory/core.mjs +318 -0
  97. package/memory/delete.mjs +242 -0
  98. package/memory/docs.mjs +431 -0
  99. package/memory/file-walk.mjs +109 -0
  100. package/memory/origin.mjs +24 -0
  101. package/memory/scan.mjs +177 -0
  102. package/memory/schema.mjs +460 -0
  103. package/memory.mjs +21 -0
  104. package/model-ref.mjs +66 -0
  105. package/model-specs.mjs +277 -0
  106. package/package.json +32 -0
  107. package/peer-domains.mjs +265 -0
  108. package/peer-instances.mjs +178 -0
  109. package/permission.mjs +79 -0
  110. package/process-probe.mjs +315 -0
  111. package/prompt-files.mjs +113 -0
  112. package/prompt-overlays.mjs +78 -0
  113. package/prompts/advisor-design.md +43 -0
  114. package/prompts/advisor-round1.md +41 -0
  115. package/prompts/advisor-round2.md +46 -0
  116. package/prompts/advisor-round3.md +42 -0
  117. package/prompts/common.md +158 -0
  118. package/prompts/consult-base.md +19 -0
  119. package/prompts/discipline-engineering.md +123 -0
  120. package/prompts/discipline-normal.md +206 -0
  121. package/prompts/persona-coder.md +21 -0
  122. package/prompts/persona-eng-coder.md +41 -0
  123. package/prompts/persona-eng-designer.md +80 -0
  124. package/prompts/persona-engineering.md +160 -0
  125. package/prompts/persona-explore.md +15 -0
  126. package/prompts/persona-normal.md +35 -0
  127. package/prompts/persona-plan.md +27 -0
  128. package/provider/anthropic.mjs +225 -0
  129. package/provider/core.mjs +491 -0
  130. package/provider/errors.mjs +101 -0
  131. package/provider/google.mjs +257 -0
  132. package/provider/index.mjs +7 -0
  133. package/provider/list-models.mjs +163 -0
  134. package/provider/normalize.mjs +81 -0
  135. package/provider/rate.mjs +168 -0
  136. package/provider/responses.mjs +495 -0
  137. package/provider/retry.mjs +88 -0
  138. package/provider/sse.mjs +264 -0
  139. package/provider/wait-status.mjs +59 -0
  140. package/proxy.mjs +274 -0
  141. package/rules.mjs +53 -0
  142. package/session-gc.mjs +248 -0
  143. package/session-guard.mjs +59 -0
  144. package/session-lifecycle.mjs +305 -0
  145. package/session-migrate.mjs +48 -0
  146. package/session-rename.mjs +38 -0
  147. package/session-segments.mjs +99 -0
  148. package/session-slot-write.mjs +168 -0
  149. package/session-slots-manifest.mjs +264 -0
  150. package/session-slots.mjs +298 -0
  151. package/session-store.mjs +441 -0
  152. package/session.mjs +244 -0
  153. package/skills.mjs +234 -0
  154. package/text-budget.mjs +79 -0
  155. package/token-ttl.mjs +285 -0
  156. package/tool-docs/apply_patch.md +15 -0
  157. package/tool-docs/bash.md +38 -0
  158. package/tool-docs/delete.md +13 -0
  159. package/tool-docs/edit.md +30 -0
  160. package/tool-docs/execute.md +21 -0
  161. package/tool-docs/fetch.md +12 -0
  162. package/tool-docs/file_ops.md +17 -0
  163. package/tool-docs/get_current_time.md +8 -0
  164. package/tool-docs/git.md +54 -0
  165. package/tool-docs/glob.md +11 -0
  166. package/tool-docs/grep.md +19 -0
  167. package/tool-docs/hashline_edit.md +14 -0
  168. package/tool-docs/insert_after.md +15 -0
  169. package/tool-docs/lint.md +10 -0
  170. package/tool-docs/ls.md +12 -0
  171. package/tool-docs/lsp.md +10 -0
  172. package/tool-docs/process.md +10 -0
  173. package/tool-docs/question.md +16 -0
  174. package/tool-docs/read.md +20 -0
  175. package/tool-docs/read_image.md +8 -0
  176. package/tool-docs/tree.md +14 -0
  177. package/tool-docs/wait_for.md +22 -0
  178. package/tool-docs/websearch.md +16 -0
  179. package/tool-docs/write.md +11 -0
  180. package/tools/bash.mjs +276 -0
  181. package/tools/edit-batch.mjs +204 -0
  182. package/tools/edit-diff.mjs +388 -0
  183. package/tools/exec-run.mjs +43 -0
  184. package/tools/execute.mjs +243 -0
  185. package/tools/file.mjs +464 -0
  186. package/tools/git-checkpoint.mjs +143 -0
  187. package/tools/git-ext.mjs +173 -0
  188. package/tools/git.mjs +415 -0
  189. package/tools/glob-dialect.mjs +130 -0
  190. package/tools/index.mjs +76 -0
  191. package/tools/linter.mjs +120 -0
  192. package/tools/lsp.mjs +335 -0
  193. package/tools/ops.mjs +293 -0
  194. package/tools/patch.mjs +290 -0
  195. package/tools/question.mjs +26 -0
  196. package/tools/repomap.mjs +314 -0
  197. package/tools/search.mjs +248 -0
  198. package/tools/shared.mjs +467 -0
  199. package/tools/tree.mjs +81 -0
  200. package/tools/web.mjs +224 -0
  201. package/tools/write-path.mjs +191 -0
  202. package/traces/trace-store.mjs +303 -0
  203. package/undo-stack.mjs +47 -0
@@ -0,0 +1,265 @@
1
+ /**
2
+ * agent-tools/batch-segment.mjs — 批次档段写入工具(ENGINEERING-MODE.md §2.20 · FR22 F1-F7)。
3
+ *
4
+ * 规则、守卫与 fail-closed 清单的**权威正文在 §2.20.1**(本文件不重述——D2 单一权威源);
5
+ * 实现要点(正文未定死、由代码承载):
6
+ * - 身份判据 = 评审实例绑定(`review` 形态)/ `agent._role`;
7
+ * - append-only:在段尾(下一条 `## §N` 或 EOF)插入,既有行字节不变;
8
+ * - 来源戳 `### 轮次 N(评审子代理)`(仅 §3)——N = §3 内该形态行计数 + 1,**调用方自带**
9
+ * 的同名标题行被丢弃(否则伪造戳会污染 N 计数——§2.20.8 #5);
10
+ * - 凭证剥除:含凭证形态的行剥掉该子串,剥后为空则整行丢弃(「零命中」+「其余逐字保留」);
11
+ * - 路径门禁:`resolveBatchDocPath`(评审侧「若传则须可读」);工具内再查一次可读性。
12
+ * - v2 增量(ENGINEERING-MODE-V2-MODULE-BATCH-SEGMENT.md §2.1):§1 状态行解析
13
+ * (`readBatchStatusLine`——「已收口」→ 冻结拒写、「进行中」→ 放行、缺失/不可解析 →
14
+ * fail-closed 拒);`resolveBatchDocPath` 加 manifest `docRoot.batches` 双基底
15
+ * (值形态 = 串 | 多根数组——逐基底按序复判;manifest 缺失/非法/读错 → v1 单基底
16
+ * 语义零变);冻结只覆盖 `batch_segment` 通道(§2.5)。
17
+ *
18
+ * 导出面:`batchSegmentTool` · `resolveBatchDocPath` · `batchDocForReview`(异步评审实例键取绑定)·
19
+ * `configureBatchSegment` / `resetBatchSegment`(#84 记账面注入缝——缺省 no-op,见下)。
20
+ *
21
+ * #84 记账面(CORE-UNIFICATION §2.13.4):写入成功后的记账(把绑定档记入调用者写域)
22
+ * **按端注入**(形态参 §2.13.5 注入缝)——核内缺省 no-op = CLI 语义零行为变;端装配
23
+ * 经 `configureBatchSegment({ onWrite })` 覆盖(VSC 侧现形 = `_touchedFiles` 记账)。
24
+ */
25
+ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
26
+ import { resolve } from "node:path"
27
+
28
+ import { docRootPaths, readManifest } from "../manifest.mjs"
29
+
30
+ /** text 单次上限(§2.20.1——超出引导分段追加,不承诺"不新盖戳")。 */
31
+ export const MAX_TEXT_CHARS = 20000
32
+ /** 身份 → 可写段号(§2.20.1 段白名单)。 */
33
+ export const SEGMENT_BY_ROLE = { "eng-designer": 2, "eng-coder": 5 }
34
+ /** 工具写入的轮次节标题形态(N 计数口径 = 该形态行;`### 轮次与发现(…)` 骨架行不匹配)。 */
35
+ const ROUND_HEADING_RE = /^### 轮次 \d+(评审子代理)/
36
+ /** 段标题定位(`## §N` 独立标题——`## §20` 不误命中 §2)。 */
37
+ const sectionHeaderRe = (seg) => new RegExp(`^## §${seg}(?=\\s|$)`, "m")
38
+ /** 凭证形态(§2.7 冒号态):`[DESIGN-TOKEN:…]` 与 `designId: …`——本节自有正则。 */
39
+ const CRED_RE = /\[DESIGN-TOKEN:[^\]]*\]|designId\s*:\s*\S+/g
40
+ const CRED_TEST_RE = /\[DESIGN-TOKEN:[^\]]*\]|designId\s*:\s*\S+/
41
+ /** §1 状态行前缀(`**状态行**:` 独立行——表格行 / 块引用行不命中;判定只认关键字)。 */
42
+ const STATUS_LINE_RE = /^\s*\*\*状态行\*\*[::]\s*(.*)$/
43
+
44
+ // ─── #84 记账面注入缝(「记账面按端注入」,形态参 §2.13.5 注入缝)───────────────
45
+ /**
46
+ * 写入成功后的记账注入面(**缺省 no-op** = CLI 语义零行为变):端装配层可覆盖——把写成的
47
+ * 批次档记入调用者写域(VSC 侧现形 = `agent._touchedFiles` 记账,供子代理合入 / recent_changes /
48
+ * verify / advisor 默认范围消费)。核内零端名分支(契约 5——本档只认 `onWrite` 函数名)。
49
+ * 形态与 `tools/write-path.mjs` 的 `configureWritePath` 同款:模块级、缺省不覆盖、null 撤销。
50
+ */
51
+ let injectedOnWrite = null
52
+ export function configureBatchSegment(impl) {
53
+ injectedOnWrite = impl && typeof impl.onWrite === "function" ? impl.onWrite : null
54
+ }
55
+ /** 撤销注入(测试与端装配生命周期用——缺省态 = 零记账)。 */
56
+ export function resetBatchSegment() { injectedOnWrite = null }
57
+
58
+ /** 可读文件判据(存在且为文件——目录/缺失同判不可读)。 */
59
+ function readableFile(abs) {
60
+ try { return existsSync(abs) && statSync(abs).isFile() } catch { return false }
61
+ }
62
+
63
+ /**
64
+ * 批次档路径门禁(评审侧 §2.20.2 口径 = **「若传则须可读」**):空/非字符串/不可读 → throw。
65
+ * 非空且可读 → 返回绝对路径(`\` 归一——照 `files`/`batchDoc` spawn 门先例)。
66
+ * v2:cwd 不可读 → manifest `docRoot.batches` 复判(M3 模块设计 §2.1#4——N3 可迁移;
67
+ * 值形态 = 串 | 多根数组——逐基底按序复判,首个可读者胜)。
68
+ */
69
+ export function resolveBatchDocPath(cwd, given) {
70
+ const base = cwd ?? process.cwd()
71
+ const raw = typeof given === "string" ? given.trim() : ""
72
+ if (!raw) {
73
+ throw new Error("batchDoc must be a non-empty path to the batch record — pass the batch record currently in flight, or omit the parameter entirely when no batch record is in flight.")
74
+ }
75
+ const abs = resolve(base, raw.replace(/\\/g, "/"))
76
+ if (readableFile(abs)) return abs
77
+ // 双基底(M3 模块设计 §2.1#4):cwd 不可读 → manifest docRoot.batches 复判(逐基底按序,
78
+ // 首个可读者胜);manifest 缺失 / 非法 / 读错 → 无第二基底(v1 单基底语义零变),
79
+ // 全不可读 → throw。
80
+ let man = { ok: false }
81
+ try { man = readManifest(base) } catch { /* 权限等读错——按无 manifest 处理(v1 语义) */ }
82
+ if (man.ok) {
83
+ for (const root of docRootPaths(man.manifest?.docRoot?.batches, base)) {
84
+ const alt = resolve(root, raw.replace(/\\/g, "/"))
85
+ if (readableFile(alt)) return alt
86
+ }
87
+ }
88
+ throw new Error(`batchDoc is not a readable file: ${raw} — pass the path of the batch record currently in flight (a path that resolves to an existing file), or omit the parameter when no batch record is in flight.`)
89
+ }
90
+
91
+ /**
92
+ * §1 状态行解析(内部——M3 模块设计 §2.1#1):解析对象 = §1 段内 `**状态行**:` 前缀行
93
+ * (§1 边界 = `## §1` 标题到下一 `## §` 标题;其他段内「状态行」字样不参与判定)。
94
+ * 判定只认关键字(emoji / 括号装饰 / 日期后缀容忍):含「已收口」→ "closed"(冻结优先)、
95
+ * 含「进行中」→ "open";无 §1 / 无该行 / 两关键字皆不命中 → "unknown"(fail-closed 视为冻结)。
96
+ */
97
+ function readBatchStatusLine(src) {
98
+ const s1 = /^## §1(?=\s|$)/m.exec(src)
99
+ if (!s1) return "unknown"
100
+ const nextRe = /^## §\d/gm
101
+ nextRe.lastIndex = s1.index + s1[0].length
102
+ const next = nextRe.exec(src)
103
+ const body = src.slice(s1.index + s1[0].length, next ? next.index : src.length)
104
+ const m = body.split("\n").map((line) => STATUS_LINE_RE.exec(line)).find(Boolean)
105
+ if (!m) return "unknown"
106
+ const value = m[1].trim()
107
+ if (value.includes("已收口")) return "closed"
108
+ if (value.includes("进行中")) return "open"
109
+ return "unknown"
110
+ }
111
+
112
+ /**
113
+ * 设计评审的实例绑定解析(§2.20.2/§2.20.3——batch_segment 的唯一路径来源):
114
+ * - 同步路径:调用方(advisor 工具)把实例绑定显式放进 callbacks(**带 batchDoc 键**,
115
+ * 未绑定即 null)——以此为准,不再回看池条目(防同步/异步混跑时串档);
116
+ * - 异步路径:本评审所在**池条目**的 `run.batchDoc`(文档集 = 实例键,各评审各取各条)。
117
+ */
118
+ export function batchDocForReview(agent, documents, callbacks = null) {
119
+ if (callbacks && "batchDoc" in callbacks) return callbacks.batchDoc ?? null
120
+ const key = JSON.stringify([...(documents ?? [])].sort())
121
+ for (const e of agent?._asyncAdvisors?.values?.() ?? []) {
122
+ if (e.status === "running" && e.reviewType === "design" && JSON.stringify([...(e.documents ?? [])].sort()) === key) {
123
+ return e.run?.batchDoc ?? null
124
+ }
125
+ }
126
+ return null
127
+ }
128
+
129
+ /** 段号解析:`§2` / `2` / `§2 批次任务` 均可;无法解析 → null。 */
130
+ function segmentNumber(raw) {
131
+ const m = /^§?\s*(\d+)/.exec(String(raw ?? "").trim())
132
+ return m ? Number(m[1]) : null
133
+ }
134
+
135
+ /** 身份 → 可写段号;无写权身份 → null。评审实例绑定优先(评审者的身份即"设计评审"。) */
136
+ function allowedSegment(agent, review) {
137
+ if (review) return 3
138
+ return SEGMENT_BY_ROLE[agent?._role] ?? null
139
+ }
140
+
141
+ /**
142
+ * 凭证剥除 + 伪造轮次标题丢弃(§2.20.1 F6/AC30/AC34)。
143
+ * 逐行处理:含凭证形态的行 → 剥除该子串,剥后为空则整行丢弃(其余内容逐字保留)。
144
+ * `dropStamp`(仅 §3 目标段):调用方自带的 `### 轮次 N(评审子代理)` 行被丢弃——
145
+ * 工具生成才是唯一来源戳(否则伪造戳会污染 N 计数)。
146
+ */
147
+ function sanitizeText(text, dropStamp) {
148
+ const kept = []
149
+ for (const line of text.replace(/\r\n?/g, "\n").split("\n")) {
150
+ if (dropStamp && ROUND_HEADING_RE.test(line)) continue
151
+ if (!CRED_TEST_RE.test(line)) { kept.push(line); continue }
152
+ const cleaned = line.replace(CRED_RE, "").replace(/[ \t]+$/, "")
153
+ if (cleaned.trim()) kept.push(cleaned)
154
+ }
155
+ return kept.join("\n")
156
+ }
157
+
158
+ /** §3 内工具写入的轮次行计数(N = 计数 + 1;收窄口径——只数工具戳形态)。 */
159
+ function roundCount(src, headerMatch, endIdx) {
160
+ const body = src.slice(headerMatch.index + headerMatch[0].length, endIdx)
161
+ return body.split("\n").filter((l) => ROUND_HEADING_RE.test(l)).length
162
+ }
163
+
164
+ /** 段尾定位 + 插入(append-only:只在段尾插入,既有字节不变;返回新全文 + 本轮 N)。 */
165
+ function insertIntoSection(src, seg, text) {
166
+ const hdr = sectionHeaderRe(seg).exec(src)
167
+ if (!hdr) {
168
+ throw new Error(`batch_segment: the bound batch record has no "## §${seg}" section header — the six-section skeleton is written by the record's creator before any segment write. Fix: ask the parent/creator to add the "## §${seg} …" heading (with the template's sub-headings) first, then call batch_segment again. Nothing was written.`)
169
+ }
170
+ const nextRe = /^## §\d/gm
171
+ nextRe.lastIndex = hdr.index + hdr[0].length
172
+ const next = nextRe.exec(src)
173
+ const endIdx = next ? next.index : src.length
174
+ // 来源戳(仅 §3):工具生成标题 + N——调用方写不进自己的标题(sanitize 已丢弃同名行)。
175
+ const roundN = seg === 3 ? roundCount(src, hdr, endIdx) + 1 : 0
176
+ const payload = (seg === 3 ? `### 轮次 ${roundN}(评审子代理)\n\n` : "") + text
177
+ const before = src.slice(0, endIdx)
178
+ const after = src.slice(endIdx)
179
+ const eol = src.includes("\r\n") ? "\r\n" : "\n"
180
+ const lead = before === "" ? "" : before.endsWith(eol + eol) ? "" : before.endsWith(eol) ? eol : eol + eol
181
+ // 尾随 eol 恒加(块自成行);后面还有段标题时再补一个空行(保持模板的空行分隔)。
182
+ const written = before + lead + payload.replaceAll("\n", eol) + eol + (after === "" ? "" : eol) + after
183
+ return { written, roundN }
184
+ }
185
+
186
+ /**
187
+ * 批次档段写入工具工厂。
188
+ * @param {string|null} batchDoc — 绑定的目标档(spawn:`child._batchDoc`;评审:实例键)
189
+ * @param {{review?: boolean}} [opts] — review=true 表示"设计评审实例"形态(写 §3 + 工具盖戳)
190
+ */
191
+ export function batchSegmentTool(batchDoc = null, { review = false } = {}) {
192
+ const own = review ? "§3" : null
193
+ return {
194
+ name: "batch_segment",
195
+ description:
196
+ "Append your own section of the batch record (一段一作者). " +
197
+ "There is NO path parameter: the target record is bound to you (at spawn for eng-designer/eng-coder, per review instance for a design review) and your identity fixes the section you may write " +
198
+ "(eng-designer → §2, design review → §3, eng-coder → §5) — a write outside your own section is refused. " +
199
+ "Append-only: the text lands at the end of your section; existing lines are never rewritten or deleted. " +
200
+ "Credential values are stripped mechanically before writing (never write a token or designId value). " +
201
+ "A design review's append is stamped by the tool with a `### 轮次 N(评审子代理)` heading — N is tool-counted; do not write your own heading (it would be dropped). " +
202
+ "Failures are hard and visible (no silent fallback): if the write is refused or fails, say so in your report — “§× 未写入”.",
203
+ parameters: {
204
+ type: "object",
205
+ properties: {
206
+ segment: {
207
+ type: "string",
208
+ description: `The batch-record section you are writing${own ? ` — yours is ${own}` : ""}. Declares the section number only; your identity decides what is actually writable.`,
209
+ },
210
+ text: {
211
+ type: "string",
212
+ description: "The markdown to append (verbatim — findings table + VERDICT + counts for a design review §3). Limit 20000 characters per call; longer content is refused — split it into multiple calls (each call becomes its own section, N continues).",
213
+ },
214
+ },
215
+ required: ["segment", "text"],
216
+ },
217
+ async execute(args, ctx) {
218
+ const agent = ctx?.agent ?? {}
219
+ const seg = allowedSegment(agent, review)
220
+ if (seg === null) {
221
+ throw new Error("batch_segment: no segment is writable by this caller — the channel exists for eng-designer (§2), eng-coder (§5) and design reviews bound to a batch record (§3); the parent agent writes §1/§4/§6 through ordinary document writes.")
222
+ }
223
+ const n = segmentNumber(args?.segment)
224
+ if (n === null) {
225
+ throw new Error(`batch_segment: unknown segment ${JSON.stringify(args?.segment ?? null)} — pass the section number you write (e.g. "§${seg}").`)
226
+ }
227
+ if (n !== seg) {
228
+ throw new Error(`batch_segment: §${n} is not yours to write — this caller writes §${seg} only (一段一作者: eng-designer → §2, design review → §3, eng-coder → §5).`)
229
+ }
230
+ if (!batchDoc) {
231
+ throw new Error("batch_segment: no batch record is bound to this caller — there is no path parameter by design (the target arrives via the spawn binding / the review instance key). Report the section as not written.")
232
+ }
233
+ const abs = resolveBatchDocPath(agent.cwd ?? process.cwd(), batchDoc)
234
+ const src = readFileSync(abs, "utf8")
235
+ const status = readBatchStatusLine(src)
236
+ if (status === "closed") {
237
+ throw new Error("batch_segment: 已收口档不回改 — the bound batch record's §1 status line contains 「已收口」, so the record is frozen: its body is never written to again (整档冻结;改 = 新批新档). Nothing was written. Report the section as not written.")
238
+ }
239
+ if (status === "unknown") {
240
+ throw new Error("batch_segment: 状态行不可解析或缺失 — the bound batch record has no §1 `**状态行**:` line whose value contains 已收口 or 进行中 (fail-closed: the write is refused as if frozen). Ask the record's creator to set the §1 status line, then call again. Nothing was written.")
241
+ }
242
+ if (typeof args?.text !== "string") {
243
+ throw new Error("batch_segment: text must be a string (the markdown to append).")
244
+ }
245
+ if (args.text.length > MAX_TEXT_CHARS) {
246
+ throw new Error(`batch_segment: text is ${args.text.length} characters — the limit is ${MAX_TEXT_CHARS} per call. Split it into multiple calls: each call becomes its own section and the round number N continues (分段追加——每次调用各成节、N 顺延).`)
247
+ }
248
+ if (/^## §\d/m.test(args.text)) {
249
+ throw new Error("batch_segment: the text contains a section header line matching `^## §N` — that would break section location and the append-only guarantee. Rewrite it (escape the heading, or drop the leading `## §N`), then call again (骨架保护). Nothing was written.")
250
+ }
251
+ const body = sanitizeText(args.text, seg === 3).replace(/^\n+/, "").replace(/\s+$/, "")
252
+ if (!body.trim()) {
253
+ throw new Error("batch_segment: nothing to append — the text is empty after credential stripping (credential values never reach the record).")
254
+ }
255
+ const { written, roundN } = insertIntoSection(src, seg, body)
256
+ writeFileSync(abs, written)
257
+ // #84(S1 续轮第二批——VSC 侧并入 ④):记账面**按端注入**(缺省 no-op = CLI 语义
258
+ // 零行为变——端装配经 configureBatchSegment 覆盖;VSC 侧现形 = 把绑定档记入调用者
259
+ // 写域 `_touchedFiles`)。核内不内建记账(设计裁定形态——CORE-UNIFICATION §2.13.4 #84
260
+ // 「记账面按端注入」)。
261
+ injectedOnWrite?.(agent, abs)
262
+ return `batch_segment: appended ${body.length} characters to §${seg} of the batch record${roundN ? ` (### 轮次 ${roundN}(评审子代理))` : ""}.`
263
+ },
264
+ }
265
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * child-permission.mjs — child permission channel(CORE-UNIFICATION §2.5 #166
3
+ * 「融合:子代理权限通道按核内结构归位(父卡归属 + 定向 signal)」;§18 C-2——2026-09-12)。
4
+ *
5
+ * 来源 = `thincoder-vscode/src/agent-tools/child-permission.mjs`(VSC 独有拆面——CLI 侧无独立
6
+ * 档,该通道内联于父卡装配)——**逐字随迁**;档内零 `vscode` 依赖(`ctx.callbacks` 驱动)⇒
7
+ * 按核内结构直接归位;两端装配层各自挂 `ctx.callbacks.onPermissionRequired`。
8
+ *
9
+ * 写权 child(coder/eng-designer)手动档的每写询问通道:把子代理的 onPermissionRequired
10
+ * 桥到**父面板**的权限卡(owner 归属 + 定向 signal),并在询问前后以
11
+ * onSubagentApproval 通知块头审批态(⏸ + 等待审批: <tool>)。eng-coder child 不配通道
12
+ * (spawn 时授权——C-3/KD-2 保持零弹卡);explore/plan 只读集不可达;无父通道
13
+ * (headless / 无关角色调用侧未挂——AUTO 不再使通道缺席:父门恒在,判定 = 询问时
14
+ * live 读,父级与子代同判据单源,ED-2 2026-09-16)→ 返回 null(调用侧省略该键——静默直通,零回归)。
15
+ *
16
+ * 顺序定死(C-2):announce(tool) → await ask → finally announce(null 清态)。owner label
17
+ * 与活动块 label 同源(KD-8——卡与块可目视配对):escalate/consult 带模型
18
+ * (`escalate <model> #<id>`),family 角色 `<role>#<id>`。
19
+ */
20
+
21
+ /** owner label(KD-8——活动块 label 同源):role ∈ {escalate, consult} 且有 model →
22
+ * `<role> <model> #<id>`;否则 `<role>#<id>`。 */
23
+ export function childOwnerLabel(role, id, model) {
24
+ if ((role === "escalate" || role === "consult") && model) return `${role} <${model}> #${id}`
25
+ return `${role}#${id}`
26
+ }
27
+
28
+ /**
29
+ * 构建 child 权限通道。返回 null(无 `ctx.callbacks.onPermissionRequired`——headless /
30
+ * 无关角色调用侧未挂;AUTO 判定走父门询问时 live 读——同判据单源,ED-2 2026-09-16)或
31
+ * `async (toolName, args, diffInfo) => boolean`。
32
+ * @param {{ ctx: object, id: number, role: string, model?: string|null, signal?: AbortSignal|null }} deps
33
+ */
34
+ export function makeChildPermission({ ctx, id, role, model = null, signal = null }) {
35
+ if (!ctx?.callbacks?.onPermissionRequired) return null
36
+ const owner = { label: childOwnerLabel(role, id, model), role, id }
37
+ return async (toolName, args, diffInfo) => {
38
+ ctx.callbacks.onSubagentApproval?.({ id, role, model, tool: toolName })
39
+ try {
40
+ return await ctx.callbacks.onPermissionRequired(toolName, args, diffInfo, { owner, signal })
41
+ } finally {
42
+ ctx.callbacks.onSubagentApproval?.({ id, role, model, tool: null })
43
+ }
44
+ }
45
+ }