@thincoder/core 0.9.5 → 0.10.2
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 +49 -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 +29 -12
- 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-ref.mjs +96 -3
- 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 +108 -27
- 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
|
@@ -7,20 +7,33 @@
|
|
|
7
7
|
* 契约(§4.15 逐字):**候选序** = cwd → 项目根 → 逐基底(基底 = `batchDocBases` 声明面单源);
|
|
8
8
|
* **判据分野** = 读面取首个**可读**文件 / create 取首个**落基底根内**者;**锚定防嵌套** = 串以任一基底的
|
|
9
9
|
* 「项目根相对前缀」打头且前缀后为**路径段边界** ⇒ 只解析项目根形(不二次拼接;不落基底内 ⇒ fail-closed);
|
|
10
|
-
*
|
|
10
|
+
* **歧义锚**(条 5 · 2026-10-02 · #828)= create 按目标所属项目落基底 ∥ 无所属显式拒;读面候选腿追加;
|
|
11
|
+
* 在飞 ∥ 写门基底集 = 候选并集(`batchDocBases`);**零变面** = 绝对路径照旧、三个错误文案逐字、
|
|
12
|
+
* 「参数在 + 路径可读」判据句、`ok` / `none` 两态。
|
|
11
13
|
*
|
|
12
14
|
* 依赖(KD-4 单向):本档为**叶档**——只 import `node:*` 与 `../manifest.mjs`;主档 re-export 保既有面。
|
|
13
15
|
*/
|
|
14
16
|
import { existsSync, statSync } from "node:fs"
|
|
15
17
|
import { isAbsolute, relative, resolve, sep } from "node:path"
|
|
16
18
|
|
|
17
|
-
import { docRootBase, docRootPaths, readManifest, resolveProjectRoot } from "../manifest.mjs"
|
|
19
|
+
import { docRootBase, docRootPaths, owningProject, projectRootView, readManifest, resolveProjectRoot } from "../manifest.mjs"
|
|
18
20
|
|
|
19
21
|
/** 可读文件判据(存在且为文件——目录 / 缺失同判不可读)。 */
|
|
20
22
|
function readableFile(abs) {
|
|
21
23
|
try { return existsSync(abs) && statSync(abs).isFile() } catch { return false }
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/** 歧义锚候选(判定单点 = `projectRootView`——`ambiguous` 态 ⇒ 候选全列**按名排序**;余者 null)。 */
|
|
27
|
+
function ambiguousCandidates(cwd) {
|
|
28
|
+
const view = projectRootView(cwd ?? process.cwd())
|
|
29
|
+
return view.state === "ambiguous" ? view.candidates : null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 路径同一性比较(win32 大小写不敏感——同越基底判据口径)。 */
|
|
33
|
+
function samePath(a, b) {
|
|
34
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
/** 「项目根」取根单源(§4.15 条 1——归属 ∨ 发现,无 manifest ⇒ `resolve(cwd ?? ".")` 兜底且**单值**):
|
|
25
38
|
* 同式 = 台账库键 / 写门指针基准的同一子表达式(`design/LEDGER.md` §6.1 口径 2)。 */
|
|
26
39
|
export function batchProjectRoot(cwd) {
|
|
@@ -37,9 +50,16 @@ function declaredBases(cwd) {
|
|
|
37
50
|
|
|
38
51
|
/** 批次档基底根数组(**声明面单源**——create / 在飞扫描 / 锚定判据共用):manifest `docRoot.batches`
|
|
39
52
|
* (串 | 多根数组)→ 声明基底;缺失 / 非法 ⇒ 回退默认基底 `"docs/batches"`(恒返回非空数组)。
|
|
53
|
+
* **歧义锚**(条 5)⇒ 候选项目基底**并集**(候选按名排序、并集保序)——在飞扫描 / 写门基底集同取此值。
|
|
40
54
|
* @param {string} cwd
|
|
41
55
|
* @returns {string[]} */
|
|
42
56
|
export function batchDocBases(cwd) {
|
|
57
|
+
const amb = ambiguousCandidates(cwd)
|
|
58
|
+
if (amb) {
|
|
59
|
+
const union = []
|
|
60
|
+
for (const c of amb) for (const b of batchDocBases(c)) if (!union.includes(b)) union.push(b)
|
|
61
|
+
return union
|
|
62
|
+
}
|
|
43
63
|
const roots = declaredBases(cwd)
|
|
44
64
|
return roots.length ? roots : [resolve(docRootBase(cwd), "docs/batches")]
|
|
45
65
|
}
|
|
@@ -76,6 +96,21 @@ function insideBases(abs, bases) {
|
|
|
76
96
|
const outsideBasesError = (raw) => new Error(`batch: create path resolves outside the batch-record base roots — a batch record must live under the declared docRoot.batches base (fail-closed). Path: ${raw}`)
|
|
77
97
|
/** create 锚定但落基底外(§4.15 新文案逐字——不二次拼接)。 */
|
|
78
98
|
const anchoredEscapeError = (raw) => new Error(`batch: create path is anchored at a batch base root but does not resolve under the declared docRoot.batches root(s) — refusing to nest it (fail-closed). Path: ${raw}`)
|
|
99
|
+
/** 歧义锚 create 拒面(**文案逐字** = `BATCH-RECORD.md` §4.15 条 5——机检锚 `ambiguous session anchor`)。 */
|
|
100
|
+
const ambiguousCreateError = (raw, candidates) => new Error(`batch: ambiguous session anchor — create target "${raw}" belongs to no candidate project; pass an explicit project path (e.g. "${candidates[0]}/docs/batches/<file>.md"). Candidates: ${candidates.join(", ")}`)
|
|
101
|
+
|
|
102
|
+
/** 歧义锚读面候选腿(条 5):逐候选项目——项目根形(`resolve(candidate, p)`)∥ 声明基底形
|
|
103
|
+
* (候选按名排序、并集保序——「首个可读」在此即为确定判读)。 */
|
|
104
|
+
function ambiguousReadLegs(cwd, p) {
|
|
105
|
+
const amb = ambiguousCandidates(cwd)
|
|
106
|
+
if (!amb) return []
|
|
107
|
+
const out = []
|
|
108
|
+
for (const c of amb) {
|
|
109
|
+
out.push(resolve(c, p))
|
|
110
|
+
for (const b of declaredBases(c)) out.push(resolve(b, p))
|
|
111
|
+
}
|
|
112
|
+
return out
|
|
113
|
+
}
|
|
79
114
|
|
|
80
115
|
/**
|
|
81
116
|
* **读面**解析(append / status / close 与评审门 / spawn 门共用):首个**可读**候选胜;无 ⇒ null
|
|
@@ -93,7 +128,7 @@ export function resolveBatchReadPath(cwd, given) {
|
|
|
93
128
|
const abs = resolve(batchProjectRoot(base), p)
|
|
94
129
|
return readableFile(abs) ? abs : null
|
|
95
130
|
}
|
|
96
|
-
const candidates = [resolve(base, p), resolve(batchProjectRoot(base), p), ...declaredBases(base).map((b) => resolve(b, p))]
|
|
131
|
+
const candidates = [resolve(base, p), resolve(batchProjectRoot(base), p), ...declaredBases(base).map((b) => resolve(b, p)), ...ambiguousReadLegs(base, p)]
|
|
97
132
|
for (const abs of candidates) if (readableFile(abs)) return abs
|
|
98
133
|
return null
|
|
99
134
|
}
|
|
@@ -116,6 +151,8 @@ export function resolveBatchDocPath(cwd, given) {
|
|
|
116
151
|
/**
|
|
117
152
|
* **create 面**解析(§4.15 条 2/3):绝对路径照旧取用(仍过越基底判据);相对路径 = 候选序中首个**落基底
|
|
118
153
|
* 根内**者(新档不存在——可读性无判别力);锚定串只解析项目根形,不落基底内 ⇒ fail-closed 新文案。
|
|
154
|
+
* **歧义锚**(条 5):目标所属项目(`owningProject`)∈ 锚候选集 ⇒ 基底 = 该项目声明面(落其基底内;
|
|
155
|
+
* 越出 ⇒ 既有 fail-closed);无所属 ⇒ **显式拒**(列候选 + 显式路径指引——不静默落锚)。
|
|
119
156
|
* @param {string} cwd 会话锚
|
|
120
157
|
* @param {string} given create `path` 原值
|
|
121
158
|
* @param {string[]} bases 已解析的基底根数组(`batchDocBases` 单源;调用方传入以免重复读档)
|
|
@@ -125,18 +162,25 @@ export function resolveBatchCreatePath(cwd, given, bases) {
|
|
|
125
162
|
const base = cwd ?? process.cwd()
|
|
126
163
|
const raw = String(given ?? "").trim()
|
|
127
164
|
const p = raw.replace(/\\/g, "/")
|
|
165
|
+
let roots = bases
|
|
166
|
+
const amb = ambiguousCandidates(base)
|
|
167
|
+
if (amb) {
|
|
168
|
+
const owner = owningProject(isAbsolute(raw) ? resolve(raw) : resolve(base, p))
|
|
169
|
+
if (!owner || !amb.some((c) => samePath(c, owner))) throw ambiguousCreateError(raw, amb)
|
|
170
|
+
roots = batchDocBases(owner)
|
|
171
|
+
}
|
|
128
172
|
if (isAbsolute(raw)) {
|
|
129
173
|
const abs = resolve(raw)
|
|
130
|
-
if (!insideBases(abs,
|
|
174
|
+
if (!insideBases(abs, roots)) throw outsideBasesError(raw)
|
|
131
175
|
return abs
|
|
132
176
|
}
|
|
133
|
-
if (anchoredPrefix(p, basePrefixes(base,
|
|
177
|
+
if (anchoredPrefix(p, basePrefixes(base, roots))) {
|
|
134
178
|
const abs = resolve(batchProjectRoot(base), p)
|
|
135
|
-
if (!insideBases(abs,
|
|
179
|
+
if (!insideBases(abs, roots)) throw anchoredEscapeError(raw)
|
|
136
180
|
return abs
|
|
137
181
|
}
|
|
138
|
-
for (const abs of [resolve(base, p), resolve(batchProjectRoot(base), p), ...
|
|
139
|
-
if (insideBases(abs,
|
|
182
|
+
for (const abs of [resolve(base, p), resolve(batchProjectRoot(base), p), ...roots.map((b) => resolve(b, p))]) {
|
|
183
|
+
if (insideBases(abs, roots)) return abs
|
|
140
184
|
}
|
|
141
185
|
throw outsideBasesError(raw)
|
|
142
186
|
}
|
|
@@ -117,7 +117,7 @@ export function readBatchStatusLine(src) {
|
|
|
117
117
|
nextRe.lastIndex = s1.index + s1[0].length
|
|
118
118
|
const next = nextRe.exec(src)
|
|
119
119
|
const body = src.slice(s1.index + s1[0].length, next ? next.index : src.length)
|
|
120
|
-
const m = body.split(
|
|
120
|
+
const m = body.split(/\r?\n/).map((line) => STATUS_LINE_RE.exec(line)).find(Boolean)
|
|
121
121
|
if (!m) return "unknown"
|
|
122
122
|
const value = m[1].trim()
|
|
123
123
|
if (value.includes(STATUS_WORDS[1].closed)) return "closed"
|
|
@@ -125,6 +125,24 @@ export function readBatchStatusLine(src) {
|
|
|
125
125
|
return "unknown"
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* §N 段内「`**状态行**:` 行存在性」判据(#422③——close 对无状态行档自建机读位的**采集面**:
|
|
130
|
+
* 与 readBatchStatusLine 的「值可解析」正交——无行 ⇒ 可自建;有行而值不可解析 ⇒ 仍 fail-closed)。
|
|
131
|
+
* 段界定消费 sectionHeaderRe(同单源——`## §20` 不误命中 `§2`)。
|
|
132
|
+
* @param {string} src — 档全文
|
|
133
|
+
* @param {number|string} seg — 段号
|
|
134
|
+
* @returns {boolean} 段内是否存在形态匹配行(段头缺失同判 false)
|
|
135
|
+
*/
|
|
136
|
+
export function sectionHasStatusLine(src, seg) {
|
|
137
|
+
const hdr = sectionHeaderRe(seg).exec(src)
|
|
138
|
+
if (!hdr) return false
|
|
139
|
+
const nextRe = /^## §\d/gm
|
|
140
|
+
nextRe.lastIndex = hdr.index + hdr[0].length
|
|
141
|
+
const next = nextRe.exec(src)
|
|
142
|
+
const body = src.slice(hdr.index + hdr[0].length, next ? next.index : src.length)
|
|
143
|
+
return body.split(/\r?\n/).some((line) => STATUS_LINE_RE.test(line))
|
|
144
|
+
}
|
|
145
|
+
|
|
128
146
|
/**
|
|
129
147
|
* create 骨架模板(BATCH-RECORD.md §4.10 行为规格的**代码单源**——红线:模板只覆盖骨架与
|
|
130
148
|
* 占位,不模板化内容):`## §1–§6` 段头(含职责署名)+ 档头 boilerplate(六段一段一作者句 +
|
package/agent-tools/batch.mjs
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 单工具 `batch`,action = create(建档,§4.11)/ append(段写入——原 batch-segment.mjs 语义
|
|
6
6
|
* 逐字迁移,§4.1 全表)/ status(段属主状态行流转,§4.12)/ close(收口冻结,§4.13)。
|
|
7
|
-
* 权威规格 = `docs/core/design/BATCH-RECORD.md` §4(契约/挂载/用例
|
|
8
|
-
* 参数面 §4.11–§4.13
|
|
7
|
+
* 权威规格 = `docs/core/design/BATCH-RECORD.md` §4(契约/挂载/用例 §4.8/骨架 §4.10/
|
|
8
|
+
* 参数面 §4.11–§4.13);骨架与判定字面单源 = `batch-skeleton.mjs`;lifecycle 面 =
|
|
9
9
|
* `batch-lifecycle.mjs`。**依赖单向(KD-4):skeleton ← lifecycle ← 主档**。
|
|
10
10
|
*
|
|
11
11
|
* 身份判据(D-BR17/D-BR18/D-BR21):主 agent 调用 ctx 带 `depth === 0`(dispatch 装配);
|
|
@@ -17,11 +17,9 @@
|
|
|
17
17
|
* ——轮 2 #3 裁定②)。append 迁移面错误串**逐字保持 "batch_segment:" 前缀**(§4.1 全表锚断言);
|
|
18
18
|
* "batch:" 前缀 = 本批新增错误面(create/close/status/findInFlight/path 门/dispatch)。
|
|
19
19
|
*
|
|
20
|
-
* 导出面:`batchTool`(主名)· `
|
|
21
|
-
* schema 仅 {segment,text}、描述逐字,execute ⇒ append 同一执行体;撤除判据 = BATCH-RECORD
|
|
22
|
-
* §4.14)· `resolveBatchDocPath` · `batchDocBases` · `batchDocForReview` ·
|
|
20
|
+
* 导出面:`batchTool`(主名)· `resolveBatchDocPath` · `batchDocBases` · `batchDocForReview` ·
|
|
23
21
|
* `configureBatchSegment` / `resetBatchSegment`(#84 记账缝——契约名不变,AC-11 零改)·
|
|
24
|
-
* `MAX_TEXT_CHARS` · `SEGMENT_BY_ROLE
|
|
22
|
+
* `MAX_TEXT_CHARS` · `SEGMENT_BY_ROLE`。
|
|
25
23
|
*
|
|
26
24
|
* #84 记账面(CORE-UNIFICATION §2.13.4):写入成功后的记账**按端注入**——核内缺省 no-op = CLI
|
|
27
25
|
* 语义零行为变;端装配经 `configureBatchSegment({ onWrite })` 覆盖(VSC 侧现形 = `_touchedFiles`
|
|
@@ -29,6 +27,8 @@
|
|
|
29
27
|
*/
|
|
30
28
|
import { readFileSync, writeFileSync } from "node:fs"
|
|
31
29
|
|
|
30
|
+
// 描述面 = `tool-docs/batch.md`(#15 描述外置统一——工厂形描述经 DESC 单一解析面,工厂调用期取值)
|
|
31
|
+
import { DESC } from "../tools/shared.mjs"
|
|
32
32
|
import { SEGMENT_BY_ROLE, readBatchStatusLine, sectionHeaderRe, findPlaceholderResidue, placeholderResidueError } from "./batch-skeleton.mjs"
|
|
33
33
|
import { batchDocBases, resolveBatchDocPath } from "./batch-paths.mjs"
|
|
34
34
|
import { closeBatchRecord, createBatchRecord, findInFlightBatch, statusBatchRecord } from "./batch-lifecycle.mjs"
|
|
@@ -36,8 +36,7 @@ import { closeBatchRecord, createBatchRecord, findInFlightBatch, statusBatchReco
|
|
|
36
36
|
export { SEGMENT_BY_ROLE, batchDocBases, resolveBatchDocPath }
|
|
37
37
|
|
|
38
38
|
/** text 单次上限(BATCH-RECORD.md §4.1——超出引导分段追加,不承诺"不新盖戳")。append 迁移域私有区常量
|
|
39
|
-
* (随执行体自 batch-segment.mjs 迁入——骨架档只住骨架/状态行单源,lifecycle
|
|
40
|
-
* export = shim 导出全超集(旧 `batch-segment.mjs` 导出面等价保持)。 */
|
|
39
|
+
* (随执行体自 batch-segment.mjs 迁入——骨架档只住骨架/状态行单源,lifecycle 档头枚举口径)。 */
|
|
41
40
|
export const MAX_TEXT_CHARS = 20000
|
|
42
41
|
/** 工具写入的轮次节标题形态(N 计数口径 = 该形态行;`### 轮次与发现(…)` 骨架行不匹配)。 */
|
|
43
42
|
const ROUND_HEADING_RE = /^### 轮次 \d+(评审子代理)/
|
|
@@ -182,7 +181,7 @@ function appendBatchRecord({ args, ctx, review, batchDoc, onWritten }) {
|
|
|
182
181
|
}
|
|
183
182
|
abs = resolveBatchDocPath(cwd, givenPath)
|
|
184
183
|
} else if (depth0) {
|
|
185
|
-
abs = findInFlightBatch(cwd, batchDocBases(cwd))
|
|
184
|
+
abs = findInFlightBatch(cwd, batchDocBases(cwd)) // 歧义锚 ⇒ 基底 = 候选并集(#828——唯一在飞批照取 / ≥2 照列)
|
|
186
185
|
} else {
|
|
187
186
|
if (!batchDoc) {
|
|
188
187
|
throw new Error("batch_segment: no batch record is bound to this caller — there is no path parameter by design (the target arrives via the spawn binding / the review instance key). Report the section as not written.")
|
|
@@ -195,7 +194,7 @@ function appendBatchRecord({ args, ctx, review, batchDoc, onWritten }) {
|
|
|
195
194
|
throw new Error("batch_segment: 已收口档不回改 — the bound batch record's §1 status line contains 「已收口」, so the record is frozen: its body is never written to again (整档冻结;改 = 新批新档). Nothing was written. Report the section as not written.")
|
|
196
195
|
}
|
|
197
196
|
if (status === "unknown") {
|
|
198
|
-
throw new Error("batch_segment: 状态行不可解析或缺失 — the bound batch record has no §1 `**状态行**:` line whose value contains 已收口 or 进行中 (fail-closed: the write is refused as if frozen). Ask the record's creator to set the §1 status line, then call again. Nothing was written.")
|
|
197
|
+
throw new Error("batch_segment: 状态行不可解析或缺失 — the bound batch record has no §1 `**状态行**:` line whose value contains 已收口 or 进行中 (fail-closed: the write is refused as if frozen). Ask the record's creator to set the §1 status line, then call again. Nothing was written. Do NOT close the record after a refused append — nothing landed, and closing freezes the missing section for good (失败 ⇒ 勿 close).")
|
|
199
198
|
}
|
|
200
199
|
if (typeof args?.text !== "string") {
|
|
201
200
|
throw new Error("batch_segment: text must be a string (the markdown to append).")
|
|
@@ -204,7 +203,7 @@ function appendBatchRecord({ args, ctx, review, batchDoc, onWritten }) {
|
|
|
204
203
|
throw new Error(`batch_segment: text is ${args.text.length} characters — the limit is ${MAX_TEXT_CHARS} per call. Split it into multiple calls: each call becomes its own section and the round number N continues (分段追加——每次调用各成节、N 顺延).`)
|
|
205
204
|
}
|
|
206
205
|
if (/^## §\d/m.test(args.text)) {
|
|
207
|
-
throw new Error("batch_segment: the text contains a section header line matching `^## §N` — that would break section location and the append-only guarantee. Rewrite it (escape the heading, or drop the leading `## §N`), then call again (骨架保护). Nothing was written.")
|
|
206
|
+
throw new Error("batch_segment: the text contains a section header line matching `^## §N` — that would break section location and the append-only guarantee. Rewrite it (escape the heading, or drop the leading `## §N`), then call again (骨架保护). Nothing was written. Do NOT close the record after a refused append — nothing landed, and closing freezes the missing section for good (失败 ⇒ 勿 close).")
|
|
208
207
|
}
|
|
209
208
|
const body = sanitizeText(args.text, n === 3).replace(/^\n+/, "").replace(/\s+$/, "")
|
|
210
209
|
if (!body.trim()) {
|
|
@@ -230,18 +229,7 @@ function appendBatchRecord({ args, ctx, review, batchDoc, onWritten }) {
|
|
|
230
229
|
export function batchTool(batchDoc = null, { review = false } = {}) {
|
|
231
230
|
return {
|
|
232
231
|
name: "batch",
|
|
233
|
-
description:
|
|
234
|
-
"Batch-record lifecycle tool (一段一作者 — six append-only sections, one author each). " +
|
|
235
|
-
"actions: create (depth-0 only — write a new six-section skeleton record; pass path + topic + source) · " +
|
|
236
|
-
"append (add text to the END of YOUR section — existing lines are never rewritten or deleted) · " +
|
|
237
|
-
"status (update YOUR section's `**状态行**:` line — the value must carry exactly ONE legal keyword of your section's STATUS_WORDS entry, decorated only by surrounding symbols / a trailing ISO date / whitespace; prose goes to the separate `note` field, which lands as a parenthetical) · " +
|
|
238
|
-
"close (depth-0 only — freeze the record: §1 → 「已收口 <date>」, every further write is refused). " +
|
|
239
|
-
"Identity fixes what you may write (段白名单): main agent (depth 0) — append §1/§4/§6, status §1 only, create/close; eng-designer — §2; eng-coder — §5; design review (review binding) — §3. A write outside your own section is refused. " +
|
|
240
|
-
"Skeleton placeholders (dead literals — `#<编号>` / `<板块>` and the older `<BATCH-ID>` / `<讨论来源>`) in the record header or in your target section block append/status: fill them first (the main agent fills the header right after create); `<§N 模板占位:…>` template lines are legal and never blocked. " +
|
|
241
|
-
"Target record: sub-agents and design reviews have NO path parameter in practice — the record arrives via the spawn binding / the review instance key (passing path is refused); depth-0 MAY pass path (若传则须可读), omitting it picks the unique in-flight record (0 or ≥2 in flight ⇒ refuse — pass path). " +
|
|
242
|
-
"A design review's append is stamped by the tool with a `### 轮次 N(评审子代理)` heading — N is tool-counted; do not write your own heading (it would be dropped). " +
|
|
243
|
-
"Credential values are stripped mechanically before writing (never write a token or designId value). " +
|
|
244
|
-
"Failures are hard and visible (no silent fallback): if a write is refused or fails, say so in your report — “§× 未写入”.",
|
|
232
|
+
description: DESC("batch"), // 模型可见文本 = tool-docs/batch.md
|
|
245
233
|
parameters: {
|
|
246
234
|
type: "object",
|
|
247
235
|
properties: {
|
|
@@ -292,7 +280,7 @@ export function batchTool(batchDoc = null, { review = false } = {}) {
|
|
|
292
280
|
async execute(args, ctx) {
|
|
293
281
|
const agent = ctx?.agent ?? {}
|
|
294
282
|
const cwd = agent.cwd ?? process.cwd()
|
|
295
|
-
const bases = batchDocBases(cwd)
|
|
283
|
+
const bases = batchDocBases(cwd) // 歧义锚 ⇒ 候选项目基底并集(#828——create / 在飞 / 写门基底集同源)
|
|
296
284
|
const onWritten = injectedOnWrite
|
|
297
285
|
/** depth-0 目标定位(D-BR21):path 传 ⇒ 可读性门;缺省 ⇒ 唯一在飞批(depth-0)/
|
|
298
286
|
* 绑定解析(子代理/评审)。status/close 经此注入 lifecycle;append 自带同款分支。 */
|
|
@@ -323,44 +311,3 @@ export function batchTool(batchDoc = null, { review = false } = {}) {
|
|
|
323
311
|
},
|
|
324
312
|
}
|
|
325
313
|
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* 过渡别名(BATCH-RECORD §4.14——撤除判据见该节):name 仍 "batch_segment"、schema 仅
|
|
329
|
-
* {segment, text}、描述**逐字**保持原 batch-segment.mjs 基准;execute ⇒ append(同一执行体,
|
|
330
|
-
* 行为等价——AC-2/AC-9)。生产挂载面已全切主名 `batch`(BATCH-RECORD §4.3);本工厂仅供
|
|
331
|
-
* 过渡 shim 导出面(旧测试 / VSC 侧 import 消费)与撤除判据实跑使用。
|
|
332
|
-
* @param {string|null} batchDoc — 绑定的目标档(spawn:`child._batchDoc`;评审:实例键)
|
|
333
|
-
* @param {{review?: boolean}} [opts] — review=true 表示"设计评审实例"形态(写 §3 + 工具盖戳)
|
|
334
|
-
*/
|
|
335
|
-
export function batchSegmentTool(batchDoc = null, { review = false } = {}) {
|
|
336
|
-
const tool = batchTool(batchDoc, { review })
|
|
337
|
-
const own = review ? "§3" : null
|
|
338
|
-
return {
|
|
339
|
-
name: "batch_segment",
|
|
340
|
-
description:
|
|
341
|
-
"Append your own section of the batch record (一段一作者). " +
|
|
342
|
-
"There is NO path parameter: the target record is bound to you (at spawn for eng-designer/eng-coder, per review instance for a design review) and your identity fixes the section you may write " +
|
|
343
|
-
"(eng-designer → §2, design review → §3, eng-coder → §5) — a write outside your own section is refused. " +
|
|
344
|
-
"Append-only: the text lands at the end of your section; existing lines are never rewritten or deleted. " +
|
|
345
|
-
"Credential values are stripped mechanically before writing (never write a token or designId value). " +
|
|
346
|
-
"A design review's append is stamped by the tool with a `### 轮次 N(评审子代理)` heading — N is tool-counted; do not write your own heading (it would be dropped). " +
|
|
347
|
-
"Failures are hard and visible (no silent fallback): if the write is refused or fails, say so in your report — “§× 未写入”.",
|
|
348
|
-
parameters: {
|
|
349
|
-
type: "object",
|
|
350
|
-
properties: {
|
|
351
|
-
segment: {
|
|
352
|
-
type: "string",
|
|
353
|
-
description: `The batch-record section you are writing${own ? ` — yours is ${own}` : ""}. Declares the section number only; your identity decides what is actually writable.`,
|
|
354
|
-
},
|
|
355
|
-
text: {
|
|
356
|
-
type: "string",
|
|
357
|
-
description: "The markdown to append (verbatim — findings table + VERDICT + counts for a design review §3). Limit 20000 characters per call; longer content is refused — split it into multiple calls (each call becomes its own section, N continues).",
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
required: ["segment", "text"],
|
|
361
|
-
},
|
|
362
|
-
async execute(args, ctx) {
|
|
363
|
-
return tool.execute({ action: "append", segment: args?.segment, text: args?.text }, ctx)
|
|
364
|
-
},
|
|
365
|
-
}
|
|
366
|
-
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkpoint.mjs — 撞帽续期检查点叶(TURN-CAP-CONTINUE.md §1 #7 / §3.1)。
|
|
3
|
+
*
|
|
4
|
+
* 异步族(后台子代理 / 异步飞刀 / 会诊)撞帽 ⇒ 不发用户卡:登记「挂起 + 父侧 ask」,
|
|
5
|
+
* 等父代理决定——`send` 兑现(新段预算、history 保留、任务文本不重注入)或
|
|
6
|
+
* `cancel` / 会话收尾 / Stop 降级(既有 partial 路径 + `TURN_CAP_MARK`)。
|
|
7
|
+
* 挂起不 settle、不重派:池条目仍在飞(status / observe 可见)、无新 child / 无新池条目、
|
|
8
|
+
* 兑现后同一 id 继续(D-TC12)。
|
|
9
|
+
*
|
|
10
|
+
* 三档逃逸(§3.1):
|
|
11
|
+
* ① 兑现 = 父 `send` 两处(池条目 `settleTurnCheckpoint` · 会诊会话 `settleConsultCheckpoint`);
|
|
12
|
+
* ② 中止信号(Stop / cancel / 会话中止)⇒ resolve(false) ⇒ 既有 onDeclined 降级;
|
|
13
|
+
* ③ 不可答(无 `_upstream.parent` ∨ `sync === true`)⇒ 不登记 ⇒ 既有路径(零新挂起)。
|
|
14
|
+
*
|
|
15
|
+
* 零依赖叶(只引父侧通道 + 池 accessor):端壳不经本档静态闭包。
|
|
16
|
+
*/
|
|
17
|
+
import { pushChildUpstream } from "./parent-channel.mjs"
|
|
18
|
+
import { getAsyncPool } from "./async-settle.mjs"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 段数 / 累计轮次二元文本(§4 留痕口径——两值同读一次):
|
|
22
|
+
* `_continueSegments`(链内段数,首段 1)· `_turnSeq`(链内累计轮次,跨段不重置)。
|
|
23
|
+
* 两值均挂子 agent 本体。
|
|
24
|
+
* #417 追加第三元「本段零落盘轮数」(`_zeroWriteTurns`——核回合环采集,与 `_turnSeq` 同源面):
|
|
25
|
+
* **只报数**(零阈值 / 零自动动作——父侧据此判断「在推进」还是「在原地读」)。缺该字段的
|
|
26
|
+
* 执行面(端侧自有回合环、未跟踪)不渲染该元——缺省不冒充 `0`(「未跟踪」不得读作「零消耗」)。
|
|
27
|
+
*/
|
|
28
|
+
function traceText(child) {
|
|
29
|
+
const base = `segments ${child?._continueSegments ?? 1} · accumulated ${child?._turnSeq ?? 0} turns`
|
|
30
|
+
return Number.isInteger(child?._zeroWriteTurns) ? `${base} · zero-write rounds this segment: ${child._zeroWriteTurns}` : base
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 留痕片段(§3.1 池条目留痕行):挂池条目摘要**文本**面(status / observe)——桥字段零新增。 */
|
|
34
|
+
export function turnCapTrace(entry) {
|
|
35
|
+
return traceText(entry?.childAgent)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 去向一句话(ask 载荷末项):答复通道随族——池条目 = `send` / `cancel`;
|
|
39
|
+
* 会诊会话 = `send` / `consult_stop`(会话级停;`cancel` 只认子代理池)。 */
|
|
40
|
+
function replyHint(parent, carrier, key) {
|
|
41
|
+
const consult = getAsyncPool(parent, "consult")?.get(key) === carrier
|
|
42
|
+
const stop = consult ? `consult_stop (id ${key})` : `subagent action:'cancel' (id ${key})`
|
|
43
|
+
return `reply with subagent action:'send' (id ${key}) to grant a new segment, or ${stop} to stop it (partial)`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 登记检查点并挂起(异步族 askContinue 的唯一实现——逃逸 ①②③):
|
|
48
|
+
* 登记 `carrier._turnCapRec` + 父侧 ask(载荷 = 段数 / 累计轮次 + 去向一句话)——
|
|
49
|
+
* ask 入队即唤醒父侧挂起驱动(`pushChildUpstream`)。
|
|
50
|
+
* @param {object} child 撞帽的子 agent(`_upstream` 携父与来源标签)。
|
|
51
|
+
* @param {object} carrier 兑现载体:池条目(子代 / 飞刀)或会诊 session。
|
|
52
|
+
* @param {{turn?: number, signal?: AbortSignal}} payload 撞帽轮次 + 中止信号。
|
|
53
|
+
* @returns {Promise<boolean>} true = 父 `send` 兑现 ⇒ 重入新段;false = 中止 / 不可答 / 兑现 miss。
|
|
54
|
+
*/
|
|
55
|
+
export function registerTurnCapCheckpoint(child, carrier, { turn = 0, signal } = {}) {
|
|
56
|
+
const parent = child?._upstream?.parent
|
|
57
|
+
// ③ 不可答降级:无父(headless 内嵌)或同步族(后台检查点只服务异步族——同步族保留用户卡)
|
|
58
|
+
if (!parent || child?._upstream?.sync === true || !carrier) return Promise.resolve(false)
|
|
59
|
+
if (signal?.aborted) return Promise.resolve(false) // ② 已中止 ⇒ 立即降级(不进挂起)
|
|
60
|
+
const key = String(carrier.id ?? "")
|
|
61
|
+
const rec = { child, carrier, turn }
|
|
62
|
+
// 先臂后报请(顺序即防御):`rec.resolve` 与中止监听在入队**前**就位——否则
|
|
63
|
+
// `pushChildUpstream` 同步唤醒等待栓的窗口里「登记已写、resolve 未挂」,兑现假成功而挂起不收敛。
|
|
64
|
+
let abortedNow = signal?.aborted === true
|
|
65
|
+
const promise = new Promise((resolve) => {
|
|
66
|
+
// 注销单点(幂等):兑现 / 中止两路共用——登记清除后重复兑现 miss(回落既有路径)。
|
|
67
|
+
const finish = (ok) => {
|
|
68
|
+
if (carrier._turnCapRec === rec) delete carrier._turnCapRec
|
|
69
|
+
signal?.removeEventListener?.("abort", onAbort)
|
|
70
|
+
resolve(ok)
|
|
71
|
+
}
|
|
72
|
+
const onAbort = () => finish(false)
|
|
73
|
+
rec.resolve = finish
|
|
74
|
+
signal?.addEventListener?.("abort", onAbort, { once: true })
|
|
75
|
+
// 注册后复检(`permission-gate.mjs:80-81` 同款序):监听只认注册后的状态转移,
|
|
76
|
+
// 窗口内已中止 ⇒ 监听不落地,此处兜住。
|
|
77
|
+
if (signal?.aborted) { abortedNow = true; onAbort() }
|
|
78
|
+
})
|
|
79
|
+
if (abortedNow) return promise // ② 窗口内已中止 ⇒ 不登记、不报请(直接走既有 partial 降级)
|
|
80
|
+
carrier._turnCapRec = rec
|
|
81
|
+
pushChildUpstream({
|
|
82
|
+
parent,
|
|
83
|
+
from: child._upstream.label,
|
|
84
|
+
kind: "ask",
|
|
85
|
+
message: `[turn cap] ${traceText(child)} — this child hit its per-segment turn cap and is suspended `
|
|
86
|
+
+ `(history kept, no re-dispatch). ${replyHint(parent, carrier, key)}`,
|
|
87
|
+
})
|
|
88
|
+
return promise
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** ① 池条目兑现(send 面第一条):读登记 → 注销 → resolve(true)。
|
|
92
|
+
* miss(无挂起 / 非检查点 send)⇒ false——调用方回落既有注入路径;兑现后幂等 miss。 */
|
|
93
|
+
export function settleTurnCheckpoint(carrier) {
|
|
94
|
+
const rec = carrier?._turnCapRec
|
|
95
|
+
if (!rec) return false
|
|
96
|
+
rec.resolve?.(true)
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** ① 会诊会话兑现(send 面第二条——id 不属子代理池时试会诊会话):父方向注入该会话在飞
|
|
101
|
+
* 子代理的注入队列(下一回合边界消费;`consumeInjected` 同族)⇒ 兑现登记。
|
|
102
|
+
* miss ⇒ false——调用方回落 `unknown async subagent id`。 */
|
|
103
|
+
export function settleConsultCheckpoint(agent, key, message) {
|
|
104
|
+
const rec = getAsyncPool(agent, "consult")?.get(String(key))?._turnCapRec
|
|
105
|
+
if (!rec) return false
|
|
106
|
+
rec.child._injected ??= []
|
|
107
|
+
rec.child._injected.push(String(message).trim())
|
|
108
|
+
rec.resolve?.(true)
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** ① 兑现后的 send 早返体(既有 send 结果同形 + 检查点语汇):兑现即不落「未命中 / 静默丢弃」
|
|
113
|
+
* ——父侧可见它确实续了新段。 */
|
|
114
|
+
export function resumedSendResult(key, label) {
|
|
115
|
+
return JSON.stringify({
|
|
116
|
+
id: String(key),
|
|
117
|
+
status: "delivered",
|
|
118
|
+
resumed: true,
|
|
119
|
+
note: `turn-cap checkpoint delivered — ${label} ${key} resumes with a fresh segment budget (history kept, no task-text re-injection); the message is consumed as an ordinary user instruction at its next turn boundary.`,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
@@ -29,15 +29,17 @@ export function childOwnerLabel(role, id, model) {
|
|
|
29
29
|
* 构建 child 权限通道。返回 null(无 `ctx.callbacks.onPermissionRequired`——headless /
|
|
30
30
|
* 无关角色调用侧未挂;AUTO 判定走父门询问时 live 读——同判据单源,ED-2 2026-09-16)或
|
|
31
31
|
* `async (toolName, args, diffInfo) => boolean`。
|
|
32
|
-
*
|
|
32
|
+
* `forceAsk`(默认 false)= 本询 AUTO 豁免**透传**(续期恒须人答——TURN-CAP-CONTINUE.md §1 #7;
|
|
33
|
+
* 置位点唯一 = VSC `panel-callbacks.mjs` 的 `continue` 名分支)——其余调用逐字零变化。
|
|
34
|
+
* @param {{ ctx: object, id: number, role: string, model?: string|null, signal?: AbortSignal|null, forceAsk?: boolean }} deps
|
|
33
35
|
*/
|
|
34
|
-
export function makeChildPermission({ ctx, id, role, model = null, signal = null }) {
|
|
36
|
+
export function makeChildPermission({ ctx, id, role, model = null, signal = null, forceAsk = false }) {
|
|
35
37
|
if (!ctx?.callbacks?.onPermissionRequired) return null
|
|
36
38
|
const owner = { label: childOwnerLabel(role, id, model), role, id }
|
|
37
39
|
return async (toolName, args, diffInfo) => {
|
|
38
40
|
ctx.callbacks.onSubagentApproval?.({ id, role, model, tool: toolName })
|
|
39
41
|
try {
|
|
40
|
-
return await ctx.callbacks.onPermissionRequired(toolName, args, diffInfo, { owner, signal })
|
|
42
|
+
return await ctx.callbacks.onPermissionRequired(toolName, args, diffInfo, { owner, signal, forceAsk })
|
|
41
43
|
} finally {
|
|
42
44
|
ctx.callbacks.onSubagentApproval?.({ id, role, model, tool: null })
|
|
43
45
|
}
|