thincoder 0.12.62 → 0.12.64
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 +27 -0
- package/README.md +46 -49
- package/bin/thincoder.mjs +68 -34
- package/package.json +7 -6
- package/src/acp/bridge.mjs +35 -15
- package/src/acp/client-caps.mjs +86 -0
- package/src/acp/ext.mjs +86 -0
- package/src/acp/handlers-session.mjs +257 -0
- package/src/acp/handlers-slots.mjs +196 -0
- package/src/acp/login.mjs +48 -0
- package/src/acp/session.mjs +6 -4
- package/src/acp.mjs +67 -379
- package/src/cli/distill-command.mjs +3 -3
- package/src/cli/make-agent.mjs +60 -17
- package/src/cli/memory-command.mjs +3 -3
- package/src/cli/permission.mjs +4 -48
- package/src/cli/setup-wizard.mjs +1 -1
- package/src/completions.mjs +3 -1
- package/src/crash-reports.mjs +1 -1
- package/src/distill.mjs +4 -4
- package/src/heap-watch.mjs +1 -1
- package/src/prompt-injections.mjs +20 -0
- package/src/tui/agent-turn.mjs +40 -9
- package/src/tui/cmd-advisor.mjs +5 -5
- package/src/tui/cmd-config.mjs +8 -8
- package/src/tui/cmd-eng.mjs +35 -9
- package/src/tui/cmd-mcp.mjs +9 -8
- package/src/tui/cmd-model.mjs +1 -1
- package/src/tui/cmd-new.mjs +6 -5
- package/src/tui/cmd-plan.mjs +9 -0
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-restore.mjs +2 -2
- package/src/tui/cmd-session.mjs +24 -1
- package/src/tui/cmd-skills.mjs +1 -1
- package/src/tui/cmd-think.mjs +22 -9
- package/src/tui/config-helpers.mjs +1 -1
- package/src/tui/display-budget.mjs +33 -11
- package/src/tui/index.mjs +18 -10
- package/src/tui/interaction.mjs +16 -7
- package/src/tui/key-modes.mjs +9 -4
- package/src/tui/ledger-surface.mjs +22 -59
- package/src/tui/model-catalog.mjs +4 -4
- package/src/tui/model-picker.mjs +8 -7
- package/src/tui/mouse.mjs +11 -6
- package/src/tui/pickers.mjs +15 -2
- package/src/tui/render-conversation.mjs +1 -1
- package/src/tui/render-frame.mjs +15 -6
- package/src/tui/render-loop.mjs +1 -1
- package/src/tui/render-segments.mjs +3 -1
- package/src/tui/slash-commands.mjs +1 -1
- package/src/tui/startup.mjs +14 -14
- package/src/tui/subagent-blocks.mjs +20 -3
- package/src/tui/subagent-freeze.mjs +73 -2
- package/src/tui/suspension-drive.mjs +57 -23
- package/src/tui/tool-events.mjs +11 -8
- package/src/tui/tui-lifecycle.mjs +9 -0
- package/src/tui/wizard.mjs +3 -3
- package/src/tui/wrapped-spawn.mjs +6 -2
- package/src/abort-provenance.mjs +0 -116
- package/src/advisor/citations.mjs +0 -139
- package/src/advisor/compaction.mjs +0 -174
- package/src/advisor/convergence.mjs +0 -80
- package/src/advisor/history.mjs +0 -77
- package/src/advisor/loop.mjs +0 -293
- package/src/advisor/messages.mjs +0 -299
- package/src/advisor/project-context.mjs +0 -194
- package/src/advisor/repos.mjs +0 -150
- package/src/advisor/run.mjs +0 -293
- package/src/advisor/truncate.mjs +0 -57
- package/src/advisor.mjs +0 -290
- package/src/agent/completion.mjs +0 -146
- package/src/agent/dispatch.mjs +0 -489
- package/src/agent/helpers.mjs +0 -384
- package/src/agent/post-turn.mjs +0 -70
- package/src/agent/record-results.mjs +0 -174
- package/src/agent/relay-prefix.mjs +0 -39
- package/src/agent/run-stages.mjs +0 -244
- package/src/agent/setup-reminders.mjs +0 -69
- package/src/agent/setup.mjs +0 -354
- package/src/agent/spawn-child.mjs +0 -243
- package/src/agent-tools/advisor-async.mjs +0 -346
- package/src/agent-tools/advisor-settle.mjs +0 -231
- package/src/agent-tools/advisor.mjs +0 -260
- package/src/agent-tools/async-settle.mjs +0 -204
- package/src/agent-tools/batch-segment.mjs +0 -195
- package/src/agent-tools/consult.mjs +0 -473
- package/src/agent-tools/design-token.mjs +0 -117
- package/src/agent-tools/digest-budget.mjs +0 -76
- package/src/agent-tools/eng.mjs +0 -67
- package/src/agent-tools/escalate-async.mjs +0 -295
- package/src/agent-tools/goal.mjs +0 -119
- package/src/agent-tools/plan.mjs +0 -81
- package/src/agent-tools/read-history.mjs +0 -309
- package/src/agent-tools/recent-changes.mjs +0 -24
- package/src/agent-tools/review-streak.mjs +0 -93
- package/src/agent-tools/settings.mjs +0 -265
- package/src/agent-tools/skill.mjs +0 -47
- package/src/agent-tools/subagent-actions.mjs +0 -482
- package/src/agent-tools/subagent-async.mjs +0 -434
- package/src/agent-tools/subagent-panel.mjs +0 -160
- package/src/agent-tools/subagent-run.mjs +0 -205
- package/src/agent-tools/subagent-scheduler.mjs +0 -392
- package/src/agent-tools/subagent-spawn.mjs +0 -459
- package/src/agent-tools/subagent.mjs +0 -404
- package/src/agent-tools/task.mjs +0 -87
- package/src/agent-tools/timer.mjs +0 -46
- package/src/agent-tools/verify.mjs +0 -271
- package/src/agent-tools.mjs +0 -17
- package/src/agent.mjs +0 -417
- package/src/auto-think.mjs +0 -115
- package/src/config-migrate.mjs +0 -70
- package/src/config.mjs +0 -496
- package/src/context.mjs +0 -392
- package/src/conventions.mjs +0 -223
- package/src/embedding.mjs +0 -120
- package/src/escape.mjs +0 -152
- package/src/expand-home.mjs +0 -16
- package/src/explore-distill.mjs +0 -155
- package/src/generate-title.mjs +0 -88
- package/src/git/checkpoint.mjs +0 -448
- package/src/git/gitmem.mjs +0 -100
- package/src/hooks.mjs +0 -97
- package/src/ledger.mjs +0 -227
- package/src/log.mjs +0 -195
- package/src/markdown.mjs +0 -106
- package/src/mcp/helpers.mjs +0 -51
- package/src/mcp/transport-http.mjs +0 -248
- package/src/mcp/transport-stdio.mjs +0 -140
- package/src/mcp/transport-ws.mjs +0 -122
- package/src/mcp.mjs +0 -295
- package/src/memory/code-index.mjs +0 -219
- package/src/memory/code-sync.mjs +0 -415
- package/src/memory/core.mjs +0 -299
- package/src/memory/delete.mjs +0 -236
- package/src/memory/docs.mjs +0 -419
- package/src/memory/file-walk.mjs +0 -109
- package/src/memory/scan.mjs +0 -95
- package/src/memory/schema.mjs +0 -452
- package/src/memory.mjs +0 -21
- package/src/model-ref.mjs +0 -66
- package/src/model-specs.mjs +0 -179
- package/src/peer-domains.mjs +0 -265
- package/src/peer-instances.mjs +0 -231
- package/src/prompt-overlays.mjs +0 -82
- package/src/prompts/advisor-design.md +0 -41
- package/src/prompts/advisor-round1.md +0 -41
- package/src/prompts/advisor-round2.md +0 -46
- package/src/prompts/advisor-round3.md +0 -42
- package/src/prompts/common.md +0 -115
- package/src/prompts/consult-base.md +0 -19
- package/src/prompts/discipline-engineering.md +0 -258
- package/src/prompts/discipline-normal.md +0 -185
- package/src/prompts/persona-coder.md +0 -21
- package/src/prompts/persona-eng-coder.md +0 -37
- package/src/prompts/persona-eng-designer.md +0 -60
- package/src/prompts/persona-engineering.md +0 -55
- package/src/prompts/persona-explore.md +0 -15
- package/src/prompts/persona-normal.md +0 -27
- package/src/prompts/persona-plan.md +0 -26
- package/src/provider/anthropic.mjs +0 -225
- package/src/provider/core.mjs +0 -476
- package/src/provider/errors.mjs +0 -101
- package/src/provider/google.mjs +0 -257
- package/src/provider/index.mjs +0 -7
- package/src/provider/list-models.mjs +0 -93
- package/src/provider/normalize.mjs +0 -81
- package/src/provider/rate.mjs +0 -108
- package/src/provider/responses.mjs +0 -495
- package/src/provider/retry.mjs +0 -88
- package/src/provider/sse.mjs +0 -264
- package/src/proxy.mjs +0 -261
- package/src/rules.mjs +0 -53
- package/src/session-gc.mjs +0 -221
- package/src/session-guard.mjs +0 -59
- package/src/session-migrate.mjs +0 -48
- package/src/session-rename.mjs +0 -38
- package/src/session-segments.mjs +0 -100
- package/src/session-slots.mjs +0 -492
- package/src/session-store.mjs +0 -441
- package/src/session.mjs +0 -492
- package/src/skills.mjs +0 -153
- package/src/text-budget.mjs +0 -46
- package/src/token-ttl.mjs +0 -274
- package/src/tools/apply_patch.md +0 -15
- package/src/tools/bash.md +0 -37
- package/src/tools/bash.mjs +0 -268
- package/src/tools/checklist-sync.mjs +0 -181
- package/src/tools/checklist.md +0 -13
- package/src/tools/checklist.mjs +0 -299
- package/src/tools/delete.md +0 -13
- package/src/tools/edit-batch.mjs +0 -191
- package/src/tools/edit-diff.mjs +0 -348
- package/src/tools/edit.md +0 -30
- package/src/tools/execute.md +0 -21
- package/src/tools/execute.mjs +0 -228
- package/src/tools/fetch.md +0 -12
- package/src/tools/file.mjs +0 -469
- package/src/tools/file_ops.md +0 -17
- package/src/tools/get_current_time.md +0 -8
- package/src/tools/git-checkpoint.mjs +0 -143
- package/src/tools/git-ext.mjs +0 -173
- package/src/tools/git.md +0 -54
- package/src/tools/git.mjs +0 -356
- package/src/tools/glob-dialect.mjs +0 -130
- package/src/tools/glob.md +0 -11
- package/src/tools/grep.md +0 -19
- package/src/tools/hashline_edit.md +0 -14
- package/src/tools/index.mjs +0 -36
- package/src/tools/insert_after.md +0 -15
- package/src/tools/lint.md +0 -10
- package/src/tools/linter.mjs +0 -128
- package/src/tools/ls.md +0 -12
- package/src/tools/lsp.md +0 -10
- package/src/tools/lsp.mjs +0 -316
- package/src/tools/ops.mjs +0 -299
- package/src/tools/patch.mjs +0 -282
- package/src/tools/process.md +0 -10
- package/src/tools/question.md +0 -16
- package/src/tools/question.mjs +0 -26
- package/src/tools/read.md +0 -20
- package/src/tools/read_image.md +0 -8
- package/src/tools/repomap.mjs +0 -314
- package/src/tools/search.mjs +0 -236
- package/src/tools/shared.mjs +0 -446
- package/src/tools/tree.md +0 -14
- package/src/tools/tree.mjs +0 -66
- package/src/tools/wait_for.md +0 -22
- package/src/tools/web.mjs +0 -224
- package/src/tools/websearch.md +0 -16
- package/src/tools/write.md +0 -11
- package/src/traces/trace-store.mjs +0 -355
|
@@ -1,69 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ledger-surface.mjs — TUI 台账可见面胶水(
|
|
2
|
+
* ledger-surface.mjs — TUI 台账可见面胶水(K-LX3 归核:实现单源 = `@thincoder/core/ledger-surface.mjs`,
|
|
3
|
+
* 本档只剩**动态 import + TUI 色表注入**两件事——端壳静态链不得达 `node:sqlite`(W8 契约②);
|
|
4
|
+
* 载入失败 = 降级不崩(N1),扫描拍重试)。
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 常驻标记,`render-frame.mjs` 消费;**不受启动行门约束**——F2 按标记态照显)。
|
|
7
|
-
* 只读台账;唯一写面 = 去重档(`~/.thincoder/ledger-notify.json`)。
|
|
6
|
+
* 三面语义(启动行 / 变化行 / 状态位)见核档头注;判活展示(F-LX1)亦随归核自动带上——
|
|
7
|
+
* 本档零实现、零复刻(KD-M2-3)。
|
|
8
8
|
*/
|
|
9
|
-
import { blameAges, detailScans, discoverFamily, formatDetailLine, formatMarker, loadNotifyState, NOTIFY_FILE, notifyKey, planChangeLines, REFRESH_MS, saveNotifyState, summarizeLedger } from "../ledger.mjs"
|
|
10
9
|
import { C } from "./ansi.mjs"
|
|
11
10
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (const p of family.projects) {
|
|
19
|
-
try { scans.push(summarizeLedger(p, { ageOf })) } catch { /* 台账不可读 → 该项目跳过(余者照常——N1/T110①) */ }
|
|
11
|
+
/** 核台账面惰性载入(动态 import——失败不缓存,下次扫描拍重试;N1 降级不崩)。 */
|
|
12
|
+
let _core = null
|
|
13
|
+
function loadCore() {
|
|
14
|
+
if (!_core) {
|
|
15
|
+
_core = import("@thincoder/core/ledger-surface.mjs")
|
|
16
|
+
_core.catch(() => { _core = null })
|
|
20
17
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
: changeLines
|
|
29
|
-
let delivered = true
|
|
30
|
-
for (const line of rows) {
|
|
31
|
-
try { pushLine(line.text, line.warn ? C.warn : C.dim) } catch { delivered = false; break }
|
|
32
|
-
}
|
|
33
|
-
// 送达门(F5):有事件行 → 仅在送达后记账;无事件行 → 仅状态确有变化时静默同步(跌回 false 即重置)
|
|
34
|
-
const changed = plans.some(({ s, plan }) => {
|
|
35
|
-
const prev = notify.ledgers[notifyKey(s.ledger)]
|
|
36
|
-
return !prev || JSON.stringify(prev.aged ?? []) !== JSON.stringify(plan.next.aged) || Boolean(prev.threshold) !== plan.next.threshold
|
|
37
|
-
})
|
|
38
|
-
if (changeLines.length ? delivered : changed) {
|
|
39
|
-
const ledgers = { ...notify.ledgers }
|
|
40
|
-
for (const { s, plan } of plans) ledgers[notifyKey(s.ledger)] = { ...plan.next, updatedAt: Date.now() }
|
|
41
|
-
saveNotifyState(notifyFile, { version: 1, ledgers })
|
|
42
|
-
}
|
|
43
|
-
state.ledger = { marker: current ? formatMarker(current) : null, warn: Boolean(current && current.aged > 0), scannedAt: Date.now() }
|
|
44
|
-
render()
|
|
18
|
+
return _core
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 单次扫描(直驱面——直通核实现;TUI 色表 `C` 注入)。 */
|
|
22
|
+
export async function runLedgerScan(opts = {}) {
|
|
23
|
+
const core = await loadCore()
|
|
24
|
+
return core.runLedgerScan({ ...opts, colors: C })
|
|
45
25
|
}
|
|
46
26
|
|
|
47
|
-
/** TUI
|
|
27
|
+
/** TUI 挂载(首扫 + 周期——直通核实现;载入失败 → 空句柄 = 无周期,N1 降级不崩)。 */
|
|
48
28
|
export function startLedgerSurface(ctx = {}) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (disposed) return
|
|
53
|
-
if (!startup && ctx.state?.processing) return // git 子进程不打断回合帧
|
|
54
|
-
try { runLedgerScan({ ...ctx, startup }) } catch { /* 可见面尽力——不崩 TUI(N1) */ }
|
|
55
|
-
}
|
|
56
|
-
setImmediate(() => {
|
|
57
|
-
if (disposed) return
|
|
58
|
-
tick(true)
|
|
59
|
-
timer = setInterval(() => tick(false), REFRESH_MS)
|
|
60
|
-
timer.unref?.()
|
|
61
|
-
})
|
|
62
|
-
return {
|
|
63
|
-
dispose() {
|
|
64
|
-
disposed = true
|
|
65
|
-
if (timer) clearInterval(timer)
|
|
66
|
-
timer = null
|
|
67
|
-
},
|
|
68
|
-
}
|
|
29
|
+
return loadCore()
|
|
30
|
+
.then((core) => core.startLedgerSurface({ ...ctx, colors: C }))
|
|
31
|
+
.catch(() => ({ dispose() {} }))
|
|
69
32
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* model-catalog.mjs — 会话级模型清单 helper(PROVIDER.md §16.2 M2/M9——2026-09-10 MODEL-SELECTION)。
|
|
3
3
|
*
|
|
4
|
-
* 清单唯一权威 = provider 运行期拉取(`GET /models`——provider/list-models.mjs 按 format 分派)。
|
|
4
|
+
* 清单唯一权威 = provider 运行期拉取(`GET /models`——thincoder-core/provider/list-models.mjs 按 format 分派)。
|
|
5
5
|
* 本模块提供:
|
|
6
6
|
* - `getProviderModels(providerConfig)`:拉取 + **会话级缓存**(TTL 60s;失败不缓存——下次重试);
|
|
7
7
|
* - `probeChannelModels(providerConfig)`:配置阶段准入探(M9——探通入缓存候选直接可用 / 探不通返回
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
* - `modelListFailureText(error)`:M8 失败消息本体(逐字长句——界面明示的唯一断言对象);
|
|
10
10
|
* - `dedupeModels` / `modelSeries`:显示归并(自 model-picker.mjs 迁入——会话面与槽位面共用)。
|
|
11
11
|
*
|
|
12
|
-
* 缓存时钟可注入(`_catalogHooks.now`——先例 rate.mjs `_rateHooks`):测试假时钟确定性断言 T6 不依赖壁钟。
|
|
12
|
+
* 缓存时钟可注入(`_catalogHooks.now`——先例 thincoder-core/provider/rate.mjs `_rateHooks`):测试假时钟确定性断言 T6 不依赖壁钟。
|
|
13
13
|
*/
|
|
14
|
-
import { listModels } from "
|
|
14
|
+
import { listModels } from "@thincoder/core/provider/list-models.mjs"
|
|
15
15
|
|
|
16
16
|
/** 会话级缓存 TTL(§16.4 a2:连续操作不重复付网络;60s 新鲜窗口)。 */
|
|
17
17
|
const CACHE_TTL_MS = 60_000
|
|
18
18
|
|
|
19
|
-
/** 测试钩子(先例
|
|
19
|
+
/** 测试钩子(先例 thincoder-core/provider/rate.mjs `_rateHooks`):时钟可注入——T6 假时钟断言。 */
|
|
20
20
|
export const _catalogHooks = { now: () => Date.now() }
|
|
21
21
|
|
|
22
22
|
/** 会话内缓存:key = name + baseURL(同渠道不同端点各自成条);value = { models, at }。 */
|
package/src/tui/model-picker.mjs
CHANGED
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
* 渠道管理流(add/remove/key/context)是 provider 级 config 写——语义不变。F-4 (ISSUE-FIX-BATCH):
|
|
11
11
|
* removeProviderFlow 级联清理悬挂引用(cascadeRemoveProvider——文件尾导出)。
|
|
12
12
|
* ctx: { agent, state, render, ansi, C, pushLine, persistRaw, askQuestion, maskKey, showPicker,
|
|
13
|
-
* closePicker, renderPickerLines }
|
|
13
|
+
* closePicker, renderPickerLines, confirmDelete }(后四者为 pickers.mjs 通用 picker 绑定——闭包入参,环安全)。
|
|
14
14
|
*/
|
|
15
15
|
import { sliceByWidth } from "./render.mjs"
|
|
16
|
-
import { PROVIDER_PRESETS as PRESETS, providerSpec, specMatch } from "
|
|
17
|
-
import { saveSession } from "
|
|
16
|
+
import { PROVIDER_PRESETS as PRESETS, providerSpec, specMatch } from "@thincoder/core/config.mjs"
|
|
17
|
+
import { saveSession } from "@thincoder/core/session.mjs"
|
|
18
18
|
import { getProviderModels, probeChannelModels, modelListFailureText, dedupeModels } from "./model-catalog.mjs"
|
|
19
19
|
|
|
20
20
|
/** createModelPicker(ctx) → { openModelPicker, selectModel, setProviderKey, setContextFlow, pickModelForSlot } */
|
|
21
21
|
export function createModelPicker(ctx) {
|
|
22
|
-
const { agent, state, pushLine, persistRaw, askQuestion, maskKey, showPicker, closePicker, renderPickerLines } = ctx
|
|
22
|
+
const { agent, state, pushLine, persistRaw, askQuestion, maskKey, showPicker, closePicker, renderPickerLines, confirmDelete = () => { throw new Error("ctx.confirmDelete missing — deletion refused (fail-closed)") } } = ctx
|
|
23
23
|
const { ansi, C } = ctx
|
|
24
24
|
|
|
25
25
|
/** entry 唯一标识:异步更新 entries 后按它恢复选中项 */
|
|
@@ -231,7 +231,7 @@ export function createModelPicker(ctx) {
|
|
|
231
231
|
agent.provider = { ...target }
|
|
232
232
|
agent.provider.model = item.model
|
|
233
233
|
if (agent.config?.agent?.compactThresholdAuto) {
|
|
234
|
-
const { resolveCompactThreshold } = await import("
|
|
234
|
+
const { resolveCompactThreshold } = await import("@thincoder/core/config.mjs")
|
|
235
235
|
agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
|
|
236
236
|
}
|
|
237
237
|
// 写会话槽(saveSession——槽双字段恒非空)——config 文件零写
|
|
@@ -280,7 +280,7 @@ export function createModelPicker(ctx) {
|
|
|
280
280
|
/** Slot 面 fetch:模型直接并入可选行(旧 pickModelForSlot 语义——自由面;槽位面零改——
|
|
281
281
|
* 仅随 M1 获得三 format 支持 + 占位行(同会话面:0 item 的 picker 不会打开))。 */
|
|
282
282
|
async function fetchSlotModels(providerName, providerConfig, entries) {
|
|
283
|
-
const { listModels } = await import("
|
|
283
|
+
const { listModels } = await import("@thincoder/core/provider/index.mjs")
|
|
284
284
|
let selKey = null
|
|
285
285
|
try {
|
|
286
286
|
const models = await listModels(
|
|
@@ -379,6 +379,7 @@ export function createModelPicker(ctx) {
|
|
|
379
379
|
...candidates.map((p) => ({ type: "item", text: `${p.name} (${defaultModelLabel(p)})`, name: p.name })),
|
|
380
380
|
])
|
|
381
381
|
if (!se) return
|
|
382
|
+
if (!(await confirmDelete(`Remove provider ${se.name}?`))) return
|
|
382
383
|
await persistRaw((raw) => {
|
|
383
384
|
raw.providers ??= []
|
|
384
385
|
const idx = raw.providers.findIndex((p) => p?.name === se.name)
|
|
@@ -460,7 +461,7 @@ export function createModelPicker(ctx) {
|
|
|
460
461
|
if (newCtx === undefined) delete agent.provider.context
|
|
461
462
|
else agent.provider.context = newCtx
|
|
462
463
|
if (agent.config?.agent?.compactThresholdAuto) {
|
|
463
|
-
const { resolveCompactThreshold } = await import("
|
|
464
|
+
const { resolveCompactThreshold } = await import("@thincoder/core/config.mjs")
|
|
464
465
|
agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
|
|
465
466
|
}
|
|
466
467
|
}
|
package/src/tui/mouse.mjs
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
import { computeLayout, subagentLineIndex } from "./layout.mjs"
|
|
18
18
|
import { buildConvLines, convViewport } from "./render-conversation.mjs"
|
|
19
19
|
import { toggleFoldBlock, scrollFoldBlock, foldScrollOffset } from "./fold-block.mjs"
|
|
20
|
-
import { cancelAsyncSubagent, cancelSyncChild } from "
|
|
21
|
-
import { cancelAsyncAdvisor } from "
|
|
22
|
-
import { maybeRefillAsync, refreshQueuedTokens } from "
|
|
20
|
+
import { cancelAsyncSubagent, cancelSyncChild } from "@thincoder/core/agent-tools/subagent-async.mjs"
|
|
21
|
+
import { cancelAsyncAdvisor, refreshAdvisorQueuedTokens } from "@thincoder/core/agent-tools/advisor-async.mjs" // ED-4:评审队列排队块刷新
|
|
22
|
+
import { maybeRefillAsync, refreshQueuedTokens } from "@thincoder/core/agent-tools/subagent-scheduler.mjs" // F-2:queued 取消后续(补位/位置刷新)——叶子模块
|
|
23
23
|
import { routeSubToken } from "./subagent-blocks.mjs" // F-2:queued 取消块移除(⟦ev⟧cancelled 就地路由——引擎动作路径同通道)
|
|
24
24
|
import { denyModalForOwner } from "./key-modes.mjs"
|
|
25
25
|
import { C } from "./ansi.mjs"
|
|
@@ -199,12 +199,14 @@ export function createMouseDispatch({ agent, state, pushLine, render, popPicker
|
|
|
199
199
|
try {
|
|
200
200
|
const id = key.slice(key.lastIndexOf("#") + 1)
|
|
201
201
|
const isAdvisorBlock = key.startsWith("advisor#")
|
|
202
|
+
// af 批 #1(发射单源化):`emit` = 本层通道,传进核调用供其单点发射(本层不另发)。
|
|
203
|
+
const emit = (t) => { if (!routeSubToken(state, t, render)) pushLine(t, C.dim) }
|
|
202
204
|
// §11.2 D-24b (②-6b): ⏹ on an advisor block cancels the background review
|
|
203
205
|
// (directed abort → cancelled settle: no pending entry / no token).
|
|
204
206
|
// SYNC-CANCEL F3: async 池/advisor miss 后查 sync registry(⏹ 门控已放开 sync——
|
|
205
207
|
// cancelSyncChild 与 async cancel 同模块同形态——subagent-async.mjs)。
|
|
206
208
|
let r = isAdvisorBlock
|
|
207
|
-
? cancelAsyncAdvisor(agent, id)
|
|
209
|
+
? cancelAsyncAdvisor(agent, id, emit)
|
|
208
210
|
: cancelAsyncSubagent(agent, id)
|
|
209
211
|
let syncStopped = false
|
|
210
212
|
if (!isAdvisorBlock && r?.status === "error") {
|
|
@@ -230,11 +232,14 @@ export function createMouseDispatch({ agent, state, pushLine, render, popPicker
|
|
|
230
232
|
// 无 settle 事件链)——UI 直连路径无回合 ctx 事件流——补 executeCancelAction
|
|
231
233
|
// 同款 TUI 维护:等待块移除(⟦ev⟧cancelled 就地路由——守卫同 routeSubToken
|
|
232
234
|
// cancelled 分支——不冻结)+ 补位 + 剩余排队头位置/依赖标注刷新(⟦ev⟧queued)。
|
|
235
|
+
// af 批去重(#1 单源化):共享行加 `!isAdvisorBlock` 守卫——评审族发射已由核单点经
|
|
236
|
+
// 上方 `emit` 通道完成;子代理族自持发射面零改(该族两路互斥、各发一次)。
|
|
233
237
|
if (r?.was === "queued") {
|
|
234
|
-
|
|
235
|
-
emit(`${key}/⟦ev⟧cancelled\x1e`)
|
|
238
|
+
if (!isAdvisorBlock) emit(`${key}/⟦ev⟧cancelled\x1e`)
|
|
236
239
|
maybeRefillAsync(agent)
|
|
237
240
|
refreshQueuedTokens(agent, emit)
|
|
241
|
+
// ED-4(评审池排队语义):advisor 队列余位位置刷新(subagent 队列同构面)。
|
|
242
|
+
if (isAdvisorBlock) refreshAdvisorQueuedTokens(agent, emit)
|
|
238
243
|
}
|
|
239
244
|
pushLine(`[subagent ${key} stop requested]`, C.warn)
|
|
240
245
|
}
|
package/src/tui/pickers.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { createModelPicker } from "./model-picker.mjs"
|
|
|
9
9
|
* 通用绑定(showPicker/closePicker/renderPickerLines)再行装配——index.mjs 消费面不变。
|
|
10
10
|
* 单一 Promise API:showPicker(title, entries, { defaultIndex }) → Promise<entry|null>。
|
|
11
11
|
* picker 栈:state.pickerStack,state.picker 始终指向栈顶(layout/render/key-handler 都只读 state.picker)。
|
|
12
|
+
* 删除类二次确认件:confirmDelete(TUI-COMMANDS.md §5.4 单源——形态同 cmd-clear / cmd-new 先例)。
|
|
12
13
|
* 选中即关闭(Enter = resolve + pop);Esc = pop 当前层并 resolve(null)。菜单循环由调用方 while 重开。 */
|
|
13
14
|
export function createPickers(ctx) {
|
|
14
15
|
const { agent, state, render, ansi, C, pushLine, persistRaw, askQuestion, maskKey } = ctx
|
|
@@ -105,13 +106,25 @@ export function createPickers(ctx) {
|
|
|
105
106
|
|
|
106
107
|
function renderPickerLines() { rebuildLines() }
|
|
107
108
|
|
|
109
|
+
/** 删除类入口二次确认件(单源——TUI-COMMANDS.md §5.4):形态 = 既有二次确认 picker
|
|
110
|
+
* (与 cmd-clear.mjs:6-9 · cmd-new.mjs:30-33 先例逐字同形,禁新造模态)。
|
|
111
|
+
* defaultIndex 恒指 Cancel(反误触——直接 Enter 不会删除);Esc / Cancel ⇒ false
|
|
112
|
+
* ⇒ 调用方零动作(取消先于落盘发生)。入口 ctx 缺此绑定 ⇒ 调用方报错(fail-closed)。 */
|
|
113
|
+
async function confirmDelete(question = "Delete?") {
|
|
114
|
+
const e = await showPicker(question, [
|
|
115
|
+
{ type: "item", text: "Yes, delete", action: "yes" },
|
|
116
|
+
{ type: "item", text: "Cancel", action: "no" },
|
|
117
|
+
], { defaultIndex: 1 })
|
|
118
|
+
return e?.action === "yes"
|
|
119
|
+
}
|
|
120
|
+
|
|
108
121
|
// /model 两级面 + selectModel + 渠道管理 + pickModelForSlot —— 迁 model-picker.mjs
|
|
109
122
|
// (MODEL-MERGE-SESSION——pick 后本文件 ≤450)。createModelPicker 返回同名单函数,
|
|
110
123
|
// 经本层原样转发(index.mjs/wizard/slash-commands 消费面零变化)。
|
|
111
124
|
const modelPicker = createModelPicker({
|
|
112
125
|
agent, state, render, ansi, C, pushLine, persistRaw, askQuestion, maskKey,
|
|
113
|
-
showPicker, closePicker, renderPickerLines,
|
|
126
|
+
showPicker, closePicker, renderPickerLines, confirmDelete,
|
|
114
127
|
})
|
|
115
128
|
|
|
116
|
-
return { showPicker, closePicker, popPicker, renderPickerLines, ...modelPicker }
|
|
129
|
+
return { showPicker, closePicker, popPicker, renderPickerLines, confirmDelete, ...modelPicker }
|
|
117
130
|
}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isExpanded, foldHintLine, renderExpandedBlock, renderBlockTimeline,
|
|
16
16
|
renderMathAndMarkdown, foldCapRows, renderFoldedHead, foldTailLines,
|
|
17
17
|
} from "./fold-block.mjs"
|
|
18
|
-
import { ADVISOR_THINKING_PLACEHOLDER } from "
|
|
18
|
+
import { ADVISOR_THINKING_PLACEHOLDER } from "@thincoder/core/advisor/run.mjs"
|
|
19
19
|
import { frozenSubSeg, toolSeg, frozenAdvSeg } from "./render-segments.mjs"
|
|
20
20
|
|
|
21
21
|
|
package/src/tui/render-frame.mjs
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
import { ansi, C, ESC } from "./ansi.mjs"
|
|
10
10
|
import { convCacheKey, renderConversation, countConvLines } from "./render-conversation.mjs"
|
|
11
11
|
import { sliceByWidth, stringWidth } from "./render.mjs"
|
|
12
|
-
import { providerSpec } from "
|
|
12
|
+
import { providerSpec } from "@thincoder/core/config.mjs"
|
|
13
13
|
import { computeLayout, subagentVisibleLines } from "./layout.mjs"
|
|
14
14
|
import { basename } from "node:path"
|
|
15
15
|
import { readFileSync } from "node:fs"
|
|
16
|
+
import { isYesNoModal } from "./interaction.mjs" // X8 跟进①:y/n-only 族判据单源
|
|
16
17
|
|
|
17
18
|
export { convCacheKey, renderConversation, countConvLines } from "./render-conversation.mjs"
|
|
18
19
|
|
|
@@ -39,7 +40,7 @@ const SLASH_HINTS = {
|
|
|
39
40
|
|
|
40
41
|
/** Header panel (always 1 line). */
|
|
41
42
|
export function renderHeader(agent, cols) {
|
|
42
|
-
// 2026-09-02 Q1(SESSION.md §8):provider 可为 null(无效 provider 被清空后用户 Esc 取消重选)
|
|
43
|
+
// 2026-09-02 Q1(SESSION.md §6.8):provider 可为 null(无效 provider 被清空后用户 Esc 取消重选)
|
|
43
44
|
// —— 可选链守卫,头部显示 "no provider" 占位
|
|
44
45
|
const model = agent.provider?.model ?? "no provider"
|
|
45
46
|
// providerSpec(PROVIDER.md §15):模型信息显示跟随 provider 级 context 覆盖(D-C5);
|
|
@@ -323,7 +324,9 @@ function inputBoxStyle(state) {
|
|
|
323
324
|
borderColor = C.tool; title = " Question "
|
|
324
325
|
} else if (state.permission) {
|
|
325
326
|
borderColor = C.warn
|
|
326
|
-
|
|
327
|
+
// X8 跟进①:y/n-only 族(continue / retry)按名分流——`a` 不属该族键面
|
|
328
|
+
title = isYesNoModal(state.permission.name)
|
|
329
|
+
? (state.permission.name === "continue" ? " Continue? (y/n) " : " Retry? (y/n) ")
|
|
327
330
|
: state.permission.batch ? ` Allow ${state.permission.name}? (a/o/n) `
|
|
328
331
|
: ` Allow ${state.permission.name}? (y/n/a) `
|
|
329
332
|
} else if (state.picker) {
|
|
@@ -349,7 +352,9 @@ function buildStatusLine(state, agent, { cols, slashCommands }) {
|
|
|
349
352
|
: " Type answer then Enter │ Esc: cancel"
|
|
350
353
|
}
|
|
351
354
|
if (state.permission) {
|
|
352
|
-
if (state.permission.name
|
|
355
|
+
if (isYesNoModal(state.permission.name)) {
|
|
356
|
+
return state.permission.name === "continue" ? " y: continue │ n: stop" : " y: retry │ n: stop"
|
|
357
|
+
}
|
|
353
358
|
if (state.permission.batch) return " a: approve all │ o: one by one │ n: deny"
|
|
354
359
|
return " y: approve │ n: deny │ a: approve all (AUTO)"
|
|
355
360
|
}
|
|
@@ -383,7 +388,7 @@ function buildStatusLine(state, agent, { cols, slashCommands }) {
|
|
|
383
388
|
const elapsed = state.processing ? ` ${Math.floor((Date.now() - state.processingStarted) / 1000)}s` : ""
|
|
384
389
|
const toolHint = state.currentTool ? ` ${state.currentTool}…` : ""
|
|
385
390
|
const statusText = state.processing ? `${state.status}${toolHint}${elapsed}` : state.status
|
|
386
|
-
// 2026-09-02 Q1(SESSION.md §8):provider 可为 null —— providerSpec(null) 保守 128K 不抛错
|
|
391
|
+
// 2026-09-02 Q1(SESSION.md §6.8):provider 可为 null —— providerSpec(null) 保守 128K 不抛错
|
|
387
392
|
// 2026-09-02 §15:context 窗口跟随 providers[].context 覆盖(T-C6——百分比基于覆盖后的窗口)
|
|
388
393
|
const modelContext = providerSpec(agent.provider).context
|
|
389
394
|
const ctxPct = Math.round((state.ctxCache.tokens / modelContext) * 100)
|
|
@@ -399,5 +404,9 @@ function buildStatusLine(state, agent, { cols, slashCommands }) {
|
|
|
399
404
|
const ledgerHint = lg?.marker
|
|
400
405
|
? ` │ ${lg.warn ? `${ansi.reset}${C.warn}${lg.marker}${ansi.reset}${ansi.dim}` : lg.marker}`
|
|
401
406
|
: ""
|
|
402
|
-
|
|
407
|
+
// D8(2026-09-21 块标题行对齐批——会话标题常显 · `docs/cli/design/TUI.md` §7.4):状态段簇尾
|
|
408
|
+
// (ledgerHint 后、键位组前);取值 = `agent.title` 活读(每帧 recompute)· 空值零注入。
|
|
409
|
+
const titleRaw = typeof agent.title === "string" ? agent.title.trim() : ""
|
|
410
|
+
const titleHint = titleRaw ? ` │ ${stringWidth(titleRaw) > 40 ? sliceByWidth(titleRaw, 39) + "…" : titleRaw}` : ""
|
|
411
|
+
return ` ${statusText}${taskHint}${turnHint}${tokenHint}${ctxHint}${scrollHint}${ledgerHint}${titleHint} │ ${enterHint} │ /: commands │ wheel/PgUp/PgDn: scroll │ Ctrl+I: inject │ Ctrl+C: exit (×2)`
|
|
403
412
|
}
|
package/src/tui/render-loop.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* so recomputing the frame each paint is cheap.
|
|
15
15
|
*/
|
|
16
16
|
import { countConvLines, renderRows } from "./render-frame.mjs"
|
|
17
|
-
import { estimateTokens } from "
|
|
17
|
+
import { estimateTokens } from "@thincoder/core/context.mjs"
|
|
18
18
|
import { ansi, C } from "./ansi.mjs"
|
|
19
19
|
import { isTuiActive } from "./tui-lifecycle.mjs" // F-2 渲染抑制守卫(RESIZE-MOUSE-LEAK-FIX)
|
|
20
20
|
|
|
@@ -86,7 +86,9 @@ function frozenSubTaskLines(state, sub, cols, maxRows) {
|
|
|
86
86
|
const modelPart = sub.model ? ` · ${sub.model}` : ""
|
|
87
87
|
const turnPart = sub.maxTurns > 0 ? ` · turn ${sub.turn}/${sub.maxTurns}` : ""
|
|
88
88
|
const errPart = sub.lastError ? ` — ${sub.lastError}` : ""
|
|
89
|
-
|
|
89
|
+
// 图标三态互斥(2026-09-20 端差·显示面消差批 M5):修前 stopped 冻结头 `✓` 与 verb 相抵
|
|
90
|
+
// ⇒ 补齐 VSC `webview/activity-view.js:46-49` 已成的 cancelled → `⏹` 形态(VSC 标尺,CLI 缺面)。
|
|
91
|
+
const icon = sub.approval ? "⏸" : sub.stopped ? "⏹" : "✓"
|
|
90
92
|
const verb = sub.stopped ? "stopped" : "done" // §19.5: cancel 冻结标题 "stopped"
|
|
91
93
|
const header = `[${icon} ${sub.key}${modePart}${modelPart} · ${verb} ${elapsed}s${turnPart}${errPart}]`
|
|
92
94
|
const out = []
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { C } from "./ansi.mjs"
|
|
12
|
-
import { specForModel } from "
|
|
12
|
+
import { specForModel } from "@thincoder/core/config.mjs"
|
|
13
13
|
import { handleClearCommand } from "./cmd-clear.mjs"
|
|
14
14
|
import { handleNewCommand } from "./cmd-new.mjs"
|
|
15
15
|
import { handleExitCommand } from "./cmd-exit.mjs"
|
package/src/tui/startup.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { listSlots } from "
|
|
1
|
+
import { listSlots } from "@thincoder/core/session.mjs"
|
|
2
2
|
import { ansi, C } from "./ansi.mjs"
|
|
3
3
|
import { describeToolArgs, toolArgsLines } from "./tool-args.mjs"
|
|
4
4
|
import { slimToolResultForDisplay } from "./tool-events.mjs"
|
|
5
5
|
import { countConvLines } from "./render-conversation.mjs"
|
|
6
|
-
// TUI-OOM-ROOTCAUSE(TUI.md §
|
|
7
|
-
import { capLine, accountLine, accountAll, syncLineBudget } from "./display-budget.mjs"
|
|
6
|
+
// TUI-OOM-ROOTCAUSE(TUI-SESSION-VIEW.md §5.3/§5.4):恢复/翻页行过额度 + state.lines 总量对账。
|
|
7
|
+
import { capLine, accountLine, accountAll, releaseLine, syncLineBudget } from "./display-budget.mjs"
|
|
8
8
|
import { shiftFreezeAnchors } from "./subagent-blocks.mjs"
|
|
9
9
|
|
|
10
10
|
/** Lazy history window (parity with VS Code HISTORY_PAGE_SIZE): first paint loads
|
|
@@ -118,7 +118,7 @@ export function historyToLines(history, startIdx, endIdx) {
|
|
|
118
118
|
* INITIAL_HISTORY_MESSAGES, set the _history* counters the loadOlder closure
|
|
119
119
|
* reads, and prepend the "… N more earlier messages" placeholder. Shared by
|
|
120
120
|
* startup restore and /session switching (the display snapshot is deprecated).
|
|
121
|
-
* TUI-OOM-ROOTCAUSE(SESSION.md §14
|
|
121
|
+
* TUI-OOM-ROOTCAUSE(SESSION.md §6.14):desc = `{ history, total, base? }`——
|
|
122
122
|
* history = 尾窗(≤200,含 ±1 页沿头一条——跨页回标判定用;不渲染)、total = 绝对总条数
|
|
123
123
|
* (「N messages」标签口径)、base = history[0] 的绝对序号(缺省由 total − len 推导)。
|
|
124
124
|
*/
|
|
@@ -132,7 +132,7 @@ export function restoreLines(state, desc) {
|
|
|
132
132
|
const start = total - loaded
|
|
133
133
|
state._lineIdCounter = state._lineIdCounter ?? 0
|
|
134
134
|
const fresh = historyToLines(window, Math.max(0, start - base), window.length)
|
|
135
|
-
for (const l of fresh) l.text = capLine(l.text) // 行额度(§
|
|
135
|
+
for (const l of fresh) l.text = capLine(l.text) // 行额度(§5.3 恢复行过 capText)
|
|
136
136
|
// Stable per-line ids (P1, 2026-08-30): fold keys for tool blocks derive from
|
|
137
137
|
// _lineId so loadOlder's head-unshift cannot re-bind an expanded block to a
|
|
138
138
|
// different tool (positional tool-{i} keys drift under unshift).
|
|
@@ -144,7 +144,7 @@ export function restoreLines(state, desc) {
|
|
|
144
144
|
if (state._hasOlder) {
|
|
145
145
|
state.lines.unshift({ text: `… ${start} more earlier messages (PgUp at top to load)`, color: C.dim })
|
|
146
146
|
}
|
|
147
|
-
accountAll(state) // 行集重建 → 总量直算重对账(§
|
|
147
|
+
accountAll(state) // 行集重建 → 总量直算重对账(§5.4)
|
|
148
148
|
syncLineBudget(state, { onTrim: (st, n) => shiftFreezeAnchors(st, n) })
|
|
149
149
|
}
|
|
150
150
|
/** 懒加载更早历史(2026-08-31 用户约定:"滚动到头自动加载"——滚轮/PgUp 到顶皆触发;
|
|
@@ -156,7 +156,7 @@ export function createLoadOlder({ agent, state, render }) {
|
|
|
156
156
|
return () => {
|
|
157
157
|
if (!state._hasOlder) return
|
|
158
158
|
// 翻页锚 = 恢复时点 total(state._historyTotal)——活消息增长不再使页码漂移
|
|
159
|
-
// (§14
|
|
159
|
+
// (§6.14 事实 4 顺带修复)。
|
|
160
160
|
const total = state._historyTotal ?? 0
|
|
161
161
|
const loaded = state._historyLoaded
|
|
162
162
|
const start = Math.max(0, total - loaded - HISTORY_PAGE_MESSAGES)
|
|
@@ -167,9 +167,9 @@ export function createLoadOlder({ agent, state, render }) {
|
|
|
167
167
|
const cols = d.cols ?? ((state.dims?.get() ?? {}).cols ?? (process.stdout.columns || 80))
|
|
168
168
|
const before = countConvLines(state, cols, d.rows ?? (process.stdout.rows || 24))
|
|
169
169
|
|
|
170
|
-
if (state.lines[0]?.text?.startsWith("… ")) state.lines.shift()
|
|
170
|
+
if (state.lines[0]?.text?.startsWith("… ")) releaseLine(state, state.lines.shift()) // 移除必出账(§5.5 记账口径——releaseLine 同额负向)
|
|
171
171
|
state._lineIdCounter = state._lineIdCounter ?? 0
|
|
172
|
-
// 页源(§14
|
|
172
|
+
// 页源(§6.14):绑定态 → store.page 绝对区间 + ±1 页沿(页前一消息供跨页回合
|
|
173
173
|
// 标签判定、页后一消息供 tool_result 配对);未绑定(模式 F)→ 内存全量数组回退。
|
|
174
174
|
const store = agent._recordStore
|
|
175
175
|
let older
|
|
@@ -179,7 +179,7 @@ export function createLoadOlder({ agent, state, render }) {
|
|
|
179
179
|
} else {
|
|
180
180
|
older = historyToLines(agent._fullHistory ?? [], start, end)
|
|
181
181
|
}
|
|
182
|
-
for (const l of older) l.text = capLine(l.text) // 行额度(§
|
|
182
|
+
for (const l of older) l.text = capLine(l.text) // 行额度(§5.3 翻页行过 capText)
|
|
183
183
|
for (const l of older) l._lineId = ++state._lineIdCounter
|
|
184
184
|
state.lines.unshift(...older)
|
|
185
185
|
for (const l of older) accountLine(state, l)
|
|
@@ -190,7 +190,7 @@ export function createLoadOlder({ agent, state, render }) {
|
|
|
190
190
|
state.lines.unshift(ph)
|
|
191
191
|
accountLine(state, ph)
|
|
192
192
|
}
|
|
193
|
-
// 页内不裁头(保锚定)——§
|
|
193
|
+
// 页内不裁头(保锚定)——§5.3:总量的裁头由 syncLineBudget 兜(此处只对账字符预算)
|
|
194
194
|
syncLineBudget(state, { onTrim: (st, n) => shiftFreezeAnchors(st, n) })
|
|
195
195
|
|
|
196
196
|
const after = countConvLines(state, cols, (state.dims?.get() ?? {}).rows ?? (process.stdout.rows || 24))
|
|
@@ -206,7 +206,7 @@ export function showStartup(ctx) {
|
|
|
206
206
|
const { agent, state, opts, pushLine, pushLabel, render, startWizard } = ctx
|
|
207
207
|
|
|
208
208
|
// Startup screen
|
|
209
|
-
// 2026-09-02 Q1(SESSION.md §8 D-S2):provider 可为 null(无效 provider 被清空后用户 Esc 取消重选)
|
|
209
|
+
// 2026-09-02 Q1(SESSION.md §6.8 D-S2):provider 可为 null(无效 provider 被清空后用户 Esc 取消重选)
|
|
210
210
|
// —— 可选链守卫;`!apiKey` 触发既有 wizard(其 provider 菜单列出已存在 providers,可选中恢复,F3)
|
|
211
211
|
if (!agent.provider?.apiKey) {
|
|
212
212
|
pushLabel(`Welcome to ThinCoder!`, ansi.bold + C.tool)
|
|
@@ -225,7 +225,7 @@ export function showStartup(ctx) {
|
|
|
225
225
|
// deprecated; it drifted out of sync with history on VS Code writes).
|
|
226
226
|
if (opts.restored?.history?.length) {
|
|
227
227
|
restoreLines(state, opts.restored)
|
|
228
|
-
// 「N messages」标签口径 = total(§14
|
|
228
|
+
// 「N messages」标签口径 = total(§6.14——非窗口长度)
|
|
229
229
|
const total = Number.isFinite(opts.restored.total) ? opts.restored.total : opts.restored.history.length
|
|
230
230
|
pushLabel(`── Restored previous session (${total} messages); /new for a fresh session ──`, C.warn)
|
|
231
231
|
}
|
|
@@ -242,7 +242,7 @@ export function showStartup(ctx) {
|
|
|
242
242
|
* Prefers git diff incremental (fast); falls back to full scan when git is unavailable or on first run. */
|
|
243
243
|
export async function backgroundIndex(ctx) {
|
|
244
244
|
const { agent, state, render } = ctx
|
|
245
|
-
const { codeSync, docSync, gitSync } = await import("
|
|
245
|
+
const { codeSync, docSync, gitSync } = await import("@thincoder/core/memory.mjs")
|
|
246
246
|
const cwd = agent.cwd
|
|
247
247
|
let codeFiles = 0, docFiles = 0
|
|
248
248
|
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
import { C } from "./ansi.mjs"
|
|
18
18
|
// 第 27 批 §12.3①/②:relay 前缀文法单一权威 = src/agent/relay-prefix.mjs——本文件
|
|
19
19
|
// 不再自持前缀正则/解析副本(防第二套平行正则再漂移)。
|
|
20
|
-
import { parseRelayPath } from "
|
|
20
|
+
import { parseRelayPath } from "@thincoder/core/agent/relay-prefix.mjs"
|
|
21
|
+
// zero-block 批(TUI.md §6.8.3.2 P1):墓碑存活闸复活分支留痕——日志面(零新增 UI 形态)。
|
|
22
|
+
import { logEvent } from "@thincoder/core/log.mjs"
|
|
21
23
|
import { describeToolArgs } from "./tool-args.mjs"
|
|
22
24
|
import {
|
|
23
25
|
appendSubBlock, descendSubChild,
|
|
@@ -28,7 +30,7 @@ export { SUB_BLOCK_LINE_LIMIT, appendSubBlock } from "./subagent-children.mjs"
|
|
|
28
30
|
// routeSub*/compression panel 内部用本地 import;re-export 保外部 import 面(index.mjs 等)
|
|
29
31
|
// CLI-ACTIVITY-DEBLOAT F-3(2026-09-10):面板手工镜像退役——re-export 面换现算导出
|
|
30
32
|
// computePanelBlocks(读时现算),本文件全部刷镜调用点删除(单账本——变更点不再手动同步)。
|
|
31
|
-
import { freezeSubTaskLines, finishSubTaskKey, finishSubTask, freezeDoneSubTasks, finishSubTasksByRole, freezeAllSubTasks, freezeReclaimDigestedBlocks, shiftFreezeAnchors } from "./subagent-freeze.mjs"
|
|
33
|
+
import { freezeSubTaskLines, finishSubTaskKey, finishSubTask, freezeDoneSubTasks, finishSubTasksByRole, freezeAllSubTasks, freezeReclaimDigestedBlocks, shiftFreezeAnchors, livePoolHas, removeFrozenSubTaskLine, tombstoneSubKey } from "./subagent-freeze.mjs"
|
|
32
34
|
export { computePanelBlocks, finishSubTask, finishSubTaskKey, freezeSubTaskLines, shiftFreezeAnchors, freezeDoneSubTasks, finishSubTasksByRole, freezeAllSubTasks, freezeReclaimDigestedBlocks } from "./subagent-freeze.mjs"
|
|
33
35
|
|
|
34
36
|
// 前缀文法/解析已迁 src/agent/relay-prefix.mjs(第 27 批 §12.3②)——本文件不自持副本。
|
|
@@ -80,7 +82,18 @@ export function throttleSubRender(scheduleRender) {
|
|
|
80
82
|
export function ensureSubTaskKey(state, key, role) {
|
|
81
83
|
// Tombstone guard:abort 子代理冻结后晚到 token 不得复活区块(2026-08-30 残项)
|
|
82
84
|
state._frozenSubKeys ??= new Set()
|
|
83
|
-
if (state._frozenSubKeys.has(key))
|
|
85
|
+
if (state._frozenSubKeys.has(key)) {
|
|
86
|
+
// 墓碑存活闸 P0-a(§6.8.3.2——zero-block 批):**墓碑只能断言「此块已终」,不得对
|
|
87
|
+
// 「仍在池中存活」的条目生效**——存活判据(逐字)见 livePoolHas(条目在池 ∧
|
|
88
|
+
// done !== true ∧ cancelled !== true)。存活 ⇒ ① 摘墓碑;② 摘旧冻结载体行
|
|
89
|
+
// (免一 key 两载体——折叠键共用);③ 照常建块(model / async 由后续 token 重填)
|
|
90
|
+
// + 留痕(**每次复活记一条(无去重状态)**)。不存活(池外 / done / cancelled
|
|
91
|
+
// ——含 done-in-pool)⇒ 维持丢弃(迟到 chunk 正常高频面——不留痕,D-ZB5)。
|
|
92
|
+
if (!livePoolHas(state, key)) return null
|
|
93
|
+
state._frozenSubKeys.delete(key)
|
|
94
|
+
removeFrozenSubTaskLine(state, key)
|
|
95
|
+
logEvent("ev:subagent-block-revived", { id: key })
|
|
96
|
+
}
|
|
84
97
|
state.subTasks ??= {}
|
|
85
98
|
if (!state.subTasks[key]) {
|
|
86
99
|
state.subTasks[key] = {
|
|
@@ -173,6 +186,10 @@ export function routeSubToken(state, t, scheduleRender) {
|
|
|
173
186
|
const live = state.subTasks?.[path.head]
|
|
174
187
|
if (live && !live.done && live.async !== true) {
|
|
175
188
|
delete state.subTasks[path.head]
|
|
189
|
+
// c2(af 批——§6.8.2「已移除块不复建」):**与移除同一守卫**(命中移除才写)落键级墓碑
|
|
190
|
+
// ⇒ 后续 ⟦ev⟧stopped 经 ensureSubTaskKey 直接丢弃(零幻影冻结块——探针实证的
|
|
191
|
+
// state.lines 0 → 1 路径封死);no-block 面不写(无可防之幻影——语义零扩)。
|
|
192
|
+
tombstoneSubKey(state, path.head)
|
|
176
193
|
}
|
|
177
194
|
scheduleRender()
|
|
178
195
|
return true
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import { C } from "./ansi.mjs"
|
|
14
14
|
import { closeOpenSubChildren } from "./subagent-children.mjs"
|
|
15
15
|
// TUI-OOM-ROOTCAUSE(TUI.md §15.3.3 落点表末行):state.lines 总量账——splice 插入路径过账。
|
|
16
|
-
|
|
16
|
+
// zero-block 批(§6.8.3.2):摘除路径负向出账——releaseLine(增删均须过账)。
|
|
17
|
+
import { accountLine, releaseLine } from "./display-budget.mjs"
|
|
17
18
|
|
|
18
19
|
// ─── §19.6 D-P1 面板视图(subagent panel 检查工具)───
|
|
19
20
|
// CLI-ACTIVITY-DEBLOAT F-3:面板区块列表由消费面**读时现算**(action:"panel" 经
|
|
@@ -95,6 +96,15 @@ export function freezeSubTaskLines(state, sub) {
|
|
|
95
96
|
accountLine(state, line)
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/** 键级墓碑(af 批 c2——§6.8.3.3 墓碑写入单点同址):key 计入 `_frozenSubKeys` ⇒ 后续 token
|
|
100
|
+
* 经 `ensureSubTaskKey` 墓碑守卫直接丢弃(`⟦ev⟧cancelled` 移除块后的幻影冻结块路径封死);
|
|
101
|
+
* 只写键集、不写载体行;存活条目复活 ⇒ 闸门摘墓碑 + 重建块(不永久失明)。 */
|
|
102
|
+
export function tombstoneSubKey(state, key) {
|
|
103
|
+
if (!state) return
|
|
104
|
+
state._frozenSubKeys ??= new Set()
|
|
105
|
+
state._frozenSubKeys.add(String(key))
|
|
106
|
+
}
|
|
107
|
+
|
|
98
108
|
/** 头裁锚点校正(index.mjs pushLine 调用):裁 removedCount 补 1 标记行 = 净位移
|
|
99
109
|
* removedCount−1(code review round1 #3);在途锚点前移,min 0 兜底。 */
|
|
100
110
|
export function shiftFreezeAnchors(state, removedCount) {
|
|
@@ -104,6 +114,62 @@ export function shiftFreezeAnchors(state, removedCount) {
|
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
// ─── 墓碑存活闸(zero-block 批——docs/cli/design/TUI.md §6.8.3)─────────────────
|
|
118
|
+
|
|
119
|
+
/** 存活查询(P0-a/P0-b 共用单点——与墓碑写点 `freezeSubTaskLines` 同址:墓碑条件 /
|
|
120
|
+
* 墓碑写入 / 载体行增删单一权威)。key 形 = `role#id`(`_frozenSubKeys` / `subTasks`
|
|
121
|
+
* 同命名空间);**存活判据(逐字 = §6.8.3.3)**:**条目在池 ∧ `entry.done !== true` ∧
|
|
122
|
+
* `entry.cancelled !== true`(running/queued)**(判据先例 = `thincoder-core/agent-tools/async-discard.mjs:55`
|
|
123
|
+
* ——零新谓词;仅「键在池内」会把 done-in-pool 误判为存活)。
|
|
124
|
+
* 映射(写死 = §6.8.3.3):最后 `#` 切分 → role + id;命中 = `pool.has(String(id))` ∧
|
|
125
|
+
* `entry.role === role`;非池键(`compress#N` 等)恒 false(维持既有丢弃语义)。
|
|
126
|
+
* 降级(§6.8.3.2 末):`state._agent` 缺省(headless / 子代理内 / 夹具)⇒ 无存活信息
|
|
127
|
+
* ⇒ false——与批前**逐字等价**(丢弃),零回归。
|
|
128
|
+
* @returns {boolean} 该 key 对应条目是否池内存活 */
|
|
129
|
+
export function livePoolHas(state, key) {
|
|
130
|
+
const agent = state?._agent
|
|
131
|
+
if (!agent) return false
|
|
132
|
+
const k = String(key ?? "")
|
|
133
|
+
const i = k.lastIndexOf("#")
|
|
134
|
+
if (i <= 0 || i === k.length - 1) return false // 非池键形态(无 `#` / 空 role / 空 id)
|
|
135
|
+
const role = k.slice(0, i)
|
|
136
|
+
const id = k.slice(i + 1)
|
|
137
|
+
for (const pool of [agent._asyncSubagents, agent._asyncAdvisors]) {
|
|
138
|
+
if (!(pool instanceof Map)) continue
|
|
139
|
+
const entry = pool.get(id) // 池键 = String(id)(subagent-run.mjs:187 / advisor-async.mjs:410)
|
|
140
|
+
if (!entry || entry.role !== role) continue
|
|
141
|
+
if (entry.done === true || entry.cancelled === true) continue // 已终态(含 done-in-pool)
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 摘旧冻结载体行(P0-a ②——复活时摘该 key 全部 `_frozenSubTask` 载体行):墓碑源已把
|
|
148
|
+
* 载体行 splice 进 `state.lines`(`freezeSubTaskLines`);不摘则一 key 两载体、折叠键
|
|
149
|
+
* `sub-${key}` 两处共用(render-segments.mjs / subagent-panel.mjs)⇒ 旧块永久留流。
|
|
150
|
+
* ① 逐行 `releaseLine` **负向出账**(增删均须过账——display-budget.mjs);② 摘除位
|
|
151
|
+
* **之后**(严格大于该位)的 `_freezeAt` 在途锚点 −1(`shiftFreezeAnchors` 同款语义
|
|
152
|
+
* ——锚点 = 流位置;摘除位之前的锚点不动;**恰等摘除位者亦不动**——逐字照 §6.8.3.2
|
|
153
|
+
* 「摘除位之后」;若需改判〔该位锚点原指被摘行槽位〕须先改设计)。
|
|
154
|
+
* @returns {number} 摘除的载体行数 */
|
|
155
|
+
export function removeFrozenSubTaskLine(state, key) {
|
|
156
|
+
const lines = state?.lines
|
|
157
|
+
if (!Array.isArray(lines)) return 0
|
|
158
|
+
const k = String(key ?? "")
|
|
159
|
+
let removed = 0
|
|
160
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
161
|
+
const l = lines[i]
|
|
162
|
+
if (l?._frozenSubTask?.key !== k) continue
|
|
163
|
+
releaseLine(state, l)
|
|
164
|
+
lines.splice(i, 1)
|
|
165
|
+
removed++
|
|
166
|
+
for (const sub of Object.values(state.subTasks ?? {})) {
|
|
167
|
+
if (sub._freezeAt !== undefined && sub._freezeAt > i) sub._freezeAt = Math.max(0, sub._freezeAt - 1)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return removed
|
|
171
|
+
}
|
|
172
|
+
|
|
107
173
|
/** 冻结 + 释放全部已 done 块(工具结果清扫路径)。锚点降序(后 settle 先插——
|
|
108
174
|
* 绝对位置 splice 语义);无锚点(-1)排末尾推;sort 稳定。 */
|
|
109
175
|
export function freezeDoneSubTasks(state) {
|
|
@@ -138,11 +204,16 @@ export function finishSubTasksByRole(state, roles, lastError = null) {
|
|
|
138
204
|
* lastError="interrupted"(Ready 态跳过)。§17.5.5:挂起自然退出时本函数只兜底
|
|
139
205
|
* **未消化残项**(已消化块由 freezeReclaimDigestedBlocks 逐条先行回收——块回收与
|
|
140
206
|
* 池空解耦)。锚点降序同 freezeDoneSubTasks(挂起期 settle 锚点交错批次各按其
|
|
141
|
-
* settle 位置落位)。
|
|
207
|
+
* settle 位置落位)。
|
|
208
|
+
* zero-block 批 P0-b(§6.8.3.2):**墓碑存活闸——池内存活(在池 ∧ `done !== true` ∧
|
|
209
|
+
* `cancelled !== true`)条目跳过**(不置 done / 不写墓碑 / 不出 `subTasks`——保留 live
|
|
210
|
+
* 驻留,待其自身 done/stopped/settled 通道收尾);**已终态(含 done-in-pool)与池外
|
|
211
|
+
* 条目照旧冻结**(中断 ghost 语义不变)。存活判据单点 = `livePoolHas`。 */
|
|
142
212
|
export function freezeAllSubTasks(state) {
|
|
143
213
|
const subs = Object.values(state.subTasks ?? {})
|
|
144
214
|
.sort((a, b) => (b._freezeAt ?? -1) - (a._freezeAt ?? -1))
|
|
145
215
|
for (const sub of subs) {
|
|
216
|
+
if (livePoolHas(state, sub.key)) continue // P0-b 存活跳过(墓碑只断言「此块已终」)
|
|
146
217
|
if (!sub.done) {
|
|
147
218
|
sub.done = true
|
|
148
219
|
sub.doneAt = Date.now()
|