thincoder 0.12.66 → 0.12.68

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 (69) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +23 -5
  3. package/bin/thincoder.mjs +43 -331
  4. package/package.json +2 -2
  5. package/src/acp/bridge.mjs +13 -12
  6. package/src/acp/handlers-session.mjs +12 -1
  7. package/src/cli/make-agent.mjs +44 -119
  8. package/src/cli/memory-command.mjs +45 -3
  9. package/src/cli/permission.mjs +1 -1
  10. package/src/command-interactive.mjs +186 -0
  11. package/src/command-table.mjs +185 -0
  12. package/src/completions.mjs +36 -12
  13. package/src/crash-reports.mjs +42 -20
  14. package/src/distill.mjs +3 -3
  15. package/src/heap-watch.mjs +5 -14
  16. package/src/prompt-injections.mjs +1 -1
  17. package/src/tui/agent-turn.mjs +114 -69
  18. package/src/tui/clipboard.mjs +74 -30
  19. package/src/tui/cmd-advisor.mjs +54 -13
  20. package/src/tui/cmd-clear.mjs +2 -2
  21. package/src/tui/cmd-config.mjs +26 -2
  22. package/src/tui/cmd-eng.mjs +1 -1
  23. package/src/tui/cmd-mcp.mjs +1 -1
  24. package/src/tui/cmd-new.mjs +2 -2
  25. package/src/tui/cmd-reindex.mjs +1 -1
  26. package/src/tui/cmd-session.mjs +13 -5
  27. package/src/tui/cmd-think.mjs +35 -17
  28. package/src/tui/cmd-timers.mjs +33 -0
  29. package/src/tui/conversation-writer.mjs +2 -2
  30. package/src/tui/display-budget.mjs +9 -9
  31. package/src/tui/fold-block.mjs +1 -1
  32. package/src/tui/index.mjs +23 -5
  33. package/src/tui/interaction.mjs +5 -3
  34. package/src/tui/key-handler-busy.mjs +17 -12
  35. package/src/tui/key-handler-ctrlc.mjs +5 -5
  36. package/src/tui/key-handler-edit.mjs +12 -8
  37. package/src/tui/key-handler-search.mjs +1 -1
  38. package/src/tui/key-handler.mjs +2 -2
  39. package/src/tui/key-modes.mjs +1 -1
  40. package/src/tui/layout.mjs +5 -5
  41. package/src/tui/ledger-surface.mjs +26 -5
  42. package/src/tui/lifecycle-records.mjs +199 -0
  43. package/src/tui/model-picker.mjs +16 -199
  44. package/src/tui/mouse.mjs +5 -5
  45. package/src/tui/pickers.mjs +6 -2
  46. package/src/tui/provider-admin.mjs +213 -0
  47. package/src/tui/queued-merge.mjs +12 -0
  48. package/src/tui/queued-pickup.mjs +34 -0
  49. package/src/tui/render-conversation.mjs +49 -4
  50. package/src/tui/render-frame.mjs +54 -11
  51. package/src/tui/render-loop.mjs +2 -3
  52. package/src/tui/render-segments.mjs +4 -4
  53. package/src/tui/render.mjs +1 -1
  54. package/src/tui/slash-commands.mjs +3 -0
  55. package/src/tui/startup.mjs +28 -3
  56. package/src/tui/subagent-blocks.mjs +37 -29
  57. package/src/tui/subagent-children.mjs +5 -5
  58. package/src/tui/subagent-freeze.mjs +43 -41
  59. package/src/tui/subagent-panel.mjs +11 -11
  60. package/src/tui/suspension-drive.mjs +173 -253
  61. package/src/tui/timer-watch.mjs +64 -0
  62. package/src/tui/tool-args.mjs +10 -71
  63. package/src/tui/tool-display.mjs +7 -7
  64. package/src/tui/tool-events.mjs +40 -35
  65. package/src/tui/tui-lifecycle.mjs +13 -4
  66. package/src/tui/tui-state.mjs +3 -2
  67. package/src/tui/turn-face.mjs +6 -3
  68. package/src/tui/wizard.mjs +7 -2
  69. package/src/tui/wrapped-spawn.mjs +3 -3
