@thincoder/core 0.9.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/abort-provenance.mjs +116 -0
- package/advisor/citations.mjs +139 -0
- package/advisor/compaction.mjs +174 -0
- package/advisor/convergence.mjs +80 -0
- package/advisor/history.mjs +77 -0
- package/advisor/loop.mjs +288 -0
- package/advisor/messages.mjs +299 -0
- package/advisor/notice.mjs +141 -0
- package/advisor/project-context.mjs +197 -0
- package/advisor/repos.mjs +150 -0
- package/advisor/run.mjs +190 -0
- package/advisor/truncate.mjs +57 -0
- package/advisor.mjs +281 -0
- package/agent/child-marks.mjs +24 -0
- package/agent/completion.mjs +145 -0
- package/agent/dispatch.mjs +493 -0
- package/agent/family-tools.mjs +174 -0
- package/agent/helpers.mjs +412 -0
- package/agent/post-turn.mjs +70 -0
- package/agent/record-results.mjs +174 -0
- package/agent/relay-prefix.mjs +39 -0
- package/agent/run-stages.mjs +244 -0
- package/agent/setup-reminders.mjs +199 -0
- package/agent/setup.mjs +234 -0
- package/agent/spawn-child.mjs +258 -0
- package/agent/suspension.mjs +240 -0
- package/agent/write-gate.mjs +87 -0
- package/agent-tools/advisor-async.mjs +481 -0
- package/agent-tools/advisor-settle.mjs +240 -0
- package/agent-tools/advisor.mjs +280 -0
- package/agent-tools/async-discard.mjs +143 -0
- package/agent-tools/async-settle.mjs +299 -0
- package/agent-tools/batch-segment.mjs +265 -0
- package/agent-tools/child-permission.mjs +45 -0
- package/agent-tools/consult.mjs +471 -0
- package/agent-tools/design-token.mjs +117 -0
- package/agent-tools/digest-budget.mjs +76 -0
- package/agent-tools/eng.mjs +102 -0
- package/agent-tools/escalate-async.mjs +302 -0
- package/agent-tools/goal.mjs +119 -0
- package/agent-tools/panel-blocks.mjs +24 -0
- package/agent-tools/parent-channel.mjs +231 -0
- package/agent-tools/plan.mjs +86 -0
- package/agent-tools/read-history.mjs +309 -0
- package/agent-tools/recent-changes.mjs +24 -0
- package/agent-tools/review-facts.mjs +31 -0
- package/agent-tools/settings.mjs +268 -0
- package/agent-tools/skill.mjs +63 -0
- package/agent-tools/spawn-gates.mjs +109 -0
- package/agent-tools/subagent-actions.mjs +496 -0
- package/agent-tools/subagent-async.mjs +456 -0
- package/agent-tools/subagent-panel.mjs +160 -0
- package/agent-tools/subagent-run.mjs +208 -0
- package/agent-tools/subagent-scheduler.mjs +446 -0
- package/agent-tools/subagent-spawn.mjs +478 -0
- package/agent-tools/subagent.mjs +419 -0
- package/agent-tools/task.mjs +87 -0
- package/agent-tools/timer.mjs +46 -0
- package/agent-tools/verify.mjs +295 -0
- package/agent-tools.mjs +23 -0
- package/agent.mjs +430 -0
- package/auto-think.mjs +115 -0
- package/compress-form.mjs +24 -0
- package/config-io.mjs +277 -0
- package/config-migrate.mjs +178 -0
- package/config-presets.mjs +49 -0
- package/config.mjs +419 -0
- package/context.mjs +495 -0
- package/conventions.mjs +223 -0
- package/embedding.mjs +120 -0
- package/escape.mjs +152 -0
- package/expand-home.mjs +16 -0
- package/explore-distill.mjs +152 -0
- package/generate-title.mjs +123 -0
- package/git/checkpoint.mjs +448 -0
- package/git/gitmem.mjs +100 -0
- package/history-window.mjs +179 -0
- package/hooks.mjs +108 -0
- package/i18n.mjs +106 -0
- package/index-bin.mjs +48 -0
- package/index-discover.mjs +176 -0
- package/ledger-cmd.mjs +209 -0
- package/ledger-db.mjs +86 -0
- package/ledger-surface.mjs +76 -0
- package/ledger.mjs +202 -0
- package/log.mjs +195 -0
- package/manifest.mjs +338 -0
- package/markdown.mjs +106 -0
- package/mcp/helpers.mjs +51 -0
- package/mcp/transport-http.mjs +248 -0
- package/mcp/transport-stdio.mjs +140 -0
- package/mcp/transport-ws.mjs +122 -0
- package/mcp.mjs +295 -0
- package/memory/code-index.mjs +219 -0
- package/memory/code-sync.mjs +427 -0
- package/memory/core.mjs +318 -0
- package/memory/delete.mjs +242 -0
- package/memory/docs.mjs +431 -0
- package/memory/file-walk.mjs +109 -0
- package/memory/origin.mjs +24 -0
- package/memory/scan.mjs +177 -0
- package/memory/schema.mjs +460 -0
- package/memory.mjs +21 -0
- package/model-ref.mjs +66 -0
- package/model-specs.mjs +277 -0
- package/package.json +32 -0
- package/peer-domains.mjs +265 -0
- package/peer-instances.mjs +178 -0
- package/permission.mjs +79 -0
- package/process-probe.mjs +315 -0
- package/prompt-files.mjs +113 -0
- package/prompt-overlays.mjs +78 -0
- package/prompts/advisor-design.md +43 -0
- package/prompts/advisor-round1.md +41 -0
- package/prompts/advisor-round2.md +46 -0
- package/prompts/advisor-round3.md +42 -0
- package/prompts/common.md +158 -0
- package/prompts/consult-base.md +19 -0
- package/prompts/discipline-engineering.md +123 -0
- package/prompts/discipline-normal.md +206 -0
- package/prompts/persona-coder.md +21 -0
- package/prompts/persona-eng-coder.md +41 -0
- package/prompts/persona-eng-designer.md +80 -0
- package/prompts/persona-engineering.md +160 -0
- package/prompts/persona-explore.md +15 -0
- package/prompts/persona-normal.md +35 -0
- package/prompts/persona-plan.md +27 -0
- package/provider/anthropic.mjs +225 -0
- package/provider/core.mjs +491 -0
- package/provider/errors.mjs +101 -0
- package/provider/google.mjs +257 -0
- package/provider/index.mjs +7 -0
- package/provider/list-models.mjs +163 -0
- package/provider/normalize.mjs +81 -0
- package/provider/rate.mjs +168 -0
- package/provider/responses.mjs +495 -0
- package/provider/retry.mjs +88 -0
- package/provider/sse.mjs +264 -0
- package/provider/wait-status.mjs +59 -0
- package/proxy.mjs +274 -0
- package/rules.mjs +53 -0
- package/session-gc.mjs +248 -0
- package/session-guard.mjs +59 -0
- package/session-lifecycle.mjs +305 -0
- package/session-migrate.mjs +48 -0
- package/session-rename.mjs +38 -0
- package/session-segments.mjs +99 -0
- package/session-slot-write.mjs +168 -0
- package/session-slots-manifest.mjs +264 -0
- package/session-slots.mjs +298 -0
- package/session-store.mjs +441 -0
- package/session.mjs +244 -0
- package/skills.mjs +234 -0
- package/text-budget.mjs +79 -0
- package/token-ttl.mjs +285 -0
- package/tool-docs/apply_patch.md +15 -0
- package/tool-docs/bash.md +38 -0
- package/tool-docs/delete.md +13 -0
- package/tool-docs/edit.md +30 -0
- package/tool-docs/execute.md +21 -0
- package/tool-docs/fetch.md +12 -0
- package/tool-docs/file_ops.md +17 -0
- package/tool-docs/get_current_time.md +8 -0
- package/tool-docs/git.md +54 -0
- package/tool-docs/glob.md +11 -0
- package/tool-docs/grep.md +19 -0
- package/tool-docs/hashline_edit.md +14 -0
- package/tool-docs/insert_after.md +15 -0
- package/tool-docs/lint.md +10 -0
- package/tool-docs/ls.md +12 -0
- package/tool-docs/lsp.md +10 -0
- package/tool-docs/process.md +10 -0
- package/tool-docs/question.md +16 -0
- package/tool-docs/read.md +20 -0
- package/tool-docs/read_image.md +8 -0
- package/tool-docs/tree.md +14 -0
- package/tool-docs/wait_for.md +22 -0
- package/tool-docs/websearch.md +16 -0
- package/tool-docs/write.md +11 -0
- package/tools/bash.mjs +276 -0
- package/tools/edit-batch.mjs +204 -0
- package/tools/edit-diff.mjs +388 -0
- package/tools/exec-run.mjs +43 -0
- package/tools/execute.mjs +243 -0
- package/tools/file.mjs +464 -0
- package/tools/git-checkpoint.mjs +143 -0
- package/tools/git-ext.mjs +173 -0
- package/tools/git.mjs +415 -0
- package/tools/glob-dialect.mjs +130 -0
- package/tools/index.mjs +76 -0
- package/tools/linter.mjs +120 -0
- package/tools/lsp.mjs +335 -0
- package/tools/ops.mjs +293 -0
- package/tools/patch.mjs +290 -0
- package/tools/question.mjs +26 -0
- package/tools/repomap.mjs +314 -0
- package/tools/search.mjs +248 -0
- package/tools/shared.mjs +467 -0
- package/tools/tree.mjs +81 -0
- package/tools/web.mjs +224 -0
- package/tools/write-path.mjs +191 -0
- package/traces/trace-store.mjs +303 -0
- package/undo-stack.mjs +47 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* advisor-async.mjs — async advisor reviews (AGENT-LOOP-SUBAGENT.md §6.10 — R13, 2026-09-06).
|
|
3
|
+
*
|
|
4
|
+
* The advisor tool runs reviews in a background pool at depth 0 (default async —
|
|
5
|
+
* ruling ②-3 A): the launch returns an ack, the turn ends naturally, the session
|
|
6
|
+
* suspends, the review settles into _pendingAsyncResults and a digest turn
|
|
7
|
+
* delivers the report (§17 consumption machinery — role-agnostic; entries carry
|
|
8
|
+
* role "advisor" and ride the subagent panel as a pseudo-role, ruling ②-4 A).
|
|
9
|
+
*
|
|
10
|
+
* This module owns:
|
|
11
|
+
* 1. the per-review INSTANCE registry (agent._advisorRuns — ruling ②-5 A:
|
|
12
|
+
* Map<reviewId, {reviewType, round, priorOutput, stale, open, docSetKey,
|
|
13
|
+
* designId}>) — rounds/prior are PER REVIEW (fix #4), no longer a run-global
|
|
14
|
+
* shared counter (design + code used to share _advisorRound);
|
|
15
|
+
* 2. launch resolution: design reviews continue the instance of the same
|
|
16
|
+
* document set (reviewId = the designId minted at the instance's first
|
|
17
|
+
* launch); code reviews continue the newest OPEN code instance (round 2+ of
|
|
18
|
+
* a fix loop) and CLOSE at normal user-run ends once no review is in flight
|
|
19
|
+
* (a converged/abandoned thread must not burn the cap of later tasks);
|
|
20
|
+
* 3. the async pool (agent._asyncAdvisors — ADVISOR_POOL_LIMIT = 4 default,
|
|
21
|
+
* agent.poolLimits.advisor configurable — POOL-CONFIG-UNIFIED; same-scope
|
|
22
|
+
* running launches are refused at once — never queued (F-5); over-limit +
|
|
23
|
+
* DIFFERENT scope launches QUEUE (ED-4 2026-09-16 — AGENT-LOOP-SUBAGENT.md §6.10:
|
|
24
|
+
* agent._asyncAdvisorQueue, ack {queued, position}, slot release auto-start,
|
|
25
|
+
* queued cancel dequeue+renumber — the former over-limit refusal ②-6a is
|
|
26
|
+
* retired); cancel (ruling ②-6b — directed abort → cancelled settle: no
|
|
27
|
+
* pending entry, no token, a "评审已取消——token 未签发" reminder);
|
|
28
|
+
* 4. SETTLE accounting (fixes #2/#4 — moved from the tool-result commit): a
|
|
29
|
+
* review whose targets mutated after launch settles STALE — it marks no
|
|
30
|
+
* _calledAdvisorThisRun and issues no token (the guard pushes back); a
|
|
31
|
+
* non-stale code review marks _calledAdvisorThisRun; a passing design review
|
|
32
|
+
* issues its token under the designId slot at settle time.
|
|
33
|
+
* 2026-09-11 第 11 批拆分:settle 记账 + 变更日志 + 陈旧/冻结判定迁 `advisor-settle.mjs`
|
|
34
|
+
* (本文件 500 行 = 硬帽在册);既有 import 面经本文件 re-export 保持不变。
|
|
35
|
+
*
|
|
36
|
+
* The sync path (depth>0 eng-coder self-review / explicit async:false) reuses
|
|
37
|
+
* the same instance resolution (rounds/prior/cap) — its accounting still lands
|
|
38
|
+
* in recordToolResults (agent.mjs run loop), marker-keyed by toolCall id.
|
|
39
|
+
*/
|
|
40
|
+
import { randomUUID } from "node:crypto"
|
|
41
|
+
// design-token 工具组(2026-09-08 自本文件迁至 design-token.mjs——再越 500 行硬限)——
|
|
42
|
+
// 既有 import 面(advisor.mjs / 测试)不变:原导出全部经此 re-export 保留。
|
|
43
|
+
export {
|
|
44
|
+
buildApprovedSuffix, stripApprovedSuffix, effectiveTokenTtlMs, generateDesignToken,
|
|
45
|
+
validateDesignToken, makeDesignTokenRegex, settleDesignReview,
|
|
46
|
+
} from "./design-token.mjs"
|
|
47
|
+
// 第 11 批拆分(2026-09-11):settle 记账 + 变更日志(noteMutations)+ 陈旧判定
|
|
48
|
+
// (reviewIsStale)+ 冻结冲突(inflightDesignReviewConflict)迁 advisor-settle.mjs——
|
|
49
|
+
// 既有 import 面(dispatch / subagent-async / escalate-async / 测试)经此 re-export 不变。
|
|
50
|
+
import { normAbs, mutationSeqOf, settleAdvisorRun } from "./advisor-settle.mjs"
|
|
51
|
+
export {
|
|
52
|
+
mutationSeqOf, noteMutations, reviewIsStale, settleAdvisorRun, inflightDesignReviewConflict,
|
|
53
|
+
} from "./advisor-settle.mjs"
|
|
54
|
+
// 第 33 批(§17.5):doc-set 键迁事实面档(实例续跑与陈旧判定同锚单源;原为私有——零 import 面)。
|
|
55
|
+
// 本文件继续在实例解析 / 池 entries 上消费它。
|
|
56
|
+
import { docSetKey } from "./review-facts.mjs"
|
|
57
|
+
// F31(2026-09-18 顾问面治理批):拒回文案对象标识行单源。
|
|
58
|
+
import { withIdentityLine, scopeSummary } from "../advisor/notice.mjs"
|
|
59
|
+
import { runAdvisorReview, resolveAdvisorProvider, ADVISOR_THINKING_PLACEHOLDER } from "../advisor/run.mjs"
|
|
60
|
+
import { stripEventToken } from "../agent/spawn-child.mjs"
|
|
61
|
+
import { escapeXml } from "../agent/helpers.mjs"
|
|
62
|
+
import { pushReal } from "../context.mjs"
|
|
63
|
+
import { logEvent } from "../log.mjs"
|
|
64
|
+
import { deathLine } from "../abort-provenance.mjs"
|
|
65
|
+
// ASYNC-RESULT-CONTAINER.md D3/D6:settle 公共收尾单点 + child signal 构建单点
|
|
66
|
+
import { bindChildController, buildChildSignal, carrierField, getAsyncPool, settleAsyncEntry, tombstoneOf, writeTombstone } from "./async-settle.mjs"
|
|
67
|
+
import { nextSubagentId, consumeSubagentToken, assertPoolKeyFree, entryTerminal } from "./subagent-scheduler.mjs"
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// Review-instance registry (_advisorRuns — per-review rounds/prior/cap)
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/** 载体吸收后的只读读取(不创建):父对象字段优先、缺字段回退跨 run 载体
|
|
74
|
+
* (`history` 数组——agent 逐 run 重建的形态靠它跨 run 存活)。无 ⇒ null。 */
|
|
75
|
+
function advisorRunsRead(agent) {
|
|
76
|
+
const m = carrierField(agent, "_advisorRuns")
|
|
77
|
+
return m instanceof Map ? m : null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function advisorRuns(agent) {
|
|
81
|
+
// 实例注册表读取吸收(与 #94 载体口径同源):既有形态(字段挂 agent)零变;
|
|
82
|
+
// 注册表已挂跨 run 载体的形态直接沿用(不另建分叉)。
|
|
83
|
+
const existing = advisorRunsRead(agent)
|
|
84
|
+
if (existing) return existing
|
|
85
|
+
// 缺省创建:落在跨 run 载体上(有 history 时)——与其余载体字段同口径。
|
|
86
|
+
const holder = agent?.history ?? agent
|
|
87
|
+
if (!holder) return new Map()
|
|
88
|
+
if (!(holder._advisorRuns instanceof Map)) holder._advisorRuns = new Map()
|
|
89
|
+
return holder._advisorRuns
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The newest OPEN code instance (the thread a fix-round launch continues), or null. */
|
|
93
|
+
export function openCodeRun(agent) {
|
|
94
|
+
const runs = advisorRunsRead(agent)
|
|
95
|
+
if (!(runs instanceof Map) || runs.size === 0) return null
|
|
96
|
+
for (const r of [...runs.values()].reverse()) {
|
|
97
|
+
if (r.reviewType === "code" && r.open) return r
|
|
98
|
+
}
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The newest OPEN design instance for the given document set, or null. */
|
|
103
|
+
function openDesignRun(agent, key) {
|
|
104
|
+
const runs = advisorRunsRead(agent)
|
|
105
|
+
if (!(runs instanceof Map) || runs.size === 0) return null
|
|
106
|
+
for (const r of [...runs.values()].reverse()) {
|
|
107
|
+
if (r.reviewType === "design" && r.open && r.docSetKey === key) return r
|
|
108
|
+
}
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the review instance a launch continues (§11.2 ③ — per-review
|
|
114
|
+
* rounds/prior; reviewId = designId for design reviews, a random id for code).
|
|
115
|
+
* - design: continue the OPEN instance of the same document set (fix rounds of
|
|
116
|
+
* a review thread keep its designId and its round/prior); none → new instance.
|
|
117
|
+
* - code: continue the newest OPEN code instance (guard-driven fix loops);
|
|
118
|
+
* none → new instance.
|
|
119
|
+
* The legacy agent._advisorRound/_lastAdvisorOutput fields are SCOPED to the
|
|
120
|
+
* instance before the launch (message building + the run.mjs cap read them) —
|
|
121
|
+
* prior-less continuation degrades to round 1 semantics (fresh full review).
|
|
122
|
+
* @returns {{run: object, isNew: boolean, reviewId: string, designId: string|null}}
|
|
123
|
+
*/
|
|
124
|
+
export function resolveAdvisorLaunch(agent, reviewType, { documents = null } = {}) {
|
|
125
|
+
const runs = advisorRuns(agent)
|
|
126
|
+
let run = null
|
|
127
|
+
if (reviewType === "design") {
|
|
128
|
+
const key = docSetKey(documents, agent.cwd)
|
|
129
|
+
run = openDesignRun(agent, key)
|
|
130
|
+
if (!run) {
|
|
131
|
+
const reviewId = randomUUID()
|
|
132
|
+
// F2h (§29.1 2026-09-07): a same-scope re-review reuses the session's
|
|
133
|
+
// designId for this doc-set (any prior instance of the scope — open OR
|
|
134
|
+
// closed — the newest wins). A passed re-review overwrites the slot under
|
|
135
|
+
// the same id (no slot residue — T14: the old token is then rejected at
|
|
136
|
+
// the gate); old slots die at TTL only, kept as the fail-safe fallback.
|
|
137
|
+
const prior = [...runs.values()].reverse().find((r) => r.reviewType === "design" && r.docSetKey === key)
|
|
138
|
+
run = {
|
|
139
|
+
reviewId, reviewType, designId: prior?.designId ?? reviewId,
|
|
140
|
+
round: 0, priorOutput: null, stale: false, open: true, docSetKey: key,
|
|
141
|
+
}
|
|
142
|
+
runs.set(reviewId, run)
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
run = openCodeRun(agent)
|
|
146
|
+
if (!run) {
|
|
147
|
+
const reviewId = randomUUID()
|
|
148
|
+
run = {
|
|
149
|
+
reviewId, reviewType, designId: null,
|
|
150
|
+
round: 0, priorOutput: null, stale: false, open: true, docSetKey: null,
|
|
151
|
+
}
|
|
152
|
+
runs.set(reviewId, run)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Scope the legacy mirror fields (message builder + run.mjs cap + displays).
|
|
156
|
+
agent._advisorRound = run.priorOutput ? run.round : 0
|
|
157
|
+
agent._lastAdvisorOutput = run.priorOutput
|
|
158
|
+
return { run, isNew: run.round === 0 && !run.priorOutput, reviewId: run.reviewId, designId: run.designId }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Guard round — the OPEN CODE instance only (2026-09-07 §8 F2): the design-written
|
|
162
|
+
* mirror never gates the code guard — no open code instance → 0. */
|
|
163
|
+
export function effectiveAdvisorRound(agent) {
|
|
164
|
+
return openCodeRun(agent)?.round ?? 0
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
168
|
+
// Async pool (agent._asyncAdvisors — pool 上限 F-1/F-2:常量 = 运行时回退权威(2 → 4),
|
|
169
|
+
// config.mjs DEFAULTS.agent.poolLimits.advisor = config 层镜像——耦合锁 config-pool
|
|
170
|
+
// .test.mjs 逐键断言——勿单侧改默认)
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
export const ADVISOR_POOL_LIMIT = 4
|
|
174
|
+
|
|
175
|
+
/** §11.2 advisor 池生效上限(纯——读 agent.poolLimits.advisor——合法 ≥1 整数生效——
|
|
176
|
+
* 非法/缺省回退 ADVISOR_POOL_LIMIT——与 subagent 域 resolvePoolLimits 独立不共享
|
|
177
|
+
* (subagent 两键表不含本键——键表语义不同——POOL-CONFIG-UNIFIED F-2))。 */
|
|
178
|
+
export function resolveAdvisorPoolLimit(raw) {
|
|
179
|
+
const v = raw?.advisor
|
|
180
|
+
return Number.isInteger(v) && v >= 1 ? v : ADVISOR_POOL_LIMIT
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** launch 判定点每次读 agent.config(/config 热应用——变更即生效下个 launch——文案
|
|
184
|
+
* 如实报本值)。 */
|
|
185
|
+
export function advisorPoolLimitFor(agent) {
|
|
186
|
+
return resolveAdvisorPoolLimit(agent?.config?.agent?.poolLimits)
|
|
187
|
+
}
|
|
188
|
+
/** §11.2 同 scope 守卫(F-5——用户裁①):同 reviewType+scope 有 running 评审 → true
|
|
189
|
+
* (拒——running 并行多实例歧义放大——settled 续跑语义不变)。design scope = 文档集键
|
|
190
|
+
* (docSetKey);code = 单 code 线程(记录不记路径键——与 openCodeRun 语义一致)。
|
|
191
|
+
* 与池容量守卫独立:容量 = 全局 ≤N——scope = 同 scope ≤1——两关都过才启动。 */
|
|
192
|
+
export function runningAdvisorOfScope(agent, reviewType, docSetKey) {
|
|
193
|
+
const pool = getAsyncPool(agent, "advisor")
|
|
194
|
+
if (!(pool instanceof Map)) return false
|
|
195
|
+
for (const e of pool.values()) {
|
|
196
|
+
if (e.status !== "running" || e.reviewType !== reviewType) continue
|
|
197
|
+
if (reviewType === "design" && e.run?.docSetKey !== docSetKey) continue
|
|
198
|
+
return true
|
|
199
|
+
}
|
|
200
|
+
return false
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function runningAdvisorCount(agent) {
|
|
204
|
+
return [...(getAsyncPool(agent, "advisor")?.values() ?? [])].filter((e) => e.status === "running").length
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Any in-flight (non-done) async advisor review? — the completion guard skips
|
|
208
|
+
* its push-back while a review is pending (T-24b4: 未决不算未评审). */
|
|
209
|
+
export function advisorReviewPending(agent) {
|
|
210
|
+
return [...(getAsyncPool(agent, "advisor")?.values() ?? [])].some((e) => !e.done)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Directed cancel (ruling ②-6b — ⏹/cancel): abort the running review's
|
|
214
|
+
* controller (run.mjs signal chain) → the settle callback runs the cancelled
|
|
215
|
+
* branch (no pending entry / no token / "评审已取消——token 未签发" reminder).
|
|
216
|
+
* queued 目标(af 批 §6.11 第 3 条):出队 + 余位重编号 + 终态 cancelled + 机读线提醒
|
|
217
|
+
* + `⟦ev⟧cancelled`(唯一发射点——经调用方通道 `onToken`)+ `ev:cancelled` 日志;
|
|
218
|
+
* 无 abort(从未 start)⇒ 无 `⟦ev⟧stopped`。`onToken` 缺省 ⇒ 不发射(不另发)。
|
|
219
|
+
* 终态确认面(af 批 fix 轮):出队后 tombstone 在册 ⇒ 重复取消返回同一确认
|
|
220
|
+
* (`{id, status:"cancelled"}`——零重复注入 / 发射 / 日志);在册 `done` / 未知 id 文案不变。 */
|
|
221
|
+
export function cancelAsyncAdvisor(agent, id, onToken) {
|
|
222
|
+
const key = String(id)
|
|
223
|
+
const map = getAsyncPool(agent, "advisor") ?? new Map()
|
|
224
|
+
const entry = map.get(key)
|
|
225
|
+
if (!entry) {
|
|
226
|
+
// af 批 fix 轮(T-AF2 终态确认面——§6.11 第 3 条「幂等与池面」):queued 取消出队即出池
|
|
227
|
+
// ⇒ 重复取消经 cancelled 墓碑返回同一确认(本分支全早退——零重复注入 / 发射 / 日志)。
|
|
228
|
+
// 非取消墓碑(consumed / failed / discarded)与无墓碑 ⇒ 既有 unknown-id 文案不变;
|
|
229
|
+
// role 守卫:本条只答评审族(子代理族墓碑不在本面)。
|
|
230
|
+
const tomb = tombstoneOf(agent, key)
|
|
231
|
+
if (tomb?.status === "cancelled" && tomb.role === "advisor") return { id: key, status: "cancelled" }
|
|
232
|
+
return { id: key, status: "error", error: `unknown async advisor review id: ${key}` }
|
|
233
|
+
}
|
|
234
|
+
if (entry.done) {
|
|
235
|
+
return { id: key, status: "error", error: `async advisor review #${key} has already finished — nothing to cancel` }
|
|
236
|
+
}
|
|
237
|
+
if (entry.cancelled) return { id: key, status: "cancelled" } // abort already in flight — idempotent
|
|
238
|
+
// ED-4(§6.10 取消路由):queued 取消 = 出队 + 余位 position 重编号 + 终态 cancelled
|
|
239
|
+
// (无 abort——从未 start;was 记 queued 供 TUI 排队块移除——subagent 取消同式
|
|
240
|
+
// subagent-async.mjs)。排队条目取消不释放槽——不做补位(槽从未被占)。
|
|
241
|
+
// af 批收尾三面同址单点:机读线提醒(逐字同 running 面模板)/ 块面事件(唯一发射点)/ 日志面(queued 不经 settle ⇒ 出队点直记)。
|
|
242
|
+
if (entry.status === "queued") {
|
|
243
|
+
dequeueAdvisor(agent, entry)
|
|
244
|
+
entry.cancelled = true
|
|
245
|
+
entry.done = true
|
|
246
|
+
entry.status = "done"
|
|
247
|
+
map.delete(key)
|
|
248
|
+
writeTombstone(agent, key, "cancelled", "advisor")
|
|
249
|
+
pushReal(agent, {
|
|
250
|
+
role: "user",
|
|
251
|
+
content: `[System reminder: async advisor review #${escapeXml(String(entry.id))} cancelled — the review did not settle; token not issued (评审已取消——token 未签发)]`,
|
|
252
|
+
})
|
|
253
|
+
// 块面事件(零字段;relay 前缀 advisor#<id>/)——不发 ⟦ev⟧stopped(非 settle 通道)
|
|
254
|
+
try { onToken?.(`${entry.relayPrefix}⟦ev⟧cancelled\x1e`) } catch { /* relay 失败不影响池状态 */ }
|
|
255
|
+
logEvent("ev:cancelled", { id: `advisor#${key}` }) // 与 running 面同形(写点互斥 ⇒ 恰一条)
|
|
256
|
+
entry._settle?.()
|
|
257
|
+
return { id: key, status: "cancelled", was: "queued" }
|
|
258
|
+
}
|
|
259
|
+
entry.cancelled = true
|
|
260
|
+
// §20.3 站点 #11(第 24 批):定向中止 = cancel(reason 载荷)
|
|
261
|
+
entry.controller?.abort?.({ abortTrigger: "cancel", abortDetail: "advisor-cancel" })
|
|
262
|
+
return { id: key, status: "cancelled" }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Relay an advisor onOutput chunk into the subagent block channel (`advisor#N/`
|
|
266
|
+
* prefix — kind-preserving: think via onReasoning, tool via onToolOutput, text
|
|
267
|
+
* via onToken; the wait placeholder is stripped — it is a live indicator, not
|
|
268
|
+
* content, and the sync path strips it at freeze too). */
|
|
269
|
+
function relayAdvisorOutput(callbacks, prefix, chunk) {
|
|
270
|
+
if (!callbacks) return
|
|
271
|
+
const kind = typeof chunk === "string" ? "text" : (chunk?.kind ?? "text")
|
|
272
|
+
const text = typeof chunk === "string" ? chunk : String(chunk?.text ?? "")
|
|
273
|
+
if (!text) return
|
|
274
|
+
if (kind === "think") {
|
|
275
|
+
callbacks.onReasoning?.(prefix + text.replaceAll(ADVISOR_THINKING_PLACEHOLDER, ""))
|
|
276
|
+
} else if (kind === "tool") {
|
|
277
|
+
callbacks.onToolOutput?.(prefix + "tool", { kind: "tool", text })
|
|
278
|
+
} else {
|
|
279
|
+
callbacks.onToken?.(prefix + stripEventToken(text))
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ─── ED-4(2026-09-16 · AGENT-LOOP-SUBAGENT.md §6.10):评审池排队(槽释放自动起跑)────────
|
|
284
|
+
|
|
285
|
+
/** 排队出队单点(ED-4):`_asyncAdvisorQueue` 剔除目标条目 + 余项 position 按 `1..n`
|
|
286
|
+
* 重编号;读面经**载体吸收**(`carrierField`——#21⑤b):部分 parent(只携池 + `history`)
|
|
287
|
+
* 下不再 no-op(原直读 ⇒ 已取消评审被补位重启)。 */
|
|
288
|
+
function dequeueAdvisor(parent, entry) {
|
|
289
|
+
const queue = carrierField(parent, "_asyncAdvisorQueue")
|
|
290
|
+
if (!Array.isArray(queue)) return
|
|
291
|
+
const qi = queue.indexOf(entry)
|
|
292
|
+
if (qi < 0) return
|
|
293
|
+
queue.splice(qi, 1)
|
|
294
|
+
for (let i = 0; i < queue.length; i++) queue[i].position = i + 1
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** 排队块刷新(⟦ev⟧queued——slot 面:advisor 队列仅容量/scope 排队,无 dep/wait 面)——
|
|
298
|
+
* `_lastQueuedSig` 幂等去重;读面经**载体吸收**(部分 parent 不得 no-op——调用点含 VSC
|
|
299
|
+
* ⏹ → `executeCancelAction` 的合成 parent)。 */
|
|
300
|
+
export function refreshAdvisorQueuedTokens(parent, onToken) {
|
|
301
|
+
if (typeof onToken !== "function") return
|
|
302
|
+
const queue = carrierField(parent, "_asyncAdvisorQueue")
|
|
303
|
+
if (!Array.isArray(queue)) return
|
|
304
|
+
for (let i = 0; i < queue.length; i++) {
|
|
305
|
+
const e = queue[i]
|
|
306
|
+
const sig = `slot\x1e${i + 1}\x1e`
|
|
307
|
+
if (e._lastQueuedSig === sig) continue
|
|
308
|
+
e._lastQueuedSig = sig
|
|
309
|
+
try { onToken(`${e.relayPrefix}⟦ev⟧queued\x1eslot\x1e${i + 1}\x1equeued\x1e`) } catch { /* relay 失败不影响池状态 */ }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* ED-4(§6.10):评审池排队补位单点——槽释放(任意族 settle 公共尾部)即按队首序补位
|
|
315
|
+
* 启动(出队复检同 scope 守卫——启动时刻同 scope 已被占 ⇒ 跳过留队)。与 subagent 队列
|
|
316
|
+
* 的先入者序不同:advisor 队列的阻塞仅来自 scope——被挡者不锁后入者(后入异 scope
|
|
317
|
+
* 排队者可越过被挡者先启动)。启动后余项 position 重编号 + ⟦ev⟧queued 刷新。
|
|
318
|
+
*/
|
|
319
|
+
export function refillAdvisorQueue(parent, onToken) {
|
|
320
|
+
// 读面经**载体吸收**(`carrierField`——§6.10 ④「出队 / 补位 / 排队刷新三读面」同式):
|
|
321
|
+
// 部分 parent(只携池 + `history`)与出队 / 排队刷新读同一容器,不 no-op。
|
|
322
|
+
const queue = carrierField(parent, "_asyncAdvisorQueue")
|
|
323
|
+
if (!Array.isArray(queue) || queue.length === 0) return
|
|
324
|
+
const limit = advisorPoolLimitFor(parent)
|
|
325
|
+
let moved = false
|
|
326
|
+
for (;;) {
|
|
327
|
+
let pick = -1
|
|
328
|
+
for (let i = 0; i < queue.length; i++) {
|
|
329
|
+
const e = queue[i]
|
|
330
|
+
// §6.9 终态守卫(c1——谓词单点 entryTerminal):终态条目永不启动(幻影唯一燃料封死)
|
|
331
|
+
if (entryTerminal(e)) continue
|
|
332
|
+
// 出队复检(§6.10 交互):启动时刻同 scope 已被占 ⇒ 跳过留队(不等不排的反面面——
|
|
333
|
+
// 排队者从未占过 scope;scope 占用以 running 为准)。
|
|
334
|
+
if (runningAdvisorOfScope(parent, e.reviewType, e.run?.docSetKey ?? null)) continue
|
|
335
|
+
pick = i
|
|
336
|
+
break
|
|
337
|
+
}
|
|
338
|
+
if (pick < 0 || runningAdvisorCount(parent) >= limit) break
|
|
339
|
+
queue.splice(pick, 1)[0].start()
|
|
340
|
+
moved = true
|
|
341
|
+
}
|
|
342
|
+
if (!moved) return
|
|
343
|
+
for (let i = 0; i < queue.length; i++) queue[i].position = i + 1
|
|
344
|
+
refreshAdvisorQueuedTokens(parent, onToken)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Launch an async advisor review (pool entry + background runner + settle
|
|
349
|
+
* wiring — mirrors subagent-run.mjs's lifecycle, plus the ED-4 queue).
|
|
350
|
+
* The runner wraps runAdvisorReview (promise → report/error) — the subagent
|
|
351
|
+
* pipeline is untouched (ruling ②-2 A). The entry carries role "advisor" so the
|
|
352
|
+
* digest/panel/freeze consumers route it like any other settled block.
|
|
353
|
+
* @returns {{ok: true, id: string}} — ack; or {{ok: true, id: string, queued: true,
|
|
354
|
+
* position: number}} — ED-4 排队 ack(池满 + 异 scope——slot 释放自动起跑); or
|
|
355
|
+
* {{error: string}} — scope guard (same reviewType+scope running — §11.2 F-5)
|
|
356
|
+
* or invalid ctx.
|
|
357
|
+
*/
|
|
358
|
+
export function launchAsyncAdvisor(parent, ctx, launch) {
|
|
359
|
+
const { reviewType, documents, paths, object, designToken, designId, run } = launch
|
|
360
|
+
const limit = advisorPoolLimitFor(parent)
|
|
361
|
+
// §11.2 same-scope guard(F-5——用户裁①):同 type+scope 有 running → 拒——与池容量
|
|
362
|
+
// 守卫独立两关都过才启动——同 scope 不等不排(settled 续跑不变;排队仅异 scope 面)。
|
|
363
|
+
if (runningAdvisorOfScope(parent, reviewType, run?.docSetKey ?? null)) {
|
|
364
|
+
const scopeNote = reviewType === "design"
|
|
365
|
+
? "a design review of this document set is still running"
|
|
366
|
+
: "a code review is still running (code reviews are a single thread — launch the next one after it settles)"
|
|
367
|
+
// F31:既有稳定前缀逐字(行首)+ 标识块尾随(单源 notice.mjs);round = 本次发起将使用的轮次号。
|
|
368
|
+
const idFields = { type: reviewType, scope: scopeSummary(documents?.length ? documents : paths), round: `${(run?.round ?? 0) + 1}/uncapped`, criterion: "scope-in-flight" }
|
|
369
|
+
return { error: withIdentityLine(`Advisor: 此 scope 已有评审在跑——settle 后逐个发起 — ${scopeNote}; round/prior continuation would be ambiguous while it is in flight — wait for it to settle, then launch the next review.`, idFields) }
|
|
370
|
+
}
|
|
371
|
+
// ED-4(AGENT-LOOP-SUBAGENT.md §6.10):池满 + 异 scope ⇒ 排队(非拒)——ack 含 queued +
|
|
372
|
+
// position;running 计数只算 running 条目(排队不占槽)。原 ②-6a 拒发退役。
|
|
373
|
+
const queued = runningAdvisorCount(parent) >= limit
|
|
374
|
+
parent._asyncAdvisors ??= new Map()
|
|
375
|
+
// SUBAGENT-ID-COUNTER-AGENT(2026-09-09):取号统一走 nextSubagentId——池活续号兜底 + 跨池共号(§11.2)。
|
|
376
|
+
const id = nextSubagentId(parent)
|
|
377
|
+
// ED-5(AGENT-LOOP-SUBAGENT.md §6.21)一次性取号令牌消费(同步配对——本函数内取号 →
|
|
378
|
+
// 消费无 await 间隙);断言通过即置 undefined(防同一令牌跨站点复用)。
|
|
379
|
+
consumeSubagentToken(parent, id, "advisor launch", "advisor")
|
|
380
|
+
const entry = {
|
|
381
|
+
id, role: "advisor", reviewType, run,
|
|
382
|
+
reviewId: run.reviewId, designId, designToken, documents, paths, object,
|
|
383
|
+
launchSeq: mutationSeqOf(parent),
|
|
384
|
+
docAbs: reviewType === "design" && Array.isArray(documents)
|
|
385
|
+
? documents.filter((d) => typeof d === "string").map((d) => normAbs(d, parent.cwd))
|
|
386
|
+
: [],
|
|
387
|
+
relayPrefix: `advisor#${id}/`,
|
|
388
|
+
status: queued ? "queued" : "running", position: undefined,
|
|
389
|
+
report: null, error: null, done: false, cancelled: false,
|
|
390
|
+
promise: null, _settle: null,
|
|
391
|
+
model: (() => { try { return resolveAdvisorProvider(parent).model ?? null } catch { return null } })(),
|
|
392
|
+
startedAt: queued ? null : Date.now(), turn: 0, maxTurns: 0,
|
|
393
|
+
controller: null,
|
|
394
|
+
}
|
|
395
|
+
// Entry controller chained to the session/run base signal (subagent-run parity):
|
|
396
|
+
// Ctrl+C / session abort propagates into the review's chat; a digest's own
|
|
397
|
+
// Ctrl+I must not orphan it (children hold the session signal while suspended).
|
|
398
|
+
// D6 buildChildSignal 单点(ASYNC-RESULT-CONTAINER.md)。
|
|
399
|
+
const ctrl = new AbortController()
|
|
400
|
+
entry.controller = ctrl
|
|
401
|
+
// §20.3 站点 #10(第 24 批):hop 逐跳保 reason;#98 链结单点(interrupt 豁免面——
|
|
402
|
+
// Ctrl+I 不逐链中止飞行评审)。
|
|
403
|
+
bindChildController(ctrl, buildChildSignal(parent, ctx))
|
|
404
|
+
entry.promise = new Promise((res) => { entry._settle = res })
|
|
405
|
+
entry.start = () => {
|
|
406
|
+
// ED-4(§6.10):补位启动锚点——queued → running(position 清位);async mark +
|
|
407
|
+
// [model] 在此后发出(入队不 paint async 块——启动才 paint)。
|
|
408
|
+
entry.status = "running"
|
|
409
|
+
entry.position = undefined
|
|
410
|
+
entry.startedAt = Date.now()
|
|
411
|
+
// Async mark + [model] — the TUI block opens at ACTUAL start (⏹ gating reads it).
|
|
412
|
+
ctx?.callbacks?.onToken?.(entry.relayPrefix + "⟦ev⟧async\x1e")
|
|
413
|
+
ctx?.callbacks?.onToken?.(entry.relayPrefix + "[model]" + (entry.model ?? ""))
|
|
414
|
+
runAdvisorReview(parent, reviewType, {
|
|
415
|
+
onOutput: (chunk) => relayAdvisorOutput(ctx?.callbacks, entry.relayPrefix, chunk),
|
|
416
|
+
signal: entry.controller.signal,
|
|
417
|
+
}, designToken, documents, paths, object, designId)
|
|
418
|
+
.then((report) => { entry.report = report })
|
|
419
|
+
// §20.3 第 3 条合成器(第 24 批):原 message 前缀逐字保留 + 来源后缀
|
|
420
|
+
.catch((err) => { entry.error = deathLine(err, entry.controller?.signal) })
|
|
421
|
+
.finally(() => {
|
|
422
|
+
// settle 公共收尾单点(ASYNC-RESULT-CONTAINER.md D3——settleAsyncEntry):日志三连
|
|
423
|
+
// /cancelled 分支(出池+墓碑+⟦ev⟧stopped+"评审已取消——token 未签发"提醒)/挂起分流
|
|
424
|
+
// (pending 单容器+出池——统一守卫 !parentAborted——D4)/公共尾部(settleSeq/_settle/
|
|
425
|
+
// 唤醒 waiter)统一走共享 helper。族特有 hook = settleAdvisorRun 记账(fix #2/#4——
|
|
426
|
+
// 陈旧判定/轮次/token D1 落盘——settle 分支输出写回 entry.report,digest 原样进);
|
|
427
|
+
// cancelled/parent-aborted 不调(不消费预算——settleAdvisorRun 自己的早退语义等价)。
|
|
428
|
+
settleAsyncEntry(parent, entry, {
|
|
429
|
+
pool: parent._asyncAdvisors,
|
|
430
|
+
ctx,
|
|
431
|
+
onAccounting: () => {
|
|
432
|
+
const settled = settleAdvisorRun(parent, entry)
|
|
433
|
+
// §29 fix B:settle 分支输出(清洗/未签发提示)写回 entry.report——digest 原样进。
|
|
434
|
+
if (settled.report != null) entry.report = settled.report
|
|
435
|
+
},
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
// ED-5(§6.21)入池键守卫:同 id 二次入池 = 覆写(静默丢报告 + status/cancel 错址)⇒ 抛错。
|
|
440
|
+
assertPoolKeyFree(parent._asyncAdvisors, id, "advisor")
|
|
441
|
+
parent._asyncAdvisors.set(String(id), entry)
|
|
442
|
+
if (queued) {
|
|
443
|
+
// ED-4(§6.10 排队面):独立评审队列入队 + 排队块 token(⟦ev⟧queued——slot 面)——
|
|
444
|
+
// 锚点 = 实际启动(入队不 paint async 块);ack 如实带 queued + position。
|
|
445
|
+
(parent._asyncAdvisorQueue ??= []).push(entry)
|
|
446
|
+
entry.position = parent._asyncAdvisorQueue.length
|
|
447
|
+
refreshAdvisorQueuedTokens(parent, ctx?.callbacks?.onToken)
|
|
448
|
+
logEvent("child:spawn", { role: "advisor", id: `advisor#${id}`, kind: "async", status: "queued", ms: 0 })
|
|
449
|
+
return { ok: true, id: String(id), queued: true, position: entry.position }
|
|
450
|
+
}
|
|
451
|
+
logEvent("child:spawn", { role: "advisor", id: `advisor#${id}`, kind: "async", status: "running", ms: 0 })
|
|
452
|
+
entry.start()
|
|
453
|
+
return { ok: true, id: String(id) }
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Close every OPEN code instance at a normal non-auto run end (finalizeAgentTurn)
|
|
458
|
+
* when no review/child activity remains: a code thread that settled and whose
|
|
459
|
+
* digest was consumed must not burn the convergence budget of a later task — the
|
|
460
|
+
* next code review starts a fresh full review (round 1). Runs that END with a review
|
|
461
|
+
* or an eng-coder child in flight keep the thread open (fix-round continuation).
|
|
462
|
+
* @returns {boolean} whether any instance was closed
|
|
463
|
+
*/
|
|
464
|
+
export function closeOpenCodeAdvisorRuns(agent) {
|
|
465
|
+
const advisors = getAsyncPool(agent, "advisor")
|
|
466
|
+
// Any pooled advisor entry — running OR settled-not-consumed — keeps the
|
|
467
|
+
// thread open: an in-run settle whose report has not reached the model (the
|
|
468
|
+
// suspension sweep / digest consumes it next) must still be continuable by
|
|
469
|
+
// the fix round that follows the disposition.
|
|
470
|
+
if ((advisors?.size ?? 0) > 0) return false
|
|
471
|
+
if ((getAsyncPool(agent, "subagent")?.size ?? 0) > 0) return false
|
|
472
|
+
if ((carrierField(agent, "_pendingAsyncResults") ?? []).some((e) => e.role === "advisor")) return false
|
|
473
|
+
let closed = false
|
|
474
|
+
const runs = advisorRunsRead(agent)
|
|
475
|
+
if (runs instanceof Map) {
|
|
476
|
+
for (const r of runs.values()) {
|
|
477
|
+
if (r.reviewType === "code" && r.open) { r.open = false; closed = true }
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return closed
|
|
481
|
+
}
|