thincoder 0.12.59 → 0.12.60
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 +38 -3
- package/README.md +2 -2
- package/bin/thincoder.mjs +80 -19
- package/package.json +4 -3
- package/src/acp/bridge.mjs +7 -4
- package/src/advisor/messages.mjs +24 -4
- package/src/advisor/run.mjs +35 -33
- package/src/advisor.mjs +25 -6
- package/src/agent/completion.mjs +17 -11
- package/src/agent/dispatch.mjs +102 -19
- package/src/agent/helpers.mjs +36 -0
- package/src/agent/record-results.mjs +46 -10
- package/src/agent/run-stages.mjs +227 -0
- package/src/agent/setup-reminders.mjs +62 -0
- package/src/agent/setup.mjs +18 -2
- package/src/agent/spawn-child.mjs +29 -4
- package/src/agent-tools/advisor-async.mjs +456 -0
- package/src/agent-tools/advisor.mjs +110 -108
- package/src/agent-tools/async-settle.mjs +191 -0
- package/src/agent-tools/consult.mjs +121 -102
- package/src/agent-tools/design-token.mjs +104 -0
- package/src/agent-tools/eng.mjs +24 -29
- package/src/agent-tools/escalate-async.mjs +286 -0
- package/src/agent-tools/read-history.mjs +155 -31
- package/src/agent-tools/recent-changes.mjs +2 -1
- package/src/agent-tools/settings.mjs +7 -17
- package/src/agent-tools/subagent-actions.mjs +168 -130
- package/src/agent-tools/subagent-async.mjs +129 -174
- package/src/agent-tools/subagent-panel.mjs +153 -0
- package/src/agent-tools/subagent-run.mjs +202 -0
- package/src/agent-tools/subagent-scheduler.mjs +45 -21
- package/src/agent-tools/subagent-spawn.mjs +406 -0
- package/src/agent-tools/subagent.mjs +107 -555
- package/src/agent-tools/verify.mjs +118 -270
- package/src/agent.mjs +57 -190
- package/src/cli/distill-command.mjs +10 -4
- package/src/cli/make-agent.mjs +3 -1
- package/src/cli/memory-command.mjs +2 -1
- package/src/cli/permission.mjs +2 -2
- package/src/cli/setup-wizard.mjs +17 -12
- package/src/config.mjs +56 -8
- package/src/context.mjs +5 -147
- package/src/crash-reports.mjs +123 -0
- package/src/distill.mjs +11 -11
- package/src/explore-distill.mjs +155 -0
- package/src/memory/code-sync.mjs +2 -1
- package/src/memory/core.mjs +6 -193
- package/src/memory/delete.mjs +234 -0
- package/src/memory/docs.mjs +58 -48
- package/src/memory.mjs +3 -1
- package/src/peer-domains.mjs +265 -0
- package/src/peer-instances.mjs +231 -0
- package/src/prompt-overlays.mjs +25 -0
- package/src/prompts/advisor-design.md +9 -76
- package/src/prompts/advisor-round1.md +9 -68
- package/src/prompts/advisor-round2.md +7 -54
- package/src/prompts/advisor-round3.md +7 -54
- package/src/prompts/coder.md +7 -50
- package/src/prompts/consult-base.md +4 -24
- package/src/prompts/discipline.md +26 -44
- package/src/prompts/eng-coder.md +7 -32
- package/src/prompts/engineering-sub.md +3 -23
- package/src/prompts/engineering.md +53 -306
- package/src/prompts/explore.md +3 -12
- package/src/prompts/main.md +10 -32
- package/src/prompts/methodology-template.md +28 -48
- package/src/prompts/plan.md +2 -9
- package/src/prompts/system.md +16 -35
- package/src/provider/core.mjs +6 -67
- package/src/provider/errors.mjs +76 -0
- package/src/provider/retry.mjs +8 -45
- package/src/session-gc.mjs +214 -0
- package/src/session-guard.mjs +47 -0
- package/src/session-rename.mjs +38 -0
- package/src/session-slots.mjs +181 -58
- package/src/session.mjs +48 -89
- package/src/token-ttl.mjs +273 -0
- package/src/tools/checklist-sync.mjs +181 -0
- package/src/tools/checklist.mjs +52 -39
- package/src/tools/edit-batch.mjs +109 -10
- package/src/tools/edit-diff.mjs +110 -27
- package/src/tools/edit.md +17 -12
- package/src/tools/execute.mjs +31 -4
- package/src/tools/file.mjs +11 -6
- package/src/tools/git.mjs +14 -6
- package/src/tools/glob-dialect.mjs +130 -0
- package/src/tools/glob.md +3 -3
- package/src/tools/grep.md +1 -1
- package/src/tools/index.mjs +5 -6
- package/src/tools/ops.mjs +175 -3
- package/src/tools/patch.mjs +3 -3
- package/src/tools/question.md +3 -0
- package/src/tools/read.md +0 -1
- package/src/tools/shared.mjs +14 -13
- package/src/tools/system.mjs +44 -9
- package/src/tools/wait_for.md +22 -0
- package/src/tui/agent-turn.mjs +17 -228
- package/src/tui/cmd-config.mjs +48 -7
- package/src/tui/cmd-eng.mjs +20 -16
- package/src/tui/cmd-mcp.mjs +8 -2
- package/src/tui/cmd-new.mjs +3 -2
- package/src/tui/cmd-session.mjs +19 -4
- package/src/tui/cmd-think.mjs +10 -10
- package/src/tui/cmd-upgrade.mjs +19 -4
- package/src/tui/config-helpers.mjs +28 -16
- package/src/tui/distill-cmd.mjs +1 -1
- package/src/tui/index.mjs +3 -2
- package/src/tui/interaction.mjs +3 -3
- package/src/tui/mouse.mjs +7 -1
- package/src/tui/pickers.mjs +40 -22
- package/src/tui/render-segments.mjs +27 -10
- package/src/tui/startup.mjs +4 -0
- package/src/tui/subagent-blocks.mjs +95 -263
- package/src/tui/subagent-children.mjs +176 -0
- package/src/tui/subagent-freeze.mjs +172 -0
- package/src/tui/subagent-panel.mjs +61 -23
- package/src/tui/suspension-drive.mjs +351 -0
- package/src/tui/tool-args.mjs +3 -3
- package/src/tui/tool-display.mjs +142 -0
- package/src/tui/tool-events.mjs +37 -173
- package/src/tui/tui-lifecycle.mjs +29 -0
- package/src/tui/update-notice.mjs +4 -0
- package/src/tui/wizard.mjs +12 -6
- package/src/tools/pdf-parse-text.mjs +0 -497
- package/src/tools/pdf-parse-xref.mjs +0 -499
- package/src/tools/pdf.mjs +0 -155
- package/src/tools/read_pdf.md +0 -21
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* subagent-actions.mjs — subagent 动作执行器(§19/§19.5/§19.6——2026-09-05 自
|
|
3
3
|
* subagent-async.mjs 拆分——Module Split Policy §20.9——纯迁移零行为变化)。
|
|
4
4
|
* 内容:executeStatusAction(§19.5 D-M5——非阻塞池查询——touchedSummary/
|
|
5
|
-
* shortTouchedPath/statusFields 摘要助手随行)/
|
|
6
|
-
*
|
|
5
|
+
* shortTouchedPath/statusFields 摘要助手随行)/ executeObserveAction + executeSendAction
|
|
6
|
+
* (SUBAGENT-OBSERVE-SEND——observe 只读/send 控制豁免注入)/ executeEscalateAction(§19
|
|
7
7
|
* D-M4——飞刀——touchedFilesNote 随行)。
|
|
8
|
-
* check
|
|
8
|
+
* §19.8 check 删除后仅 cancel 动作执行器与共享 post-spawn 管线留在 subagent-async.mjs;
|
|
9
9
|
* §20 调度器 + 文件域组在 ./subagent-scheduler.mjs(describeBlockers 由此导入)。
|
|
10
|
+
* 2026-09-08 二次拆分:§19.6 面板段迁至 ./subagent-panel.mjs(601 > 500 硬限跨档——
|
|
11
|
+
* 尾部 re-export executePanelAction 保 subagent.mjs 既有 import 面)+ ASYNC-RESULT-
|
|
12
|
+
* CONTAINER.md D1 落地(池访问点改 getAsyncPool accessor)。
|
|
10
13
|
*/
|
|
11
14
|
import { isAbsolute, relative } from "node:path"
|
|
12
15
|
import { runAgent, createAgent, CODER_OVERLAY, DEFAULT_SUBAGENT_TURNS } from "../agent.mjs"
|
|
@@ -17,16 +20,20 @@ import {
|
|
|
17
20
|
import { logEvent, errText } from "../log.mjs"
|
|
18
21
|
import { describeBlockers, detectStall, STALL_NOTE } from "./subagent-scheduler.mjs"
|
|
19
22
|
import { resolveChildProvider, mergeChildMutations } from "./subagent-async.mjs"
|
|
23
|
+
import { launchEscalateAsync } from "./escalate-async.mjs"
|
|
24
|
+
// ASYNC-RESULT-CONTAINER.md D1:池 accessor(absorb 双池——advisor 独立池无队列)
|
|
25
|
+
import { getAsyncPool } from "./async-settle.mjs"
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* subagent action:"status" (§19 D-M2, new): NON-BLOCKING async-pool query —
|
|
23
|
-
* returns immediately
|
|
24
|
-
*
|
|
25
|
-
* to _pendingAsyncResults during a suspension
|
|
26
|
-
* run start) are no longer in the pool and
|
|
29
|
+
* returns immediately and never consumes a result (results belong to the auto
|
|
30
|
+
* channel — §19.8: no check action any more). Source of truth = the pool
|
|
31
|
+
* (_asyncSubagents): entries moved to _pendingAsyncResults during a suspension
|
|
32
|
+
* (§17 D-S3 ② — injected at the next run start) are no longer in the pool and
|
|
33
|
+
* are NOT counted as done-waiting.
|
|
27
34
|
* - id given → { id, role, status, model?, elapsedSec?, turn?, maxTurns?,
|
|
28
35
|
* touchedFiles?/touchedMore?/touched? ... } for that entry; unknown id → error
|
|
29
|
-
* (same wording as
|
|
36
|
+
* (T12 semantics — unknown async-subagent id error, same wording as the pool).
|
|
30
37
|
* - id omitted → { overview: { running: [{id, role, model, elapsedSec, turn,
|
|
31
38
|
* maxTurns, touchedFiles?/touched?}], queued: [{id, role, position, touched?}],
|
|
32
39
|
* done: [{id, role}] } } — live queue positions (index in _asyncQueue + 1).
|
|
@@ -34,7 +41,8 @@ import { resolveChildProvider, mergeChildMutations } from "./subagent-async.mjs"
|
|
|
34
41
|
* 计数——相对查询方 cwd;0 改动 → touched 占位);queued 条目带 touched 占位
|
|
35
42
|
* "—(未启动)";done/error/取消条目无 touched 字段(round3 #9)。
|
|
36
43
|
* A settled-but-unconsumed entry (settled during a NORMAL turn) reports done
|
|
37
|
-
* with a "not yet consumed" note —
|
|
44
|
+
* with a "not yet consumed" note — the auto channel (turn-end collection / the
|
|
45
|
+
* suspension digest) still delivers it afterwards (§19.8: sole consumption path).
|
|
38
46
|
*/
|
|
39
47
|
/** §19.5 D-M5 decision-field assembly (F9): running entries report
|
|
40
48
|
* {id, role, model, elapsedSec, turn, maxTurns} — the data needed to decide
|
|
@@ -86,7 +94,8 @@ function statusFields(entry, cwd) {
|
|
|
86
94
|
|
|
87
95
|
export function executeStatusAction(args, ctx) {
|
|
88
96
|
const agent = ctx.agent
|
|
89
|
-
const map = agent
|
|
97
|
+
const map = getAsyncPool(agent, "subagent") ?? new Map()
|
|
98
|
+
const advisors = getAsyncPool(agent, "advisor") ?? new Map()
|
|
90
99
|
const queue = agent._asyncQueue ?? []
|
|
91
100
|
const queuedPosition = (id) => {
|
|
92
101
|
const i = queue.findIndex((e) => String(e.id) === id)
|
|
@@ -95,8 +104,13 @@ export function executeStatusAction(args, ctx) {
|
|
|
95
104
|
const { id } = args ?? {}
|
|
96
105
|
if (id !== undefined && id !== null && String(id) !== "") {
|
|
97
106
|
const key = String(id)
|
|
98
|
-
|
|
107
|
+
// §24 D-24b: by-id queries fall through to the advisor pool (shared counter —
|
|
108
|
+
// ids are unique across both pools; role identifies the kind).
|
|
109
|
+
const entry = map.get(key) ?? advisors.get(key)
|
|
99
110
|
if (!entry) {
|
|
111
|
+
// Wording kept exact — locked by subagent-async/tool tests (unknown-id error
|
|
112
|
+
// semantics); an unknown id simply names neither pool (the pools share the
|
|
113
|
+
// id counter, so the message stays unambiguous).
|
|
100
114
|
return JSON.stringify({ id: key, status: "error", error: `unknown async subagent id: ${key}` })
|
|
101
115
|
}
|
|
102
116
|
const target = statusFields(entry, agent.cwd)
|
|
@@ -119,16 +133,17 @@ export function executeStatusAction(args, ctx) {
|
|
|
119
133
|
}
|
|
120
134
|
return JSON.stringify(out)
|
|
121
135
|
}
|
|
122
|
-
// done = settled during this turn and not yet consumed —
|
|
123
|
-
// (§
|
|
136
|
+
// done = settled during this turn and not yet consumed — the auto channel still
|
|
137
|
+
// delivers it (§19.8: turn-end collection / the suspension digest — no fetch action).
|
|
124
138
|
target.status = "done"
|
|
125
139
|
target.done = true
|
|
126
140
|
if (entry.error) target.error = entry.error
|
|
127
|
-
target.note = "settled, not yet consumed —
|
|
141
|
+
target.note = "settled, not yet consumed — delivered by the auto channel (turn-end collection or the suspension digest injects it)"
|
|
128
142
|
return JSON.stringify(target)
|
|
129
143
|
}
|
|
130
144
|
const overview = { running: [], queued: [], done: [] }
|
|
131
|
-
|
|
145
|
+
const mapEntries = [...map.values(), ...advisors.values()]
|
|
146
|
+
for (const entry of mapEntries) {
|
|
132
147
|
if (entry.status === "running") overview.running.push(statusFields(entry, agent.cwd))
|
|
133
148
|
else if (entry.status === "queued") {
|
|
134
149
|
// §20:queued 条目补 waiting/reason(F-SD4——依赖/冲突原因模型可见);
|
|
@@ -157,145 +172,158 @@ export function executeStatusAction(args, ctx) {
|
|
|
157
172
|
}
|
|
158
173
|
|
|
159
174
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
160
|
-
//
|
|
175
|
+
// SUBAGENT-OBSERVE-SEND(docs/design/SUBAGENT-OBSERVE-SEND.md——CLI 端)
|
|
176
|
+
// observe(D1——readonly 查询)+ send(D2——控制类豁免注入引导)——动作执行器。
|
|
177
|
+
// 目标 = 父自身 spawn 的异步子代理池条目(_asyncSubagents——非 advisor/escalate——
|
|
178
|
+
// 后者共享 id 计数但非"读写子代理",错误路径明示)。
|
|
161
179
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
162
180
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
/** D1/N2 摘要形态:从子代理 _fullHistory(real 消息——pushReal 累积)倒序收最近 N 个
|
|
182
|
+
* assistant 回合的一行描述(newest-first;assistant 消息 = 一回合——带 tool_calls =
|
|
183
|
+
* 工具轮、独立 content = 回复首行)。返回纯摘要非全量(N2 隔离——不把子代理噪音灌父)。 */
|
|
184
|
+
function recentTurnLines(child, limit) {
|
|
185
|
+
const hist = child?._fullHistory
|
|
186
|
+
if (!Array.isArray(hist)) return []
|
|
187
|
+
const out = []
|
|
188
|
+
for (let i = hist.length - 1; i >= 0 && out.length < limit; i--) {
|
|
189
|
+
const m = hist[i]
|
|
190
|
+
if (m?.role !== "assistant") continue
|
|
191
|
+
const tcs = Array.isArray(m.tool_calls)
|
|
192
|
+
? m.tool_calls.filter((t) => typeof t?.function?.name === "string")
|
|
193
|
+
: []
|
|
194
|
+
if (tcs.length) out.push(`tools: ${tcs.map((t) => t.function.name).join(", ")}`)
|
|
195
|
+
else if (typeof m.content === "string" && m.content.trim()) {
|
|
196
|
+
const first = m.content.trim().split(/\r?\n/).find((l) => l.trim())
|
|
197
|
+
out.push((first ?? m.content.trim()).slice(0, 120))
|
|
198
|
+
} else out.push("(assistant — no content)")
|
|
199
|
+
}
|
|
200
|
+
return out
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function queuedPositionOf(agent, key) {
|
|
204
|
+
const i = (agent?._asyncQueue ?? []).findIndex((e) => String(e.id) === key)
|
|
205
|
+
return i >= 0 ? i + 1 : undefined
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** recent 参数钳制(默认 ~5 条摘要——可参数;钳到 1..20 防超长 N2 失控)。 */
|
|
209
|
+
function clampRecent(n) {
|
|
210
|
+
const v = Number.parseInt(n, 10)
|
|
211
|
+
if (!Number.isFinite(v)) return 5
|
|
212
|
+
return Math.max(1, Math.min(20, v))
|
|
167
213
|
}
|
|
168
214
|
|
|
169
215
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* 错误信息明确(模型可解释 + 自助修正)。返回 { ok:true } 或 { err }。
|
|
216
|
+
* subagent action:"observe"(D1——READONLY 查询,父回合内):按 id 拉运行中异步子代理的
|
|
217
|
+
* 最近活动快照——从 entry.childAgent(subagent-run.mjs start() 绑定)读**已落回合**摘要
|
|
218
|
+
* (_fullHistory 最近 N 条)+ _touchedFiles + turn/maxTurns + status + **in-flight 当前
|
|
219
|
+
* 工具**(评审 #1——从 dispatch runOne 维护的 child._inflightTools 读——非只读已落
|
|
220
|
+
* history:卡在长工具调用时 history 无新回合、恰是 observe 要检测的卡死态)。返摘要非
|
|
221
|
+
* 全量(N2)。observe = readonly——running/queued/done 均可查(done 终报 / queued 占位)。
|
|
177
222
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return { err: "panel unavailable — no CLI TUI panel mirror in this session (headless / VS Code / subagent contexts — freeze unavailable; panel is CLI-TUI-only, AC-P4)" }
|
|
223
|
+
export function executeObserveAction(args, ctx) {
|
|
224
|
+
if ((ctx.depth ?? 0) > 0) {
|
|
225
|
+
return JSON.stringify({ status: "error", error: "observe is only available at depth 0 — a child agent has no async pool of its own (AGENT-LOOP.md §7.2)" })
|
|
182
226
|
}
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return {
|
|
227
|
+
const agent = ctx.agent
|
|
228
|
+
const id = args?.id
|
|
229
|
+
if (id === undefined || id === null || String(id) === "") {
|
|
230
|
+
return JSON.stringify({ status: "error", error: "observe requires the id of the async subagent to inspect (from the async spawn return) — pass the id; omitting it observes nothing (SUBAGENT-OBSERVE-SEND)" })
|
|
187
231
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
232
|
+
const key = String(id)
|
|
233
|
+
const entry = getAsyncPool(agent, "subagent")?.get(key)
|
|
234
|
+
if (!entry) {
|
|
235
|
+
if (getAsyncPool(agent, "advisor")?.has(key)) {
|
|
236
|
+
return JSON.stringify({ status: "error", error: `id ${key} is an async ADVISOR review — observe is for async subagents (read/edit children); track an advisor with action:'status' or wait for its auto-delivered report` })
|
|
191
237
|
}
|
|
192
|
-
|
|
193
|
-
return { err: `block ${key} is still running — freeze only reclaims awaitingDigest blocks whose report is already digested; a running block freezes on its own settle (or stop it with action:'cancel' if it is a background async child)` }
|
|
194
|
-
}
|
|
195
|
-
return { err: `block ${key} is in state ${block.status} — freeze only reclaims awaitingDigest blocks whose report is already digested` }
|
|
196
|
-
}
|
|
197
|
-
if (blockKeyIn(agent._asyncSubagents, key)) {
|
|
198
|
-
return { err: `block ${key} still has a live pool entry — it is NOT a digested-stuck block (freeze refused; status action shows the pool)` }
|
|
238
|
+
return JSON.stringify({ status: "error", error: `unknown async subagent id: ${key}` })
|
|
199
239
|
}
|
|
200
|
-
|
|
201
|
-
|
|
240
|
+
const child = entry.childAgent
|
|
241
|
+
const out = { id: key, role: entry.role, status: entry.status }
|
|
242
|
+
if (entry.status === "running") {
|
|
243
|
+
out.turn = entry.turn ?? 0
|
|
244
|
+
out.maxTurns = entry.maxTurns ?? 0
|
|
245
|
+
Object.assign(out, touchedSummary(entry, agent.cwd))
|
|
246
|
+
// in-flight 当前工具(评审 #1):child._inflightTools Set——dispatch runOne 在工具
|
|
247
|
+
// 执行前后维护——LLM 生成/工具间空隙为空;子代理 await 长工具调用时父回合可见它。
|
|
248
|
+
const inflight = child?._inflightTools
|
|
249
|
+
if (inflight instanceof Set && inflight.size > 0) out.currentTool = [...inflight]
|
|
250
|
+
const n = clampRecent(args?.recent)
|
|
251
|
+
out.recentTurns = recentTurnLines(child, n)
|
|
252
|
+
out.note = `observing running ${entry.role}#${key} — recentTurns newest-first (cap ${n}); currentTool shown only while a tool is executing (stuck detection); sent directions consume at the next turn boundary`
|
|
253
|
+
} else if (entry.status === "queued") {
|
|
254
|
+
out.position = queuedPositionOf(agent, key) ?? entry.position ?? null
|
|
255
|
+
out.note = "queued — not started yet; no activity to observe (starts when the slot frees / dependencies clear; action:'send' targets running only)"
|
|
256
|
+
} else {
|
|
257
|
+
// done = settled this turn, not yet consumed — final report rides the auto channel.
|
|
258
|
+
out.done = true
|
|
259
|
+
out.turn = entry.turn ?? 0
|
|
260
|
+
out.maxTurns = entry.maxTurns ?? 0
|
|
261
|
+
out.recentTurns = child ? recentTurnLines(child, clampRecent(args?.recent)) : []
|
|
262
|
+
out.note = "settled — the final report is delivered by the auto channel (turn-end collection or the suspension digest); observe returns the activity summary, not the full report (N2)"
|
|
202
263
|
}
|
|
203
|
-
return
|
|
264
|
+
return JSON.stringify(out)
|
|
204
265
|
}
|
|
205
266
|
|
|
206
267
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* = 报告已消化但块仍驻留——异常块——freeze 候选;模型可定位解释 UI 怪相)。
|
|
214
|
-
* - freeze:key(D-P3 门控通过 → 发 key + "/" + ⟦ev⟧done 哨兵字面 token——
|
|
215
|
-
* onToken——TUI routeSubToken 冻结回收——落位复用 sub._freezeAt settle 锚点
|
|
216
|
-
* splice,无锚点尾推兜底——§17.5.5 同口径——round1 #2)。
|
|
217
|
-
* 无镜像(headless/VS Code——D-P2 round1 #1:webview 无 state.subTasks 对应物——
|
|
218
|
-
* 7.2.3.2 #8 先例)→ view 恒降级池视图(_asyncSubagents + _pendingAsyncResults
|
|
219
|
-
* 合成)+ no panel 注;freeze 报不可用。CLI-only 完整能力(AC-P4)。
|
|
268
|
+
* subagent action:"send"(D2——控制类豁免,同 cancel/panel-freeze——父回合内显式调用即
|
|
269
|
+
* 授权):按 id 向运行中异步子代理注入一条引导消息——push 进 entry._injected(仅 running
|
|
270
|
+
* 异步可 send);子回合边界经 consumeInjected 回调消费 → pushReal 成 user 回合进子历史
|
|
271
|
+
* → 当作普通指令处理(注入不等同偏离豁免——子收敛/审计纪律不变)。settle/cancel/unknown
|
|
272
|
+
* /queued → 明确错误。send→settle 竞态:入队后子未及下回合边界即 settle → 消息未投递
|
|
273
|
+
* → settle 收尾附报告提示(不在此报错——send 返回时无法预知)。
|
|
220
274
|
*/
|
|
221
|
-
export function
|
|
275
|
+
export function executeSendAction(args, ctx) {
|
|
276
|
+
if ((ctx.depth ?? 0) > 0) {
|
|
277
|
+
return JSON.stringify({ status: "error", error: "send is only available at depth 0 — a child agent has no async pool of its own (AGENT-LOOP.md §7.2)" })
|
|
278
|
+
}
|
|
222
279
|
const agent = ctx.agent
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
:
|
|
226
|
-
// ── freeze 面(优先——D-P2 单动作双参互斥)──
|
|
227
|
-
if (freezeKey) {
|
|
228
|
-
if ((ctx.depth ?? 0) > 0) {
|
|
229
|
-
return JSON.stringify({ status: "error", error: "panel freeze is only available at depth 0 — a child agent has no panel of its own (AGENT-LOOP.md §19.6 D-P2)" })
|
|
230
|
-
}
|
|
231
|
-
const gate = panelFreezeGate(agent, freezeKey)
|
|
232
|
-
if (gate.err) return JSON.stringify({ status: "error", error: gate.err })
|
|
233
|
-
if (!ctx.callbacks?.onToken) {
|
|
234
|
-
return JSON.stringify({ status: "error", error: `panel mirror present but no token relay in this context — the freeze of ${freezeKey} cannot reach the TUI` })
|
|
235
|
-
}
|
|
236
|
-
// 门控通过 → 发 done 冻结事件(settle 同机制字面格式——TUI routeSubToken done
|
|
237
|
-
// 分支冻结回收——落位 _freezeAt settle 锚点 splice;无锚点(旧会话残留)时
|
|
238
|
-
// freezeSubTaskLines 尾推兜底——注明两种落位,模型不被误导(advisor 🟡1)。
|
|
239
|
-
ctx.callbacks.onToken(`${freezeKey}/⟦ev⟧done\x1e0\x1e0\x1edone\x1e`)
|
|
240
|
-
return JSON.stringify({
|
|
241
|
-
key: freezeKey,
|
|
242
|
-
status: "frozen",
|
|
243
|
-
note: "done freeze event issued — the TUI reclaimed the block into the conversation (spliced at its settle anchor when one is recorded, else appended at the current stream end — §17.5.5 same-rule position)",
|
|
244
|
-
})
|
|
280
|
+
const id = args?.id
|
|
281
|
+
if (id === undefined || id === null || String(id) === "") {
|
|
282
|
+
return JSON.stringify({ status: "error", error: "send requires the id of the running async subagent to direct (from the async spawn return) — omitting it means an unspecified target (SUBAGENT-OBSERVE-SEND)" })
|
|
245
283
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return JSON.stringify({ status: "error", error: "panel has nothing to do — view:false with no freeze key; pass freeze:'role#N' to reclaim a digested-stuck block, or omit view (defaults to true)" })
|
|
284
|
+
const message = args?.message
|
|
285
|
+
if (typeof message !== "string" || !message.trim()) {
|
|
286
|
+
return JSON.stringify({ status: "error", error: "send requires the message to inject — the direction the running subagent should treat as an ordinary user instruction at its next turn boundary" })
|
|
250
287
|
}
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const queue = agent._asyncQueue ?? []
|
|
257
|
-
for (const e of [...(agent._asyncSubagents?.values() ?? [])]) {
|
|
258
|
-
const b = { key: `${e.role}#${e.id}`, role: e.role }
|
|
259
|
-
if (e.status === "running") {
|
|
260
|
-
b.status = "running"
|
|
261
|
-
b.elapsedSec = e.startedAt ? Math.max(0, Math.floor((Date.now() - e.startedAt) / 1000)) : 0
|
|
262
|
-
} else if (e.status === "queued") {
|
|
263
|
-
b.status = "queued"
|
|
264
|
-
const qi = queue.indexOf(e)
|
|
265
|
-
b.position = qi >= 0 ? qi + 1 : (e.position ?? null)
|
|
266
|
-
} else {
|
|
267
|
-
b.status = "done" // 回合内 settle 未取——status action 可查/check 可取回
|
|
268
|
-
}
|
|
269
|
-
blocks.push(b)
|
|
288
|
+
const key = String(id)
|
|
289
|
+
const entry = getAsyncPool(agent, "subagent")?.get(key)
|
|
290
|
+
if (!entry) {
|
|
291
|
+
if (getAsyncPool(agent, "advisor")?.has(key)) {
|
|
292
|
+
return JSON.stringify({ status: "error", error: `id ${key} is an async ADVISOR review — send is for async subagents; you cannot inject direction into a running review (AGENT-LOOP.md §7.2)` })
|
|
270
293
|
}
|
|
271
|
-
|
|
272
|
-
blocks.push({ key: `${e.role}#${e.id}`, role: e.role, status: "awaitingDigest", note: "report pending — injected at the next run start (§17)" })
|
|
273
|
-
}
|
|
274
|
-
return JSON.stringify({
|
|
275
|
-
degraded: true,
|
|
276
|
-
note: "no panel — this session has no CLI TUI panel mirror (headless / VS Code / subagent context — panel view is CLI-TUI-only, AC-P4); pool-derived view below; action:'status' shows the full pool",
|
|
277
|
-
panel: blocks,
|
|
278
|
-
})
|
|
294
|
+
return JSON.stringify({ status: "error", error: `unknown async subagent id: ${key}` })
|
|
279
295
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
296
|
+
if (entry.done || entry.cancelled) {
|
|
297
|
+
return JSON.stringify({ status: "error", error: `async subagent #${key} has ${entry.cancelled ? "been cancelled" : "already settled"} — nothing to send (you cannot inject into a finished subagent; re-spawn with the direction instead)` })
|
|
298
|
+
}
|
|
299
|
+
if (entry.status !== "running") {
|
|
300
|
+
return JSON.stringify({ status: "error", error: `async subagent #${key} is ${entry.status} (not running) — send only targets a RUNNING async subagent; a queued one has not started its turn loop yet` })
|
|
301
|
+
}
|
|
302
|
+
entry._injected ??= []
|
|
303
|
+
entry._injected.push(String(message).trim())
|
|
304
|
+
return JSON.stringify({
|
|
305
|
+
id: key,
|
|
306
|
+
status: "delivered",
|
|
307
|
+
queued: entry._injected.length,
|
|
308
|
+
note: `message queued for ${entry.role}#${key} — consumed as an ordinary user instruction at its next turn boundary (after its current tool finishes — non-interrupting). If it settles first, its report carries an "undelivered" note.`,
|
|
290
309
|
})
|
|
291
|
-
return JSON.stringify({ panel })
|
|
292
310
|
}
|
|
293
311
|
|
|
312
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
313
|
+
// §19.6 subagent panel 检查工具(AGENT-LOOP.md §19.6——F-P1..P3/D-P1..P4)
|
|
314
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
315
|
+
// 2026-09-08 拆分(Module Split Policy——601 > 500 硬限跨档):§19.6 面板段迁至
|
|
316
|
+
// ./subagent-panel.mjs(executePanelAction/panelFreezeGate/blockKeyIn——verbatim +
|
|
317
|
+
// ASYNC-RESULT-CONTAINER D1/D2 落地);本面保留 re-export 保 subagent.mjs 既有
|
|
318
|
+
// import 面(subagent-async 尾部 re-export 先例)。
|
|
319
|
+
export { executePanelAction } from "./subagent-panel.mjs"
|
|
320
|
+
|
|
294
321
|
/**
|
|
295
|
-
* subagent action:"escalate"(§19 D-M4——退役 escalate 工具语义原样,ESCALATE.md
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
322
|
+
* subagent action:"escalate"(§19 D-M4——退役 escalate 工具语义原样,ESCALATE.md;
|
|
323
|
+
* §25 D-R17b——R17:缺省 async——后台飞刀 + settle 三分类 digest——async:false 保
|
|
324
|
+
* 同步旧路径)。飞刀——交给 consultModels 池里更强模型(WRITE + 术后报告)。约束全
|
|
325
|
+
* 保留:depth-0 only / 工程模式拒 / consultModels 空拒 / relay 前缀 `escalate#N/`
|
|
326
|
+
* (与既有前缀同名——TUI 路由零改动)/ mutations merge 回父(async 路径在 settle 分类)。
|
|
299
327
|
*/
|
|
300
328
|
export async function executeEscalateAction(args, ctx) {
|
|
301
329
|
const parent = ctx.agent
|
|
@@ -334,6 +362,16 @@ export async function executeEscalateAction(args, ctx) {
|
|
|
334
362
|
}
|
|
335
363
|
|
|
336
364
|
const tag = label(pick)
|
|
365
|
+
|
|
366
|
+
// §25 D-R17b (R17 — 决策点 ③): escalate 缺省 async — the launch returns an ack
|
|
367
|
+
// {id, role:"escalate", status:"running"|"queued"} and the flight runs in the
|
|
368
|
+
// background (shared other pool; settle 三分类 → pending 单容器 digest——
|
|
369
|
+
// ASYNC-RESULT-CONTAINER.md D2)。
|
|
370
|
+
// `async: false` keeps the legacy synchronous flight below (backward compat).
|
|
371
|
+
if (args?.async !== false) {
|
|
372
|
+
return launchEscalateAsync(parent, ctx, { task: String(task), provider, tag, effortNote })
|
|
373
|
+
}
|
|
374
|
+
|
|
337
375
|
const relayPrefix = makeRelay(parent, "escalate", ctx.callbacks?.onToken, provider.model ?? tag)
|
|
338
376
|
|
|
339
377
|
// 无墙钟 watchdog——turn cap 即成本预算(2026-08-16 rationale:固定墙钟会误杀正常慢速
|