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
package/src/tui/render-frame.mjs
CHANGED
|
@@ -72,13 +72,6 @@ export function renderPermission(permPreviewLines) {
|
|
|
72
72
|
return [`${ansi.bold}${C.warn}❯ Permission Request${ansi.reset}`, ...permPreviewLines.map((w) => `${C.warn}${w}${ansi.reset}`)]
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/** Queue preview (1 line when queue has items during processing). */
|
|
76
|
-
export function renderQueue(state, W) {
|
|
77
|
-
if (state.queue.length === 0 || !state.processing) return ""
|
|
78
|
-
const preview = sliceByWidth(state.queue[0].text, W - 20)
|
|
79
|
-
return `${C.dim}❯ Queue: ${state.queue.length} pending${state.queue.length > 1 ? ` (next: ${preview}…)` : ` (next: ${preview})`} — Ctrl+D delete │ Ctrl+I inject${ansi.reset}`
|
|
80
|
-
}
|
|
81
|
-
|
|
82
75
|
/** Picker/wizard overlay panel. Returns empty when no overlay. */
|
|
83
76
|
export function renderPicker(state, cols, panel, overlay) {
|
|
84
77
|
if (!panel || !overlay) return []
|
|
@@ -139,8 +132,8 @@ export function renderInputBox(state, W, boxLines, cols, inputLayout, inputOffse
|
|
|
139
132
|
? " ↑↓ select │ Enter confirm │ Esc: cancel "
|
|
140
133
|
: " Enter submit │ Esc: cancel │ Ctrl+U clear │ Ctrl+V paste ")
|
|
141
134
|
}
|
|
142
|
-
if (title === " Inject Message ") parts.push(" Enter send, Esc cancel ")
|
|
143
|
-
if (title
|
|
135
|
+
if (title === " Inject Message ") parts.push(" Enter send, Esc cancel │ ←→↑↓ Home/End move │ Ctrl+V paste ")
|
|
136
|
+
if (title === " Input " || title === " Processing... ") {
|
|
144
137
|
parts.push(" Shift+Enter / Ctrl+J newline ")
|
|
145
138
|
parts.push(" Ctrl+V paste ")
|
|
146
139
|
parts.push(" Ctrl+I inject ")
|
|
@@ -160,8 +153,8 @@ export function renderInputBox(state, W, boxLines, cols, inputLayout, inputOffse
|
|
|
160
153
|
const curLine = (!hasOverlay && inputLayout) ? inputLayout.cursorLine - (inputOffset ?? 0) : -1
|
|
161
154
|
const curCol = (!hasOverlay && inputLayout) ? inputLayout.cursorCol : -1
|
|
162
155
|
|
|
163
|
-
// Interrupt prompt
|
|
164
|
-
const isInterruptEmpty = state.interruptPrompt &&
|
|
156
|
+
// Interrupt prompt 空文本占位符:灰色提示用户该做什么(第 31 批:chars 数组判空——§8)
|
|
157
|
+
const isInterruptEmpty = state.interruptPrompt && state.interruptPrompt.chars.length === 0
|
|
165
158
|
const interruptPlaceholder = "Type message to inject (Enter send, Esc cancel)"
|
|
166
159
|
|
|
167
160
|
for (let li = 0; li < boxLines.length; li++) {
|
|
@@ -212,6 +205,15 @@ export function renderInputBox(state, W, boxLines, cols, inputLayout, inputOffse
|
|
|
212
205
|
return out
|
|
213
206
|
}
|
|
214
207
|
|
|
208
|
+
/** 第 33 批(TUI §14.3(a)——纯函数派生,无副作用):blocked = 审批/提问卡挂起(实时派生,
|
|
209
|
+
* 提示消解即消失);awaiting = 回合结束等待输入(链尾置位——processing / 挂起两态期间不派生)。
|
|
210
|
+
* @returns {"blocked"|"awaiting"|null} */
|
|
211
|
+
export function attentionKind(state) {
|
|
212
|
+
if (state.permission || state.question) return "blocked"
|
|
213
|
+
if (state.attentionAwaiting && !state.processing && !state.suspended && !state._suspPending) return "awaiting"
|
|
214
|
+
return null
|
|
215
|
+
}
|
|
216
|
+
|
|
215
217
|
/** Status bar (always 1 line). */
|
|
216
218
|
export function renderStatus(state, agent, cols, slashCommands) {
|
|
217
219
|
const statusLine = buildStatusLine(state, agent, { cols, slashCommands })
|
|
@@ -220,8 +222,20 @@ export function renderStatus(state, agent, cols, slashCommands) {
|
|
|
220
222
|
const advisorBanner = agent.config?.advisor?.guard === true ? `${C.advisor} ADVISOR${ansi.reset}${ansi.dim}│` : ""
|
|
221
223
|
const engBanner = agent.config?.agent?.engineering ? `${C.advisor} ENG${ansi.reset}${ansi.dim}│` : ""
|
|
222
224
|
const bannerPrefix = (agent.planMode ? " PLAN│ " : "") + (agent.autoApprove ? " AUTO│ " : "") + (agent.config?.advisor?.guard === true ? " ADVISOR│ " : "") + (agent.config?.agent?.engineering ? " ENG│ " : "")
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
// 第 33 批(TUI §14.3(b)/(c)):attention 态 = chip 行首 + 整行注意力色对包裹(blocked >
|
|
226
|
+
// awaiting;blocked 内 permission > question——与按键分发优先级同序);平态零注入。
|
|
227
|
+
const kind = attentionKind(state)
|
|
228
|
+
const chip = kind === "blocked" ? (state.permission ? "⚠ 等待你的审批" : "⚠ 等待你的回答")
|
|
229
|
+
: kind === "awaiting" ? "⚠ 等待你的输入" : ""
|
|
230
|
+
const attentionPad = chip ? `${chip} │ ` : ""
|
|
231
|
+
// 宽度预算(N9④):chip 占位计入——整行仍 ≤ cols − 1(既有口径)。
|
|
232
|
+
const statusMax = cols - 1 - (bannerPrefix ? stringWidth(bannerPrefix) : 0) - (attentionPad ? stringWidth(attentionPad) : 0)
|
|
233
|
+
const inner = `${ansi.dim}${planBanner}${autoBanner}${advisorBanner}${engBanner}${sliceByWidth(statusLine, Math.max(10, statusMax))}`
|
|
234
|
+
// 负向锁(§14.3):attention 为 null ⇒ 半态路径逐字节等价(不经包裹 / 零字节注入)。
|
|
235
|
+
if (!chip) return `${inner}${ansi.reset}`
|
|
236
|
+
// 底色存活(§14.3):既有内容含内部 ansi.reset(banner / ctx 警示段)——每次内部复位后
|
|
237
|
+
// 重施加注意力色对,再整体包裹(内容零省略)。
|
|
238
|
+
return `${C.attention}${(chip + " │ " + inner).replaceAll(ansi.reset, ansi.reset + C.attention)}${ansi.reset}`
|
|
225
239
|
}
|
|
226
240
|
|
|
227
241
|
// ====================================================================
|
|
@@ -266,7 +280,7 @@ export function renderRows(state, agent, opts) {
|
|
|
266
280
|
if (panels.todo) put(panels.todo.y, renderTodo(visibleTasks, cols))
|
|
267
281
|
if (panels.picker) put(panels.picker.y, renderPicker(state, cols, panels.picker, overlay))
|
|
268
282
|
if (panels.permission) put(panels.permission.y, renderPermission(permPreviewLines))
|
|
269
|
-
|
|
283
|
+
// 排队面板提示已撤(INPUT-LOCK-ASYNC F-7——2026-09-09——busy 提交吞——无排队展示)
|
|
270
284
|
// question 自由文本态:box 内容是 layoutAnswer 行——光标布局/滚动随 box 内容走
|
|
271
285
|
// (questionLayout/questionOffset);主输入 inputLayout 此时无意义(被 question 行替换)。
|
|
272
286
|
const qFree = Boolean(state.question && state.question.options.length === 0)
|
|
@@ -376,6 +390,8 @@ function buildStatusLine(state, agent, { cols, slashCommands }) {
|
|
|
376
390
|
const ctxTokensHint = state.ctxCache.tokens > 0 ? ` ${fmtK(state.ctxCache.tokens)}` : ""
|
|
377
391
|
const ctxHint = ctxPct > 0
|
|
378
392
|
? ctxPct >= 80 ? ` │ ${ansi.reset}${C.warn}context ${ctxPct}%${ctxTokensHint}${ansi.reset}${ansi.dim}` : ` │ context ${ctxPct}%${ctxTokensHint}` : ""
|
|
379
|
-
|
|
380
|
-
|
|
393
|
+
// INPUT-LOCK-ASYNC(C'——F-3):busy(processing 含 digest)状态栏提示——取代旧
|
|
394
|
+
// 排队提示(F-7 已删)——“主会话处理中——Enter 提交禁用”
|
|
395
|
+
const enterHint = state.processing ? `主会话处理中 — Enter 提交禁用(字符可输入,回合结束请重按 Enter)` : "Enter: send"
|
|
396
|
+
return ` ${statusText}${taskHint}${turnHint}${tokenHint}${ctxHint}${scrollHint} │ ${enterHint} │ /: commands │ wheel/PgUp/PgDn: scroll │ Ctrl+I: inject │ Ctrl+C: exit (×2)`
|
|
381
397
|
}
|
package/src/tui/render-loop.mjs
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { countConvLines, renderRows } from "./render-frame.mjs"
|
|
17
17
|
import { estimateTokens } from "../context.mjs"
|
|
18
18
|
import { ansi, C } from "./ansi.mjs"
|
|
19
|
+
import { isTuiActive } from "./tui-lifecycle.mjs" // F-2 渲染抑制守卫(RESIZE-MOUSE-LEAK-FIX)
|
|
19
20
|
|
|
20
21
|
const MIN_RENDER_INTERVAL_MS = 16
|
|
21
22
|
|
|
@@ -55,6 +56,7 @@ export function createRenderLoop(state, agent, ctx, pushLine, write = (s) => pro
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
function doRender() {
|
|
59
|
+
if (!isTuiActive()) return // F-2 渲染抑制(RESIZE-MOUSE-LEAK-FIX):cleanup 已清活动态——退出期不再把 TUI 帧重绘到已恢复的主屏
|
|
58
60
|
try {
|
|
59
61
|
// Single source (Windows ConPTY instability, 2026-08-30). CACHE ONLY —
|
|
60
62
|
// no per-frame refresh: during heavy streaming output ConPTY reports a
|
|
@@ -10,7 +10,6 @@ import { formatTables, sanitizeDisplay, sliceByWidth, wrapText } from "./render.
|
|
|
10
10
|
import {
|
|
11
11
|
foldTailLines, isExpanded, renderBlockTimeline, renderExpandedBlock, renderFoldedHead, renderMathAndMarkdown,
|
|
12
12
|
} from "./fold-block.mjs"
|
|
13
|
-
import { renderSubChildSections, subChildFoldKey } from "./subagent-panel.mjs"
|
|
14
13
|
import { SUBAGENT_ROLES } from "./subagent-blocks.mjs"
|
|
15
14
|
|
|
16
15
|
const _toolSegCache = new WeakMap()
|
|
@@ -71,7 +70,8 @@ export function toolSeg(state, l, i, cols, maxRows) {
|
|
|
71
70
|
/** 冻结子agent 活动块渲染(§7.2 D4):state.lines 载体 {_frozenSubTask: sub}(subagent-blocks.mjs
|
|
72
71
|
* freezeSubTaskLines 推入)。折叠 = 身份头 + tail 3;展开 = 共享组件(60% 屏幕封顶 + 底部可达
|
|
73
72
|
* 折叠控制)。折叠键 `sub-${key}`——与运行中面板区块同键,折叠态跨冻结边界无缝延续。
|
|
74
|
-
*
|
|
73
|
+
* SUBAGENT-TAIL:内层内容已并入 sub.blocks(数据层——取代小节;docs/design/TUI.md §6)
|
|
74
|
+
* ——本段只渲染单流(无子块段/无子块头)。 */
|
|
75
75
|
function frozenSubTaskLines(state, sub, cols, maxRows) {
|
|
76
76
|
const foldKey = `sub-${sub.key}`
|
|
77
77
|
const elapsed = Math.floor(((sub.doneAt ?? Date.now()) - sub.started) / 1000)
|
|
@@ -106,33 +106,19 @@ function frozenSubTaskLines(state, sub, cols, maxRows) {
|
|
|
106
106
|
out.push({ text: `│ ${sliceByWidth(line, cols - 4)}`, color: C.dim, _skipDimFold: true })
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
// §27 R23:子块段随冻结载体(折叠/展开两态都渲染——子块自身折叠键独立控制)。
|
|
110
|
-
out.push(...renderSubChildSections(state, sub, cols, maxRows))
|
|
111
109
|
return out
|
|
112
110
|
}
|
|
113
111
|
|
|
114
|
-
/** 冻结子agent 段入口(2026-08-31 段缓存:冻结后内容不变——签名含 sub.key + blocks
|
|
115
|
-
*
|
|
116
|
-
*
|
|
112
|
+
/** 冻结子agent 段入口(2026-08-31 段缓存:冻结后内容不变——签名含 sub.key + blocks 计数;
|
|
113
|
+
* 注:foldKey 由位置键升级为 sub.key(loadOlder unshift 后不重绑——sub 自身携带 key)。
|
|
114
|
+
* SUBAGENT-TAIL:子块树签名退役(子块无内容行、无独立折叠键——渲染零读 children)。 */
|
|
117
115
|
export function frozenSubSeg(state, l, i, cols, maxRows) {
|
|
118
116
|
const fKey = `sub-${l._frozenSubTask.key}`
|
|
119
|
-
// §27 R23:子块树渲染输入签名(key/done/stopped/blocks 数/折叠态/窗口偏移——递归)
|
|
120
117
|
const sub = l._frozenSubTask
|
|
121
|
-
let childSig = ""
|
|
122
|
-
const walk = (carrier, path) => {
|
|
123
|
-
for (const c of carrier.children ?? []) {
|
|
124
|
-
const innerPath = path ? `${path}/${c.key}` : c.key
|
|
125
|
-
const fk = subChildFoldKey(sub.key, innerPath)
|
|
126
|
-
childSig += `${innerPath}:${c.done ? 1 : 0}:${c.stopped ? 1 : 0}:${c.blocks?.length ?? 0}:${state.expandedBlocks?.has(fk) ? 1 : 0}:${state._foldScroll?.get(fk) ?? 0};`
|
|
127
|
-
walk(c, innerPath)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
walk(sub, "")
|
|
131
118
|
const fSig = [
|
|
132
119
|
cols, maxRows ?? 0, sub.key, sub.blocks?.length ?? 0,
|
|
133
120
|
sub.done ? 1 : 0, state.foldEnabled === false ? 0 : 1,
|
|
134
121
|
state.expandedBlocks?.has(fKey) ? 1 : 0, state._foldScroll?.get(fKey) ?? 0,
|
|
135
|
-
childSig,
|
|
136
122
|
].join("|")
|
|
137
123
|
const hit = _frozenSubCache.get(l)
|
|
138
124
|
if (hit && hit.textRef === sub && hit.sig === fSig) return hit.rows
|
package/src/tui/render.mjs
CHANGED
|
@@ -161,14 +161,19 @@ export function layoutInput(chars, cursor, width) {
|
|
|
161
161
|
const PROMPT = "\u25b8 " // first-line prefix (display width 2)
|
|
162
162
|
const CONT = " " // continuation prefix (width 2) — keeps left edge aligned
|
|
163
163
|
const lines = []
|
|
164
|
+
const starts = [] // lineStarts so far(第 31 批 additive):每个可视行的行首下标
|
|
165
|
+
let lineStart = 0 // 当前行的行首下标(chars 索引空间)
|
|
164
166
|
let cursorLine = 0
|
|
165
167
|
let cursorCol = 0
|
|
166
168
|
let cur = ""
|
|
167
169
|
let col = 0
|
|
168
170
|
let firstLine = true
|
|
169
171
|
const avail = () => width - 2 // every line reserves 2 cols for its prefix
|
|
170
|
-
|
|
172
|
+
// next = 下一行的行首下标(折行 = 当前字符 i;行尾 \n 之后 = i + 1)
|
|
173
|
+
const flush = (next) => {
|
|
171
174
|
lines.push((firstLine ? PROMPT : CONT) + cur)
|
|
175
|
+
starts.push(lineStart)
|
|
176
|
+
lineStart = next
|
|
172
177
|
firstLine = false
|
|
173
178
|
cur = ""
|
|
174
179
|
col = 0
|
|
@@ -177,7 +182,7 @@ export function layoutInput(chars, cursor, width) {
|
|
|
177
182
|
const ch = chars[i]
|
|
178
183
|
if (ch !== undefined && ch !== "\n") {
|
|
179
184
|
const w = charWidth(ch.codePointAt(0))
|
|
180
|
-
if (col + w > avail()) flush()
|
|
185
|
+
if (col + w > avail()) flush(i)
|
|
181
186
|
if (i === cursor) {
|
|
182
187
|
cursorLine = lines.length
|
|
183
188
|
cursorCol = 2 + col
|
|
@@ -189,12 +194,39 @@ export function layoutInput(chars, cursor, width) {
|
|
|
189
194
|
cursorLine = lines.length
|
|
190
195
|
cursorCol = 2 + col
|
|
191
196
|
}
|
|
192
|
-
if (ch === "\n") flush()
|
|
197
|
+
if (ch === "\n") flush(i + 1)
|
|
193
198
|
}
|
|
194
199
|
}
|
|
195
200
|
const endsWithNewline = chars.length > 0 && chars[chars.length - 1] === "\n"
|
|
196
|
-
if (cur || lines.length === 0 || endsWithNewline) flush()
|
|
197
|
-
|
|
201
|
+
if (cur || lines.length === 0 || endsWithNewline) flush(chars.length)
|
|
202
|
+
// lineStarts[k] = 第 k 行行首;行 k 区间 = [lineStarts[k], lineStarts[k+1])(含行尾 \n);
|
|
203
|
+
// 末项哨兵 = chars.length(上界、不指向行)——竖移定位用(TUI-INPUT-BOX.md §1 不变量 5)。
|
|
204
|
+
return { lines, cursorLine, cursorCol, lineStarts: [...starts, chars.length] }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** 竖直移动定位(第 31 批——TUI-INPUT-BOX.md §3 规则 2 / §9.3 #2):纯函数,
|
|
208
|
+
* `(chars, cursor, width, dir) → number | null`(null = 该方向无邻行)。
|
|
209
|
+
* 目标行显示列 = 当前光标显示列(列保持——`layoutInput` 同源行列;行前缀 2 列等宽故整体
|
|
210
|
+
* 相减);目标行更短则钳制到行尾(行尾含 \n → 落在 \n 前)。逐键现算——不记忆列(D-31.2)。
|
|
211
|
+
* 不读不写任何 state。 */
|
|
212
|
+
export function moveCursorVertical(chars, cursor, width, dir) {
|
|
213
|
+
const { cursorLine, cursorCol, lineStarts } = layoutInput(chars, cursor, width)
|
|
214
|
+
const lineCount = lineStarts.length - 1
|
|
215
|
+
const targetLine = dir === "up" ? cursorLine - 1 : cursorLine + 1
|
|
216
|
+
if (targetLine < 0 || targetLine >= lineCount) return null
|
|
217
|
+
const targetCol = Math.max(0, cursorCol - 2) // 2 列行前缀(`▸ ` / 续行空格)——各行等宽
|
|
218
|
+
const start = lineStarts[targetLine]
|
|
219
|
+
let end = lineStarts[targetLine + 1]
|
|
220
|
+
if (end > start && chars[end - 1] === "\n") end -= 1 // 行尾 \n 不占显示列
|
|
221
|
+
let col = 0
|
|
222
|
+
let idx = start
|
|
223
|
+
while (idx < end) {
|
|
224
|
+
const w = charWidth(chars[idx].codePointAt(0))
|
|
225
|
+
if (col + w > targetCol) break // 宽字符列中不落点——落在该字符起点
|
|
226
|
+
col += w
|
|
227
|
+
idx += 1
|
|
228
|
+
}
|
|
229
|
+
return idx
|
|
198
230
|
}
|
|
199
231
|
|
|
200
232
|
/**
|
|
@@ -42,7 +42,7 @@ export const SLASH_COMMANDS = [
|
|
|
42
42
|
{ name: "/eng", group: "Agent", desc: "toggle engineering mode — strict methodology enforcement" },
|
|
43
43
|
{ name: "/advisor", group: "Agent", desc: "advisor settings (model, thinking, review gate)" },
|
|
44
44
|
{ name: "/model", group: "Agent", desc: "select model & manage providers" },
|
|
45
|
-
{ name: "/submodel", group: "Agent", desc: "subagent model per type (explore/plan/coder/eng-coder)" },
|
|
45
|
+
{ name: "/submodel", group: "Agent", desc: "subagent model per type (explore/plan/coder/eng-coder/eng-designer)" },
|
|
46
46
|
{ name: "/shell", group: "System", desc: "bash tool shell (git-bash/pwsh path; win11 cmd encoding fix)" },
|
|
47
47
|
{ name: "/goal", group: "Agent", desc: "set/view/cancel long-term goal" },
|
|
48
48
|
{ name: "/think", group: "Agent", desc: "thinking mode & reasoning effort" },
|
|
@@ -144,7 +144,7 @@ export function createSlashCommands(ctx) {
|
|
|
144
144
|
const match = (cands) => cands.filter((c) => c.startsWith(last)).map((c) => `${head} ${c}`)
|
|
145
145
|
if (cmd === "/model" && argIndex === 0) return match(agent.providers.map((p) => p.name))
|
|
146
146
|
if (cmd === "/submodel") {
|
|
147
|
-
if (argIndex === 0) return match(["explore", "plan", "coder", "eng-coder", "reset"])
|
|
147
|
+
if (argIndex === 0) return match(["explore", "plan", "coder", "eng-coder", "eng-designer", "reset"])
|
|
148
148
|
if (parts[1] && !["reset"].includes(parts[1]) && argIndex === 1) return match(agent.providers.map((p) => p.name))
|
|
149
149
|
}
|
|
150
150
|
if (cmd === "/think") {
|
|
@@ -4,27 +4,34 @@
|
|
|
4
4
|
* undefined = sync), started, done, doneAt, blocks:
|
|
5
5
|
* [{kind,text}], currentTool, toolArgs, turn, maxTurns, approval, lastError,
|
|
6
6
|
* dropped, blockEpoch, awaitingDigest(§17), _freezeAt(冻结锚点), stopped(§19.5),
|
|
7
|
-
* children: [](
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* children: [](SUBAGENT-TAIL:嵌套子代理**守护载体**——内容并入本块 blocks——见
|
|
8
|
+
* subagent-children.mjs)}。
|
|
9
|
+
* 职责:前缀路由(parseRelayPath——**SUBAGENT-TAIL(D-ST1):嵌套段内容行并入外层
|
|
10
|
+
* blocks——append 目标上移,子块载体只余守护元数据**)、
|
|
11
|
+
* 事件 token 解析(**内层 ⟦ev⟧done/stopped 生成侧补发射(D-R23c1)路由到子块
|
|
12
|
+
* 定格;F2:done 后完全定格——迟到 chunk 丢弃**)、kind 合并、N2 单环 500 显示行上限
|
|
13
|
+
* (SUBAGENT-TAIL D-ST4——内层行同环计数、单载体最旧先行)、N1 渲染节流、
|
|
14
|
+
* 完成冻结(freezeSubTaskLines 家族——锚点 splice)。显示契约 = docs/design/TUI.md §6。
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { C } from "./ansi.mjs"
|
|
18
|
+
// 第 27 批 §12.3①/②:relay 前缀文法单一权威 = src/agent/relay-prefix.mjs——本文件
|
|
19
|
+
// 不再自持前缀正则/解析副本(防第二套平行正则再漂移)。
|
|
20
|
+
import { parseRelayPath } from "../agent/relay-prefix.mjs"
|
|
15
21
|
import { describeToolArgs } from "./tool-args.mjs"
|
|
16
22
|
import {
|
|
17
|
-
|
|
23
|
+
appendSubBlock, descendSubChild,
|
|
18
24
|
closeSubChild,
|
|
19
25
|
} from "./subagent-children.mjs"
|
|
20
26
|
export { SUB_BLOCK_LINE_LIMIT, appendSubBlock } from "./subagent-children.mjs"
|
|
21
|
-
// 2026-09-05 module-split:finish/freeze 族 + §19.6
|
|
27
|
+
// 2026-09-05 module-split:finish/freeze 族 + §19.6 面板现算迁 subagent-freeze.mjs——
|
|
22
28
|
// routeSub*/compression panel 内部用本地 import;re-export 保外部 import 面(index.mjs 等)
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
// CLI-ACTIVITY-DEBLOAT F-3(2026-09-10):面板手工镜像退役——re-export 面换现算导出
|
|
30
|
+
// computePanelBlocks(读时现算),本文件全部刷镜调用点删除(单账本——变更点不再手动同步)。
|
|
31
|
+
import { freezeSubTaskLines, finishSubTaskKey, finishSubTask, freezeDoneSubTasks, finishSubTasksByRole, freezeAllSubTasks, freezeReclaimDigestedBlocks, shiftFreezeAnchors } from "./subagent-freeze.mjs"
|
|
32
|
+
export { computePanelBlocks, finishSubTask, finishSubTaskKey, freezeSubTaskLines, shiftFreezeAnchors, freezeDoneSubTasks, finishSubTasksByRole, freezeAllSubTasks, freezeReclaimDigestedBlocks } from "./subagent-freeze.mjs"
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
export const SUB_PREFIX_RE = /^([\w-]+)#(\d+)\//
|
|
34
|
+
// 前缀文法/解析已迁 src/agent/relay-prefix.mjs(第 27 批 §12.3②)——本文件不自持副本。
|
|
28
35
|
/** ⟦ev⟧ token parser:`⟦ev⟧<name>\x1e<n>\x1e<max>\x1e<phase>\x1e<detail>`。phase done
|
|
29
36
|
* = async 完成即冻结(settle 时发);settled = 挂起期完成——冻结延迟至 digest 消化
|
|
30
37
|
* 完成(§17.5.5 freezeReclaimDigestedBlocks 逐条回收——不等池空)或池空退出兜底补发;
|
|
@@ -38,32 +45,14 @@ export const SUB_PREFIX_RE = /^([\w-]+)#(\d+)\//
|
|
|
38
45
|
* (块尚未创建)缓冲 `state._pendingAsyncKeys`——ensureSubTaskKey 块创建时应用(兜底)。 */
|
|
39
46
|
export const SUB_EVENT_RE = /^⟦ev⟧(turn|approval|done|settled|stopped|queued)\x1e([^\x1e]*)\x1e([^\x1e]*)\x1e([^\x1e]*)\x1e?([\s\S]*)$/
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* 子块折叠键/外层 currentTool 全路径用), rest }。
|
|
44
|
-
* 单层 = inner[]/label ""——与既有单段匹配语义零改;无前缀 → null。 */
|
|
45
|
-
export function parseRelayPath(text) {
|
|
46
|
-
const segments = []
|
|
47
|
-
let rest = String(text)
|
|
48
|
-
for (;;) {
|
|
49
|
-
const m = rest.match(SUB_PREFIX_RE)
|
|
50
|
-
if (!m) break
|
|
51
|
-
segments.push(`${m[1]}#${m[2]}`)
|
|
52
|
-
rest = rest.slice(m[0].length)
|
|
53
|
-
}
|
|
54
|
-
if (segments.length === 0) return null
|
|
55
|
-
return {
|
|
56
|
-
head: segments[0],
|
|
57
|
-
inner: segments.slice(1),
|
|
58
|
-
label: segments.slice(1).join("/"),
|
|
59
|
-
rest,
|
|
60
|
-
}
|
|
61
|
-
}
|
|
48
|
+
// parseRelayPath 自 src/agent/relay-prefix.mjs import(第 27 批 §12.3①——文法单一权威,
|
|
49
|
+
// 语义与迁移前逐字一致;“嵌套解析任意深度/无前缀 → null”见该模块)。
|
|
62
50
|
|
|
63
51
|
/** N1: render-layer throttle for child tool-output appends (generation relays verbatim). */
|
|
64
52
|
export const SUB_RELAY_THROTTLE_MS = 250
|
|
65
|
-
/** Roles a subagent tool child can take
|
|
66
|
-
|
|
53
|
+
/** Roles a subagent tool child can take(⏹ 门控/渲染角色判据;F-2 后角色匹配完成面 =
|
|
54
|
+
* finishSubTasksByRole——finishSubTask 已收窄 no-op)。 */
|
|
55
|
+
export const SUBAGENT_ROLES = ["sub", "explore", "plan", "coder", "eng-coder", "eng-designer"]
|
|
67
56
|
|
|
68
57
|
let _subRenderLast = 0
|
|
69
58
|
let _subRenderTimer = null
|
|
@@ -99,13 +88,12 @@ export function ensureSubTaskKey(state, key, role) {
|
|
|
99
88
|
blocks: [], currentTool: null, toolArgs: null, turn: 0, maxTurns: 0, approval: null,
|
|
100
89
|
lastError: null, dropped: 0,
|
|
101
90
|
stopped: false, // §19.5: ⟦ev⟧stopped 冻结标记(标题 "stopped")
|
|
102
|
-
children: [], //
|
|
91
|
+
children: [], // SUBAGENT-TAIL: 嵌套子代理守护载体(内容并入本块——subagent-children.mjs)
|
|
103
92
|
}
|
|
104
93
|
// §19.5 D-M7b ①(处置 #4 兜底——2026-09-03):缺失 key 的 ⟦ev⟧async 事件已缓冲
|
|
105
94
|
// pending 标志(routeSubToken——async 先于块创建到达:排队条目补位启动的时序窗口)
|
|
106
95
|
// ——块创建(此处)即应用——sub.async 区块创建即知(⏹ 门控/头标判定源保真)。
|
|
107
96
|
if (state._pendingAsyncKeys?.delete(key)) state.subTasks[key].async = true
|
|
108
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 块创建 → running 入镜
|
|
109
97
|
}
|
|
110
98
|
return state.subTasks[key]
|
|
111
99
|
}
|
|
@@ -114,9 +102,10 @@ export function ensureSubTask(state, subMatch) {
|
|
|
114
102
|
return ensureSubTaskKey(state, `${subMatch[1]}#${subMatch[2]}`, subMatch[1])
|
|
115
103
|
}
|
|
116
104
|
|
|
117
|
-
/** Append(kind 合并追加)+ N2
|
|
118
|
-
* R23(2026-09-07):appendSubBlock/trimSubBlocks 实现迁 subagent-children.mjs
|
|
119
|
-
*
|
|
105
|
+
/** Append(kind 合并追加)+ N2 单环 500 显示行上限;fresh=true 强制新块(每工具调用)。
|
|
106
|
+
* R23(2026-09-07):appendSubBlock/trimSubBlocks 实现迁 subagent-children.mjs——
|
|
107
|
+
* SUBAGENT-TAIL(D-ST4)后为单载体最旧先行(内层并入行同环计数);本文件 import +
|
|
108
|
+
* re-export 保 import 面(压缩面板等)。 */
|
|
120
109
|
/** 事件 token → 区块头部(turn/approval 更新 turn n/max + 等待态)。事件永不进
|
|
121
110
|
* blocks/主流——仅头部。@returns {boolean} 良构事件(已消费) */
|
|
122
111
|
export function applySubEvent(sub, payload) {
|
|
@@ -136,10 +125,12 @@ export function applySubEvent(sub, payload) {
|
|
|
136
125
|
|
|
137
126
|
// ─── Prefix routing(agent-turn callbacks 委派):true = 带前缀已消费;false = 主路径
|
|
138
127
|
|
|
139
|
-
/** onToken 分支:子文本 / [model] 元数据 / ⟦ev⟧
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
128
|
+
/** onToken 分支:子文本 / [model] 元数据 / ⟦ev⟧ 事件。SUBAGENT-TAIL(D-ST1——supersede
|
|
129
|
+
* R23 子块段方案,显示契约 docs/design/TUI.md §6):head 段路由块——inner 段**内容行
|
|
130
|
+
* 并入外层块 blocks**(append 目标上移),子块载体只余守护元数据(done 守卫——F2
|
|
131
|
+
* 迟到丢弃;currentTool——fresh 判别);内层 [model] 记子块模型;内层 ⟦ev⟧ 除
|
|
132
|
+
* done/stopped(生成侧补发射——D-R23c1——路由子块定格)外剥除不路由(防 explore
|
|
133
|
+
* 进度污染外层块头——round1 #4)。 */
|
|
143
134
|
export function routeSubToken(state, t, scheduleRender) {
|
|
144
135
|
const path = parseRelayPath(t)
|
|
145
136
|
if (!path) return false
|
|
@@ -163,7 +154,6 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
163
154
|
if (live.queued) {
|
|
164
155
|
delete live.queued
|
|
165
156
|
live.started = Date.now()
|
|
166
|
-
syncPanelSnapshot(state) // §19.6 D-P1: queued → running 状态变更刷镜
|
|
167
157
|
}
|
|
168
158
|
}
|
|
169
159
|
else {
|
|
@@ -182,7 +172,6 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
182
172
|
const live = state.subTasks?.[path.head]
|
|
183
173
|
if (live && !live.done && live.async !== true) {
|
|
184
174
|
delete state.subTasks[path.head]
|
|
185
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 移除后刷镜(块出面板)
|
|
186
175
|
}
|
|
187
176
|
scheduleRender()
|
|
188
177
|
return true
|
|
@@ -195,16 +184,17 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
195
184
|
// queued(§20 D-SD3b)= 排队 spawn 等待块(spawn 返回即建/状态变迁刷新——覆盖式
|
|
196
185
|
// 更新 sub.queued——块不可展开(无活动流);启动后 async 事件清标转 running)。
|
|
197
186
|
if (payload.startsWith("⟦ev⟧")) {
|
|
198
|
-
//
|
|
199
|
-
// 生成侧补发射(D-R23c1——sync
|
|
200
|
-
//
|
|
187
|
+
// 内层完成信号(沿革:D-M8 子标 → R23 子块段 → SUBAGENT-TAIL 并入——显示契约
|
|
188
|
+
// docs/design/TUI.md §6):done/stopped = 生成侧补发射(D-R23c1——sync 同步收尾段带
|
|
189
|
+
// 完整嵌套前缀)→ 路由到子块定格(closeSubChild——不冻结外层、不落 preview);
|
|
190
|
+
// 缺失子块(迟到事件/
|
|
201
191
|
// 从未开块)→ 不建幻影(no-op)。其余内层事件(turn/approval/queued/settled/
|
|
202
192
|
// async/cancelled)剥除不路由(防 explore 进度污染外层块头——round1 #4)。
|
|
203
193
|
if (nested) {
|
|
204
194
|
const innerEv = payload.match(SUB_EVENT_RE)
|
|
205
195
|
if (innerEv && (innerEv[1] === "done" || innerEv[1] === "stopped")) {
|
|
206
196
|
const leaf = descendSubChild(sub, path.inner, { create: false })
|
|
207
|
-
//
|
|
197
|
+
// F2 保全:done 子块完全定格——迟到 done/stopped 丢弃(重复 done 不重放
|
|
208
198
|
// 定格;stopped 不打回 done 定格块)
|
|
209
199
|
if (leaf && !leaf.done) closeSubChild(sub, leaf, innerEv[1] === "stopped", path.label)
|
|
210
200
|
scheduleRender()
|
|
@@ -224,7 +214,6 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
224
214
|
sub.currentTool = null
|
|
225
215
|
sub.approval = null
|
|
226
216
|
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
227
|
-
syncPanelSnapshot(state) // §19.6 D-P1: waiting 块建/刷新刷镜
|
|
228
217
|
scheduleRender()
|
|
229
218
|
return true
|
|
230
219
|
}
|
|
@@ -239,7 +228,6 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
239
228
|
sub.currentTool = null
|
|
240
229
|
sub.approval = null
|
|
241
230
|
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
242
|
-
syncPanelSnapshot(state) // §19.6 D-P1: settled → awaitingDigest 状态变更刷镜
|
|
243
231
|
scheduleRender()
|
|
244
232
|
return true
|
|
245
233
|
}
|
|
@@ -251,7 +239,6 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
251
239
|
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
252
240
|
freezeSubTaskLines(state, sub)
|
|
253
241
|
delete state.subTasks[sub.key]
|
|
254
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 冻结删除后刷镜
|
|
255
242
|
scheduleRender()
|
|
256
243
|
return true
|
|
257
244
|
}
|
|
@@ -264,7 +251,6 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
264
251
|
sub.blockEpoch = (sub.blockEpoch ?? 0) + 1
|
|
265
252
|
freezeSubTaskLines(state, sub)
|
|
266
253
|
delete state.subTasks[sub.key]
|
|
267
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 冻结删除后刷镜
|
|
268
254
|
scheduleRender()
|
|
269
255
|
return true
|
|
270
256
|
}
|
|
@@ -276,11 +262,12 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
276
262
|
// parent's) — shown in the block header, NOT appended to its content stream.
|
|
277
263
|
// Only treat as metadata when the model isn't set yet (it's always the FIRST token);
|
|
278
264
|
// a child content token that happens to start with "[model]" must not be swallowed.
|
|
279
|
-
//
|
|
265
|
+
// SUBAGENT-TAIL:内层 [model] 记到守护载体(类目 B 字面量——不渲染、无正读者;
|
|
266
|
+
// docs/design/TUI.md §6);单层规则不变。
|
|
280
267
|
if (payload.startsWith("[model]") && (nested || sub.model === undefined)) {
|
|
281
268
|
if (nested) {
|
|
282
269
|
const leaf = descendSubChild(sub, path.inner)
|
|
283
|
-
//
|
|
270
|
+
// F2: done 子块完全定格——迟到 [model] 不写定格块头
|
|
284
271
|
if (!leaf.done && leaf.model === undefined) {
|
|
285
272
|
leaf.model = payload.slice(7)
|
|
286
273
|
scheduleRender()
|
|
@@ -291,82 +278,76 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
291
278
|
}
|
|
292
279
|
return true
|
|
293
280
|
}
|
|
294
|
-
// Child LLM text → text block (N2 cap inside appendSubBlock).
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
appendSubChild(sub, leaf, "text", payload)
|
|
301
|
-
} else appendSubBlock(sub, "text", payload)
|
|
281
|
+
// Child LLM text → text block (N2 cap inside appendSubBlock). SUBAGENT-TAIL(D-ST1):
|
|
282
|
+
// 内层文本**并入外层 blocks**(append 目标上移——子块不再承载内容行,取代小节)。
|
|
283
|
+
// F2:done 子块完全定格——迟到 chunk 丢弃(leaf.done 守卫——免无谓 render)。
|
|
284
|
+
const leaf = nested ? descendSubChild(sub, path.inner) : null
|
|
285
|
+
if (leaf?.done) return true
|
|
286
|
+
appendSubBlock(sub, "text", payload)
|
|
302
287
|
scheduleRender()
|
|
303
288
|
return true
|
|
304
289
|
}
|
|
305
290
|
|
|
306
291
|
/** Child reasoning token → think block (F2: same treatment as main reasoning).
|
|
307
|
-
*
|
|
292
|
+
* SUBAGENT-TAIL:内层 think 并入外层 blocks(append 目标上移——显示契约 TUI.md §6)。 */
|
|
308
293
|
export function routeSubReasoning(state, t, scheduleRender) {
|
|
309
294
|
const path = parseRelayPath(t)
|
|
310
295
|
if (!path) return false
|
|
311
296
|
const sub = ensureSubTaskKey(state, path.head, path.head.slice(0, path.head.lastIndexOf("#")))
|
|
312
297
|
if (!sub) return true // frozen tombstone — drop late token
|
|
313
298
|
const nested = path.inner.length > 0
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
appendSubChild(sub, leaf, "think", path.rest)
|
|
318
|
-
} else appendSubBlock(sub, "think", path.rest)
|
|
299
|
+
const leaf = nested ? descendSubChild(sub, path.inner) : null
|
|
300
|
+
if (leaf?.done) return true // F2: done 子块完全定格——迟到 think 丢弃
|
|
301
|
+
appendSubBlock(sub, "think", path.rest)
|
|
319
302
|
scheduleRender()
|
|
320
303
|
return true
|
|
321
304
|
}
|
|
322
305
|
|
|
323
|
-
/** Child tool call → fresh tool block + header currentTool
|
|
324
|
-
*
|
|
325
|
-
* (`explore#1/read`——评审 #8:外层块头状态区现状保持不缩改)+
|
|
326
|
-
*
|
|
306
|
+
/** Child tool call → fresh tool block + header currentTool。SUBAGENT-TAIL(D-ST1):内层
|
|
307
|
+
* 工具行并入外层 blocks(同款 `❯ tool` 行——取代小节);currentTool 外层存全路径
|
|
308
|
+
* (`explore#1/read`——评审 #8:外层块头状态区现状保持不缩改)+ 子块存工具名(守护
|
|
309
|
+
* 元数据——fresh 判别源,routeSubToolOutput 读)。 */
|
|
327
310
|
export function routeSubToolCall(state, name, args, scheduleRender) {
|
|
328
311
|
const path = parseRelayPath(name)
|
|
329
312
|
if (!path) return false
|
|
330
313
|
const sub = ensureSubTaskKey(state, path.head, path.head.slice(0, path.head.lastIndexOf("#")))
|
|
331
314
|
if (!sub) return true // frozen tombstone — drop late token
|
|
332
315
|
const nested = path.inner.length > 0
|
|
333
|
-
//
|
|
316
|
+
// F2: done 子块完全定格——迟到 tool call 丢弃(不复活外层 currentTool/子块状态)
|
|
334
317
|
const leaf = nested ? descendSubChild(sub, path.inner) : null
|
|
335
318
|
if (leaf?.done) return true
|
|
336
319
|
sub.currentTool = nested ? `${path.label}/${path.rest}` : path.rest
|
|
337
320
|
sub.toolArgs = args
|
|
338
321
|
sub.approval = null
|
|
339
322
|
const argsDesc = describeToolArgs(path.rest, args)
|
|
340
|
-
if (
|
|
323
|
+
if (leaf) {
|
|
341
324
|
leaf.currentTool = path.rest
|
|
342
325
|
leaf.toolArgs = args
|
|
343
326
|
leaf.approval = null
|
|
344
|
-
appendSubChild(sub, leaf, "tool", `❯ ${path.rest}${argsDesc ? " " + argsDesc : ""}\n`, { fresh: true })
|
|
345
|
-
} else {
|
|
346
|
-
appendSubBlock(sub, "tool", `❯ ${path.rest}${argsDesc ? " " + argsDesc : ""}\n`, { fresh: true })
|
|
347
327
|
}
|
|
328
|
+
appendSubBlock(sub, "tool", `❯ ${path.rest}${argsDesc ? " " + argsDesc : ""}\n`, { fresh: true })
|
|
348
329
|
scheduleRender()
|
|
349
330
|
return true
|
|
350
331
|
}
|
|
351
332
|
|
|
352
333
|
/** Child tool output(D1 前缀 name relay)→ 追加当前 tool block。RAW 拼接(2026-09-03
|
|
353
334
|
* 修复轮——relay chunk 是任意字节边界碎片,逐 chunk 补 \n 会把词拦腰断行 + 烧 N2
|
|
354
|
-
* 配额;emit
|
|
355
|
-
*
|
|
356
|
-
*
|
|
335
|
+
* 配额;emit 端自带换行结构无损还原)。SUBAGENT-TAIL(D-ST1):内层输出并入外层
|
|
336
|
+
* blocks(append 目标上移);fresh 判别照旧在子块层(守护元数据 leaf.currentTool=
|
|
337
|
+
* 工具名)——内外/同层同名工具互不串块。 */
|
|
357
338
|
export function routeSubToolOutput(state, name, part, scheduleRender) {
|
|
358
339
|
const path = parseRelayPath(name)
|
|
359
340
|
if (!path) return false
|
|
360
341
|
const sub = ensureSubTaskKey(state, path.head, path.head.slice(0, path.head.lastIndexOf("#")))
|
|
361
342
|
if (!sub) return true // frozen tombstone — drop late token
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
343
|
+
const nested = path.inner.length > 0
|
|
344
|
+
const leaf = nested ? descendSubChild(sub, path.inner) : null
|
|
345
|
+
if (leaf?.done) return true // F2: done 子块完全定格——迟到输出丢弃(不复活 currentTool)
|
|
346
|
+
const toolName = path.rest
|
|
347
|
+
if (leaf) {
|
|
348
|
+
appendSubBlock(sub, "tool", part.text, { fresh: leaf.currentTool !== toolName })
|
|
349
|
+
if (leaf.currentTool !== toolName) leaf.currentTool = toolName
|
|
368
350
|
} else {
|
|
369
|
-
const toolName = path.rest
|
|
370
351
|
appendSubBlock(sub, "tool", part.text, { fresh: sub.currentTool !== toolName })
|
|
371
352
|
if (sub.currentTool !== toolName) sub.currentTool = toolName
|
|
372
353
|
}
|
|
@@ -410,7 +391,6 @@ export function ensureCompressPanel(state, info = {}) {
|
|
|
410
391
|
const messages = Number.isInteger(info.messages) && info.messages >= 0 ? info.messages : "?"
|
|
411
392
|
appendSubBlock(panel, "status", "Compressing context…\n", { fresh: true })
|
|
412
393
|
appendSubBlock(panel, "meta", `summarizing ${messages} messages\n`, { fresh: true })
|
|
413
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 压缩面板建/重置刷镜
|
|
414
394
|
return panel
|
|
415
395
|
}
|
|
416
396
|
|
|
@@ -427,7 +407,6 @@ export function markCompressFailed(state, error) {
|
|
|
427
407
|
function freezeCompressPanel(state, panel) {
|
|
428
408
|
freezeSubTaskLines(state, panel)
|
|
429
409
|
delete state.subTasks[panel.key]
|
|
430
|
-
syncPanelSnapshot(state) // §19.6 D-P1: 压缩面板冻结删除后刷镜
|
|
431
410
|
}
|
|
432
411
|
|
|
433
412
|
/** 完成态(onCompress):"Compressed: N tokens freed → summary (Xs)"——冻结可折叠。 */
|