thincoder 0.12.67 → 0.12.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +17 -5
- package/bin/thincoder.mjs +41 -344
- package/package.json +2 -2
- package/src/acp/bridge.mjs +5 -2
- package/src/acp/handlers-session.mjs +12 -1
- package/src/cli/make-agent.mjs +44 -119
- package/src/cli/memory-command.mjs +45 -3
- package/src/command-interactive.mjs +186 -0
- package/src/command-table.mjs +185 -0
- package/src/completions.mjs +36 -12
- package/src/crash-reports.mjs +42 -20
- package/src/distill.mjs +3 -3
- package/src/heap-watch.mjs +5 -14
- package/src/tui/agent-turn.mjs +88 -50
- package/src/tui/clipboard.mjs +74 -30
- package/src/tui/cmd-advisor.mjs +39 -13
- package/src/tui/cmd-clear.mjs +2 -2
- package/src/tui/cmd-config.mjs +17 -1
- package/src/tui/cmd-eng.mjs +1 -1
- package/src/tui/cmd-mcp.mjs +1 -1
- package/src/tui/cmd-new.mjs +2 -2
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-session.mjs +13 -5
- package/src/tui/cmd-think.mjs +35 -17
- package/src/tui/cmd-timers.mjs +33 -0
- package/src/tui/conversation-writer.mjs +2 -2
- package/src/tui/display-budget.mjs +9 -9
- package/src/tui/fold-block.mjs +1 -1
- package/src/tui/index.mjs +20 -1
- package/src/tui/interaction.mjs +4 -2
- package/src/tui/key-handler-ctrlc.mjs +1 -1
- package/src/tui/key-handler-search.mjs +1 -1
- package/src/tui/key-handler.mjs +1 -1
- package/src/tui/key-modes.mjs +1 -1
- package/src/tui/layout.mjs +5 -5
- package/src/tui/lifecycle-records.mjs +199 -0
- package/src/tui/model-picker.mjs +16 -199
- package/src/tui/mouse.mjs +3 -3
- package/src/tui/pickers.mjs +6 -2
- package/src/tui/provider-admin.mjs +213 -0
- package/src/tui/queued-merge.mjs +4 -60
- package/src/tui/queued-pickup.mjs +3 -3
- package/src/tui/render-conversation.mjs +13 -4
- package/src/tui/render-frame.mjs +43 -6
- package/src/tui/render-loop.mjs +2 -3
- package/src/tui/render-segments.mjs +2 -2
- package/src/tui/render.mjs +1 -1
- package/src/tui/slash-commands.mjs +3 -0
- package/src/tui/startup.mjs +28 -3
- package/src/tui/subagent-blocks.mjs +27 -19
- package/src/tui/subagent-children.mjs +5 -5
- package/src/tui/subagent-freeze.mjs +39 -37
- package/src/tui/subagent-panel.mjs +7 -7
- package/src/tui/suspension-drive.mjs +172 -259
- package/src/tui/timer-watch.mjs +64 -0
- package/src/tui/tool-args.mjs +10 -71
- package/src/tui/tool-display.mjs +6 -6
- package/src/tui/tool-events.mjs +33 -28
- package/src/tui/tui-lifecycle.mjs +13 -4
- package/src/tui/tui-state.mjs +3 -2
- package/src/tui/turn-face.mjs +4 -1
- package/src/tui/wizard.mjs +7 -2
- package/src/tui/wrapped-spawn.mjs +3 -3
package/src/completions.mjs
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* completions.mjs — `thincoder completion <shell>` shell 补全脚本发射(bash/zsh/fish)。
|
|
3
3
|
* bin/thincoder.mjs case "completion" 直写段拆分(2026-09-08——500 行硬限触碰执行:
|
|
4
4
|
* 段逐字迁出,脚本字节与既有输出一致)。
|
|
5
|
+
* 发射契约(CLI-ENTRY.md §3 —— #704 收正 2026-09-30):**源档形 ≡ 发射字节形**——bash 直写形零反斜杠
|
|
6
|
+
* `\$(...)` 非命令替换 ⇒ `bash -n` 语法错;zsh 分派行同排直写(双引号内 `\$` = 字面 `$` ⇒ 分派永不匹配);
|
|
7
|
+
* 余反斜杠仅续行 `\\`(行尾接续,发射 `\`)——保留。
|
|
8
|
+
* 例外(JS 插值险位):须发射字面 `${…}` 的段在源档保留 `\${` 转义。
|
|
5
9
|
*/
|
|
6
10
|
export function printCompletion(shell) {
|
|
7
11
|
if (shell === "bash") {
|
|
@@ -11,17 +15,23 @@ export function printCompletion(shell) {
|
|
|
11
15
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
12
16
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
13
17
|
case "\${COMP_WORDS[1]}" in
|
|
14
|
-
chat) COMPREPLY=(
|
|
18
|
+
chat) COMPREPLY=( $(compgen -W "--auto" -- "$cur") ) ;;
|
|
15
19
|
memory)
|
|
16
|
-
case "
|
|
17
|
-
memory) COMPREPLY=(
|
|
18
|
-
list) COMPREPLY=(
|
|
19
|
-
put) COMPREPLY=(
|
|
20
|
+
case "$prev" in
|
|
21
|
+
memory) COMPREPLY=( $(compgen -W "list search put remove sweep" -- "$cur") ) ;;
|
|
22
|
+
list) COMPREPLY=( $(compgen -W "--type=rule --type=knowledge --type=decision --type=pattern" -- "$cur") ) ;;
|
|
23
|
+
put) COMPREPLY=( $(compgen -W "--type= --title= --content= --tags=" -- "$cur") ) ;;
|
|
24
|
+
sweep) COMPREPLY=( $(compgen -W "--origin= --dry-run --confirm" -- "$cur") ) ;;
|
|
20
25
|
esac ;;
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
ledger) case "$prev" in
|
|
27
|
+
ledger) COMPREPLY=( $(compgen -W "migrate audit" -- "$cur") ) ;;
|
|
28
|
+
migrate) COMPREPLY=( $(compgen -W "--dry-run --confirm --from" -- "$cur") ) ;;
|
|
29
|
+
audit) COMPREPLY=( $(compgen -W "--root" -- "$cur") ) ;;
|
|
30
|
+
esac ;;
|
|
31
|
+
distill) COMPREPLY=( $(compgen -W "--yes --layer=" -- "$cur") ) ;;
|
|
32
|
+
completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") ) ;;
|
|
23
33
|
*)
|
|
24
|
-
COMPREPLY=(
|
|
34
|
+
COMPREPLY=( $(compgen -W "tui chat acp memory sync reindex distill upgrade completion session ledger -v --version -h --help" -- "$cur") ) ;;
|
|
25
35
|
esac
|
|
26
36
|
}
|
|
27
37
|
complete -F _thincoder thincoder
|
|
@@ -36,7 +46,7 @@ _thincoder() {
|
|
|
36
46
|
'1: :->cmd' \\
|
|
37
47
|
'*:: :->args'
|
|
38
48
|
|
|
39
|
-
case "
|
|
49
|
+
case "$state" in
|
|
40
50
|
cmd)
|
|
41
51
|
_values 'command' \\
|
|
42
52
|
'tui[Launch the interactive TUI]' \\
|
|
@@ -48,16 +58,21 @@ _thincoder() {
|
|
|
48
58
|
'distill[Extract knowledge from session transcript]' \\
|
|
49
59
|
'upgrade[Update to latest version from npm]' \\
|
|
50
60
|
'completion[Generate shell completion script]' \\
|
|
51
|
-
'session[Session dir GC: session gc --dry-run|--confirm]'
|
|
61
|
+
'session[Session dir GC: session gc --dry-run|--confirm]' \\
|
|
62
|
+
'ledger[Ledger variants: migrate / audit]'
|
|
52
63
|
;;
|
|
53
64
|
args)
|
|
54
|
-
case "
|
|
65
|
+
case "$words[1]" in
|
|
55
66
|
chat) _arguments '--auto[Auto-approve all tool calls]' ;;
|
|
56
67
|
memory)
|
|
57
|
-
case "
|
|
68
|
+
case "$words[2]" in
|
|
58
69
|
list) _arguments '--type=[Filter by type]' ;;
|
|
59
70
|
put) _arguments '--type=[Entry type]' '--title=[Title]' '--content=[Content]' '--tags=[Space-separated tags]' ;;
|
|
71
|
+
sweep) _arguments '--origin=[Origin path]' '--dry-run[Report only]' '--confirm[Apply writes]' ;;
|
|
60
72
|
esac ;;
|
|
73
|
+
ledger) case "$words[2]" in
|
|
74
|
+
migrate) _arguments '--dry-run[Report only]' '--confirm[Apply migration]' '--from:Source key:' ;;
|
|
75
|
+
audit) _arguments '--root:Scan root dir:' ;; esac ;;
|
|
61
76
|
distill) _arguments '--yes[Skip confirmation]' '--layer=[Layer filter]' ;;
|
|
62
77
|
completion) _values 'shell' 'bash' 'zsh' 'fish' ;;
|
|
63
78
|
esac ;;
|
|
@@ -79,6 +94,7 @@ complete -c thincoder -a distill -d 'Extract knowledge from session'
|
|
|
79
94
|
complete -c thincoder -a upgrade -d 'Update to latest version'
|
|
80
95
|
complete -c thincoder -a completion -d 'Shell completion'
|
|
81
96
|
complete -c thincoder -a session -d 'Session dir GC (session gc --dry-run|--confirm)'
|
|
97
|
+
complete -c thincoder -a ledger -d 'Ledger variants: migrate / audit'
|
|
82
98
|
complete -c thincoder -a acp -d 'Agent Client Protocol server for IDEs'
|
|
83
99
|
|
|
84
100
|
# Flags
|
|
@@ -93,6 +109,7 @@ complete -c thincoder -n '__fish_seen_subcommand_from memory' -a list -d 'List
|
|
|
93
109
|
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a search -d 'Search memory'
|
|
94
110
|
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a put -d 'Add entry'
|
|
95
111
|
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a remove -d 'Remove entry'
|
|
112
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory' -a sweep -d 'Sweep dead origins'
|
|
96
113
|
|
|
97
114
|
# memory list flags
|
|
98
115
|
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from list' -l type -d 'Filter by type' -xa 'rule knowledge decision pattern'
|
|
@@ -103,6 +120,11 @@ complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_su
|
|
|
103
120
|
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l content -d 'Content'
|
|
104
121
|
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from put' -l tags -d 'Space-separated tags'
|
|
105
122
|
|
|
123
|
+
# memory sweep flags
|
|
124
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from sweep' -l origin -d 'Origin path'
|
|
125
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from sweep' -l dry-run -d 'Report only'
|
|
126
|
+
complete -c thincoder -n '__fish_seen_subcommand_from memory; and __fish_seen_subcommand_from sweep' -l confirm -d 'Apply writes'
|
|
127
|
+
|
|
106
128
|
# distill flags
|
|
107
129
|
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l yes -d 'Skip confirmation'
|
|
108
130
|
complete -c thincoder -n '__fish_seen_subcommand_from distill' -l layer -d 'Layer filter'
|
|
@@ -111,6 +133,8 @@ complete -c thincoder -n '__fish_seen_subcommand_from distill' -l layer -d 'Laye
|
|
|
111
133
|
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a bash -d 'Bash completions'
|
|
112
134
|
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a zsh -d 'Zsh completions'
|
|
113
135
|
complete -c thincoder -n '__fish_seen_subcommand_from completion' -a fish -d 'Fish completions'
|
|
136
|
+
complete -c thincoder -n '__fish_seen_subcommand_from ledger' -a 'migrate audit' -d 'Ledger variants: migrate / audit'
|
|
137
|
+
complete -c thincoder -n '__fish_seen_subcommand_from ledger' -l dry-run -l confirm -l from -l root -d 'Ledger flags'
|
|
114
138
|
`)
|
|
115
139
|
}
|
|
116
140
|
}
|
package/src/crash-reports.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* crash-reports.mjs — 崩溃捕获与取证(docs/design/CRASH-REPORTS.md——F1/F2 为 R25 与
|
|
2
|
+
* crash-reports.mjs — 崩溃捕获与取证(docs/cli/design/CRASH-REPORTS.md——F1/F2 为 R25 与
|
|
3
3
|
* TUI-STDERR-CAPTURE 存量迁移;F3 近堆上限堆快照 = TUI-OOM-FORENSICS 批新增)。
|
|
4
4
|
*
|
|
5
5
|
* 能力(与设计逐项对应):
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* 自动写 report.*.json。实现批实测(2026-09-07):目录缺失时 Node 对 fatal 静默不写
|
|
9
9
|
* 报告——预建是必要动作而非"零成本保险"。
|
|
10
10
|
* F3①:同点武装近堆上限堆快照(v8.setHeapSnapshotNearHeapLimit——对象级证据,回答
|
|
11
|
-
* "谁在持内存"
|
|
11
|
+
* "谁在持内存";全路径单源,默认关、配置键 `diagnostics.heapSnapshot` 取 `true` 才武装
|
|
12
|
+
* (fail-closed;bin 入口读键后经 `heapSnapshot` 参数显式传入——F3②)。
|
|
12
13
|
* - writeCrashRecord()(F-R25a):JS 异常钩子同步落盘 crash-{ts}-{pid}.json(ts = epoch ms
|
|
13
14
|
* UTC + pid——跨进程同 ms 防覆盖——复审 #3)——权限 0600(config.json 先例)。
|
|
14
15
|
* - recentCrashHint()(F-R25c):启动扫描 24h 窗内记录(两类文件模式定死——评审 #8:
|
|
@@ -23,15 +24,12 @@
|
|
|
23
24
|
* 纪律:全部同步 API(崩溃路径无异步);调用方按执行序列各步独立 try/catch(复审 #2——
|
|
24
25
|
* 写失败/恢复失败不阻断后续步——exit 非 0 恒达)。
|
|
25
26
|
*/
|
|
26
|
-
import { chmodSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs"
|
|
27
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs"
|
|
27
28
|
import { join } from "node:path"
|
|
28
29
|
// F3① 命名空间 import:API 缺失(旧 Node)降级为调用期异常并被武装 try 吞掉——不做 import 期硬失败
|
|
29
30
|
import * as v8 from "node:v8"
|
|
30
31
|
import { configDir } from "@thincoder/core/config.mjs"
|
|
31
32
|
|
|
32
|
-
/** F3② 关值集合(trim + 大小写不敏感);其余取值(未设 / 空串 / 未知串)默认开——fail-open 向取证。 */
|
|
33
|
-
const HEAP_SNAPSHOT_OFF_VALUES = new Set(["0", "false", "off", "no"])
|
|
34
|
-
|
|
35
33
|
/** crash-reports 运行时目录(~/.thincoder/crash-reports——非仓内——写时自清理)。 */
|
|
36
34
|
export function crashReportsDir() {
|
|
37
35
|
return join(configDir, "crash-reports")
|
|
@@ -43,8 +41,14 @@ export function crashReportsDir() {
|
|
|
43
41
|
* Heap.*.heapsnapshot(CRASH-REPORTS F3③——快照 GB 级、TUI 落点须自动兜底)——但
|
|
44
42
|
* recentCrashHint 两类均不计(N3):tui-stderr 每次 TUI 启动都生成(正常退出也留档)、
|
|
45
43
|
* 快照非「异常终止」证据类——计入即正常会话误报。 */
|
|
44
|
+
/** 记录类判定单源(`isCrashRecordName` 与 `recordFacts` 共用——防两处正则漂移):自写 / Node fatal / 其余。 */
|
|
45
|
+
function recordClassOf(name) {
|
|
46
|
+
if (/^crash-.+\.json$/.test(name)) return "self"
|
|
47
|
+
if (/^report\..+\.json$/.test(name)) return "native"
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
46
50
|
function isCrashRecordName(name) {
|
|
47
|
-
return
|
|
51
|
+
return recordClassOf(name) !== null
|
|
48
52
|
}
|
|
49
53
|
function isPurgeRecordName(name) {
|
|
50
54
|
return isCrashRecordName(name) || /^tui-stderr-.+\.log$/.test(name) || /^Heap\..+\.heapsnapshot$/.test(name)
|
|
@@ -63,21 +67,16 @@ function purgeOldCrashReports(dir) {
|
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
/** F3② 武装判定(单点——包装器不判 env,防两处判定漂移):关值集合命中 → 不武装;其余 → 武装。 */
|
|
67
|
-
function heapSnapshotEnabled(env) {
|
|
68
|
-
return !HEAP_SNAPSHOT_OFF_VALUES.has(String(env?.THINCODER_HEAP_SNAPSHOT ?? "").trim().toLowerCase())
|
|
69
|
-
}
|
|
70
|
-
|
|
71
70
|
/**
|
|
72
71
|
* F-R25b:入口最前调用(一切重活前——缩编程期窗口)——预建目录 + process.report 启用。
|
|
73
72
|
* 任何失败不阻断启动(尽力面——record 路径自带降级)。返回目录路径。
|
|
74
|
-
* F3
|
|
75
|
-
* @param {object} [opts]
|
|
73
|
+
* F3①:同点武装近堆上限堆快照(默认关——fail-closed,显式 `true` 才武装;全路径单源;开关 = bin 入口读配置键后经 `heapSnapshot` 传入)。
|
|
74
|
+
* @param {object} [opts] 注入缝(默认参数 = 生产常态)
|
|
76
75
|
* @param {string} [opts.dir] 目录注入(测试用——同时服务 mkdir / report / purge / 返回)
|
|
77
|
-
* @param {
|
|
76
|
+
* @param {boolean} [opts.heapSnapshot] 快照武装开关(bin 入口经配置键判定后显式传入;配置默认关、显式 `true` 才武装——fail-closed;形参缺省 = 内部约定,生产调用点恒显式传值)
|
|
78
77
|
* @param {(n: number) => void} [opts.armHeapSnapshot] 武装实现注入(测试用)——默认真实 node:v8 API
|
|
79
78
|
*/
|
|
80
|
-
export function prepareCrashReporting({ dir = crashReportsDir(),
|
|
79
|
+
export function prepareCrashReporting({ dir = crashReportsDir(), heapSnapshot = true, armHeapSnapshot = v8.setHeapSnapshotNearHeapLimit } = {}) {
|
|
81
80
|
try {
|
|
82
81
|
mkdirSync(dir, { recursive: true })
|
|
83
82
|
// 代码内启用:shebang 入口无法携带启动参数(env 单参数限制 + execArgv 仅子进程——评审 #1)
|
|
@@ -85,7 +84,7 @@ export function prepareCrashReporting({ dir = crashReportsDir(), env = process.e
|
|
|
85
84
|
process.report.reportOnFatalError = true
|
|
86
85
|
} catch { /* mkdir/启用失败不阻断启动——尽力面 */ }
|
|
87
86
|
// F3① 武装(独立 try——失败不阻断启动、不影响上方 F1 既有步骤;尽力面静默)
|
|
88
|
-
if (
|
|
87
|
+
if (heapSnapshot) {
|
|
89
88
|
try { armHeapSnapshot(1) } catch { /* 武装失败不阻断——N1 */ }
|
|
90
89
|
}
|
|
91
90
|
purgeOldCrashReports(dir) // F-R25b 入口 mkdir 搭车清理(复审 #4——纯 fatal 序列也触发)
|
|
@@ -124,10 +123,33 @@ export function writeCrashRecord({ type, error }) {
|
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
126
|
+
/**
|
|
127
|
+
* 提示行事实段(台账 #212 · 用户裁 B):从选中记录读回 `uptime` / `cwd` 两段——段序固定 =
|
|
128
|
+
* uptime 段(` · 运行 <u>s`,一位小数)在前 / cwd 段(` · cwd <c>`)在后。
|
|
129
|
+
* 字段源按记录类分:自写记录(`crash-*.json`)取顶部 `uptime` / `cwd`;Node fatal 报告
|
|
130
|
+
* (`report.*.json`)取 `header.cwd`——Node 报告无 uptime 字段(2026-09-25 实测),该段略去。
|
|
131
|
+
* 退化(尽力面 · N1):读档 / 解析失败、记录类不明、字段缺 / 型不符(`uptime` 非有限非负数 ·
|
|
132
|
+
* `cwd` 非非空字符串)⇒ 对应段略去;两段皆缺 ⇒ 空串(基础形)。不抛、不阻断启动。
|
|
133
|
+
*/
|
|
134
|
+
function recordFacts(file, name) {
|
|
135
|
+
const cls = recordClassOf(name)
|
|
136
|
+
if (cls === null) return "" // 记录类不明 ⇒ 零事实
|
|
137
|
+
let rec
|
|
138
|
+
try { rec = JSON.parse(readFileSync(file, "utf8")) } catch { return "" }
|
|
139
|
+
if (rec === null || typeof rec !== "object") return ""
|
|
140
|
+
let facts = ""
|
|
141
|
+
if (cls === "self" && typeof rec.uptime === "number" && Number.isFinite(rec.uptime) && rec.uptime >= 0) {
|
|
142
|
+
facts += ` · 运行 ${rec.uptime.toFixed(1)}s`
|
|
143
|
+
}
|
|
144
|
+
const cwd = cls === "native" ? rec.header?.cwd : rec.cwd
|
|
145
|
+
if (typeof cwd === "string" && cwd !== "") facts += ` · cwd ${cwd}`
|
|
146
|
+
return facts
|
|
147
|
+
}
|
|
148
|
+
|
|
127
149
|
/**
|
|
128
150
|
* F-R25c:启动扫描——crash-reports 24h 窗内是否有记录(两类模式——mtime 判定)。
|
|
129
151
|
* @param {object} [opts] 可选 { dir }——目录注入(测试用);默认 crashReportsDir()
|
|
130
|
-
* @returns {string|null}
|
|
152
|
+
* @returns {string|null} 提示文本(含最新记录完整路径 + 事实段——#212);无匹配 → null(负例——不提示)
|
|
131
153
|
*/
|
|
132
154
|
export function recentCrashHint({ dir = crashReportsDir() } = {}) {
|
|
133
155
|
purgeOldCrashReports(dir) // F-R25c 启动扫描搭车清理(复审 #4)
|
|
@@ -141,10 +163,10 @@ export function recentCrashHint({ dir = crashReportsDir() } = {}) {
|
|
|
141
163
|
try {
|
|
142
164
|
const st = statSync(join(dir, name))
|
|
143
165
|
if (st.mtimeMs >= cutoff && (!best || st.mtimeMs > best.mtimeMs)) {
|
|
144
|
-
best = { path: join(dir, name), mtimeMs: st.mtimeMs }
|
|
166
|
+
best = { path: join(dir, name), name, mtimeMs: st.mtimeMs }
|
|
145
167
|
}
|
|
146
168
|
} catch { /* 单文件 stat 失败跳过 */ }
|
|
147
169
|
}
|
|
148
170
|
if (!best) return null
|
|
149
|
-
return `上次运行异常终止(记录:${best.path})`
|
|
171
|
+
return `上次运行异常终止(记录:${best.path}${recordFacts(best.path, best.name)})`
|
|
150
172
|
}
|
package/src/distill.mjs
CHANGED
|
@@ -40,7 +40,7 @@ If the session is long, prioritize conclusions that appeared last and are still
|
|
|
40
40
|
Session log:
|
|
41
41
|
`
|
|
42
42
|
|
|
43
|
-
/** §
|
|
43
|
+
/** TRACES.md §6.1 D-TR6(2026-09-04 fix round1):distill 调用点无 agent 作用域——traces 开关
|
|
44
44
|
* 缺省回退磁盘配置(loadConfig——与 agent.config 同源:traces.enabled 缺省 on);
|
|
45
45
|
* 配置不可读时按缺省 on(注:CLI 启动早已 loadConfig——此处仅是兜底防御)。 */
|
|
46
46
|
function tracesEnabledFromConfig() {
|
|
@@ -54,14 +54,14 @@ function tracesEnabledFromConfig() {
|
|
|
54
54
|
/**
|
|
55
55
|
* Extract candidates from a session transcript. transcript: plain-text session record.
|
|
56
56
|
* Returns [{ type, title, content, tags, layer }], or [] on parse failure.
|
|
57
|
-
* opts.traces
|
|
57
|
+
* opts.traces(可选):TRACES.md §6.1 D-TR6 开关显式透传(测试隔离/未来调用方)——缺省回退
|
|
58
58
|
* 磁盘配置(tracesEnabledFromConfig)——关 = chat() 出口不落盘。
|
|
59
59
|
*/
|
|
60
60
|
export async function extractCandidates(provider, transcript, opts = {}) {
|
|
61
61
|
const traces = opts.traces ?? tracesEnabledFromConfig()
|
|
62
62
|
const res = await chat(provider, {
|
|
63
63
|
messages: [{ role: "user", content: DISTILL_PROMPT + transcript }],
|
|
64
|
-
// §
|
|
64
|
+
// TRACES.md §6.1 D-TR4/D-TR6(fix round1):distill 调用经 chat() 唯一采集点——补轨迹
|
|
65
65
|
// 元数据 + traces 开关透传(关=不落盘必须全覆盖——不再静默越过开关)
|
|
66
66
|
logCtx: { stage: "distill", kind: "distill", traces },
|
|
67
67
|
})
|
package/src/heap-watch.mjs
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 无绝对 MB 常量);双档边缘触发(每档一次/进程——不重复刷屏);零常态开销
|
|
10
10
|
* (60s 一次 memoryUsage(),无输出——D-HW5)。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* 开关:配置键 `diagnostics.heapWatch`(默认开——判定形 `!== false`,与 F3 快照面默认相反〔快照默认关〕)——bin 入口读键后经
|
|
13
|
+
* `enabled` 参数显式传入;关 ⇒ 不注册定时器(返回惰性句柄)。
|
|
14
14
|
*
|
|
15
15
|
* 注入缝(N5 可测):`sample` / `heapLimit` / `timer`——测试以假实现 + `checkNow()`
|
|
16
16
|
* 直驱(零等待);失败面全吞(采样抛错不阻断——样本失败静默跳过本次)。
|
|
@@ -18,15 +18,6 @@
|
|
|
18
18
|
import { getHeapStatistics } from "node:v8"
|
|
19
19
|
import { logEvent } from "@thincoder/core/log.mjs"
|
|
20
20
|
|
|
21
|
-
/** 关值集合(单点判定——§3.2 同约定复用)。 */
|
|
22
|
-
export const HEAP_WATCH_OFF = new Set(["0", "false", "off", "no"])
|
|
23
|
-
|
|
24
|
-
/** 开关判定(单点——grep 面:仅本模块读 env.THINCODER_HEAP_WATCH)。 */
|
|
25
|
-
export function heapWatchEnabled(env = process.env) {
|
|
26
|
-
const v = String(env.THINCODER_HEAP_WATCH ?? "").trim().toLowerCase()
|
|
27
|
-
return !HEAP_WATCH_OFF.has(v)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
21
|
// 订阅表(TUI 行接线——§8.3 订阅制;模块级:武装在 bin、订阅在 startTUI)
|
|
31
22
|
const _subscribers = new Set()
|
|
32
23
|
|
|
@@ -45,14 +36,14 @@ export function heapWarnLine(used, limit, ratio) {
|
|
|
45
36
|
|
|
46
37
|
/**
|
|
47
38
|
* 启动看门狗(返回 `{ stop(), checkNow() }`——checkNow 直驱一次采样并返回本轮新发预警行)。
|
|
48
|
-
*
|
|
39
|
+
* 开关关(`enabled: false`)→ 不注册定时器(返回惰性句柄)。
|
|
49
40
|
*/
|
|
50
41
|
export function startHeapWatch({
|
|
51
42
|
intervalMs = 60_000,
|
|
52
43
|
ratios = [0.7, 0.85],
|
|
53
44
|
sample = process.memoryUsage,
|
|
54
45
|
heapLimit = () => getHeapStatistics().heap_size_limit,
|
|
55
|
-
|
|
46
|
+
enabled = true,
|
|
56
47
|
timer = setInterval,
|
|
57
48
|
} = {}) {
|
|
58
49
|
const warned = new Set()
|
|
@@ -76,7 +67,7 @@ export function startHeapWatch({
|
|
|
76
67
|
} catch { /* 失败面全吞(N5——采样/上限抛错不阻断、状态保持) */ }
|
|
77
68
|
return out
|
|
78
69
|
}
|
|
79
|
-
if (!
|
|
70
|
+
if (!enabled) return { stop() {}, checkNow }
|
|
80
71
|
const handle = timer(checkNow, intervalMs)
|
|
81
72
|
try { handle?.unref?.() } catch { /* unref 失败不阻断(一次性命令自然退出) */ }
|
|
82
73
|
return {
|
package/src/tui/agent-turn.mjs
CHANGED
|
@@ -14,15 +14,19 @@
|
|
|
14
14
|
* 递归回本文件(函数级静态环——模块求值期无顶层调用,环安全——session-slots ↔
|
|
15
15
|
* session.mjs 同款先例)。
|
|
16
16
|
*/
|
|
17
|
-
import { runAgent
|
|
17
|
+
import { runAgent } from "@thincoder/core/agent.mjs"
|
|
18
18
|
import { saveSession } from "@thincoder/core/session.mjs"
|
|
19
|
+
import { continueDecision } from "@thincoder/core/agent/continue-decision.mjs" // #677 · I10 续跑判定单源(#127 ④ 双写退役)
|
|
19
20
|
import { ansi, C } from "./ansi.mjs"
|
|
20
21
|
import { buildToolCallbacks, sweepToolBlocks } from "./tool-events.mjs"
|
|
21
22
|
import { freezeAllSubTasks } from "./subagent-blocks.mjs" // freezeReclaimDigestedBlocks 随 AGENT-LOOP-ASYNC-POOL.md §6.8 段迁 suspension-drive.mjs
|
|
22
23
|
import { ensureSessionTitle } from "@thincoder/core/generate-title.mjs"
|
|
23
24
|
import { logEvent, errText } from "@thincoder/core/log.mjs"
|
|
24
|
-
import {
|
|
25
|
+
import { pushRecord } from "@thincoder/core/context.mjs"
|
|
25
26
|
import { suspensionSession, poolLive } from "./suspension-drive.mjs"
|
|
27
|
+
// #726 写点②:cap 行 ∥ cap 记录同点双动作(文案/形 = lifecycle-records 单一实现)
|
|
28
|
+
import { digestCapRecord, digestTraceLines, recordCarrier } from "./lifecycle-records.mjs"
|
|
29
|
+
import { timerWakeArmed } from "./timer-watch.mjs" // §6.30.3 门三件③:在途 timer ∧ 开关开 ⇒ 唤醒会武装
|
|
26
30
|
import { planQueuedInput } from "./queued-merge.mjs"
|
|
27
31
|
import { pickupQueuedAtStepBoundary } from "./queued-pickup.mjs"
|
|
28
32
|
|
|
@@ -31,20 +35,21 @@ import { pickupQueuedAtStepBoundary } from "./queued-pickup.mjs"
|
|
|
31
35
|
* never let shutdown hang on the background summary call. */
|
|
32
36
|
const DISTILL_FLUSH_TIMEOUT_MS = 5000
|
|
33
37
|
|
|
34
|
-
/** Provider 失败面 URL 脱敏(X8——判据与 VSC `panel-turn-loop.mjs
|
|
35
|
-
* 字面 = 双写登记——单源化须动 VSC
|
|
38
|
+
/** Provider 失败面 URL 脱敏(X8——判据与 VSC `panel-turn-loop.mjs` 的 `e.message` 首行脱敏处同式;两端各自持有
|
|
39
|
+
* 字面 = 双写登记——单源化须动 VSC/核写域,出本批射程)。 */
|
|
36
40
|
const PROVIDER_URL_RE = /https?:\/\/[^\s,)"]+/g
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
43
|
* 第 33 批(TUI §14.3(d)——纯函数,供测试直驱):回合链尾「需要用户」谓词——agent 已停
|
|
40
44
|
* 且无自动续跑 ⇒ 置 attention 位。排除项语义(F13):`skipSession`(digest / 会话内回合
|
|
41
45
|
* ——由外层链尾统一置位)· 挂起两态 / 池 live / 队列非空(自动续跑中——不由用户接手)·
|
|
42
|
-
* processing
|
|
46
|
+
* processing(回合在跑)· **在途 timer ∧ 唤醒会武装**(`docs/cli/design/TUI.md` §7.1 awaiting
|
|
47
|
+
* 行除外 / §7.2 置位谓词——自动续跑在途 ⇒ 不计 awaiting;机判 = T-TW13;timer-wake 批 2026-09-27)。
|
|
43
48
|
* @returns {boolean}
|
|
44
49
|
*/
|
|
45
50
|
export function userNeededAtTurnEnd(state, agent, skipSession) {
|
|
46
51
|
return !skipSession && !state.suspended && !state._suspPending && !poolLive(agent)
|
|
47
|
-
&& state.queue.length === 0 && !state.processing
|
|
52
|
+
&& state.queue.length === 0 && !state.processing && !timerWakeArmed(agent)
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
/**
|
|
@@ -75,9 +80,32 @@ export async function runAgentTurn(ctx, text, opts = {}) {
|
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
/**
|
|
83
|
+
/** #448② 重武装守卫(KD-6「异常径重武装——会话停 ∕ 显式撤销除外」):**粘滞位 `_timerRearmRevoked`**
|
|
84
|
+
* = 主线(两实落点:Ctrl+C 全停 `{abortTrigger:"stop"}` 中止分支 ∕ 挂起会话中止(`_suspAborted`)复位前捕获;
|
|
85
|
+
* 链头复位 ⇒ 下一链正常重入)。余两腿为**防御面**(仅在正文异常早退、控制器尚未置空时可达):
|
|
86
|
+
* `_suspAborted` 未及复位 ∥ 本链控制器 stop 语义在场 ∥ 会话 abort 句柄已中止。 */
|
|
87
|
+
function timerRearmBlocked(state, agent) {
|
|
88
|
+
return state?._timerRearmRevoked === true
|
|
89
|
+
|| state?._suspAborted === true
|
|
90
|
+
|| state?.controller?.signal?.reason?.abortTrigger === "stop"
|
|
91
|
+
|| agent?._sessionAbort?.signal?.aborted === true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** runAgentTurn 本体(LOGGING 包装之外——见上方包装器):**链尾收口层**——正文(`runAgentTurnBody`)
|
|
95
|
+
* 之后收「链尾闩重同步」(§6.30.5 载体②武装点——履行原正文内同点调用;#448②:入 `finally` ⇒
|
|
96
|
+
* 异常 ∕ 中止逃逸的回合同样重武装;守卫 = `timerRearmBlocked`)。attention 仍在正文链尾原位
|
|
97
|
+
* (谓词读 `_pendingTimers`、不读闩状态 ⇒ 与 sync 无相对序依赖)。 */
|
|
79
98
|
async function runAgentTurnInner(ctx, text, opts) {
|
|
80
|
-
|
|
99
|
+
try {
|
|
100
|
+
await runAgentTurnBody(ctx, text, opts)
|
|
101
|
+
} finally {
|
|
102
|
+
if (!timerRearmBlocked(ctx?.state, ctx?.agent)) ctx?.timerWatch?.sync()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** runAgentTurn 正文(原 `runAgentTurnInner` 逐字——唯一改动 = 链尾 `sync()` 上收收口层 `finally`)。 */
|
|
107
|
+
async function runAgentTurnBody(ctx, text, opts) {
|
|
108
|
+
const { autoTurn = false, skipSession = false, upstreamTurn = false, timerTurn = false } = opts ?? {} // upstreamTurn / timerTurn:系统轮旗标(§6.27.12.5 D/I · §6.30.3 D-TW4)——透传核 `runAgent`,仅供域文本选择
|
|
81
109
|
const { agent, state, pushLine, pushLabel, render, scheduleRender, ensureAssistantLabel, askPermission, askBatchPermission, askQuestion, handleSlash } = ctx
|
|
82
110
|
// 可注入覆盖(测试用);默认走真实实现
|
|
83
111
|
const runAgentImpl = ctx.runAgent ?? runAgent
|
|
@@ -107,17 +135,20 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
107
135
|
state.streaming = ""
|
|
108
136
|
state.reasoning = ""
|
|
109
137
|
state._advisorBlocks = []
|
|
110
|
-
// NOTE (§
|
|
138
|
+
// NOTE (docs/cli/design/TUI.md §6.8 D4): state.subTasks is intentionally NOT reset here — subagent
|
|
111
139
|
// activity blocks persist across turns (the user can still expand a finished
|
|
112
140
|
// child's block from a previous turn). Child tool calls never enter the parent
|
|
113
141
|
// history, so the blocks are the only trace of child activity; memory is
|
|
114
142
|
// bounded by the N2 per-child 500-line ring buffer.
|
|
115
143
|
state.currentTool = null
|
|
116
144
|
state.processingStarted = Date.now()
|
|
145
|
+
// 停滞轻显形(docs/cli/design/TUI.md §7.7 · stall-indicator 批 2026-09-29):静默起算初始锚 =
|
|
146
|
+
// 回合起刻(此后三类可见输出重置——流式 ∕ 工具面 ∕ 子代理面,见 tool-events ∕ subagent-blocks)。
|
|
147
|
+
state.lastOutputAt = Date.now()
|
|
117
148
|
// AGENT-LOOP-ASYNC-POOL.md §6.8 偏差修复 #3(会话 abort 全覆盖):回合链 controller 登记。链头 = 非挂起会话内
|
|
118
149
|
// 且非释放窗口期(suspended/_suspPending 均 false)开启的回合——登记表清零;队列
|
|
119
150
|
// 递归回合(_suspPending 置位期)与会话内回合(suspended=true)继续累积。链条内每次
|
|
120
|
-
// 重建(Ctrl+I
|
|
151
|
+
// 重建(Ctrl+I 中断续跑 ∕ 撞帽续跑——核 `continueDecision` 两续跑格)都登记——挂起会话的 abort 集合
|
|
121
152
|
// (_sessionAbortAll)必须在会话建立时覆盖进入会话以来的全部 controller:只 abort
|
|
122
153
|
// 最后一个会让旧 controller 下 spawn 的 async children 逃逸中止(偏差 #3)。
|
|
123
154
|
// 链头同时清掉上一链条残留的会话句柄(上一链 pool 先 live 后耗尽、未进入会话即结束
|
|
@@ -127,6 +158,9 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
127
158
|
state._turnControllers = []
|
|
128
159
|
agent._sessionAbort = null
|
|
129
160
|
agent._sessionAbortAll = null
|
|
161
|
+
// 链头复位「重武装撤销」粘滞位(#448② KD-6——上一链的显式停止只约束到下一链头;新链
|
|
162
|
+
// 正常重入 ⇒ 链尾照常重武装)。会话内回合不在此列(suspended)——链条内标志始终有效。
|
|
163
|
+
state._timerRearmRevoked = false
|
|
130
164
|
}
|
|
131
165
|
state._turnControllers ??= []
|
|
132
166
|
const makeController = () => {
|
|
@@ -137,7 +171,7 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
137
171
|
state.controller = makeController()
|
|
138
172
|
state.interruptPrompt = null
|
|
139
173
|
// Refresh status bar every second during processing; also refresh when any
|
|
140
|
-
// subagent block is still running so its header elapsed ticks (§
|
|
174
|
+
// subagent block is still running so its header elapsed ticks (docs/cli/design/TUI.md §6.8 D4 —
|
|
141
175
|
// no new timer, the existing ticker carries it). Blocks stay visible after
|
|
142
176
|
// the turn ends, but frozen headers don't need 1s refreshes.
|
|
143
177
|
const subRunning = () => Object.values(state.subTasks ?? {}).some((s) => !s.done)
|
|
@@ -156,55 +190,53 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
156
190
|
try {
|
|
157
191
|
for (let resume = false; ; resume = true) {
|
|
158
192
|
try {
|
|
159
|
-
await runAgentImpl(agent, text, callbacks, { signal: state.controller.signal, resume, autoTurn, upstreamTurn, suspDriven: true, consumeQueuedInput })
|
|
193
|
+
await runAgentImpl(agent, text, callbacks, { signal: state.controller.signal, resume, autoTurn, upstreamTurn, timerTurn, suspDriven: true, consumeQueuedInput })
|
|
160
194
|
flushStream()
|
|
161
195
|
break // Normal completion, exit loop
|
|
162
196
|
} catch (error) {
|
|
163
197
|
flushStream()
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
198
|
+
// 续跑判定 = 核单源(#677 · I10——continueDecision;本端零第二分支——#127 ④ 双写退役)。
|
|
199
|
+
// reason = 回合 controller 的 `signal.reason`(中止面语义原源——D-C1 两义同式)。
|
|
200
|
+
const reason = state.controller?.signal?.reason
|
|
201
|
+
const decision = continueDecision(error, { autoTurn, autoApprove: agent.autoApprove, reason })
|
|
202
|
+
if (decision === "resume") {
|
|
203
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.8 D-C1(round1 #1 区分机制——2026-09-03):interrupt 有 message
|
|
204
|
+
// (Ctrl+I 注入——key-modes handleInterruptMode)⇒ 重建 controller 续跑(agent loop 已把消息注入
|
|
205
|
+
// history,中止的 signal 不能重试)。
|
|
206
|
+
state.controller = makeController()
|
|
207
|
+
resume = true
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
if (decision === "stop") {
|
|
178
211
|
if (reason?.interrupt) {
|
|
179
|
-
// 无 message interrupt
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
// ——interrupt 家族语义(§2 Ctrl+I 同款"提交部分输出")——advisor round1
|
|
186
|
-
// 🟡 裁定:回滚需区分工具/子代理路径的既有完整消息(history 层不可靠)。
|
|
212
|
+
// 无 message interrupt 的注入副作用回滚(Ctrl+C 首按停回合——key-handler abort({ interrupt: true })
|
|
213
|
+
// 不带 message;agent.mjs 中断三段无条件把 "[User interrupt: <msg>]" 落 history,对 message
|
|
214
|
+
// 存在性无守卫 ⇒ 无 message 时成为 "[User interrupt: undefined]" 垃圾上下文)。D-C2 agent.mjs
|
|
215
|
+
// 零改动约束下在回合层回滚尾部垃圾(确定性:中断注入恒为最后一条——break 前 history 不再追加)。
|
|
216
|
+
// partial 部分输出不回滚——interrupt 家族语义(§2 Ctrl+I 同款"提交部分输出");advisor round1 🟡
|
|
217
|
+
// 裁定:回滚需区分工具/子代理路径的既有完整消息(history 层不可靠)。
|
|
187
218
|
const h = agent.history
|
|
188
219
|
while (h?.length > 0 && String(h.at(-1)?.content ?? "") === "[User interrupt: undefined]") h.pop()
|
|
189
220
|
}
|
|
190
221
|
pushLine("[stopped]", C.warn)
|
|
222
|
+
// #448② KD-6「显式撤销」:Ctrl+C 全停(`{ abortTrigger: "stop" }`——key-handler 二按语义)
|
|
223
|
+
// ⇒ 落粘滞位:链尾闩不重武装(链头复位后新链恢复——见 `timerRearmBlocked`)。
|
|
224
|
+
if (reason?.abortTrigger === "stop") state._timerRearmRevoked = true
|
|
191
225
|
if (opts?._logOutcome) opts._logOutcome.result = "stopped"
|
|
192
226
|
break
|
|
193
227
|
}
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
break
|
|
207
|
-
}
|
|
228
|
+
if (decision === "cap-stop") {
|
|
229
|
+
// TURN-CAP-CONTINUE.md §1 #7 / D-TC15(2026-09-26):digest 无人值守档**不再自续**
|
|
230
|
+
// (原 AUTO 自动 resume 支退役——静默续期 = F8 禁止;无人可答 ⇒ 撞帽即收口)。
|
|
231
|
+
// 部分消化留在历史,会话回挂起——结果不丢,只是不再烧轮次。
|
|
232
|
+
// #726 写点2:cap 可见行 ∥ cap 记录同点双动作(文案 = lifecycle-records 单一实现)。
|
|
233
|
+
const capRec = digestCapRecord(error.turn)
|
|
234
|
+
for (const l of digestTraceLines(capRec)) pushLine(l.text, l.color)
|
|
235
|
+
pushRecord(recordCarrier(state, agent), capRec)
|
|
236
|
+
if (opts?._logOutcome) opts._logOutcome.result = "stopped"
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
if (decision === "ask") {
|
|
208
240
|
pushLabel(`❯ Continue`, ansi.bold + C.warn)
|
|
209
241
|
pushLine(`Ran ${error.turn} turns (limit ${error.turn}). Continue?`, C.warn)
|
|
210
242
|
// Pause to ask: reuse permission mechanism
|
|
@@ -228,6 +260,7 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
228
260
|
state.controller = makeController()
|
|
229
261
|
continue
|
|
230
262
|
}
|
|
263
|
+
// decision === "error":其余异常 —— provider 失败面(X8 原语义)——
|
|
231
264
|
// X8(2026-09-20 端差·显示面消差批):provider 失败面 = 友好首行(判据同 VSC
|
|
232
265
|
// panel-turn-loop.mjs:152-153:原文首行 + URL 脱敏)+ 诊断两行(Provider/Model)+ Retry
|
|
233
266
|
// 询问(复用 permission 机制——同意 ⇒ 重建 controller + resume 重入,与 ContinueError
|
|
@@ -263,9 +296,9 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
263
296
|
state.processing = false
|
|
264
297
|
state._advisorBlocks = []
|
|
265
298
|
// AGENT-LOOP-ASYNC-POOL.md §6.8 D-S1/D-S8:回合正常结束且后台池仍 live → 挂起会话:子agent 区块保持 live
|
|
266
|
-
//
|
|
299
|
+
// (不冻结——冻结落消费窗:起跑窗 ∥ reclaim ∥ 退出兜底),本次回合 controller 交会话层作 abort 句柄
|
|
267
300
|
// (挂起期 Ctrl+C 中止全部后台子代理);池空 / 中断 / 错误 → 现状 freezeAllSubTasks
|
|
268
|
-
// (中断态块标 interrupted
|
|
301
|
+
// (中断态块标 interrupted;正常态块已在消费窗各自冻结)。
|
|
269
302
|
const willSuspend = poolLive(agent)
|
|
270
303
|
// AGENT-LOOP-ASYNC-POOL.md §6.8:挂起会话内回合(digest/会话内用户回合——skipSession 且 suspended)的
|
|
271
304
|
// 收尾**不冻结驻留块**——已消化(pinned 且条目已注入)块由 suspensionSession 在
|
|
@@ -371,12 +404,17 @@ async function runAgentTurnInner(ctx, text, opts) {
|
|
|
371
404
|
// 首行复位成死代码)——子代理结果退化到"下个回合尾才注入"、状态行/自动消化/区块
|
|
372
405
|
// 驻留全消失;释放窗口守卫同步失效(Enter 并发开第二个 runAgentTurn——双驱动器
|
|
373
406
|
// 竞态复现)。
|
|
407
|
+
if (state._suspAborted) state._timerRearmRevoked = true // #448② KD-6「会话停」:链尾闩不重武装(粘滞位——复位前捕获)
|
|
374
408
|
state._suspAborted = false
|
|
375
409
|
}
|
|
376
410
|
|
|
411
|
+
// §6.30.5 载体②武装点(回合链尾「无人自动接手」判据**邻位**):已上收收口层 `finally`
|
|
412
|
+
// (#448② 异常径重武装 —— 见 `runAgentTurnInner`;本处零注册 ∕ 零动作)。
|
|
413
|
+
|
|
377
414
|
// 第 33 批(TUI §14.3(d)):顶层链尾(队列续发循环与挂起会话退出**之后**)——无人自动
|
|
378
415
|
// 接手 ⇒ 置 attention 位(渲染层实时派生 blocked/awaiting;用户任意输入清位)。D-AT6:
|
|
379
416
|
// 不放在回合末 finally——那之后还有队列续发 / 挂起会话(digest 自动消费),不是「需要用户」。
|
|
417
|
+
// timer-wake 批:谓词内新增「在途 timer ∧ 唤醒会武装」除外(§7.1 awaiting 行限定)。
|
|
380
418
|
if (userNeededAtTurnEnd(state, agent, skipSession)) {
|
|
381
419
|
state.attentionAwaiting = true
|
|
382
420
|
render()
|