@thincoder/core 0.9.5 → 0.10.1
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 +36 -0
- package/README.md +1 -1
- package/abort-provenance.mjs +10 -10
- package/advisor/citations.mjs +5 -5
- package/advisor/compaction.mjs +16 -15
- package/advisor/loop.mjs +12 -12
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +22 -12
- package/advisor.mjs +4 -4
- package/agent/assemble.mjs +111 -0
- package/agent/chat-call.mjs +68 -0
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +9 -2
- package/agent/continue-decision.mjs +25 -0
- package/agent/dispatch-gates.mjs +140 -0
- package/agent/dispatch-run.mjs +167 -0
- package/agent/dispatch.mjs +54 -296
- package/agent/family-tools.mjs +5 -5
- package/agent/helpers.mjs +53 -3
- package/agent/live-beat.mjs +49 -0
- package/agent/post-turn.mjs +4 -8
- package/agent/record-results.mjs +23 -20
- package/agent/relay-prefix.mjs +2 -2
- package/agent/run-stages.mjs +15 -11
- package/agent/run-start.mjs +111 -0
- package/agent/setup-reminders.mjs +9 -9
- package/agent/setup.mjs +32 -4
- package/agent/spawn-child.mjs +9 -9
- package/agent/suspension.mjs +89 -29
- package/agent/timers.mjs +113 -0
- package/agent/turn-loop.mjs +244 -0
- package/agent/write-gate.mjs +103 -6
- package/agent-tools/advisor-async.mjs +9 -9
- package/agent-tools/advisor-settle.mjs +12 -12
- package/agent-tools/advisor.mjs +36 -41
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +21 -18
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +57 -18
- package/agent-tools/batch-paths.mjs +52 -8
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +12 -65
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +56 -56
- package/agent-tools/context.mjs +4 -20
- package/agent-tools/design-token.mjs +3 -3
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/eng.mjs +5 -5
- package/agent-tools/escalate-async.mjs +16 -11
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +2 -2
- package/agent-tools/parent-channel.mjs +3 -13
- package/agent-tools/plan.mjs +3 -4
- package/agent-tools/read-history.mjs +12 -28
- package/agent-tools/recent-changes.mjs +4 -5
- package/agent-tools/review-facts.mjs +1 -1
- package/agent-tools/settings.mjs +47 -20
- package/agent-tools/skill.mjs +3 -5
- package/agent-tools/spawn-gates.mjs +3 -3
- package/agent-tools/subagent-actions-query.mjs +270 -0
- package/agent-tools/subagent-actions.mjs +21 -265
- package/agent-tools/subagent-async.mjs +25 -20
- package/agent-tools/subagent-panel.mjs +33 -4
- package/agent-tools/subagent-run.mjs +36 -21
- package/agent-tools/subagent-scheduler.mjs +28 -27
- package/agent-tools/subagent-spawn.mjs +45 -14
- package/agent-tools/subagent.mjs +33 -53
- package/agent-tools/task.mjs +3 -9
- package/agent-tools/timer.mjs +12 -12
- package/agent-tools/verify.mjs +14 -13
- package/agent-tools.mjs +0 -1
- package/agent.mjs +18 -355
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +11 -6
- package/config-watch.mjs +83 -0
- package/config.mjs +17 -4
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/history-window.mjs +17 -2
- package/i18n.mjs +2 -0
- package/index-discover.mjs +7 -7
- package/ledger-cmd.mjs +34 -126
- package/ledger-db.mjs +10 -2
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +9 -4
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-specs.mjs +16 -7
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +2 -2
- package/peer-domains.mjs +7 -11
- package/peer-instances.mjs +9 -10
- package/process-probe-exec.mjs +187 -0
- package/process-probe.mjs +27 -175
- package/prompt-overlays.mjs +11 -10
- package/prompts/advisor-design.md +2 -2
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +16 -8
- package/prompts/discipline-engineering.md +57 -16
- package/prompts/discipline-normal.md +6 -3
- package/prompts/persona-coder.md +0 -1
- package/prompts/persona-eng-coder.md +1 -5
- package/prompts/persona-eng-designer.md +2 -3
- package/prompts/persona-engineering.md +32 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +1 -1
- package/prompts/persona-plan.md +3 -4
- package/prompts/subagent-base.md +14 -0
- package/provider/core.mjs +11 -48
- package/provider/list-models.mjs +3 -0
- package/provider/responses-request.mjs +237 -0
- package/provider/responses.mjs +6 -228
- package/provider-flows.mjs +248 -0
- package/queued.mjs +90 -0
- package/rules.mjs +209 -2
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +90 -18
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -241
- package/session-slots.mjs +99 -71
- package/session.mjs +30 -10
- package/shell-candidates.mjs +78 -0
- package/text-budget.mjs +2 -2
- package/think-off.mjs +49 -0
- package/token-ttl.mjs +15 -4
- package/tool-args.mjs +73 -0
- package/tool-docs/advisor.md +9 -0
- package/tool-docs/batch.md +1 -0
- package/tool-docs/code_search.md +1 -0
- package/tool-docs/consult_start.md +5 -0
- package/tool-docs/consult_stop.md +3 -0
- package/tool-docs/context.md +7 -0
- package/tool-docs/doc_search.md +1 -0
- package/tool-docs/edit.md +2 -13
- package/tool-docs/eng.md +1 -0
- package/tool-docs/execute.md +1 -9
- package/tool-docs/git.md +25 -51
- package/tool-docs/goal.md +6 -0
- package/tool-docs/ledger_add.md +1 -0
- package/tool-docs/ledger_close.md +1 -0
- package/tool-docs/ledger_count.md +1 -0
- package/tool-docs/ledger_query.md +1 -0
- package/tool-docs/ledger_update.md +1 -0
- package/tool-docs/main_history.md +1 -0
- package/tool-docs/memory.md +10 -0
- package/tool-docs/notify_parent.md +9 -0
- package/tool-docs/peer_instances.md +1 -0
- package/tool-docs/plan.md +5 -0
- package/tool-docs/process.md +7 -3
- package/tool-docs/read_history.md +7 -0
- package/tool-docs/recent_changes.md +1 -0
- package/tool-docs/repo_outline.md +1 -0
- package/tool-docs/settings.md +6 -0
- package/tool-docs/skill.md +5 -0
- package/tool-docs/subagent.md +31 -0
- package/tool-docs/task.md +7 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/verify.md +1 -0
- package/tool-docs/wait_for.md +2 -1
- package/tools/bash.mjs +28 -4
- package/tools/edit-batch.mjs +4 -4
- package/tools/edit-diff.mjs +4 -23
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +4 -9
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/index.mjs +2 -1
- package/tools/linter.mjs +2 -1
- package/tools/node-child.mjs +21 -0
- package/tools/ops.mjs +50 -13
- package/tools/patch.mjs +4 -2
- package/tools/repomap.mjs +20 -9
- package/tools/shared.mjs +8 -6
- package/tools/web.mjs +5 -2
- package/tools/write-path.mjs +1 -1
- package/traces/trace-store.mjs +17 -14
- package/vision-reader.mjs +76 -0
- package/agent-tools/batch-segment.mjs +0 -18
package/agent-tools/consult.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* consult.mjs — multi-model consultation ("会诊", docs/design/CONSULTATION.md). CLI port.
|
|
3
3
|
*
|
|
4
|
-
* Two tools (
|
|
4
|
+
* Two tools (CONSULTATION.md §6.3 — R17, 2026-09-06): consult_start
|
|
5
5
|
* (non-blocking spawn) / consult_stop (cancel a running session). consult_check
|
|
6
6
|
* was RETIRED with the digest auto-injection: the mechanism does ZERO judging —
|
|
7
7
|
* when every model of a session settles (pending 0), the session moves to the
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
* prefix `consult#<childRelayN>/` (one per consultant child — the shared subagent
|
|
20
20
|
* relay channel; the child relay number is NOT the session id — sessions key
|
|
21
21
|
* their own `_consultIdCounter`), not onSubagent/onToolPanel.
|
|
22
|
-
* Each child settles its own TUI block with a ⟦ev⟧
|
|
23
|
-
*
|
|
22
|
+
* Each child settles its own TUI block with a ⟦ev⟧settled event at settle (consult
|
|
23
|
+
* 同族收齐批 · #748; a cancelled session's children emit ⟦ev⟧stopped — R17: the old
|
|
24
|
+
* in-turn check consumption is gone; consumed blocks archive at the consumption window).
|
|
24
25
|
*/
|
|
25
26
|
import { createAgent, runAgent, readonlyToolNames, excludeSubagentTools } from "../agent.mjs"
|
|
26
27
|
import { resolveChildProvider } from "./subagent.mjs"
|
|
@@ -29,11 +30,15 @@ import { offloadToolResult, escapeXml } from "../agent/helpers.mjs"
|
|
|
29
30
|
import { logEvent, errText } from "../log.mjs"
|
|
30
31
|
import { deathLine } from "../abort-provenance.mjs"
|
|
31
32
|
import { makeRelay, wrapChildCallbacks, runWithContinue, ensureChildApiKey, clampEffort } from "../agent/spawn-child.mjs"
|
|
32
|
-
// TUI-OOM-ROOTCAUSE §
|
|
33
|
+
// TUI-OOM-ROOTCAUSE·AGENT-LOOP.md §6.15:子代理人读线窗口常量(单源——store 零依赖)。
|
|
33
34
|
import { RECORD_WINDOW_MESSAGES } from "../session-store.mjs"
|
|
34
35
|
// ASYNC-RESULT-CONTAINER.md D2/D3/D6:pending 单容器停靠 + settle 公共收尾 + child signal 单点
|
|
35
36
|
import { bindChildController, buildChildSignal, settleAsyncEntry } from "./async-settle.mjs"
|
|
36
|
-
import {
|
|
37
|
+
import { registerTurnCapCheckpoint } from "./checkpoint.mjs"
|
|
38
|
+
import { drainInjectedQueue } from "./subagent-run.mjs"
|
|
39
|
+
import { digestBudgetOver, persistOverflowReport } from "./digest-budget.mjs" // B5(群 B 批 AGENT-LOOP.md §6.14 D-DG2):digest 注入预算单源
|
|
40
|
+
// #15 描述外置:描述文本单点 = tool-docs/{consult_start,consult_stop,main_history}.md(DESC 单解析面,缺档抛错语义不变)
|
|
41
|
+
import { DESC } from "../tools/shared.mjs"
|
|
37
42
|
|
|
38
43
|
// Named consult defaults (consult P2, 2026-08-30).
|
|
39
44
|
const CONSULT_TIMEOUT_MS = 600_000 // default consult lifecycle timeout
|
|
@@ -83,11 +88,7 @@ export function makeMainHistoryTool(parentAgent) {
|
|
|
83
88
|
return {
|
|
84
89
|
name: "main_history",
|
|
85
90
|
readonly: true,
|
|
86
|
-
description:
|
|
87
|
-
"Read the main agent's conversation history — what has been tried, the exact errors, recent context. " +
|
|
88
|
-
"Use it to ground your analysis in the actual failure trail instead of guessing.\n" +
|
|
89
|
-
"Parameters:\n" +
|
|
90
|
-
"- limit: Number of recent messages to return (default 20, max 100)",
|
|
91
|
+
description: DESC("main_history"),
|
|
91
92
|
parameters: {
|
|
92
93
|
type: "object",
|
|
93
94
|
properties: { limit: { type: "number", description: "Recent messages (default 20, max 100)" } },
|
|
@@ -132,7 +133,7 @@ export function makeMainHistoryTool(parentAgent) {
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
|
-
* Full-session settle routing (R17 —
|
|
136
|
+
* Full-session settle routing (R17 — CONSULTATION.md §6.2): a session whose
|
|
136
137
|
* pending count reached 0 has no more replies coming — the session leaves
|
|
137
138
|
* `_consultSessions` and, unless it was cancelled (consult_stop / turn-end
|
|
138
139
|
* abort), moves into the pending single container (`_pendingAsyncResults` +role
|
|
@@ -144,8 +145,8 @@ export function makeMainHistoryTool(parentAgent) {
|
|
|
144
145
|
* is woken (settle-event parity with the async pools) so an idle settle still
|
|
145
146
|
* triggers the digestion round (T-R17j).
|
|
146
147
|
* D3:公共收尾统一走 settleAsyncEntry 共享 helper(四族同机制)——consult 族参数:
|
|
147
|
-
* 无池(会话池无条目——settle 即出池)、无 ctx(无 TUI
|
|
148
|
-
*
|
|
148
|
+
* 无池(会话池无条目——settle 即出池)、无 ctx(无 TUI 冻结事件——子块由消费窗三端
|
|
149
|
+
* 收口——#748)、无 onAccounting;helper 按 role "consult" 恒停靠 pending(非挂起期也停靠)。
|
|
149
150
|
* Cancelled sessions produce no digest (T-R17c).
|
|
150
151
|
*/
|
|
151
152
|
function sessionSettled(agent, session) {
|
|
@@ -156,6 +157,7 @@ function sessionSettled(agent, session) {
|
|
|
156
157
|
role: "consult",
|
|
157
158
|
report: composeConsultDigest(session),
|
|
158
159
|
error: null, done: true, status: "done", cancelled: false,
|
|
160
|
+
childIds: [...(session.childIds ?? [])], // #748:随 settle 携子块映射表(消费窗三端展开同源)
|
|
159
161
|
relayPrefix: null, startedAt: null, _settle: null, _settleSeq: 0,
|
|
160
162
|
}
|
|
161
163
|
settleAsyncEntry(agent, entry, { pool: null, ctx: null })
|
|
@@ -183,7 +185,7 @@ export function composeConsultDigest(session) {
|
|
|
183
185
|
*/
|
|
184
186
|
export async function injectConsultResult(agent, entry) {
|
|
185
187
|
const full = String(entry?.report ?? "(no consultation result)")
|
|
186
|
-
// §
|
|
188
|
+
// §6.14 D-DG3(群 B 批 B5):计入预算的 raw = 报告正文(标签行不计)——首行标签拆出;超限
|
|
187
189
|
// 路径保留族标签行(与 VSC 镜像同形);落盘内容 = raw(与其余三族同口径)。
|
|
188
190
|
const at = full.indexOf("\n")
|
|
189
191
|
const label = at === -1 ? "" : full.slice(0, at + 1)
|
|
@@ -197,7 +199,11 @@ export async function injectConsultResult(agent, entry) {
|
|
|
197
199
|
})
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
|
|
202
|
+
/** Per-child settle 路由(consult 同族收齐批 · 2026-09-30 · 台账 #748):settle 一律发
|
|
203
|
+
* `⟦ev⟧settled`(区块驻留「done · awaiting digestion」——冻结 ∥ 归档落消费窗,三端补发面同源);
|
|
204
|
+
* 取消径(会话 stopped——consult_stop ∥ cleanup)⇒ `⟦ev⟧stopped` 即折(消费永不来——与四族取消面同判)。
|
|
205
|
+
* 无块子块(relayPrefix 缺省——spawn 前失败,从未开块)零发射;测试缝 = 最小导出接桩(会话桩直调)。 */
|
|
206
|
+
export function settleChild(agent, session, id, label, ok, payload, relayPrefix, emit) {
|
|
201
207
|
if (ok) {
|
|
202
208
|
session.received++
|
|
203
209
|
session.replies.push({ model: label, reply: payload })
|
|
@@ -208,7 +214,10 @@ function settleChild(agent, session, id, label, ok, payload, emitDone) {
|
|
|
208
214
|
session.replies.push({ model: label, reply: `(consultation failed: ${payload})`, failed: true })
|
|
209
215
|
}
|
|
210
216
|
session.pending--
|
|
211
|
-
|
|
217
|
+
if (relayPrefix) {
|
|
218
|
+
const kind = session.stopped ? "stopped" : "settled" // 块发射两态(消费面口径 = AGENT-LOOP-ASYNC-POOL.md §6.8)
|
|
219
|
+
emit?.(`${relayPrefix}⟦ev⟧${kind}\x1e0\x1e0\x1e${kind}\x1e`)
|
|
220
|
+
}
|
|
212
221
|
if (session.pending === 0) sessionSettled(agent, session)
|
|
213
222
|
}
|
|
214
223
|
|
|
@@ -219,7 +228,7 @@ async function runConsultChild(ctx, session, id, m, problem, ctrl) {
|
|
|
219
228
|
const armWatchdog = () => {
|
|
220
229
|
const t = setTimeout(() => {
|
|
221
230
|
timedOut = true
|
|
222
|
-
// §
|
|
231
|
+
// AGENT-LOOP-SUBAGENT.md §6.12 站点 #12 信号面(第 24 批):watchdog 中止带 timeout reason
|
|
223
232
|
try { ctrl.abort({ abortTrigger: "timeout", abortDetail: "consult-watchdog" }) } catch { /* already settled */ }
|
|
224
233
|
}, timeoutMs)
|
|
225
234
|
t.unref?.()
|
|
@@ -243,11 +252,9 @@ async function runConsultChild(ctx, session, id, m, problem, ctrl) {
|
|
|
243
252
|
else logEvent("child:error", { ...base, err: errText(payload, 200) })
|
|
244
253
|
}
|
|
245
254
|
// R17: relay prefix assigned before the child runner arms — the per-child TUI
|
|
246
|
-
// block
|
|
255
|
+
// block event emits only when a block actually exists (relay established).
|
|
247
256
|
let relayPrefix = null
|
|
248
|
-
const settle = (ok, payload) => settleChild(agent, session, id, label, ok, payload, relayPrefix
|
|
249
|
-
? () => ctx.callbacks?.onToken?.(`${relayPrefix}⟦ev⟧done\x1e0\x1e0\x1edone\x1e`)
|
|
250
|
-
: null)
|
|
257
|
+
const settle = (ok, payload) => settleChild(agent, session, id, label, ok, payload, relayPrefix, ctx.callbacks?.onToken)
|
|
251
258
|
try {
|
|
252
259
|
// Provider resolution: consultModels entries are { provider, model, effort? } — resolve
|
|
253
260
|
// via the subagent's provider resolver ("provider:model" handles cross-provider picks).
|
|
@@ -282,16 +289,22 @@ async function runConsultChild(ctx, session, id, m, problem, ctrl) {
|
|
|
282
289
|
// role "consult" (overlay + base would concatenate it twice).
|
|
283
290
|
role: "consult",
|
|
284
291
|
})
|
|
285
|
-
// TUI-OOM-ROOTCAUSE §
|
|
292
|
+
// TUI-OOM-ROOTCAUSE·AGENT-LOOP.md §6.15:子代理人读线窗口(四处创建点同置)——consult 会话跨回合驻留,
|
|
286
293
|
// 不置窗则 _fullHistory 仍无界(F-O1 覆盖 depth>0 全部创建点)
|
|
287
294
|
child._historyWindow = RECORD_WINDOW_MESSAGES
|
|
295
|
+
// SUBAGENT-UPSTREAM-CHANNEL 补赋(TURN-CAP-CONTINUE.md §3.1):会诊 = 后台族(consult_start
|
|
296
|
+
// 发后即返)⇒ 父在飞可答——撞帽检查点的上行 ask 与 notify_parent 同源通道。
|
|
297
|
+
child._upstream = { parent: agent, label, sync: false }
|
|
288
298
|
|
|
289
|
-
// Activity relay via the unified spawn-child pipeline (§
|
|
299
|
+
// Activity relay via the unified spawn-child pipeline (docs/cli/design/TUI.md §6.8 D3): `consult#<subId>/`
|
|
290
300
|
// prefix (same channel subagent uses — parallel consultants stay independent) +
|
|
291
301
|
// onToolOutput passthrough so the consultant's tool output lands in its TUI block.
|
|
292
302
|
relayPrefix = makeRelay(agent, "consult", ctx.callbacks?.onToken, provider.model ?? "")
|
|
293
303
|
// LOGGING:arm(spawn 事件——relay 建立后;子内事件归属 _logId)
|
|
294
304
|
childLogId = relayPrefix.slice(0, -1)
|
|
305
|
+
// #748:会话自持 `childIds`(= 子块 relay 号——块键形 `consult#<N>` 的 N);无块子块不入表
|
|
306
|
+
// (relayPrefix 缺省 = spawn 前失败——从未开块)。消费窗三端按该表逐子块展开(同源)。
|
|
307
|
+
session.childIds.push(childLogId.slice(childLogId.lastIndexOf("#") + 1))
|
|
295
308
|
child._logId = childLogId
|
|
296
309
|
logT0 = Date.now()
|
|
297
310
|
logArmed = true
|
|
@@ -300,24 +313,29 @@ async function runConsultChild(ctx, session, id, m, problem, ctrl) {
|
|
|
300
313
|
let declined = false // review #1: guard against double-settle when onDeclined fired
|
|
301
314
|
|
|
302
315
|
// Turn-cap continue loop (TURN-CAP-CONTINUE.md) via runWithContinue (§7.2 D3): hitting
|
|
303
|
-
// the cap
|
|
304
|
-
//
|
|
305
|
-
// continue is a fresh budget, the clock restarts
|
|
306
|
-
// their prompts through a session-level queue.
|
|
307
|
-
// (partial diagnosis).
|
|
316
|
+
// the cap reports to the PARENT agent through the turn-cap checkpoint — the user y/n
|
|
317
|
+
// card is gone (F8; see the askContinue hook below). Each grant = a fresh turn budget
|
|
318
|
+
// AND a re-armed wall-clock watchdog (a continue is a fresh budget, the clock restarts
|
|
319
|
+
// too). Parallel consultants serialize their prompts through a session-level queue.
|
|
320
|
+
// Declined / headless → failed reply (partial diagnosis).
|
|
308
321
|
const runner = ctx.runAgent ?? runAgent
|
|
309
322
|
try {
|
|
310
323
|
const result = await runWithContinue(
|
|
311
324
|
(childAgent, input, cbs, opts) => runner(childAgent, input, cbs, opts),
|
|
312
325
|
child, "# Problem\n" + problem,
|
|
313
326
|
childCallbacks,
|
|
314
|
-
{ depth: 1, maxTurns: agent?.config?.agent?.consultTurns ?? CONSULT_TURNS, signal: ctrl.signal },
|
|
315
327
|
{
|
|
328
|
+
depth: 1, maxTurns: agent?.config?.agent?.consultTurns ?? CONSULT_TURNS, signal: ctrl.signal,
|
|
329
|
+
// send 注入队列回合边界消费(缺口补配——续段文本落 rec.child._injected 后由此消费)。
|
|
330
|
+
consumeInjected: (ag) => { drainInjectedQueue(child, ag ?? child) },
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
// F8(TURN-CAP-CONTINUE.md §1 #7 / #5):撞帽 ⇒ 报请父代理(检查点)——不再弹
|
|
334
|
+
// continue 卡;报请串行在会话级 `continueQueue` 上(唯一会话级队列消费者)。
|
|
335
|
+
// 续期 ⇒ 新段预算 + watchdog 重挂(N3)。headless 亦登记(答复通道 = 父 send,
|
|
336
|
+
// 非权限 handler——§2.5 用例 13)。
|
|
316
337
|
askContinue: (e) => {
|
|
317
|
-
|
|
318
|
-
// 残环批(2026-09-16):归属键形态(`consult#<relayN>`——端侧键形解析的输入契约;
|
|
319
|
-
// 原 label = 显示名不携 id);relay 未建立(spawn 前失败路径不可达此 ask)时回落 label。
|
|
320
|
-
const ask = () => ctx.onPermissionRequest("continue", { turns: e.turn, agent: relayPrefix ? relayPrefix.slice(0, -1) : label })
|
|
338
|
+
const ask = () => registerTurnCapCheckpoint(child, session, { turn: e.turn, signal: ctrl.signal })
|
|
321
339
|
session.continueQueue = (session.continueQueue ?? Promise.resolve()).then(ask, ask)
|
|
322
340
|
return session.continueQueue.then((go) => {
|
|
323
341
|
if (go) {
|
|
@@ -381,24 +399,12 @@ export const consultStartTool = {
|
|
|
381
399
|
name: "consult_start",
|
|
382
400
|
readonly: false,
|
|
383
401
|
sideEffectExempt: true,
|
|
384
|
-
description:
|
|
385
|
-
"Start a parallel multi-model consultation (会诊) for a hard problem you are stuck on (repeated failures, no headway). " +
|
|
386
|
-
"Call it directly when the user asks for 会诊 / consult — an explicit user request applies even if you are not 'stuck'. " +
|
|
387
|
-
"Several configured models (agent.consultModels) analyze the same problem INDEPENDENTLY and in parallel. " +
|
|
388
|
-
"Non-blocking: returns immediately with a consult id; the consultants keep running in the background across turns. " +
|
|
389
|
-
"When EVERY model has replied (or failed), the full verdict text is delivered to you automatically — as a system " +
|
|
390
|
-
"reminder at the next run start, or digested on its own while the session is idle — judge and adopt each opinion " +
|
|
391
|
-
"yourself with your own tools (opinions are suggestions, not gates). To stop a session early (user changed their " +
|
|
392
|
-
"mind / wants the tokens back), call consult_stop(id) — a stopped session delivers no digest.\n" +
|
|
393
|
-
"Parameters:\n" +
|
|
394
|
-
"- problem (required): a brief — the symptom, what you already tried (failure trail), and entry-point files. " +
|
|
395
|
-
"Do NOT paste raw error logs; consultants pull the main session history themselves via their main_history tool.\n" +
|
|
396
|
-
"- models (optional): subset of agent.consultModels to run — an array of \"provider:model\", bare provider, or bare model names (case-insensitive). Omit to run all.",
|
|
402
|
+
description: DESC("consult_start"),
|
|
397
403
|
parameters: {
|
|
398
404
|
type: "object",
|
|
399
405
|
properties: {
|
|
400
406
|
problem: { type: "string", description: "Problem brief (symptom + failure trail + entry files)" },
|
|
401
|
-
models: { type: "array", items: { type: "string" }, description: 'Optional subset of agent.consultModels to run (default: all)
|
|
407
|
+
models: { type: "array", items: { type: "string" }, description: 'Optional subset of agent.consultModels to run (default: all) — each entry "provider:model", a bare provider, or a bare model name (case-insensitive).' },
|
|
402
408
|
},
|
|
403
409
|
required: ["problem"],
|
|
404
410
|
},
|
|
@@ -419,7 +425,7 @@ export const consultStartTool = {
|
|
|
419
425
|
agent._consultSessions ??= new Map()
|
|
420
426
|
const id = String((agent._consultIdCounter = (agent._consultIdCounter ?? 0) + 1))
|
|
421
427
|
const session = {
|
|
422
|
-
id, controllers: [], replies: [], pending: 0,
|
|
428
|
+
id, controllers: [], replies: [], childIds: [], pending: 0,
|
|
423
429
|
failed: 0, terminated: 0, stopped: false, received: 0, total: run.length,
|
|
424
430
|
models: run.map(consultLabel),
|
|
425
431
|
}
|
|
@@ -445,13 +451,7 @@ export const consultStopTool = {
|
|
|
445
451
|
name: "consult_stop",
|
|
446
452
|
readonly: false,
|
|
447
453
|
sideEffectExempt: true,
|
|
448
|
-
description:
|
|
449
|
-
"Cancel a still-running consultation session (会诊) — the user changed their mind, the problem resolved, or you want the tokens back. " +
|
|
450
|
-
"Aborts every consultant that is still running; a stopped session delivers NO digest (R17 — its already-collected partial replies are dropped). " +
|
|
451
|
-
"Sessions that finished on their own are no longer cancellable — their verdict text is delivered automatically.\n" +
|
|
452
|
-
"Returns JSON {abandoned: <pending count>, cancelled: true} — or {error: \"unknown consult id\"} (already finished/cancelled).\n" +
|
|
453
|
-
"Parameters:\n" +
|
|
454
|
-
"- id (required): the consult id from consult_start",
|
|
454
|
+
description: DESC("consult_stop"),
|
|
455
455
|
parameters: {
|
|
456
456
|
type: "object",
|
|
457
457
|
properties: {
|
|
@@ -464,7 +464,7 @@ export const consultStopTool = {
|
|
|
464
464
|
if (!s) return JSON.stringify({ error: "unknown consult id" })
|
|
465
465
|
const abandoned = s.pending
|
|
466
466
|
s.stopped = true
|
|
467
|
-
// §
|
|
467
|
+
// §6.12 站点 #11(第 24 批):会话级停 = stop
|
|
468
468
|
for (const c of s.controllers) { try { c.abort({ abortTrigger: "stop", abortDetail: "consult-stop" }) } catch { /* already settled */ } }
|
|
469
469
|
return JSON.stringify({ abandoned, cancelled: true })
|
|
470
470
|
},
|
package/agent-tools/context.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* agent-tools/context.mjs — `context` 工具(CONTEXT-COMPACTION.md §6.16 · context-tool 批 2026-09-21)。
|
|
3
3
|
* 单一工具三操作(用户定形 F-CC5):`stats`(感知 F-CC1)· `prune`(噪声清理 F-CC3)· `compact`(主动压缩 F-CC2,携 focus + 自动附任务/目标 + 可回查锚——裁令 A/D)。
|
|
4
|
-
*
|
|
4
|
+
* 描述外置(tool-docs/context.md——#15 统一面:文本单点 = tool-docs,DESC 加载);装配面 = 登记册 `agent-tools.mjs`(#83)+ `agent/family-tools.mjs` 的 depth-0 段(`depthOnly`)——两端自动同表;**depth>0 不给**(§6.16.6)。
|
|
5
5
|
* 本档同时是**注记文案 / 轻推行 / 回执文案的单源**(核与 VSC 的 run-stages 消费点经动态 import 取用——零新增静态边)。
|
|
6
6
|
* 机械面:`stats` 不新增第三口径(阈值取本回合判定值;百分比走 `historyPercent` 与状态行同式);`prune` = 合格集(`token-window.mjs` 单源)+「原位换内容」应用面(`context.mjs`);`compact` **当次零执行**(只登记 `agent._pendingCompact`,由下一安全点消费——§6.16.2)。
|
|
7
7
|
*/
|
|
8
8
|
import { contextUsage, estimateTokens, collectStaleToolOutputs, historyPercent, PRUNE_MIN_TOKENS } from "../token-window.mjs"
|
|
9
9
|
import { pruneStaleToolOutputs, COMPRESS_FAILURE_LIMIT } from "../context.mjs"
|
|
10
10
|
import { slotPath } from "../session-slots.mjs"
|
|
11
|
+
// #15 描述外置:描述文本单点 = tool-docs/context.md(DESC 单解析面,缺档抛错语义不变)
|
|
12
|
+
import { DESC } from "../tools/shared.mjs"
|
|
11
13
|
|
|
12
14
|
/** 轻推行前缀(单活体去重判据——§6.16.5「同前缀旧行先滤」)。 */
|
|
13
15
|
export const CONTEXT_NUDGE_PREFIX = "[System reminder: task/goal changed —"
|
|
@@ -125,25 +127,7 @@ function compactReceipt(agent, focus, replaced) {
|
|
|
125
127
|
|
|
126
128
|
export const contextTool = {
|
|
127
129
|
name: "context",
|
|
128
|
-
description:
|
|
129
|
-
"Manage your own context window — see how full it is, drop stale tool output, or compact the earlier " +
|
|
130
|
-
"conversation into a summary at a moment of your own choosing.\n" +
|
|
131
|
-
"- action='stats': current usage — total vs the compaction threshold, per-segment shares, and how much " +
|
|
132
|
-
"is prunable / reclaimable.\n" +
|
|
133
|
-
"- action='prune': drop the CONTENT of stale tool results (older than the protected tail) and replace it " +
|
|
134
|
-
"with a short stub — tool pairing stays intact, the session record keeps the full text, and the tool can " +
|
|
135
|
-
"simply be re-run.\n" +
|
|
136
|
-
"- action='compact': compact the earlier conversation now, with 'focus' — write what the UPCOMING work " +
|
|
137
|
-
"needs (1–3 sentences: the goal, the files, the constraints that must survive). The summary is written to " +
|
|
138
|
-
"serve that focus; the current task list and goal are attached automatically. It runs at the next safe " +
|
|
139
|
-
"point (before the next request — never mid-exchange) through the same machine as the automatic " +
|
|
140
|
-
"compaction: same summary chain, same panel, same failure chain (3 consecutive failures degrade to a " +
|
|
141
|
-
"deterministic truncation). The receipt carries this session's record path, so nothing is lost for good.\n" +
|
|
142
|
-
"Nothing here runs on its own — the automatic threshold compaction stays in place as a fallback; whether " +
|
|
143
|
-
"to compact is your call. Errors come back as 'Error: ...' and change nothing.\n" +
|
|
144
|
-
"Parameters:\n" +
|
|
145
|
-
"- action (required): stats | prune | compact\n" +
|
|
146
|
-
"- focus (required for compact): what the upcoming work needs — the summary is weighted toward it",
|
|
130
|
+
description: DESC("context"),
|
|
147
131
|
parameters: {
|
|
148
132
|
type: "object",
|
|
149
133
|
properties: {
|
|
@@ -23,7 +23,7 @@ export function buildApprovedSuffix(designToken, designId, slotCount) {
|
|
|
23
23
|
return `Approved. Pass this exact token to eng-coder (designToken parameter): ${designToken}\ndesignId: ${designId} (pass as the designId parameter when spawning eng-coder — optional while this session holds a single design; ${slotCount} approved design slot(s) held as of this approval, and the count may have changed since — with several designs the spawn gate refuses a missing designId and lists the held ids)`
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** F2e
|
|
26
|
+
/** F2e: strip the engine-generated Approved suffix from a report before
|
|
27
27
|
* it becomes a prior — the suffix is deterministic (buildApprovedSuffix), so the
|
|
28
28
|
* truncation is exact; a text not ending in it passes through untouched. */
|
|
29
29
|
export function stripApprovedSuffix(text, suffix) {
|
|
@@ -83,7 +83,7 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
83
83
|
if (!designToken || typeof rawResult !== "string") {
|
|
84
84
|
return { passed: false, output: rawResult ?? "" }
|
|
85
85
|
}
|
|
86
|
-
// A(F11
|
|
86
|
+
// A(F11/ADVISOR-GUARDS.md §1 消费点 1):未完成即不签发——判据与 code 完成守卫同源(单谓词)。
|
|
87
87
|
const incomplete = opts?.incomplete ?? null
|
|
88
88
|
if (incomplete) {
|
|
89
89
|
const stripped = rawResult.replace(makeDesignTokenRegex(designToken, "g"), "").trim()
|
|
@@ -107,7 +107,7 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
107
107
|
if (agent._role === "eng-coder") agent._engDesignReviewed = true
|
|
108
108
|
run.open = false // approval closes this doc-set instance — next review is fresh
|
|
109
109
|
const clean = rawResult.replace(makeDesignTokenRegex(designToken, "g"), "").trim()
|
|
110
|
-
// F2c
|
|
110
|
+
// F2c: id echo + omission guidance + point-in-time slot snapshot — the
|
|
111
111
|
// same suffix the F2e prior stores strip with (stored for the exact truncation).
|
|
112
112
|
run.approvedSuffix = buildApprovedSuffix(designToken, run.designId, agent._engDesignTokens.size)
|
|
113
113
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* digest-budget.mjs — digest 注入批量预算单源(群 B 批 B5——AGENT-LOOP.md §
|
|
2
|
+
* digest-budget.mjs — digest 注入批量预算单源(群 B 批 B5——AGENT-LOOP.md §6.14 语义源)。
|
|
3
3
|
* 原预算块居 subagent-async.mjs(只覆盖经 injectAsyncResult 的三族:subagent / advisor /
|
|
4
4
|
* escalate),consult 族注入器(injectConsultResult)绕过 ⇒ 多族条目同轮合并注入时可累计
|
|
5
5
|
* 超限(BATCH-3-STRUCTURE F-2 原始事故面 1.3MB 请求体)。本档把常量 / 判超 / 记账 / 落盘
|
package/agent-tools/eng.mjs
CHANGED
|
@@ -18,6 +18,8 @@ import { ENG_ON_REMINDER, ENG_OFF_REMINDER } from "../agent.mjs"
|
|
|
18
18
|
import { clearPlanMode } from "./plan.mjs"
|
|
19
19
|
import { purgeExpiredDesignTokens } from "../token-ttl.mjs"
|
|
20
20
|
import { resolveEngineeringManifest } from "../manifest.mjs"
|
|
21
|
+
// 描述面 = `tool-docs/eng.md`(#15 描述外置统一——DESC 单一解析面,与内置工具族同径)
|
|
22
|
+
import { DESC } from "../tools/shared.mjs"
|
|
21
23
|
|
|
22
24
|
// ─── 持久化镜像 / 面板提示注入缝(#91——「写盘镜像 / 面板提示按端注入」,形态参 §2.13.5 注入缝)──
|
|
23
25
|
/**
|
|
@@ -41,9 +43,7 @@ async function mirrorNotice(enabled, ctx) {
|
|
|
41
43
|
|
|
42
44
|
export const engTool = {
|
|
43
45
|
name: "eng",
|
|
44
|
-
description:
|
|
45
|
-
"Enter or exit engineering mode. In engineering mode, follow design-before-code: write a design document, run advisor design review, get user approval, then implement via eng-coder subagents. " +
|
|
46
|
-
"Returns the mode state — 'Engineering mode activated/exited' (an already-active state is acknowledged).",
|
|
46
|
+
description: DESC("eng"),
|
|
47
47
|
parameters: {
|
|
48
48
|
type: "object",
|
|
49
49
|
properties: {
|
|
@@ -59,7 +59,7 @@ export const engTool = {
|
|
|
59
59
|
// R16 (F-R16a): OFF 不清 token——有效 token 跨模式存活(设计评审过的产物不因
|
|
60
60
|
// 开关重复烧)。过期清理跑在另外三处(恢复过滤 / 开模式 / spawn 门禁拒)。
|
|
61
61
|
ctx.agent._advisorRound = 0 // reset convergence budget
|
|
62
|
-
ctx.agent._advisorRuns = new Map() // §
|
|
62
|
+
ctx.agent._advisorRuns = new Map() // AGENT-LOOP-ASYNC-POOL.md §6.10 D-24b: per-review instances die with the mode (fresh cycles)
|
|
63
63
|
ctx.agent._touchedFiles = [] // clear mutation tracking
|
|
64
64
|
ctx.agent._lastEngState = false
|
|
65
65
|
ctx.agent._pendingReminders = ctx.agent._pendingReminders ?? []
|
|
@@ -90,7 +90,7 @@ export const engTool = {
|
|
|
90
90
|
// R16 (F-R16b ②): off→on 不重评——只清过期 token(用户裁定"打开工程模式时
|
|
91
91
|
// 应该清理"),有效 token 原样保留——遍历 Map 删过期,返回文案含清理个数。
|
|
92
92
|
const cleared = purgeExpiredDesignTokens(ctx.agent)
|
|
93
|
-
ctx.agent._advisorRuns = new Map() // §
|
|
93
|
+
ctx.agent._advisorRuns = new Map() // §6.10 D-24b: per-review instances die with the mode (fresh cycles)
|
|
94
94
|
ctx.agent._lastEngState = true
|
|
95
95
|
ctx.agent._pendingReminders = ctx.agent._pendingReminders ?? []
|
|
96
96
|
ctx.agent._pendingReminders.push(ENG_ON_REMINDER)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* escalate-async.mjs — async 飞刀 runner (
|
|
2
|
+
* escalate-async.mjs — async 飞刀 runner (ESCALATE.md §5 — R17, 2026-09-06).
|
|
3
3
|
*
|
|
4
4
|
* The subagent action:"escalate" is DEFAULT-async at depth 0 (decision ③: the
|
|
5
5
|
* depth-0-only escalate flips to async like every other background family;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { relative, isAbsolute } from "node:path"
|
|
28
28
|
import { runAgent, createAgent, DEFAULT_SUBAGENT_TURNS, excludeSubagentTools } from "../agent.mjs"
|
|
29
29
|
import { runWithContinue, TURN_CAP_MARK, wrapChildCallbacks } from "../agent/spawn-child.mjs"
|
|
30
|
-
// TUI-OOM-ROOTCAUSE §
|
|
30
|
+
// TUI-OOM-ROOTCAUSE·AGENT-LOOP.md §6.15:子代理人读线窗口常量(单源——store 零依赖)。
|
|
31
31
|
import { RECORD_WINDOW_MESSAGES } from "../session-store.mjs"
|
|
32
32
|
import { logEvent } from "../log.mjs"
|
|
33
33
|
import { deathLine } from "../abort-provenance.mjs"
|
|
@@ -38,6 +38,8 @@ import { refreshQueuedTokens, nextSubagentId, consumeSubagentToken, assertPoolKe
|
|
|
38
38
|
import { mutationSeqOf } from "./advisor-async.mjs"
|
|
39
39
|
// ASYNC-RESULT-CONTAINER.md D3/D6:settle 公共收尾单点 + child signal 构建单点
|
|
40
40
|
import { bindChildController, buildChildSignal, settleAsyncEntry } from "./async-settle.mjs"
|
|
41
|
+
import { registerTurnCapCheckpoint } from "./checkpoint.mjs"
|
|
42
|
+
import { drainInjectedQueue } from "./subagent-run.mjs"
|
|
41
43
|
|
|
42
44
|
/** Parent-side mutations (absolute paths) committed AFTER the escalate launch —
|
|
43
45
|
* the overlap scan feeds the settle classification (review #4/round2 #4: the
|
|
@@ -90,7 +92,7 @@ function touchedFilesNote(child, cwd) {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
/**
|
|
93
|
-
* Async escalate settle — three-way classification (§
|
|
95
|
+
* Async escalate settle — three-way classification (ESCALATE.md §5 D-R17b review #4):
|
|
94
96
|
* - done: merge ALL mutations (the escalation's changes are the parent's —
|
|
95
97
|
* verify/advisor guards must see them) + overlap warning into the report
|
|
96
98
|
* (report-level hint — not a gate — round2 #4);
|
|
@@ -104,7 +106,7 @@ function touchedFilesNote(child, cwd) {
|
|
|
104
106
|
*/
|
|
105
107
|
export function classifyEscalateSettle(parent, entry) {
|
|
106
108
|
if (entry.cancelled) return { cancelled: true }
|
|
107
|
-
// §
|
|
109
|
+
// AGENT-LOOP.md §6.15(TUI-OOM-ROOTCAUSE)——childAgent 语义面:settle 分类(touched 摘要/partial
|
|
108
110
|
// merge)与 status/observe 摘要的读取源;**消化注入完成后由 `releaseSettledEntry`
|
|
109
111
|
// (async-settle.mjs)置 null**(池内未消化窗口不释放——表 2 候选 2 否决)。
|
|
110
112
|
const child = entry.childAgent
|
|
@@ -148,7 +150,7 @@ export function launchEscalateAsync(parent, ctx, launch) {
|
|
|
148
150
|
const { task, provider, tag, effortNote } = launch
|
|
149
151
|
parent._asyncSubagents ??= new Map()
|
|
150
152
|
parent._asyncQueue ??= []
|
|
151
|
-
// Async id allocation (AGENT-LOOP.md §
|
|
153
|
+
// Async id allocation (AGENT-LOOP-SUBAGENT.md §6.7.3 D-A1 precedent): reserve the relay
|
|
152
154
|
// counter at launch — the returned id stays stable while the entry sits queued.
|
|
153
155
|
// The [model] token (TUI block creation) is DEFERRED to actual start so queued
|
|
154
156
|
// flights don't paint an empty panel block (subagent-parity).
|
|
@@ -161,7 +163,7 @@ export function launchEscalateAsync(parent, ctx, launch) {
|
|
|
161
163
|
const relayPrefix = `escalate#${id}/`
|
|
162
164
|
const entry = {
|
|
163
165
|
id, role: "escalate", relayPrefix,
|
|
164
|
-
_pool: poolDomainOf("escalate"), // other — shares the domain with explore/plan/coder (§
|
|
166
|
+
_pool: poolDomainOf("escalate"), // other — shares the domain with explore/plan/coder (AGENT-LOOP-ASYNC-POOL.md §6.10 D-24a)
|
|
165
167
|
status: "queued",
|
|
166
168
|
position: undefined,
|
|
167
169
|
report: null, error: null, done: false, cancelled: false,
|
|
@@ -200,7 +202,7 @@ export function launchEscalateAsync(parent, ctx, launch) {
|
|
|
200
202
|
role: "coder",
|
|
201
203
|
})
|
|
202
204
|
entry.childAgent = child // settle 分类/status touched 摘要绑定(start 时刻)
|
|
203
|
-
child._historyWindow = RECORD_WINDOW_MESSAGES // TUI-OOM-ROOTCAUSE §
|
|
205
|
+
child._historyWindow = RECORD_WINDOW_MESSAGES // TUI-OOM-ROOTCAUSE·AGENT-LOOP.md §6.15:子代理人读线窗口(四处创建点同置)
|
|
204
206
|
child._logId = relayPrefix.slice(0, -1)
|
|
205
207
|
// SUBAGENT-UPSTREAM-CHANNEL(§6.27.4 W3——escalate **async**:工具返回注走异步形)。
|
|
206
208
|
child._upstream = { parent, label: relayPrefix.slice(0, -1), sync: false }
|
|
@@ -224,6 +226,9 @@ export function launchEscalateAsync(parent, ctx, launch) {
|
|
|
224
226
|
signal: entry.controller.signal,
|
|
225
227
|
// §2.5 #78 并入:escalate 子代理输出流式(VSC subagent-escalate-async 同款豁免)。
|
|
226
228
|
streamOutput: true,
|
|
229
|
+
// SUBAGENT-OBSERVE-SEND 缺口补配(TURN-CAP-CONTINUE.md §3.1 :85):send 注入队列
|
|
230
|
+
// 回合边界消费(否则续段文本静默丢弃)。空队列 no-op。
|
|
231
|
+
consumeInjected: (ag) => { drainInjectedQueue(entry, ag ?? child) },
|
|
227
232
|
}
|
|
228
233
|
// 权限按 async 子代理同款装配:AUTO 直放行;手动档经父 _permQueue(并行子代理
|
|
229
234
|
// 审批不叠弹窗)——背景飞行撞门时无 handler → denied 不悬挂(D-S7 同规则)。
|
|
@@ -242,9 +247,9 @@ export function launchEscalateAsync(parent, ctx, launch) {
|
|
|
242
247
|
{ ...childCallbacks, onPermissionRequest: childPermission },
|
|
243
248
|
runOpts,
|
|
244
249
|
{
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
askContinue: () =>
|
|
250
|
+
// F8(TURN-CAP-CONTINUE.md §1 #7):撞帽 ⇒ 父代理检查点(同池、同载具——send 兑现 /
|
|
251
|
+
// cancel 走既有 abort ⇒ partial + TURN_CAP_MARK);原 AUTO 自动 resume 由 #7 取代。
|
|
252
|
+
askContinue: (e) => registerTurnCapCheckpoint(child, entry, { turn: e.turn, signal: entry.controller.signal }),
|
|
248
253
|
onDeclined: (e, output) => `escalate (${tag})${entry.effortNote} ${TURN_CAP_MARK} (${e.turn} turns) — work may be partial; review recent_changes before deciding next steps.\nPartial output: ${output.slice(0, 2000)}`,
|
|
249
254
|
},
|
|
250
255
|
)
|
|
@@ -265,7 +270,7 @@ export function launchEscalateAsync(parent, ctx, launch) {
|
|
|
265
270
|
// 运行失败/中止:错误文本落 entry.error(子代理同款——cancel 分支忽略它;
|
|
266
271
|
// Ctrl+I 中止的残条目由收尾消化带错误文本——不落空 "(no report)" digest)。
|
|
267
272
|
const child = entry.childAgent
|
|
268
|
-
// §
|
|
273
|
+
// AGENT-LOOP-SUBAGENT.md §6.12 第 3 条合成器(第 24 批):原 message 前缀逐字保留 + 来源后缀
|
|
269
274
|
entry.error = `escalate (${tag}) error: ${deathLine(e, entry.controller?.signal)}\nPartial output: ${(child?._capturedOutput ?? "").slice(0, 2000)}`
|
|
270
275
|
})
|
|
271
276
|
.finally(() => {
|
package/agent-tools/goal.mjs
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
// F-CC4(CONTEXT-COMPACTION.md §6.16.5):goal 任一状态变更 ⇒ 方向转换轻推(本档只调用;文案/去重单源 = agent-tools/context.mjs)
|
|
2
2
|
import { pushContextNudge } from "./context.mjs"
|
|
3
|
+
import { DESC } from "../tools/shared.mjs"
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* goal tool: lifecycle management for long-running autonomous goals (completion contract).
|
|
6
|
-
*
|
|
7
|
-
* blocked is only accepted after the same condition persists 3 consecutive times.
|
|
8
|
-
* The system injects status + budget progress + audit discipline every turn.
|
|
7
|
+
* 描述面 = `tool-docs/goal.md`(DESC() 加载);完成阈 / 阻塞计次 / 每回合注入 = execute 内实现注。
|
|
9
8
|
*/
|
|
10
9
|
export const goalTool = {
|
|
11
10
|
name: "goal",
|
|
12
|
-
description:
|
|
13
|
-
"Manage a long-running autonomous goal. " +
|
|
14
|
-
"action='set': create or replace the goal — must have a verifiable completion criterion (a machine-checkable proof, not vague effort). " +
|
|
15
|
-
"action='complete': mark achieved — only after the criterion's check has actually passed. " +
|
|
16
|
-
"action='blocked': report an impasse (requires 'reason') — only after 3 genuine attempts. " +
|
|
17
|
-
"action='cancel': abandon the goal. " +
|
|
18
|
-
"Returns a status line — the goal set/updated/completed/blocked/cancelled confirmation, or Error: ... with the reason.",
|
|
11
|
+
description: DESC("goal"),
|
|
19
12
|
parameters: {
|
|
20
13
|
type: "object",
|
|
21
14
|
properties: {
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* 现算面板块列表(state.subTasks 活值纯推导——无时点快照语义)。
|
|
10
10
|
* status 三态映射:done+awaitingDigest → "awaitingDigest";done → "done";
|
|
11
|
-
* queued(waiting/等位未启动)→ "queued";其余 → "running"
|
|
11
|
+
* queued(waiting/等位未启动)→ "queued";其余 → "running"(AGENT-LOOP-SUBAGENT.md §6.9 D-SD3b queued 态入镜口径保留)。
|
|
12
12
|
*/
|
|
13
13
|
export function computePanelBlocks(state) {
|
|
14
14
|
const subs = state?.subTasks
|
|
15
15
|
if (!subs) return null // 未挂载(headless/mock 无 TUI state)——无面板
|
|
16
16
|
const blocks = []
|
|
17
17
|
for (const sub of Object.values(subs)) {
|
|
18
|
-
// §
|
|
18
|
+
// AGENT-LOOP-SUBAGENT.md §6.9 D-SD3b:waiting/等位块(sub.queued——未启动)以 queued 态入镜(视图与面板
|
|
19
19
|
// 所见一致——action:'panel' 视图 + freeze 门控读此态)。
|
|
20
20
|
const status = sub.done ? (sub.awaitingDigest ? "awaitingDigest" : "done") : (sub.queued ? "queued" : "running")
|
|
21
21
|
blocks.push({ key: sub.key, role: sub.role ?? null, status, startedAt: sub.started ?? Date.now() })
|
|
@@ -43,6 +43,8 @@ import { carrierField, getAsyncPool, tombstoneOf, wakeAsyncWaiters } from "./asy
|
|
|
43
43
|
import { pushReal } from "../context.mjs"
|
|
44
44
|
import { escapeXml } from "../agent/helpers.mjs"
|
|
45
45
|
import { logEvent } from "../log.mjs"
|
|
46
|
+
// #15 描述外置:描述文本单点 = tool-docs/notify_parent.md(DESC 单解析面,缺档抛错语义不变)
|
|
47
|
+
import { DESC } from "../tools/shared.mjs"
|
|
46
48
|
|
|
47
49
|
/** message 长度上限(字符——闸三上界)。 */
|
|
48
50
|
export const UPSTREAM_MSG_MAX = 1500
|
|
@@ -180,19 +182,7 @@ export function drainChildUpstream(agent) {
|
|
|
180
182
|
*/
|
|
181
183
|
export const parentChannelTool = {
|
|
182
184
|
name: "notify_parent",
|
|
183
|
-
description:
|
|
184
|
-
"A one-way channel to your PARENT (the agent that spawned you) — you are a subagent, so there is no user to ask.\n" +
|
|
185
|
-
"Queue a message on the parent's side; your turn is not interrupted and nothing is consumed from it.\n" +
|
|
186
|
-
"- kind:'ask' — a question whose answer changes your next step and that you cannot answer from the materials you can read\n" +
|
|
187
|
-
" (task book / design doc / repo). The parent replies with subagent action:'send'; you receive it as an ordinary\n" +
|
|
188
|
-
" instruction at your next turn boundary. One ask at a time while the previous one is still waiting in the parent's queue.\n" +
|
|
189
|
-
"- kind:'note' — an FYI that needs no answer (a premise you found broken, a conflict you resolved and want visible early).\n" +
|
|
190
|
-
"- NON-BLOCKING: the call returns immediately. Keep working on the unaffected parts; keep the affected part pending.\n" +
|
|
191
|
-
" There is no fetch and no waiting — if you finish first, report the unanswered part as not done (never idle, never poll).\n" +
|
|
192
|
-
"- SYNCHRONOUS SPAWN: if the parent is blocked on your run (sync spawn), nothing can be sent back to you — the reply path (`send`) reaches only a RUNNING ASYNC child. The message is read when the parent's call returns (it may re-spawn you with an answer); report the unanswered part as not done.\n" +
|
|
193
|
-
"- Out of scope: naming / implementation / wording details, anything a read or a command would answer, trade-offs the\n" +
|
|
194
|
-
" task book already states. When in doubt use the stop-and-report discipline — this is not an escape from your own judgment.\n" +
|
|
195
|
-
"- Availability: subagents only (depth > 0). At depth 0 you talk to the user through your normal reply or the question tool.",
|
|
185
|
+
description: DESC("notify_parent"),
|
|
196
186
|
parameters: {
|
|
197
187
|
type: "object",
|
|
198
188
|
properties: {
|
package/agent-tools/plan.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* plan tool: enter/exit plan mode.
|
|
3
|
-
*
|
|
4
|
-
* Exit plan mode to present the plan for the user's approval; implement only after they approve.
|
|
3
|
+
* 描述面 = `tool-docs/plan.md`(DESC() 加载)。
|
|
5
4
|
*
|
|
6
5
|
* Reminder cadence (kimi-code style): while plan mode is active the agent loop
|
|
7
6
|
* re-injects reminders — sparse every 2 turns, full every 5 turns or when the
|
|
8
7
|
* user sends a new message — so the constraint never fades from context.
|
|
9
8
|
*/
|
|
9
|
+
import { DESC } from "../tools/shared.mjs"
|
|
10
10
|
|
|
11
11
|
const PLAN_FULL_REMINDER =
|
|
12
12
|
"[System reminder: plan mode is ON. Workflow: (1) explore/read codebase with read-only tools, " +
|
|
@@ -88,8 +88,7 @@ export function planReminderForTurn(agent, userMessageSince) {
|
|
|
88
88
|
|
|
89
89
|
export const planTool = {
|
|
90
90
|
name: "plan",
|
|
91
|
-
description:
|
|
92
|
-
"Enter or exit plan mode. In plan mode you are restricted to READ-ONLY tools: read files, search code, run read-only shell commands. Use plan mode before complex multi-step tasks — explore the codebase, design the architecture, present a plan to the user. Exit plan mode to present the plan for the user's approval; implement only after they approve. For simple single-file edits, skip plan mode and just make the change.",
|
|
91
|
+
description: DESC("plan"),
|
|
93
92
|
parameters: {
|
|
94
93
|
type: "object",
|
|
95
94
|
properties: {
|