thincoder 0.12.60 → 0.12.61
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 +24 -1
- package/README.md +8 -6
- package/bin/thincoder.mjs +27 -122
- package/package.json +3 -2
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +38 -17
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +36 -134
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +124 -329
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +3 -2
- package/src/agent/completion.mjs +1 -1
- package/src/agent/dispatch.mjs +47 -12
- package/src/agent/helpers.mjs +71 -13
- package/src/agent/record-results.mjs +13 -5
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +21 -6
- package/src/agent/setup-reminders.mjs +16 -9
- package/src/agent/setup.mjs +92 -128
- package/src/agent/spawn-child.mjs +26 -9
- package/src/agent-tools/advisor-async.mjs +70 -180
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +69 -20
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +23 -10
- package/src/agent-tools/design-token.mjs +14 -1
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +3 -3
- package/src/agent-tools/escalate-async.mjs +16 -13
- package/src/agent-tools/read-history.mjs +13 -3
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +130 -17
- package/src/agent-tools/subagent-actions.mjs +15 -6
- package/src/agent-tools/subagent-async.mjs +66 -14
- package/src/agent-tools/subagent-panel.mjs +22 -15
- package/src/agent-tools/subagent-run.mjs +9 -6
- package/src/agent-tools/subagent-scheduler.mjs +57 -8
- package/src/agent-tools/subagent-spawn.mjs +63 -16
- package/src/agent-tools/subagent.mjs +175 -49
- package/src/agent-tools/verify.mjs +13 -34
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +38 -21
- package/src/cli/distill-command.mjs +2 -2
- package/src/cli/make-agent.mjs +23 -7
- package/src/cli/memory-command.mjs +2 -2
- package/src/cli/setup-wizard.mjs +29 -9
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +132 -63
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +7 -2
- package/src/expand-home.mjs +16 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +70 -31
- package/src/memory/delete.mjs +2 -0
- package/src/memory/docs.mjs +10 -6
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/prompt-overlays.mjs +73 -16
- package/src/prompts/advisor-design.md +18 -8
- package/src/prompts/advisor-round1.md +7 -1
- package/src/prompts/advisor-round2.md +13 -2
- package/src/prompts/advisor-round3.md +13 -2
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +2 -0
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +13 -32
- package/src/provider/errors.mjs +26 -1
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-slots.mjs +7 -2
- package/src/session.mjs +59 -38
- package/src/token-ttl.mjs +2 -1
- package/src/tools/{system.mjs → bash.mjs} +6 -243
- package/src/tools/file.mjs +30 -10
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +15 -34
- package/src/tools/index.mjs +4 -2
- package/src/tools/ops.mjs +20 -7
- package/src/tools/question.md +1 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -1
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tui/agent-turn.mjs +32 -13
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +108 -37
- package/src/tui/cmd-eng.mjs +11 -27
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/index.mjs +38 -37
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +47 -10
- package/src/tui/pickers.mjs +28 -410
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +5 -19
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/subagent-blocks.mjs +69 -90
- package/src/tui/subagent-children.mjs +50 -64
- package/src/tui/subagent-freeze.mjs +40 -43
- package/src/tui/subagent-panel.mjs +21 -66
- package/src/tui/suspension-drive.mjs +28 -82
- package/src/tui/tool-args.mjs +4 -2
- package/src/tui/tool-display.mjs +4 -3
- package/src/tui/tool-events.mjs +23 -16
- package/src/tui/tui-lifecycle.mjs +18 -5
- package/src/tui/wizard.mjs +52 -18
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -13
- package/src/prompts/discipline.md +0 -84
- package/src/prompts/eng-coder.md +0 -19
- package/src/prompts/engineering-sub.md +0 -14
- package/src/prompts/engineering.md +0 -87
- package/src/prompts/explore.md +0 -12
- package/src/prompts/main.md +0 -34
- package/src/prompts/methodology-template.md +0 -38
- package/src/prompts/plan.md +0 -9
- package/src/prompts/system.md +0 -44
|
@@ -1,82 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* subagent-children.mjs —
|
|
2
|
+
* subagent-children.mjs — 嵌套子代理子块载体数据层(显示契约:docs/design/TUI.md §6
|
|
3
|
+
* 「内层活动并入外层流(SUBAGENT-TAIL)」节——2026-09-11 批)。
|
|
3
4
|
*
|
|
4
5
|
* 子块 = 外层 state.subTasks 块内的嵌套载体(外层 .children[]——每层一子块——D-R23d):
|
|
5
6
|
* carrier = { key(父内段标 role#N), role, model, started, done, doneAt, stopped,
|
|
6
7
|
* blocks, currentTool, toolArgs, approval, lastError, dropped,
|
|
7
|
-
* blockEpoch, children: [], _lineCount }
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* blockEpoch, children: [], _lineCount }
|
|
9
|
+
* SUBAGENT-TAIL(D-ST1)后内层 relay 行**不再存子块**——append 目标上移外层块 blocks
|
|
10
|
+
* (单存储单账本;子块 blocks 恒空)。子块载体降为**守护元数据**,职责 = ① done 后
|
|
11
|
+
* 迟到 chunk 丢弃(F2 语义不回退)② 外层冻结时未收尾子块不悬空(closeOpenSubChildren
|
|
12
|
+
* ——写路径保留,无正读者)③ 内层工具 fresh 判别(leaf.currentTool)。
|
|
10
13
|
*
|
|
11
|
-
* N2
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* N2 单环配额(D-ST4):全部内容行(含内层并入行)就地计入外层 500 **显示行**环——
|
|
15
|
+
* trim = 单载体最旧先行(原树级 trim「子块先丢 / done 豁免」随子块内容层退役)。
|
|
16
|
+
* 省略计数真值(N6——D-ST5):`…(已省略 N 行)` 的 N 只随内容移除增长;标记自身
|
|
17
|
+
* 不占额度、不被丢弃、不计入 N。
|
|
14
18
|
*/
|
|
15
19
|
export const SUB_BLOCK_LINE_LIMIT = 500
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let n = carrier._lineCount ?? 0
|
|
22
|
-
for (const c of carrier.children ?? []) n += carrierTreeLines(c)
|
|
23
|
-
return n
|
|
21
|
+
/** 显示行计数(N6 口径):块尾 `\n` 的空元素不计(行结束符非空行)。 */
|
|
22
|
+
const countBlockLines = (text) => {
|
|
23
|
+
const lines = String(text ?? "").split("\n")
|
|
24
|
+
return lines[lines.length - 1] === "" ? lines.length - 1 : lines.length
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/** 省略标记块判定(trim 产物——不占额度/不可被丢;N6)。 */
|
|
28
|
+
const isTrimMarker = (b) => b?.kind === "meta" && b._trimMarker === true
|
|
29
|
+
|
|
30
|
+
/** 载体级丢行(D-ST4 单环最旧先行):从头部内容块 FIFO 丢行;省略标记跳过(不占额度/
|
|
31
|
+
* 不被丢弃/不计入 N——N6)。返回实际丢弃行数(= N 增量——无幽灵行)。 */
|
|
28
32
|
function dropCarrierLines(carrier, want) {
|
|
29
|
-
if (want <= 0
|
|
33
|
+
if (want <= 0) return 0
|
|
30
34
|
let dropped = 0
|
|
31
|
-
while (want > dropped
|
|
32
|
-
const
|
|
35
|
+
while (want > dropped) {
|
|
36
|
+
const idx = carrier.blocks.findIndex((b) => !isTrimMarker(b))
|
|
37
|
+
if (idx === -1) break // 仅余省略标记(内容耗尽)——不再产生幽灵行
|
|
38
|
+
const first = carrier.blocks[idx]
|
|
33
39
|
const lines = countBlockLines(first.text)
|
|
40
|
+
if (lines <= 0) { carrier.blocks.splice(idx, 1); continue } // 空块无显示行:移除不计数
|
|
34
41
|
const take = Math.min(lines, want - dropped)
|
|
35
42
|
if (take >= lines) {
|
|
36
|
-
carrier.blocks.
|
|
43
|
+
carrier.blocks.splice(idx, 1)
|
|
37
44
|
dropped += lines
|
|
38
45
|
} else {
|
|
39
46
|
first.text = first.text.split("\n").slice(take).join("\n")
|
|
40
47
|
dropped += take
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
|
-
carrier._lineCount = (carrier._lineCount ?? 0) - dropped
|
|
50
|
+
carrier._lineCount = Math.max(0, (carrier._lineCount ?? 0) - dropped)
|
|
44
51
|
carrier.dropped = (carrier.dropped ?? 0) + dropped
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
if (dropped > 0) {
|
|
53
|
+
const marker = `…(已省略 ${carrier.dropped} 行)`
|
|
54
|
+
const head = carrier.blocks[0]
|
|
55
|
+
if (head && isTrimMarker(head)) head.text = marker // 既有标记原位更新(不重复 unshift)
|
|
56
|
+
else carrier.blocks.unshift({ kind: "meta", text: marker, _trimMarker: true }) // 标记不占额度(N6)
|
|
57
|
+
}
|
|
49
58
|
return dropped
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* §27.1 F1(2026-09-07 三缺陷修复批——缺陷③主修):丢行遍历**跳过 done 子块**——
|
|
57
|
-
* done = 定格快照——配额压力不再蚕食已定格子块行,丢行落点移到外层自身行(live
|
|
58
|
-
* 块「已省略 N 行」增长语义正确)。边界(评审 #6):live 行耗尽(全树皆 done)→
|
|
59
|
-
* 允许超限(定格快照优先于配额——N2 上限对 done 存量 best-effort)。 */
|
|
60
|
-
export function trimSubTree(root) {
|
|
61
|
-
let over = carrierTreeLines(root) - SUB_BLOCK_LINE_LIMIT
|
|
62
|
-
if (over <= 0) return
|
|
63
|
-
const walk = (carrier) => {
|
|
64
|
-
for (const c of carrier.children ?? []) {
|
|
65
|
-
if (over <= 0) return
|
|
66
|
-
if (c.done) continue // §27.1 F1: done 子块 = 定格快照——丢行遍历跳过(豁免)
|
|
67
|
-
walk(c)
|
|
68
|
-
}
|
|
69
|
-
if (over <= 0) return
|
|
70
|
-
over -= dropCarrierLines(carrier, over)
|
|
71
|
-
}
|
|
72
|
-
walk(root)
|
|
61
|
+
/** 单载体 trim(原树级 trimSubTree 收窄——D-ST4):超 500 显示行 → 最旧先行丢行。 */
|
|
62
|
+
function trimSubCarrier(carrier) {
|
|
63
|
+
const over = (carrier._lineCount ?? 0) - SUB_BLOCK_LINE_LIMIT
|
|
64
|
+
if (over > 0) dropCarrierLines(carrier, over)
|
|
73
65
|
}
|
|
74
66
|
|
|
75
|
-
/** kind
|
|
67
|
+
/** kind 合并追加(载体级)——净增行记账(显示行口径;省略标记不参与合并——N6)。 */
|
|
76
68
|
function pushBlock(carrier, kind, text, fresh) {
|
|
77
69
|
if (!text) return
|
|
78
70
|
const last = carrier.blocks.at(-1)
|
|
79
|
-
if (!fresh && last && last.kind === kind) {
|
|
71
|
+
if (!fresh && last && last.kind === kind && !isTrimMarker(last)) {
|
|
80
72
|
const before = countBlockLines(last.text)
|
|
81
73
|
last.text += text
|
|
82
74
|
carrier._lineCount = (carrier._lineCount ?? 0) + countBlockLines(last.text) - before
|
|
@@ -87,27 +79,18 @@ function pushBlock(carrier, kind, text, fresh) {
|
|
|
87
79
|
carrier.blockEpoch = (carrier.blockEpoch ?? 0) + 1
|
|
88
80
|
}
|
|
89
81
|
|
|
90
|
-
/**
|
|
91
|
-
*
|
|
82
|
+
/** 追加到块载体(外层块/压缩面板——自身即配额环):单载体 trim——无子块时与既有
|
|
83
|
+
* trimSubBlocks 同效(N2 既有测试口径不变)。SUBAGENT-TAIL:内层行经 routeSub* 也
|
|
84
|
+
* 走本函数(append 目标上移——单一配额环)。 */
|
|
92
85
|
export function appendSubBlock(sub, kind, text, { fresh = false } = {}) {
|
|
93
86
|
if (!text) return
|
|
94
87
|
pushBlock(sub, kind, text, fresh)
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** R23:追加到子块载体——行数计入根(外层)树配额(NFR——同配额不独立扩容)。
|
|
99
|
-
* §27.1 F2(2026-09-07):child.done 守卫——done 后完全定格,迟到 chunk 丢弃
|
|
100
|
-
* (与 §7.2 D4 完成态冻结语义统一——数据层 tombstone 拒绝)。 */
|
|
101
|
-
export function appendSubChild(root, child, kind, text, { fresh = false } = {}) {
|
|
102
|
-
if (!text) return
|
|
103
|
-
if (child.done) return
|
|
104
|
-
pushBlock(child, kind, text, fresh)
|
|
105
|
-
root.blockEpoch = (root.blockEpoch ?? 0) + 1
|
|
106
|
-
trimSubTree(root)
|
|
88
|
+
trimSubCarrier(sub)
|
|
107
89
|
}
|
|
108
90
|
|
|
109
91
|
/** R23 D-R23a:子块载体创建/定位——按段(role#N)在父载体 children 查找,缺失即建
|
|
110
|
-
* (每层一子块——任意 inner 深度同路径——D-R23d
|
|
92
|
+
* (每层一子块——任意 inner 深度同路径——D-R23d)。SUBAGENT-TAIL 后子块只承载守护
|
|
93
|
+
* 元数据(done/currentTool/children——内容行归外层 blocks)。@returns 子块载体 */
|
|
111
94
|
export function ensureSubChild(carrier, seg) {
|
|
112
95
|
carrier.children ??= []
|
|
113
96
|
let c = carrier.children.find((x) => x.key === seg)
|
|
@@ -142,6 +125,7 @@ export function descendSubChild(root, inner, { create = true } = {}) {
|
|
|
142
125
|
|
|
143
126
|
/** R23 D-R23c2 子块完成定格(内层 ⟦ev⟧done/stopped → 子块尾定格——不落 preview、不冻结
|
|
144
127
|
* 外层);stopped = interrupted(外层 abort 传播/内层错误收尾——T-R23a.3 不悬空)。
|
|
128
|
+
* SUBAGENT-TAIL 后 done 为 F2 正读者(迟到 chunk 丢弃守卫);currentTool 为 fresh 判别源。
|
|
145
129
|
* 外层块头 currentTool 若指向该子块(innerPath 前缀——评审 #8 全路径现状保持)→ 清空
|
|
146
130
|
* (子块已定格——工具不再是 current)。 */
|
|
147
131
|
export function closeSubChild(root, child, stopped, innerPath) {
|
|
@@ -158,7 +142,9 @@ export function closeSubChild(root, child, stopped, innerPath) {
|
|
|
158
142
|
}
|
|
159
143
|
|
|
160
144
|
/** R23 D-R23c2 收尾语义(T-R23c.2a):外层先冻结而内层未收尾(外层 abort/中断)→
|
|
161
|
-
* 内层子块随外层冻结时定格 stopped——不悬空。
|
|
145
|
+
* 内层子块随外层冻结时定格 stopped——不悬空。SUBAGENT-TAIL:② 语义照旧保留(防御性
|
|
146
|
+
* 写路径——定格后无正读者、不设断言;见 docs/design/TUI.md §6 守护字段类目 A)。
|
|
147
|
+
* freezeSubTaskLines 冻结前调用。 */
|
|
162
148
|
export function closeOpenSubChildren(sub) {
|
|
163
149
|
const walk = (carrier) => {
|
|
164
150
|
for (const c of carrier.children ?? []) {
|
|
@@ -1,51 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* subagent-freeze.mjs — 子agent 区块完成/冻结族(2026-09-05 module-split:
|
|
3
|
-
* subagent-blocks.mjs 625 > 500 硬限——§19.6
|
|
4
|
-
* freeze
|
|
5
|
-
*
|
|
3
|
+
* subagent-blocks.mjs 625 > 500 硬限——§19.6 面板现算 computePanelBlocks + finish/
|
|
4
|
+
* freeze 家族迁入;subagent-blocks.mjs import 回(routeSub* 与 compression panel
|
|
5
|
+
* 调用点)+ re-export 保外部 import 面(index.mjs 等)。
|
|
6
|
+
* CLI-ACTIVITY-DEBLOAT F-3(2026-09-10):§19.6 面板**手工镜像退役**(镜像读写
|
|
7
|
+
* 全删)——改读时现算 computePanelBlocks(state):四字段 key/
|
|
8
|
+
* role/status/startedAt 均为 state.subTasks 活值纯推导(评审 #2 确证),读时现算
|
|
9
|
+
* 与 sync 时刻镜像等价——双账本结构性漂移根治(单账本)。完成/冻结族不再逐点刷镜
|
|
10
|
+
* (无镜像可刷——调用点全清)。
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import { C } from "./ansi.mjs"
|
|
9
14
|
import { closeOpenSubChildren } from "./subagent-children.mjs"
|
|
10
15
|
|
|
11
|
-
// ─── §19.6 D-P1
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// ─── §19.6 D-P1 面板视图(subagent panel 检查工具)───
|
|
17
|
+
// CLI-ACTIVITY-DEBLOAT F-3:面板区块列表由消费面**读时现算**(action:"panel" 经
|
|
18
|
+
// ctx.state = agent._tuiState(index.mjs startTUI 装配)取 TUI state——subTasks
|
|
19
|
+
// 活值纯推导——视图与用户所见一致(同一数据源单账本))。未挂载(headless/VSC/
|
|
20
|
+
// 子代理——agent._tuiState 缺省)→ 返 null = 无面板——消费面降级池视图/freeze 报
|
|
21
|
+
// 不可用(T-P5 语义不变)。status 三态映射(纯函数):done+awaitingDigest →
|
|
22
|
+
// "awaitingDigest";done → "done";queued(waiting/等位未启动)→ "queued";
|
|
23
|
+
// 其余 → "running"(§20 D-SD3b queued 态入镜口径保留)。
|
|
18
24
|
|
|
19
|
-
/**
|
|
20
|
-
export function
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
23
|
-
const
|
|
24
|
-
for (const sub of Object.values(
|
|
25
|
-
// §20 D-SD3b:waiting/等位块(sub.queued——未启动)以 queued
|
|
25
|
+
/** 现算面板块列表(state.subTasks 活值纯推导——无时点快照语义)。 */
|
|
26
|
+
export function computePanelBlocks(state) {
|
|
27
|
+
const subs = state?.subTasks
|
|
28
|
+
if (!subs) return null // 未挂载(headless/mock 无 TUI state)——无面板
|
|
29
|
+
const blocks = []
|
|
30
|
+
for (const sub of Object.values(subs)) {
|
|
31
|
+
// §20 D-SD3b:waiting/等位块(sub.queued——未启动)以 queued 态入镜(视图与面板
|
|
26
32
|
// 所见一致——action:'panel' 视图 + freeze 门控读此态)。
|
|
27
33
|
const status = sub.done ? (sub.awaitingDigest ? "awaitingDigest" : "done") : (sub.queued ? "queued" : "running")
|
|
28
|
-
|
|
34
|
+
blocks.push({ key: sub.key, role: sub.role ?? null, status, startedAt: sub.started ?? Date.now() })
|
|
29
35
|
}
|
|
30
|
-
|
|
36
|
+
return blocks
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
/**
|
|
39
|
+
/** §7.2.3 完成冻结——CLI-ACTIVITY-DEBLOAT F-2(2026-09-10)收窄:本函数是历史启发式
|
|
40
|
+
* 入口("最早 started"角色匹配——§15 async 化后的 7.2.3.1 实测误冻源:async eng-coder
|
|
41
|
+
* 先启动时 explore 完成会误冻其块)。评审 #4 路线定死:签名保留、函数体收窄为精确
|
|
42
|
+
* 匹配校验——本签名无 key 参数,无从精确归属 → 恒 no-op 返 null,不再猜任何块
|
|
43
|
+
* (宁可 no-op 不误冻——被否决备选"保留兜底加告警日志":冻错块是错动作,日志救不回)。
|
|
44
|
+
* finishSubTaskKey 为唯一完成路径;roles/lastError 仅签名兼容保留(老调用面 import
|
|
45
|
+
* 不断——误冻面归零,无 key 窗口的块由回合尾 freezeAllSubTasks 兜底清场)。
|
|
46
|
+
* @returns {null} 恒 null(无精确 key 命中面——无从归属不归属)。 */
|
|
34
47
|
export function finishSubTask(state, roles, lastError = null) {
|
|
35
|
-
state
|
|
36
|
-
|
|
37
|
-
const running = Object.values(state.subTasks)
|
|
38
|
-
.filter((s) => !s.done && roleSet.has(s.role))
|
|
39
|
-
.sort((a, b) => a.started - b.started)
|
|
40
|
-
if (running.length === 0) return
|
|
41
|
-
const sub = running[0]
|
|
42
|
-
sub.done = true
|
|
43
|
-
sub.doneAt = Date.now()
|
|
44
|
-
sub.currentTool = null
|
|
45
|
-
sub.approval = null
|
|
46
|
-
if (lastError) sub.lastError = lastError
|
|
47
|
-
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
48
|
-
syncPanelSnapshot(state) // §19.6 D-P1: done 状态变更刷镜
|
|
48
|
+
void state; void roles; void lastError // 收窄后零副作用——参数仅签名兼容
|
|
49
|
+
return null
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/** §7.2.3 sync spawn 完成精确冻结(2026-09-03):按 relay key 精确 settle。finishSubTask
|
|
@@ -64,20 +65,20 @@ export function finishSubTaskKey(state, key, lastError = null) {
|
|
|
64
65
|
sub.approval = null
|
|
65
66
|
if (lastError) sub.lastError = lastError
|
|
66
67
|
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
67
|
-
syncPanelSnapshot(state) // §19.6 D-P1: done 状态变更刷镜
|
|
68
68
|
return sub
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/** 冻结完成/中断区块进 state.lines(§7.2 D4——_frozenSubTask 载体行,渲染端
|
|
72
72
|
* render-conversation 识别;折叠交互 key = sub-${key} 与运行面板同源跨冻结延续)。
|
|
73
73
|
* §27 R23 D-R23c2 收尾语义(T-R23c.2a):外层先冻结而内层子块未收尾(外层 abort/
|
|
74
|
-
* 中断)→ 子块随外层冻结定格 stopped(closeOpenSubChildren——不悬空)。
|
|
74
|
+
* 中断)→ 子块随外层冻结定格 stopped(closeOpenSubChildren——不悬空)。SUBAGENT-TAIL:
|
|
75
|
+
* ② 语义照旧(防御性保留——无正读者、不设断言;显示契约 docs/design/TUI.md §6)。
|
|
75
76
|
* 锚点插入(2026-09-03 修复轮):settled 块带 _freezeAt(settle 时刻流位置)——
|
|
76
77
|
* splice 落位使挂起期补发冻结块位于其 digest 总览文本之前;无锚点尾推不变;
|
|
77
78
|
* 多锚点批量冻结按降序(绝对位置 splice——先插小锚点会移走大锚点目标)。 */
|
|
78
79
|
export function freezeSubTaskLines(state, sub) {
|
|
79
80
|
if (!sub) return
|
|
80
|
-
closeOpenSubChildren(sub) // R23 D-R23c2——开子块随外层冻结定格 stopped
|
|
81
|
+
closeOpenSubChildren(sub) // R23 D-R23c2——开子块随外层冻结定格 stopped(SUBAGENT-TAIL:② 防御性保留)
|
|
81
82
|
state._frozenSubKeys ??= new Set()
|
|
82
83
|
state._frozenSubKeys.add(sub.key)
|
|
83
84
|
sub.done = true
|
|
@@ -107,11 +108,10 @@ export function freezeDoneSubTasks(state) {
|
|
|
107
108
|
freezeSubTaskLines(state, sub)
|
|
108
109
|
delete state.subTasks[sub.key]
|
|
109
110
|
}
|
|
110
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 删除后刷镜(块移出面板)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
/** 按角色整组标记 done——consult N 并行 children 的会话级 settle
|
|
114
|
-
* finishSubTask
|
|
113
|
+
/** 按角色整组标记 done——consult N 并行 children 的会话级 settle(2026-08-30;
|
|
114
|
+
* F-2 收窄后 finishSubTask 已退役 no-op——本函数为唯一按角色完成面)。 */
|
|
115
115
|
export function finishSubTasksByRole(state, roles, lastError = null) {
|
|
116
116
|
state.subTasks ??= {}
|
|
117
117
|
const roleSet = new Set(Array.isArray(roles) ? roles : [roles])
|
|
@@ -125,7 +125,6 @@ export function finishSubTasksByRole(state, roles, lastError = null) {
|
|
|
125
125
|
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
syncPanelSnapshot(state) // §19.6 D-P1: done 状态变更刷镜
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/** 回合尾/挂起退出清扫(runAgentTurn finally / suspensionSession finally):冻结全部
|
|
@@ -146,7 +145,6 @@ export function freezeAllSubTasks(state) {
|
|
|
146
145
|
freezeSubTaskLines(state, sub)
|
|
147
146
|
delete state.subTasks[sub.key]
|
|
148
147
|
}
|
|
149
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 冻结回收后刷镜(块移出面板)
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
/** §17.5.5 消化完成逐条冻结回收(2026-09-03 实测修订 + round1 #1 位置裁定):digest/会话内
|
|
@@ -167,6 +165,5 @@ export function freezeReclaimDigestedBlocks(state, pendingList) {
|
|
|
167
165
|
freezeSubTaskLines(state, sub) // splice 落 settle 锚点(digest 总览文本之前)
|
|
168
166
|
delete state.subTasks[sub.key]
|
|
169
167
|
}
|
|
170
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 逐条回收后刷镜(块移出面板)
|
|
171
168
|
return targets.length
|
|
172
169
|
}
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* §19.5 D-M7 ⏹:运行中(非 done)折叠头右缘停止标记(dim,仅折叠头)——点击 =
|
|
14
14
|
* cancel(mouse.mjs 列级命中 _stopCol——不触发折叠翻转)。
|
|
15
|
-
* §27 R23
|
|
16
|
-
*
|
|
15
|
+
* SUBAGENT-TAIL(supersede §27 R23 子块段方案;显示契约 docs/design/TUI.md §6):嵌套
|
|
16
|
+
* 子代理内容行并入本块 blocks(数据层——append 目标上移)——面板与冻结渲染只读
|
|
17
|
+
* sub.blocks(无子块段/无子块头/无每层折叠键);折叠键只剩 `sub-${key}`(面板 ↔
|
|
18
|
+
* 冻结同键——D5 无缝衔接不变)。
|
|
17
19
|
*
|
|
18
20
|
* 中立模块(D1 评审 #6):layout.mjs 调 renderSubagentPanel 预计算面板高度
|
|
19
21
|
* (subagentLines → subagentH),render-frame.mjs 直接 put 预计算行(不重复
|
|
@@ -31,61 +33,6 @@ import { sliceByWidth, stringWidth } from "./render.mjs"
|
|
|
31
33
|
import { isExpanded, renderBlockTimeline, renderExpandedBlock, foldTailLines } from "./fold-block.mjs"
|
|
32
34
|
import { SUBAGENT_ROLES } from "./subagent-blocks.mjs"
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
* §27 R23 子块(嵌套子代理)段渲染——面板与冻结渲染共用(中立纯函数模块):
|
|
36
|
-
* 每子块 = 工具式头行(`❯ explore#N · model · elapsed`——D-R23b,dim ❯ + role#N 亮 +
|
|
37
|
-
* model/elapsed dim——与 C 工具头同排式;done/stopped 定格动词——D-R23c2)+ 折叠态
|
|
38
|
-
* tail 2 / 展开态全量窗口(60% 封顶 + 块内滚动——fold-block 公共组件);孙块递归
|
|
39
|
-
* (任意 inner 深度——D-R23d——每层独立折叠键)。折叠键 = `sub-{outerKey}/{innerPath}`
|
|
40
|
-
* (D-R23e——与既有 sub-{key} 同族——_foldToggle 命中映射复用 fold-block 通用通道——
|
|
41
|
-
* mouse.mjs 零改动)。行数配额在数据层(子块计入外层 500 行环——NFR)。
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/** 子块折叠键(D-R23e):sub-{outerKey}/{innerPath}。 */
|
|
45
|
-
export function subChildFoldKey(rootKey, innerPath) {
|
|
46
|
-
return `sub-${rootKey}/${innerPath}`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** 子块头行(D-R23b/c2)——返回带 _foldToggle 的行(可点击折叠/展开——fold-block 通道)。 */
|
|
50
|
-
function subChildHeadRow(rootKey, child, innerPath, cols) {
|
|
51
|
-
const foldKey = subChildFoldKey(rootKey, innerPath)
|
|
52
|
-
const end = child.done ? (child.doneAt ?? Date.now()) : Date.now()
|
|
53
|
-
const secs = Math.max(0, Math.floor((end - (child.started ?? end)) / 1000))
|
|
54
|
-
const verb = child.done ? (child.stopped ? `stopped ${secs}s` : `done ${secs}s`) : `${secs}s`
|
|
55
|
-
const raw = `❯ ${child.key}${child.model ? ` · ${child.model}` : ""} · ${verb}`
|
|
56
|
-
let text = sliceByWidth(raw, Math.max(1, cols - 2))
|
|
57
|
-
// dim 注入(截断后——自闭合:行末由打印端 reset 兜底;灰色段间 restore 恢复行基色)。
|
|
58
|
-
const restore = ansi.fg(6) // 行基色 C.tool(cyan)
|
|
59
|
-
if (text.startsWith("❯")) text = `${ansi.gray}❯${restore}${text.slice(1)}`
|
|
60
|
-
const sep = text.indexOf(" · ")
|
|
61
|
-
if (sep > 0) text = `${text.slice(0, sep)}${ansi.gray}${text.slice(sep)}`
|
|
62
|
-
return { text, color: C.tool, _foldToggle: foldKey }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** 子块段行(head + 折叠 tail2 / 展开窗口)——递归孙块。root = 外层块(key 为折叠前缀源)。 */
|
|
66
|
-
export function renderSubChildSections(state, root, cols, maxRows) {
|
|
67
|
-
const out = []
|
|
68
|
-
const walk = (carrier, path) => {
|
|
69
|
-
for (const child of carrier.children ?? []) {
|
|
70
|
-
const innerPath = path ? `${path}/${child.key}` : child.key
|
|
71
|
-
out.push(subChildHeadRow(root.key, child, innerPath, cols))
|
|
72
|
-
const foldKey = subChildFoldKey(root.key, innerPath)
|
|
73
|
-
if (isExpanded(state, foldKey)) {
|
|
74
|
-
// 展开:全量时间线窗口化(per-kind 着色,60% 封顶 + 块内滚动——公共组件)
|
|
75
|
-
const body = renderBlockTimeline(child.blocks, cols)
|
|
76
|
-
out.push(...renderExpandedBlock({ body, foldKey, state, maxRows, cols, label: `${child.key} activity` }))
|
|
77
|
-
} else {
|
|
78
|
-
// 折叠:tail 2 非空行(最近活动)——dim(与既有外层折叠 tail 同型)
|
|
79
|
-
for (const line of foldTailLines(child.blocks, 2)) {
|
|
80
|
-
out.push({ text: `│ ${sliceByWidth(line, cols - 4)}`, color: C.dim, _skipDimFold: true })
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
walk(child, innerPath)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
walk(root, "")
|
|
87
|
-
return out
|
|
88
|
-
}
|
|
89
36
|
/**
|
|
90
37
|
* 面板行构建(纯函数):顶部分隔线 `─` + 各运行中区块(折叠头 + tail 3 /
|
|
91
38
|
* 展开全量)。maxRows = 终端行数(展开态 60% 封顶窗口化);省略 = 不封顶
|
|
@@ -109,7 +56,7 @@ export function renderSubagentPanel(state, cols, maxRows) {
|
|
|
109
56
|
// §19.5 D-M7b ②: sync/async 显式头标(B 形态——不靠"没标推断")——async 由
|
|
110
57
|
// ⟦ev⟧async 标记置位;sync 区块(无标记)显式标 sync。真实 subagent 角色
|
|
111
58
|
// (escalate/consult/compress 等复用面板槽的条目无语义——非 spawn 角色豁免);
|
|
112
|
-
// §
|
|
59
|
+
// §11.2 D-24b:role "advisor" 伪角色同面板(块/⏹/冻结复用——ruling ②-4 A)。
|
|
113
60
|
// 冻结后保留(与 model 标识同生命周期——render-conversation frozenSubTaskLines
|
|
114
61
|
// 同款 modePart)。**颜色后置注入**(code review 🔵#4):bracket 宽度预算用纯文
|
|
115
62
|
// 本(dim ANSI 内嵌会被 sliceByWidth 截断在 restore 之前 → 行尾残留 dim)——
|
|
@@ -119,7 +66,9 @@ export function renderSubagentPanel(state, cols, maxRows) {
|
|
|
119
66
|
// §20 D-SD3b waiting 块(sub.queued——排队 spawn 返回即建——未启动无 relay 流):
|
|
120
67
|
// 括号状态词 = waiting(依赖/域冲突等位——detail 即原因)或 queued(槽满等位——
|
|
121
68
|
// 状态区显示 position);不显示 sync/async 词(尚未启动——无 async 标记可言——
|
|
122
|
-
// sync 词会误导:async spawn 排队的块不是 sync);⏹
|
|
69
|
+
// sync 词会误导:async spawn 排队的块不是 sync);⏹ 门控(QUEUED-VISIBILITY F-2——
|
|
70
|
+
// 2026-09-09):queued 块头即置取消 ⏹(slot/wait/depc 三态——用户可撤销排队决策——
|
|
71
|
+
// 点击 = 出队——覆盖 SESSION-ACTIVITY-REVISED F-6“接受无取消”旧裁)。
|
|
123
72
|
const queued = sub.queued ?? null
|
|
124
73
|
const statusWord = queued ? (queued.kind === "slot" ? "queued" : "waiting") : null
|
|
125
74
|
const modeWord = isSubRole && !queued ? (sub.async === true ? "async" : "sync") : null
|
|
@@ -160,14 +109,22 @@ export function renderSubagentPanel(state, cols, maxRows) {
|
|
|
160
109
|
: ""
|
|
161
110
|
let headText = `${bracket} ${sliceByWidth(statePart + argSummary, Math.max(0, cols - 2 - bracketWidth))}`
|
|
162
111
|
const line = { color: C.tool, _foldToggle: foldKey }
|
|
163
|
-
// §19.5 D-M7 ⏹ + D-M7b ③
|
|
164
|
-
// sub.async
|
|
165
|
-
//
|
|
166
|
-
//
|
|
112
|
+
// §19.5 D-M7 ⏹ + D-M7b ③ 门控 + SYNC-CANCEL F3(2026-09-09):⏹ 只对 **live 可中止**
|
|
113
|
+
// 区块——async(running && sub.async——⟦ev⟧async 置位)∪ **sync registry live**
|
|
114
|
+
// (state._agent?._syncChildAborts?.has(sub.key)——阻塞 spawn 运行期注册——成功/折叠/
|
|
115
|
+
// 整回合停后 finally 注销——门控自动放下)——杜绝"可见但不可中止"误导。真实 subagent
|
|
116
|
+
// 角色(SUBAGENT_ROLES)+ advisor;escalate/consult/compress 无 registry 无 ⏹。
|
|
117
|
+
// **headless/测试无 _agent → sync 不钉 ⏹**(registry miss——零回归——AC6);async
|
|
118
|
+
// 区块不依赖 _agent(sub.async 置位即钉)。done/awaitingDigest 无标记(点击只对池内/
|
|
119
|
+
// registry 内 live 子代理有意义):dim 停止标记钉在折叠头右缘**内收一列**(code review
|
|
167
120
|
// 🟡#1——glyph 在 cols−1、最右列留 margin——避免终端最末列点击不可靠/全角字形
|
|
168
121
|
// 顶格被裁;命中区 = col ≥ _stopCol = cols−1——含 glyph 与其右 margin,左邻
|
|
169
122
|
// padding 空格仍走折叠)。整行 ≤ cols:内容先按 cols−3 截断。
|
|
170
|
-
|
|
123
|
+
const registryLive = state._agent?._syncChildAborts?.has(sub.key) === true
|
|
124
|
+
// F-2(QUEUED-VISIBILITY——2026-09-09):门控扩 queued——排队块(sub.queued——
|
|
125
|
+
// slot/wait/depc 三态)头置取消 ⏹(用户可撤销排队决策)——queued 恒先于 async 置位
|
|
126
|
+
// (subagent-blocks queued 分支守卫)——async/registry-live 判定不变(running 停面)。
|
|
127
|
+
if (!sub.done && (sub.async === true || registryLive || !!queued) && (SUBAGENT_ROLES.includes(sub.role) || sub.role === "advisor")) {
|
|
171
128
|
const cut = sliceByWidth(headText, Math.max(0, cols - 3))
|
|
172
129
|
headText = cut + " ".repeat(Math.max(0, cols - 3 - stringWidth(cut)))
|
|
173
130
|
line.text = `${headText} ${ansi.dim}⏹${ansi.reset} `
|
|
@@ -187,8 +144,6 @@ export function renderSubagentPanel(state, cols, maxRows) {
|
|
|
187
144
|
out.push({ text: `│ ${sliceByWidth(line, cols - 4)}`, color: C.dim })
|
|
188
145
|
}
|
|
189
146
|
}
|
|
190
|
-
// §27 R23:子块段(嵌套子代理——头 + tail2/展开窗口)随外层块渲染(面板与冻结共用)。
|
|
191
|
-
out.push(...renderSubChildSections(state, sub, cols, maxRows))
|
|
192
147
|
}
|
|
193
148
|
return out
|
|
194
149
|
}
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* re-import(runAgentTurn ↔ suspensionSession 函数级静态环——模块求值期无顶层调用,
|
|
5
5
|
* 环安全——session-slots ↔ session.mjs 同款先例)。
|
|
6
6
|
*
|
|
7
|
-
* §17(2026-09-02,AGENT-LOOP.md §
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* §17(2026-09-02,AGENT-LOOP.md §9 D-S1..S9):回合尾后台池非空 → 不阻塞等待,
|
|
8
|
+
* 进入挂起态——挂起空闲输入开放(Enter = 新回合填单槽 + 唤醒)、busy(processing 含
|
|
9
|
+
* digest)输入禁用(INPUT-LOCK-ASYNC C'——提交吞——2026-09-09)、settle 事件驱动
|
|
10
|
+
* auto-turn 消化(手动档 organize-only / AUTO 档全语义)、池空 + 无待处理输入 → 补发
|
|
11
|
+
* done 冻结自然退出。状态机行表见 AGENT-LOOP.md §9.2。
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
// 函数级静态环(2026-09-05):drive 的 digestTurn/用户回合经 runAgentTurn 递归进入
|
|
@@ -20,57 +21,13 @@ import { C } from "./ansi.mjs"
|
|
|
20
21
|
// ASYNC-RESULT-CONTAINER.md D1/D2:池 accessor(双池 absorb)+ pending 单容器停靠
|
|
21
22
|
import { getAsyncPool, parkAsyncPending } from "../agent-tools/async-settle.mjs"
|
|
22
23
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
export const MAX_MERGE_CHARS = 2000
|
|
28
|
-
|
|
29
|
-
/** 合并注入形态(编号逐条——模型一次处理全部——设计措辞锚)。 */
|
|
30
|
-
export function formatMergedMessages(items) {
|
|
31
|
-
return `你排队了 ${items.length} 条消息:\n${items.map((m, i) => `${i + 1}. ${m}`).join("\n")}\n——一次处理`
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* R15 攒批计划(纯函数——消费点共享同一事实源):按队列顺序产出动作序列。
|
|
36
|
-
* - { kind:"slash", text, count:1 }——/cmd 逐条即时执行(保序——不进合并缓冲);
|
|
37
|
-
* - { kind:"turn", text, count, merged }——文本批次:连续非 / 条目攒批(单批 ≤8 条;
|
|
38
|
-
* 合并注入(格式化后)≤2000 字符;count ≥2 → merged 编号注入;count === 1 → 原文
|
|
39
|
-
* 直发(含单条 >2000 字符——不进批);超限截批先行——余下条目留待下批(不丢)。
|
|
40
|
-
* 消费者每次取动作 [0] 并按 count 从源队列移除——下轮重新计划余项(边界幂等)。
|
|
41
|
-
*/
|
|
42
|
-
export function planQueuedInput(items) {
|
|
43
|
-
const actions = []
|
|
44
|
-
const n = items.length
|
|
45
|
-
let i = 0
|
|
46
|
-
while (i < n) {
|
|
47
|
-
const cur = String(items[i])
|
|
48
|
-
if (cur.startsWith("/")) { actions.push({ kind: "slash", text: cur, count: 1 }); i++; continue }
|
|
49
|
-
let j = i
|
|
50
|
-
for (;;) {
|
|
51
|
-
if (j - i >= MAX_MERGE_ITEMS || j >= n) break
|
|
52
|
-
const it = String(items[j])
|
|
53
|
-
if (it.startsWith("/")) break
|
|
54
|
-
if (it.length > MAX_MERGE_CHARS) break // 单条超长——不进批(留作直发)
|
|
55
|
-
const cand = items.slice(i, j + 1)
|
|
56
|
-
if (cand.length > 1 && formatMergedMessages(cand).length > MAX_MERGE_CHARS) break // 批总长超限——截批
|
|
57
|
-
j++
|
|
58
|
-
}
|
|
59
|
-
// 超长条目堵头(内环一步未进)→ 该条直发(不进批——也不与前后合并)
|
|
60
|
-
if (j === i) { actions.push({ kind: "turn", text: String(items[i]), count: 1, merged: false }); i++; continue }
|
|
61
|
-
const taken = items.slice(i, j)
|
|
62
|
-
if (taken.length === 1) {
|
|
63
|
-
actions.push({ kind: "turn", text: String(taken[0]), count: 1, merged: false })
|
|
64
|
-
} else {
|
|
65
|
-
actions.push({ kind: "turn", text: formatMergedMessages(taken), count: taken.length, merged: true })
|
|
66
|
-
}
|
|
67
|
-
i = j
|
|
68
|
-
}
|
|
69
|
-
return actions
|
|
70
|
-
}
|
|
24
|
+
// INPUT-LOCK-ASYNC(C'——2026-09-09,本档 INPUT-LOCK-ASYNC.md):R15 排队
|
|
25
|
+
// 用户指令合并(§11.3 D-24c——攒批计划/合并文案/上限常量)整批废弃
|
|
26
|
+
// ——busy 提交吞 + pendingInput 单槽化(至多一条待交接——单消息逐发不攒批)。
|
|
27
|
+
// 废弃记录见 AGENT-LOOP.md §11.3。
|
|
71
28
|
|
|
72
29
|
/** 后台池计数(LOGGING susp/digest 事件字段——pendingN/poolN)。
|
|
73
|
-
* poolN = _asyncSubagents + _asyncAdvisors(§
|
|
30
|
+
* poolN = _asyncSubagents + _asyncAdvisors(§11.2 D-24b advisor 池同面板计数——queued
|
|
74
31
|
* 条目同样在 map 内——2026-09-03 code review #2:不再 +queue.length 双计)。
|
|
75
32
|
* R17(§25 D-R17a/b):pendingN = pending 单容器 `_pendingAsyncResults` 条数(
|
|
76
33
|
* ASYNC-RESULT-CONTAINER.md D2——四族统一停靠——digest 驱动判据同单容器)。 */
|
|
@@ -117,7 +74,7 @@ export function allPendingEntries(agent) {
|
|
|
117
74
|
/** 后台池存活判据(D-S2/F5 口径):running/queued 子代理或后台评审,或已 settle 未注入结果
|
|
118
75
|
* (pending 单容器非空 = D-S3 "未注入"),或 running consult 会话(会诊跨回合——
|
|
119
76
|
* §25 D-R17a 挂起活度钩子——consult 启动回合尾即入挂起态)。回合尾与每次轮末都用它
|
|
120
|
-
* 评估退出。§
|
|
77
|
+
* 评估退出。§11.2 D-24b:_asyncAdvisors(后台评审池)与子代理池同判——评审飞行中挂起
|
|
121
78
|
* 会话必须存活。 */
|
|
122
79
|
export function poolLive(agent) {
|
|
123
80
|
const map = agent._asyncSubagents
|
|
@@ -173,7 +130,7 @@ function waitForSettleOrWake(agent, state) {
|
|
|
173
130
|
}
|
|
174
131
|
|
|
175
132
|
/** 后台模式状态行文本(D-S8;17.5.4 #6 顺手对齐):"后台 N 子代理运行中 · M 完成待消化"
|
|
176
|
-
* ——"运行中" = running + queued(含后台评审——§
|
|
133
|
+
* ——"运行中" = running + queued(含后台评审——§11.2 D-24b 同面板计数)+ running consult
|
|
177
134
|
* children(R17——会诊跨回合);"完成待消化" = pending 任一族移交项 + §17.5 回合尾留池
|
|
178
135
|
* 的 settled 未消费项(挂起会话 sweep 前的可见窗口)。 */
|
|
179
136
|
function backgroundStatusText(agent) {
|
|
@@ -217,7 +174,8 @@ async function digestTurn(ctx) {
|
|
|
217
174
|
/**
|
|
218
175
|
* §17 挂起会话驱动(D-S9 行表;由 runAgentTurn 回合尾进入,池空自然退出):
|
|
219
176
|
* - suspension:池项 settle → 入 pending → 开 auto-turn(合并消化近邻 settle);
|
|
220
|
-
*
|
|
177
|
+
* 挂起空闲用户 Enter → pendingInput 单槽(busy 含 digest 提交吞——INPUT-LOCK-ASYNC)
|
|
178
|
+
* ——用户输入优先于 digest;
|
|
221
179
|
* - auto-turn:消化中 settle 不并发开新轮(单 runAgent 循环),轮末按 pending/池态
|
|
222
180
|
* 续开合并消化轮或回挂起;pendingInput 非空 → 以该消息开新回合(不触发新 digest);
|
|
223
181
|
* - §17.5.5:每次消化/会话内用户回合消费 pending 后 → freezeReclaimDigestedBlocks
|
|
@@ -250,25 +208,14 @@ export async function suspensionSession(ctx) {
|
|
|
250
208
|
try {
|
|
251
209
|
while (!state._suspAborted && !agent._sessionAbort.signal.aborted) {
|
|
252
210
|
sweepSettledToPending(agent)
|
|
253
|
-
// 1. 用户输入优先(D-S5):pendingInput
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
// /cmd
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const srcLen = fromPending ? state.pendingInput.length : state.queue.length
|
|
260
|
-
if (srcLen > 0) {
|
|
261
|
-
const src = fromPending ? state.pendingInput : state.queue.map((q) => q.text)
|
|
262
|
-
const head = planQueuedInput(src)[0]
|
|
263
|
-
if (fromPending) state.pendingInput.splice(0, head.count)
|
|
264
|
-
else state.queue.splice(0, head.count)
|
|
265
|
-
if (head.kind === "slash") {
|
|
266
|
-
await ctx.handleSlash?.(head.text)
|
|
267
|
-
render()
|
|
268
|
-
continue
|
|
269
|
-
}
|
|
211
|
+
// 1. 用户输入优先(D-S5):pendingInput 单槽(INPUT-LOCK-ASYNC C'——busy(processing
|
|
212
|
+
// 含 digest)提交吞——Enter 只可能落在挂起空闲/释放窗口——至多一条待交接——driver
|
|
213
|
+
// 消费清槽即开新回合)。R15 攒批/queue 双源已随排队机制废弃收敛(2026-09-09——
|
|
214
|
+
// 单消息交接——无合并无 /cmd 分流——key-handler 只收非斜杠文本——单消息逐发)。
|
|
215
|
+
if ((state.pendingInput?.length ?? 0) > 0) {
|
|
216
|
+
const head = String(state.pendingInput.shift())
|
|
270
217
|
agent._suspended = false // 用户回合 = 普通回合语义(① 直注入 + settle 即冻结)
|
|
271
|
-
await runAgentTurn(ctx, head
|
|
218
|
+
await runAgentTurn(ctx, head, { skipSession: true })
|
|
272
219
|
agent._suspended = true
|
|
273
220
|
// §17.5.5:该回合消化完 pending(run 首行注入)→ 逐条冻结回收驻留块
|
|
274
221
|
// (不等池空——settle 锚点 splice——digest 总览文本之前;与 digest 回收同规则)
|
|
@@ -319,14 +266,13 @@ export async function suspensionSession(ctx) {
|
|
|
319
266
|
agent._pendingAsyncResults = []
|
|
320
267
|
const { cleanupConsultSessions } = await import("../agent-tools/consult.mjs")
|
|
321
268
|
cleanupConsultSessions(agent)
|
|
322
|
-
// §17 round2 偏差 #2-CLI(code review round2 #2-CLI
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
pushLine(`[background work stopped — ${queuedN} queued message${queuedN > 1 ? "s" : ""} will run as a normal turn]`, C.warn)
|
|
269
|
+
// §17 round2 偏差 #2-CLI(code review round2 #2-CLI)+ INPUT-LOCK 单槽化:中止时
|
|
270
|
+
// 不静默丢弃挂起期输入——Enter 已清空输入框并入 pendingInput(用户视为已发送)——
|
|
271
|
+
// 单槽语义下残余至多一条——转回 state.queue({text} 单条目,下个普通回合的队列
|
|
272
|
+
// 循环续发——零丢失)+ 提示行明示去向(不静默丢)。
|
|
273
|
+
if ((state.pendingInput?.length ?? 0) > 0) {
|
|
274
|
+
state.queue.push({ text: String(state.pendingInput.shift()) })
|
|
275
|
+
pushLine(`[background work stopped — the message you entered will run as a normal turn]`, C.warn)
|
|
330
276
|
}
|
|
331
277
|
} else {
|
|
332
278
|
// D-S3 ③ 兜底:退出前残余(极端竞态)直注入再退——结果零丢失(AC-S2;
|