@@ -16,7 +16,7 @@
16
16
  * 省略计数真值(N6——D-ST5):`…(已省略 N 行)` 的 N 只随内容移除增长;标记自身
17
17
  * 不占额度、不被丢弃、不计入 N。
18
18
  *
19
- * TUI-OOM-ROOTCAUSE(TUI.md §15.1 事实 3 / §15.3.3):原环按 `\n` 计数——无换行巨 chunk
19
+ * TUI-OOM-ROOTCAUSE(TUI-SESSION-VIEW.md §5 动机注 / §5.3):原环按 `\n` 计数——无换行巨 chunk
20
20
  * 净增 0 行 → 环永不触发、文本无界。本模块加**字符维**(第二维——行数维逐字不动,D-TB5):
21
21
  * `carrier._charCount` 与 `SUB_BLOCK_CHAR_LIMIT` 双维同时满足(先到先裁);字符维裁剪
22
22
  * 按被裁文本行数折算 N(无幽灵计数——N6)。
@@ -25,7 +25,7 @@ import { SUB_BLOCK_CHAR_LIMIT, MIDDLE_TRUNC_MARKER, capText } from "./display-bu
25
25
 
26
26
  export const SUB_BLOCK_LINE_LIMIT = 500
27
27
 
28
- /** 单块自身额度(TUI-OOM-ROOTCAUSE §15.1 事实 3:无换行巨 chunk —— 行数维净增 0、
28
+ /** 单块自身额度(TUI-OOM-ROOTCAUSE·TUI-SESSION-VIEW.md §5 动机注 / §5.1:无换行巨 chunk —— 行数维净增 0、
29
29
  * 环永不触发;入块文本先过本条(头 3/4 + 尾 1/4 + 中段标记)——单行内容不因字符维
30
30
  * 裁剪被整行丢光(环总量裁剪仍按最旧先行)。 */
