thincoder 0.12.67 → 0.12.69
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.
- package/CHANGELOG.md +32 -0
- package/README.md +21 -5
- package/bin/thincoder.mjs +41 -344
- package/package.json +2 -2
- package/src/acp/bridge.mjs +5 -2
- package/src/acp/handlers-session.mjs +12 -1
- package/src/cli/make-agent.mjs +44 -119
- package/src/cli/memory-command.mjs +45 -3
- package/src/command-interactive.mjs +196 -0
- package/src/command-table.mjs +185 -0
- package/src/completions.mjs +36 -12
- package/src/crash-reports.mjs +42 -20
- package/src/distill.mjs +3 -3
- package/src/heap-watch.mjs +5 -14
- package/src/tui/agent-turn.mjs +88 -50
- package/src/tui/clipboard.mjs +74 -30
- package/src/tui/cmd-advisor.mjs +39 -13
- package/src/tui/cmd-clear.mjs +2 -2
- package/src/tui/cmd-config.mjs +17 -1
- package/src/tui/cmd-eng.mjs +1 -1
- package/src/tui/cmd-mcp.mjs +1 -1
- package/src/tui/cmd-new.mjs +2 -2
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-session.mjs +13 -5
- package/src/tui/cmd-think.mjs +35 -17
- package/src/tui/cmd-timers.mjs +33 -0
- package/src/tui/conversation-writer.mjs +2 -2
- package/src/tui/display-budget.mjs +9 -9
- package/src/tui/fold-block.mjs +1 -1
- package/src/tui/index.mjs +54 -18
- package/src/tui/interaction.mjs +4 -2
- package/src/tui/key-handler-ctrlc.mjs +1 -1
- package/src/tui/key-handler-search.mjs +1 -1
- package/src/tui/key-handler.mjs +1 -1
- package/src/tui/key-modes.mjs +1 -1
- package/src/tui/layout.mjs +5 -5
- package/src/tui/lifecycle-records.mjs +199 -0
- package/src/tui/model-picker.mjs +16 -199
- package/src/tui/mouse.mjs +3 -3
- package/src/tui/pickers.mjs +6 -2
- package/src/tui/provider-admin.mjs +213 -0
- package/src/tui/queued-merge.mjs +4 -60
- package/src/tui/queued-pickup.mjs +3 -3
- package/src/tui/render-conversation.mjs +13 -4
- package/src/tui/render-frame.mjs +43 -6
- package/src/tui/render-loop.mjs +2 -3
- package/src/tui/render-segments.mjs +2 -2
- package/src/tui/render.mjs +1 -1
- package/src/tui/slash-commands.mjs +3 -0
- package/src/tui/startup.mjs +28 -3
- package/src/tui/subagent-blocks.mjs +27 -19
- package/src/tui/subagent-children.mjs +5 -5
- package/src/tui/subagent-freeze.mjs +39 -37
- package/src/tui/subagent-panel.mjs +7 -7
- package/src/tui/suspension-drive.mjs +172 -259
- package/src/tui/timer-watch.mjs +64 -0
- package/src/tui/tool-args.mjs +10 -71
- package/src/tui/tool-display.mjs +6 -6
- package/src/tui/tool-events.mjs +33 -28
- package/src/tui/tui-lifecycle.mjs +13 -4
- package/src/tui/tui-state.mjs +3 -2
- package/src/tui/turn-face.mjs +4 -1
- package/src/tui/wizard.mjs +7 -2
- package/src/tui/wrapped-spawn.mjs +3 -3
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
import { C } from "./ansi.mjs"
|
|
14
14
|
import { closeOpenSubChildren } from "./subagent-children.mjs"
|
|
15
|
-
//
|
|
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
|
|
|
@@ -23,7 +26,7 @@ import { accountLine, releaseLine } from "./display-budget.mjs"
|
|
|
23
26
|
// 子代理——agent._tuiState 缺省)→ 返 null = 无面板——消费面降级池视图/freeze 报
|
|
24
27
|
// 不可用(T-P5 语义不变)。status 三态映射(纯函数):done+awaitingDigest →
|
|
25
28
|
// "awaitingDigest";done → "done";queued(waiting/等位未启动)→ "queued";
|
|
26
|
-
// 其余 → "running"
|
|
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
|
-
// §
|
|
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"
|
|
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
|
|
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
|
|
77
|
+
/** 冻结完成/中断区块进 state.lines(docs/cli/design/TUI.md §6.8 D4——_frozenSubTask 载体行,渲染端
|
|
75
78
|
* render-conversation 识别;折叠交互 key = sub-${key} 与运行面板同源跨冻结延续)。
|
|
76
|
-
* §
|
|
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
|
-
|
|
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
|
|
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(
|
|
94
|
-
// TUI-OOM-ROOTCAUSE(TUI.md §
|
|
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,23 +190,6 @@ 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
195
|
* lastError="interrupted"(Ready 态跳过)。AGENT-LOOP-ASYNC-POOL.md §6.8:挂起自然退出时本函数只兜底
|
|
@@ -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
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
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,
|
|
237
|
-
const
|
|
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 &&
|
|
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 固定底部面板渲染(
|
|
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,✓ 头 +
|
|
8
|
+
* freezeSubTaskLines,✓ 头 + 可展开,现状不变),面板下一帧自然移除
|
|
9
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
13
|
* AGENT-LOOP-SUBAGENT.md §6.7.2 D-M7 ⏹:运行中(非 done)折叠头右缘停止标记(dim,仅折叠头)——点击 =
|
|
14
14
|
* cancel(mouse.mjs 列级命中 _stopCol——不触发折叠翻转)。
|
|
15
|
-
* SUBAGENT-TAIL(supersede §
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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) {
|
|
@@ -56,14 +56,14 @@ export function renderSubagentPanel(state, cols, maxRows) {
|
|
|
56
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
|
-
// §
|
|
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
|
-
// §
|
|
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——
|