31
31
  const SUB_BLOCK_CAP_OPTS = {
@@ -105,7 +105,7 @@ function dropCarrierLines(carrier, want) {
105
105
  return dropped
106
106
  }
107
107
 
108
- /** 字符维裁剪(§15.3.3——无换行巨 chunk 的堵口):逐行丢最旧直至释放 ≥ want 字符;
108
+ /** 字符维裁剪(§5.3——无换行巨 chunk 的堵口):逐行丢最旧直至释放 ≥ want 字符;
109
109
  * 行数按实际被丢行折算 N(无幽灵计数——N6);标记不占额度。返回释放字符数。 */
110
110
  function dropCarrierChars(carrier, want) {
111
111
  if (!(want > 0)) return 0
@@ -123,7 +123,7 @@ function dropCarrierChars(carrier, want) {
123
123
  }
124
124
 
125
125
  /** 单载体 trim(原树级 trimSubTree 收窄——D-ST4):超 500 显示行 → 最旧先行丢行;
126
- * 超 SUB_BLOCK_CHAR_LIMIT 字符 → 同款最旧先行(双维先到先裁——TUI.md §15.3.4)。 */
126
+ * 超 SUB_BLOCK_CHAR_LIMIT 字符 → 同款最旧先行(双维先到先裁——TUI-SESSION-VIEW.md §5.4)。 */
127
127
  function trimSubCarrier(carrier) {
128
128
  const over = (carrier._lineCount ?? 0) - SUB_BLOCK_LINE_LIMIT
129
129
  if (over > 0) dropCarrierLines(carrier, over)
@@ -132,7 +132,7 @@ function trimSubCarrier(carrier) {
132
132
  }
133
133
 
134
134
  /** kind 合并追加(载体级)——净增行记账(显示行口径;省略标记不参与合并——N6);
135
- * 字符账同步累加(第二维——§15.3.4)。 */
135
+ * 字符账同步累加(第二维——§5.4)。 */
136
136
  function pushBlock(carrier, kind, text, fresh) {
137
137
  if (!text) return
138
138
  const capped = capText(text, SUB_BLOCK_CAP_OPTS) // 单块自身额度(无换行巨 chunk 堵口)
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * subagent-freeze.mjs — 子agent 区块完成/冻结族(2026-09-05 module-split:
3
- * subagent-blocks.mjs 625 > 500 硬限——§19.6 面板现算 computePanelBlocks + finish/
3
+ * subagent-blocks.mjs 625 > 500 硬限——AGENT-LOOP-SUBAGENT.md §6.7.2 面板现算 computePanelBlocks + finish/
4
4
  * freeze 家族迁入;subagent-blocks.mjs import 回(routeSub* 与 compression panel
5
5
  * 调用点)+ re-export 保外部 import 面(index.mjs 等)。
6
- * CLI-ACTIVITY-DEBLOAT F-3(2026-09-10):§19.6 面板**手工镜像退役**(镜像读写
6
+ * CLI-ACTIVITY-DEBLOAT F-3(2026-09-10):AGENT-LOOP-SUBAGENT.md §6.7.2 面板**手工镜像退役**(镜像读写
7
7
  * 全删)——改读时现算 computePanelBlocks(state):四字段 key/
8
8
  * role/status/startedAt 均为 state.subTasks 活值纯推导(评审 #2 确证),读时现算
9
9
  * 与 sync 时刻镜像等价——双账本结构性漂移根治(单账本)。完成/冻结族不再逐点刷镜
@@ -12,18 +12,21 @@
12
12
 
13
13
  import { C } from "./ansi.mjs"
14
14
  import { closeOpenSubChildren } from "./subagent-children.mjs"
15
- // TUI-OOM-ROOTCAUSE(TUI.md §15.3.3 落点表末行):state.lines 总量账——splice 插入路径过账。
15
+ // #726 写点③:冻结载体行 ∥ 归档快照记录同点双动作(记录形/行集 = lifecycle-records 单一实现)
16
+ import { pushRecord } from "@thincoder/core/context.mjs"
17
+ import { subagentRecord } from "./lifecycle-records.mjs"
18
+ // TUI-OOM-ROOTCAUSE(TUI-SESSION-VIEW.md §5.3 落点表末行):state.lines 总量账——splice 插入路径过账。
16
19
  // zero-block 批(§6.8.3.2):摘除路径负向出账——releaseLine(增删均须过账)。
17
20
  import { accountLine, releaseLine } from "./display-budget.mjs"
18
21
 
19
- // ─── §19.6 D-P1 面板视图(subagent panel 检查工具)───
22
+ // ─── AGENT-LOOP-SUBAGENT.md §6.7.2 D-P1 面板视图(subagent panel 检查工具)───
20
23
  // CLI-ACTIVITY-DEBLOAT F-3:面板区块列表由消费面**读时现算**(action:"panel" 经
21
24
  // ctx.state = agent._tuiState(index.mjs startTUI 装配)取 TUI state——subTasks
22
25
  // 活值纯推导——视图与用户所见一致(同一数据源单账本))。未挂载(headless/VSC/
23
26
  // 子代理——agent._tuiState 缺省)→ 返 null = 无面板——消费面降级池视图/freeze 报
24
27
  // 不可用(T-P5 语义不变)。status 三态映射(纯函数):done+awaitingDigest →
25
28
  // "awaitingDigest";done → "done";queued(waiting/等位未启动)→ "queued";
26
- // 其余 → "running"(§20 D-SD3b queued 态入镜口径保留)。
29
+ // 其余 → "running"(AGENT-LOOP-SUBAGENT.md §6.9 D-SD3b queued 态入镜口径保留)。
27
30
 
28
31
  /** 现算面板块列表(state.subTasks 活值纯推导——无时点快照语义)。 */
29
32
  export function computePanelBlocks(state) {
@@ -31,7 +34,7 @@ export function computePanelBlocks(state) {
31
34
  if (!subs) return null // 未挂载(headless/mock 无 TUI state)——无面板
32
35
  const blocks = []
33
36
  for (const sub of Object.values(subs)) {
34
- // §20 D-SD3b:waiting/等位块(sub.queued——未启动)以 queued 态入镜(视图与面板
37
+ // AGENT-LOOP-SUBAGENT.md §6.9 D-SD3b:waiting/等位块(sub.queued——未启动)以 queued 态入镜(视图与面板
35
38
  // 所见一致——action:'panel' 视图 + freeze 门控读此态)。
36
39
  const status = sub.done ? (sub.awaitingDigest ? "awaitingDigest" : "done") : (sub.queued ? "queued" : "running")
37
40
  blocks.push({ key: sub.key, role: sub.role ?? null, status, startedAt: sub.started ?? Date.now() })
@@ -40,7 +43,7 @@ export function computePanelBlocks(state) {
40
43
  }
41
44
 
42
45
  /** §7.2.3 完成冻结——CLI-ACTIVITY-DEBLOAT F-2(2026-09-10)收窄:本函数是历史启发式
43
- * 入口("最早 started"角色匹配——§15 async 化后的 7.2.3.1 实测误冻源:async eng-coder
46
+ * 入口("最早 started"角色匹配——AGENT-LOOP-SUBAGENT.md §6.7.3 async 化后的 7.2.3.1 实测误冻源:async eng-coder
44
47
  * 先启动时 explore 完成会误冻其块)。评审 #4 路线定死:签名保留、函数体收窄为精确
45
48
  * 匹配校验——本签名无 key 参数,无从精确归属 → 恒 no-op 返 null,不再猜任何块
46
49
  * (宁可 no-op 不误冻——被否决备选"保留兜底加告警日志":冻错块是错动作,日志救不回)。
@@ -53,7 +56,7 @@ export function finishSubTask(state, roles, lastError = null) {
53
56
  }
54
57
 
55
58
  /** §7.2.3 sync spawn 完成精确冻结(2026-09-03):按 relay key 精确 settle。finishSubTask
56
- * 的"最早 started"启发式只在面板单 running 块时成立——§15 async 化后 async eng-coder
59
+ * 的"最早 started"启发式只在面板单 running 块时成立——AGENT-LOOP-SUBAGENT.md §6.7.3 async 化后 async eng-coder
57
60
  * (先启动)与 sync explore 并存时 explore 完成会误冻先启动的 eng-coder 块(7.2.3.1)。
58
61
  * dispatch 沿 ctx._subagentKey(relayPrefix 去尾 = `role#N`)把 key 传进 onToolResult——
59
62
  * 有 key 即精确标 done(冻结载体进流 + 删条目由调用方 freezeDoneSubTasks 承接——与
@@ -71,29 +74,34 @@ export function finishSubTaskKey(state, key, lastError = null) {
71
74
  return sub
72
75
  }
73
76
 
74
- /** 冻结完成/中断区块进 state.lines(§7.2 D4——_frozenSubTask 载体行,渲染端
77
+ /** 冻结完成/中断区块进 state.lines(docs/cli/design/TUI.md §6.8 D4——_frozenSubTask 载体行,渲染端
75
78
  * render-conversation 识别;折叠交互 key = sub-${key} 与运行面板同源跨冻结延续)。
76
- * §27 R23 D-R23c2 收尾语义(T-R23c.2a):外层先冻结而内层子块未收尾(外层 abort/
79
+ * docs/cli/design/TUI.md §6.8.2 R23 D-R23c2 收尾语义(T-R23c.2a):外层先冻结而内层子块未收尾(外层 abort/
77
80
  * 中断)→ 子块随外层冻结定格 stopped(closeOpenSubChildren——不悬空)。SUBAGENT-TAIL:
78
81
  * ② 语义照旧(防御性保留——无正读者、不设断言;显示契约 docs/design/TUI.md §6)。
79
82
  * 锚点插入(2026-09-03 修复轮):settled 块带 _freezeAt(settle 时刻流位置)——
80
83
  * splice 落位使挂起期补发冻结块位于其 digest 总览文本之前;无锚点尾推不变;
81
- * 多锚点批量冻结按降序(绝对位置 splice——先插小锚点会移走大锚点目标)。 */
82
- export function freezeSubTaskLines(state, sub) {
84
+ * 多锚点批量冻结按降序(绝对位置 splice——先插小锚点会移走大锚点目标)。
85
+ * **本批 2026-10-01(台账 #754 —— 用户 02:21 裁 A)**:第三参 `anchor` = 显式落位锚
86
+ * (起跑窗 = 起跑族尾——`digestTurn` 逐条传入;缺省 ⇒ `_freezeAt` settle 锚 ∥ 流末)——
87
+ * 落位语义 = 「固化块居本族文档序末元素之后」(与桌面 ∥ VSC 同形同义)。 */
88
+ export function freezeSubTaskLines(state, sub, anchor = undefined) {
83
89
  if (!sub) return
84
90
  closeOpenSubChildren(sub) // R23 D-R23c2——开子块随外层冻结定格 stopped(SUBAGENT-TAIL:② 防御性保留)
85
91
  state._frozenSubKeys ??= new Set()
86
92
  state._frozenSubKeys.add(sub.key)
87
93
  sub.done = true
88
94
  sub.doneAt = sub.doneAt ?? Date.now()
89
- const anchor = sub._freezeAt ?? state.lines.length
95
+ const at = anchor ?? sub._freezeAt ?? state.lines.length
90
96
  const line = {
91
97
  text: `subagent activity: ${sub.key}`, color: C.dim, _frozenSubTask: sub,
92
98
  }
93
- state.lines.splice(Math.min(anchor, state.lines.length), 0, line)
94
- // TUI-OOM-ROOTCAUSE(TUI.md §15.3.3 落点表末行「冻结子代理 splice 插入——经
99
+ state.lines.splice(Math.min(at, state.lines.length), 0, line)
100
+ // TUI-OOM-ROOTCAUSE(TUI-SESSION-VIEW.md §5.3 落点表末行「冻结子代理 splice 插入——经
95
101
  // syncLineBudget 同款对账」):冻结行进 state.lines 总量账(splice 插入路径)。
96
102
  accountLine(state, line)
103
+ // #726 写点③:归档快照记录(与冻结载体行插入同点);载体缺位(headless ∕ 子代理内)⇒ 核口零动作
104
+ pushRecord(state?._agent, subagentRecord(sub))
97
105
  }
98
106
 
99
107
  /** 键级墓碑(af 批 c2——§6.8.3.3 墓碑写入单点同址):key 计入 `_frozenSubKeys` ⇒ 后续 token
@@ -182,26 +190,9 @@ export function freezeDoneSubTasks(state) {
182
190
  }
183
191
  }
184
192
 
185
- /** 按角色整组标记 done——consult N 并行 children 的会话级 settle(2026-08-30;
186
- * F-2 收窄后 finishSubTask 已退役 no-op——本函数为唯一按角色完成面)。 */
187
- export function finishSubTasksByRole(state, roles, lastError = null) {
188
- state.subTasks ??= {}
189
- const roleSet = new Set(Array.isArray(roles) ? roles : [roles])
190
- for (const sub of Object.values(state.subTasks)) {
191
- if (!sub.done && roleSet.has(sub.role)) {
192
- sub.done = true
193
- sub.doneAt = Date.now()
194
- sub.currentTool = null
195
- sub.approval = null
196
- if (lastError) sub.lastError = lastError
197
- sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
198
- }
199
- }
200
- }
201
-
202
193
  /** 回合尾/挂起退出清扫(runAgentTurn finally / suspensionSession finally):冻结全部
203
194
  * 剩余块——中断(Ctrl+C/错误)不留下钉住输入框的 ghost;未 done 者
204
- * lastError="interrupted"(Ready 态跳过)。§17.5.5:挂起自然退出时本函数只兜底
195
+ * lastError="interrupted"(Ready 态跳过)。AGENT-LOOP-ASYNC-POOL.md §6.8:挂起自然退出时本函数只兜底
205
196
  * **未消化残项**(已消化块由 freezeReclaimDigestedBlocks 逐条先行回收——块回收与
206
197
  * 池空解耦)。锚点降序同 freezeDoneSubTasks(挂起期 settle 锚点交错批次各按其
207
198
  * settle 位置落位)。
@@ -227,16 +218,27 @@ export function freezeAllSubTasks(state) {
227
218
  /** 消化完成逐条冻结回收(2026-09-03 实测修订 + round1 #1 位置裁定):digest/会话内
228
219
  * 用户回合消化完 pending 条目(run 首行已注入)后调用——把"已消化但仍驻留面板"的
229
220
  * awaitingDigest 块立即冻结进流(不等池空——块回收与池空解耦;池空 freezeAllSubTasks
230
- * 仅兜底未消化残项)。归属不变式:会话内任何 run 开始前 pinned 块的条目必在 pending
231
- * (settle 即移交);run 消费后条目不在 pending 的 pinned 块 = 本 run 消化者——无需
232
- * 快照即精确归属。位置(round1 #1 裁定——与早版文本的矛盾已消解;digest 面 =
233
- * AGENT-LOOP-ASYNC-POOL.md §6.8):**settle 锚点 splice 落位——digest 总览文本之前**——同
234
- * §7.2 D4 修复轮/D-S8 锚点语义(T-S6/T-S14 位置断言同口径)——锚点降序逐块冻结
221
+ * 仅兜底未消化残项)。**归属 = `consumed` 实参**(核 `hooks.reclaim(consumed)` 直传——本 run
222
+ * 已消费条目;consult 同族收齐批 · #748 收正:原「pending 全表反查」被否——回收点判据与两端不同源)。
223
+ * **本批 2026-10-01(台账 #754)**:起跑窗(`digestTurn`)已逐条冻结起跑快照
224
+ * ⇒ 本径 = **兜底幂等**(起跑窗漏口 ∥ 迟结算面——已冻结者已出 `subTasks` ⇒ 零命中)。
225
+ * **consult 条目(#748)**:会话本体无行——按其 `childIds` 展开子块键(`consult#<N>`——子块各居一行)。
226
+ * 位置(round1 #1 裁定——与早版文本的矛盾已消解;digest 面 =
227
+ * AGENT-LOOP-ASYNC-POOL.md §6.8):**settle 锚点 splice 落位**——启动窗径居起跑族尾(显式锚)∥
228
+ * 本径 = settle 锚(迟结算块之 settle 位次在族输出区内 ⇒ 恒居本族之后);锚点降序逐块冻结
235
229
  * (splice 绝对位互不位移)。@returns {number} 回收块数 */
236
- export function freezeReclaimDigestedBlocks(state, pendingList) {
237
- const pend = pendingList ?? []
230
+ export function freezeReclaimDigestedBlocks(state, consumed) {
231
+ const keys = new Set()
232
+ for (const e of consumed ?? []) {
233
+ if (e === null || e === undefined || e.id === undefined || e.id === null) continue
234
+ if (e.role === "consult") { // #748:会话本体无行——展开子块键(childIds = 子块 relay 号)
235
+ for (const cid of e.childIds ?? []) keys.add(`consult#${cid}`)
236
+ continue
237
+ }
238
+ keys.add(`${e.role ?? "subagent"}#${e.id}`)
239
+ }
238
240
  const targets = Object.values(state.subTasks ?? {})
239
- .filter((s) => s.awaitingDigest && !pend.some((e) => `${e.role}#${e.id}` === s.key))
241
+ .filter((s) => s.awaitingDigest && keys.has(s.key))
240
242
  .sort((a, b) => (b._freezeAt ?? -1) - (a._freezeAt ?? -1)) // 锚点降序(同 freezeAllSubTasks)
241
243
  for (const sub of targets) {
242
244
  freezeSubTaskLines(state, sub) // splice 落 settle 锚点(digest 总览文本之前)
@@ -1,18 +1,18 @@
1
1
  /**
2
- * subagent-panel.mjs — 运行中子 agent 固定底部面板渲染(AGENT-LOOP.md §7.2.1 D1/D2)。
2
+ * subagent-panel.mjs — 运行中子 agent 固定底部面板渲染(docs/cli/design/TUI.md §6.8)。
3
3
  *
4
4
  * 面板位于 conversation 与 todo 之间(布局顺序 header → conversation → 面板 →
5
5
  * todo → picker → permission → queue → input → status),高度完全自适应 = 全部
6
6
  * 运行中区块的渲染行数(F2,会话区被挤小);无运行中区块 → 返回 [](F6 空态,
7
7
  * 无悬空分隔线)。子 agent 完成后立即冻结进会话流(subagent-blocks.mjs
8
- * freezeSubTaskLines,✓ 头 + 可展开,§7.2 D4 现状不变),面板下一帧自然移除
9
- * 该区块(F5)——本模块只渲染 `!done` 条目。§17 T-S14 中间态例外:挂起期已结算
8
+ * freezeSubTaskLines,✓ 头 + 可展开,现状不变),面板下一帧自然移除
9
+ * 该区块(F5)——本模块只渲染 `!done` 条目。AGENT-LOOP-ASYNC-POOL.md §6.8 T-S14 中间态例外:挂起期已结算
10
10
  * 区块(sub.done && sub.awaitingDigest)冻结被延迟,驻留面板显示
11
11
  * "done · awaiting digestion",池空补发冻结后才移除。
12
12
  *
13
- * §19.5 D-M7 ⏹:运行中(非 done)折叠头右缘停止标记(dim,仅折叠头)——点击 =
13
+ * AGENT-LOOP-SUBAGENT.md §6.7.2 D-M7 ⏹:运行中(非 done)折叠头右缘停止标记(dim,仅折叠头)——点击 =
14
14
  * cancel(mouse.mjs 列级命中 _stopCol——不触发折叠翻转)。
15
- * SUBAGENT-TAIL(supersede §27 R23 子块段方案;显示契约 docs/design/TUI.md §6):嵌套
15
+ * SUBAGENT-TAIL(supersede §6.8.2 R23 子块段方案;显示契约 docs/cli/design/TUI.md §6.8):嵌套
16
16
  * 子代理内容行并入本块 blocks(数据层——append 目标上移)——面板与冻结渲染只读
17
17
  * sub.blocks(无子块段/无子块头/无每层折叠键);折叠键只剩 `sub-${key}`(面板 ↔
18
18
  * 冻结同键——D5 无缝衔接不变)。
@@ -22,7 +22,7 @@
22
22
  * 渲染)——若本函数放 render-frame 会引入 layout↔render-frame 循环依赖。
23
23
  *
24
24
  * 区块渲染逻辑自 render-conversation.mjs buildConvLines runningSubs 段迁移
25
- * (§7.2.1 D2):折叠头 `[▶/⏸ key · model · elapsed · turn] state`(⏸ = 等待
25
+ * (docs/cli/design/TUI.md §6.8 D2):折叠头 `[▶/⏸ key · model · elapsed · turn] state`(⏸ = 等待
26
26
  * 审批态图标,sub.approval 非空时显示)+ tail 3;展开态经 fold-block.mjs 公共
27
27
  * 组件(renderBlockTimeline + renderExpandedBlock,60% 封顶 + 块内滚动)。
28
28
  * 折叠状态 key = `sub-${key}` 跨 turn 保持(D5,与冻结区块同一 key——冻结边界
@@ -43,7 +43,7 @@ export function renderSubagentPanel(state, cols, maxRows) {
43
43
  const runningSubs = Object.values(state.subTasks ?? {}).filter((s) => !s.done || s.awaitingDigest)
44
44
  if (runningSubs.length === 0) return []
45
45
  const out = []
46
- // 面板顶部边界线(现状分隔线语义迁移,§7.2.1 D2/NF2)——面板存在即画线,
46
+ // 面板顶部边界线(现状分隔线语义迁移,docs/cli/design/TUI.md §6.8 D2/NF2)——面板存在即画线,
47
47
  // 无运行区块时面板整体不渲染(F6:无悬空线)。
48
48
  out.push({ text: "─".repeat(Math.max(1, cols - 1)), color: C.dim, _skipDimFold: true })
49
49
  for (const sub of runningSubs) {
@@ -53,17 +53,17 @@ export function renderSubagentPanel(state, cols, maxRows) {
53
53
  // 与冻结头 `[✓ …]` 格式统一(任务简报 UI 决策)。
54
54
  const icon = sub.approval ? "⏸" : sub.done ? "✓" : "▶"
55
55
  const elapsed = Math.floor(((sub.done ? (sub.doneAt ?? Date.now()) : Date.now()) - sub.started) / 1000)
56
- // §19.5 D-M7b ②: sync/async 显式头标(B 形态——不靠"没标推断")——async 由
56
+ // AGENT-LOOP-SUBAGENT.md §6.7.2 D-M7b ②: sync/async 显式头标(B 形态——不靠"没标推断")——async 由
57
57
  // ⟦ev⟧async 标记置位;sync 区块(无标记)显式标 sync。真实 subagent 角色
58
58
  // (escalate/consult/compress 等复用面板槽的条目无语义——非 spawn 角色豁免);
59
- // §11.2 D-24b:role "advisor" 伪角色同面板(块/⏹/冻结复用——ruling ②-4 A)。
59
+ // AGENT-LOOP-ASYNC-POOL.md §6.10 D-24b:role "advisor" 伪角色同面板(块/⏹/冻结复用——ruling ②-4 A)。
60
60
  // 冻结后保留(与 model 标识同生命周期——render-conversation frozenSubTaskLines
61
61
  // 同款 modePart)。**颜色后置注入**(code review 🔵#4):bracket 宽度预算用纯文
62
62
  // 本(dim ANSI 内嵌会被 sliceByWidth 截断在 restore 之前 → 行尾残留 dim)——
63
63
  // 截断后对完整存活的 mode word 单独套 dim + 恢复行色(自闭合——截断落在词内
64
64
  // 则 replace 不命中 → 无 ANSI 泄漏,词以行色显示)。
65
65
  const isSubRole = SUBAGENT_ROLES.includes(sub.role) || sub.role === "advisor"
66
- // §20 D-SD3b waiting 块(sub.queued——排队 spawn 返回即建——未启动无 relay 流):
66
+ // AGENT-LOOP-SUBAGENT.md §6.9 D-SD3b waiting 块(sub.queued——排队 spawn 返回即建——未启动无 relay 流):
67
67
  // 括号状态词 = waiting(依赖/域冲突等位——detail 即原因)或 queued(槽满等位——
68
68
  // 状态区显示 position);不显示 sync/async 词(尚未启动——无 async 标记可言——
69
69
  // sync 词会误导:async spawn 排队的块不是 sync);⏹ 门控(QUEUED-VISIBILITY F-2——
@@ -109,7 +109,7 @@ export function renderSubagentPanel(state, cols, maxRows) {
109
109
  : ""
110
110
  let headText = `${bracket} ${sliceByWidth(statePart + argSummary, Math.max(0, cols - 2 - bracketWidth))}`
111
111
  const line = { color: C.tool, _foldToggle: foldKey }
112
- // §19.5 D-M7 ⏹ + D-M7b ③ 门控 + SYNC-CANCEL F3(2026-09-09):⏹ 只对 **live 可中止**
112
+ // AGENT-LOOP-SUBAGENT.md §6.7.2 D-M7 ⏹ + D-M7b ③ 门控 + SYNC-CANCEL F3(2026-09-09):⏹ 只对 **live 可中止**
113
113
  // 区块——async(running && sub.async——⟦ev⟧async 置位)∪ **sync registry live**
114
114
  // (state._agent?._syncChildAborts?.has(sub.key)——阻塞 spawn 运行期注册——成功/折叠/
115
115
  // 整回合停后 finally 注销——门控自动放下)——杜绝"可见但不可中止"误导。真实 subagent