@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
package/agent/dispatch.mjs
CHANGED
|
@@ -1,136 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* agent/dispatch.mjs — two-phase tool call execution
|
|
3
|
+
* 2026-09-28 拆分(Module Split Policy——498/499 贴 500 硬限跨档):门面谓词 ∕ 记账外提
|
|
4
|
+
* `./dispatch-gates.mjs`(四谓词 + logToolError + noteExecutedMutation)、单条执行体外提
|
|
5
|
+
* `./dispatch-run.mjs`(runPreparedItem——原 runOne 闭包,verbatim + 形参代入);
|
|
6
|
+
* `executeToolCalls` 签名与对外缝零改(本档导出面零变)。
|
|
3
7
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import { offloadToolResult, FILE_MUTATORS } from "./helpers.mjs"
|
|
8
|
+
import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
|
|
6
9
|
import { runHooks } from "../hooks.mjs"
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
// R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3b / §4.4 D-MI17–D-MI21):写工具钩子——peerCollabNote
|
|
10
|
-
// (执行前冲突与认领命中检测——软提示不阻止)+ recordPeerWrites(成功后足迹与认领登记)。
|
|
11
|
-
import { PEER_WRITE_TOOLS, peerCollabNote, recordPeerWrites, markClaimNoted } from "../peer-domains.mjs"
|
|
12
|
-
import { writeFileSync, mkdirSync, existsSync } from "node:fs"
|
|
13
|
-
import { join, resolve, relative } from "node:path"
|
|
14
|
-
import { homedir } from "node:os"
|
|
15
|
-
// §29 fix A(AGENT-LOOP.md §29——2026-09-07):FILE_MUTATORS 的 mutation-seq 记账从
|
|
16
|
-
// 批后提交(record-results noteMutations)移到执行成功即刻——唯一记账点(取代批后段
|
|
17
|
-
// + agent.mjs 中断分支记账——不双计)——同消息 [写 + async advisor launch] 时 launch 前
|
|
18
|
-
// 完成的写在 launchSeq 之前落地 → settle 不再误判 stale(§29 症状根因)。
|
|
19
|
-
import { noteMutations } from "../agent-tools/advisor-async.mjs"
|
|
10
|
+
import { isCodePath, loadProjectDeclaration } from "../conventions.mjs"
|
|
11
|
+
import { resolve, relative } from "node:path"
|
|
20
12
|
import { anyLiveDesignSlot } from "../token-ttl.mjs"
|
|
21
13
|
// M4 写权门禁(模块设计 §2.1#2):冻结窗口判据组装(被审文件集 = 声明文档集 + 批次档
|
|
22
14
|
// 的合流点)落 write-gate.mjs 单一权威源——本档只 import 消费(KD-M4-4 拆分点)。
|
|
23
|
-
import { freezeWindowConflict } from "./write-gate.mjs"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const now = new Date()
|
|
35
|
-
const ymd = now.toISOString().slice(0, 10)
|
|
36
|
-
const ts = now.toISOString().replace(/:/g, "").replace(/\..+/, "").replace("T", "-")
|
|
37
|
-
const dir = join(ERRORS_DIR, ymd)
|
|
38
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
39
|
-
const file = join(dir, `${ts}-${toolName.replace(/[/\\]/g, "_")}.log`)
|
|
40
|
-
const entry = [
|
|
41
|
-
`time: ${now.toISOString()}`,
|
|
42
|
-
`tool: ${toolName}`,
|
|
43
|
-
`args: ${JSON.stringify(args, null, 2).slice(0, 2000)}${JSON.stringify(args, null, 2).length > 2000 ? "… (truncated)" : ""}`,
|
|
44
|
-
`error: ${error?.message ?? String(error)}`,
|
|
45
|
-
error?.stack ? `stack:\n${error.stack}` : "",
|
|
46
|
-
].filter(Boolean).join("\n") + "\n"
|
|
47
|
-
writeFileSync(file, entry, "utf8")
|
|
48
|
-
} catch {
|
|
49
|
-
// Log failure itself must not crash the agent
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* action-level classification (AGENT-LOOP-SUBAGENT.md §6.7 D-M1): the merged subagent
|
|
55
|
-
* tool expresses spawn (side effect) and status (read-only query) through
|
|
56
|
-
* its `action` parameter — the tool-level readonly flag can no longer express both.
|
|
57
|
-
* dispatch Phase-1/Phase-2 classifies per action: status behaves as readonly
|
|
58
|
-
* (planMode pass / no permission ask / batchable), spawn keeps its non-readonly
|
|
59
|
-
* gates, escalate runs non-readonly AND serially (the retired escalate tool had no
|
|
60
|
-
* parallel flag — zero behavior change under the merged surface).
|
|
61
|
-
* AGENT-LOOP-SUBAGENT.md §6.7.2 cancel (19.5.2b round2 #4): CONTROL-class exemption — cancel only
|
|
62
|
-
* stops, never starts. isSubagentControlAction feeds the SAME two gate sites as
|
|
63
|
-
* readonly (planMode pass / no permission ask — never joins a batch approval
|
|
64
|
-
* group / no handler → not denied — digest 内 cancel 放行).
|
|
65
|
-
* AGENT-LOOP-SUBAGENT.md §6.7.2 panel (round1 #5): view 面归只读类(同 status——planMode 放行、免
|
|
66
|
-
* 审批、可批并行);freeze 面归控制类(同 cancel——planMode 放行、免权限审批、
|
|
67
|
-
* 批审批不入组、digest 内放行)。freeze 存在(非空 key)即控制类——否则只读类。
|
|
68
|
-
*/
|
|
69
|
-
function isSubagentReadonlyAction(toolName, args) {
|
|
70
|
-
// §6 memory 工具面重构(MEMORY.md §6 D-M5):memory search/list 是只读动作——与
|
|
71
|
-
// subagent status 同分类(planMode 放行/免审批——Phase-2 批并行只认工具级
|
|
72
|
-
// readonly/parallel,memory 无 parallel → 按非只读串行,见 MEMORY.md §6.4 实现注)。
|
|
73
|
-
// 动作级判定——不能按工具名(同一 memory 工具的 put/delete/clear 保持侧效门)。
|
|
74
|
-
if (toolName === "memory") {
|
|
75
|
-
const action = args?.action
|
|
76
|
-
return action === "search" || action === "list"
|
|
77
|
-
}
|
|
78
|
-
// SETTINGS-TOOL.md(2026-09-05):settings list/get 是只读动作(memory search/list 同分类——
|
|
79
|
-
// planMode 放行/免审批);set 保持侧效门。
|
|
80
|
-
if (toolName === "settings") {
|
|
81
|
-
const action = args?.action
|
|
82
|
-
return action === "list" || action === "get"
|
|
83
|
-
}
|
|
84
|
-
if (toolName !== "subagent" || !args || typeof args !== "object") return false
|
|
85
|
-
const action = args.action
|
|
86
|
-
// AGENT-LOOP-SUBAGENT.md §6.7.5: check 动作已删除——只读面仅剩 status(planMode 放行/免权限审批/可批并行)
|
|
87
|
-
if (action === "status") return true
|
|
88
|
-
// SUBAGENT-OBSERVE-SEND:observe = readonly 查询(同 status——digest/planMode 放行)
|
|
89
|
-
if (action === "observe") return true
|
|
90
|
-
// AGENT-LOOP-SUBAGENT.md §6.7.2 panel view 面(freeze 缺省/空 = 视图请求——readonly;非空 freeze 归控制类)
|
|
91
|
-
if (action === "panel" && (args.freeze === undefined || args.freeze === null || String(args.freeze) === "")) return true
|
|
92
|
-
return false
|
|
93
|
-
}
|
|
94
|
-
function isSubagentControlAction(toolName, args) {
|
|
95
|
-
if (toolName !== "subagent") return false
|
|
96
|
-
if (args?.action === "cancel") return true
|
|
97
|
-
// SUBAGENT-OBSERVE-SEND:send = 控制类豁免(同 cancel——父回合内显式调用即授权——
|
|
98
|
-
// 写子输入队列属父对子轻量引导,非产品代码写——免审批、planMode 放行、digest 内放行)
|
|
99
|
-
if (args?.action === "send") return true
|
|
100
|
-
// AGENT-LOOP-SUBAGENT.md §6.7.2 panel freeze 面(D-P3 门控在 executor——只读/控制分类在此)
|
|
101
|
-
if (args?.action === "panel" && args.freeze !== undefined && args.freeze !== null && String(args.freeze) !== "") return true
|
|
102
|
-
return false
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* §2.6 token 链终消费制(2026-09-07——评审 #7d dispatch 分类):consume-design =
|
|
106
|
-
* 非只读控制动作——planMode 拒绝(不入 readonly/control 豁免——与其他非只读动作同门)、
|
|
107
|
-
* 免权限审批、不入批审批分组(无文件写——控制类直行——只停既有状态不起新副作用)。
|
|
108
|
-
* 与 cancel 的不同:cancel 是控制类豁免(planMode 放行),consume-design 按设计
|
|
109
|
-
* planMode 拒绝——故不并入 isSubagentControlAction,单独谓词只接权限豁免位。
|
|
110
|
-
*/
|
|
111
|
-
function isSubagentConsumeDesignAction(toolName, args) {
|
|
112
|
-
return toolName === "subagent" && args?.action === "consume-design"
|
|
113
|
-
}
|
|
114
|
-
function isSubagentEscalateAction(toolName, args) {
|
|
115
|
-
return toolName === "subagent" && args?.action === "escalate"
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* §29 fix A — 唯一记账点:FILE_MUTATORS 工具执行成功即刻记 mutation seq(abs 路径)。
|
|
120
|
-
* 取代 record-results 批后段 + agent.mjs 中断分支的 noteMutations(不双计——中断+同批
|
|
121
|
-
* launch 场景 seq 单计,AGENT-LOOP.md §29 T-A1i)。调用时机 = 写执行成功(非 Error 前缀
|
|
122
|
-
* 结果——recordPeerWrites 同款门);routed(M2 ACP 客户端执行)成功同样记账。
|
|
123
|
-
*/
|
|
124
|
-
function noteExecutedMutation(agent, tool, args) {
|
|
125
|
-
let paths
|
|
126
|
-
try {
|
|
127
|
-
paths = tool.touchedPaths ? tool.touchedPaths(args ?? {}) : [args?.path]
|
|
128
|
-
} catch { return }
|
|
129
|
-
const abs = (paths ?? [])
|
|
130
|
-
.filter((p) => typeof p === "string" && p)
|
|
131
|
-
.map((p) => resolve(agent.cwd, p))
|
|
132
|
-
if (abs.length > 0) noteMutations(agent, abs)
|
|
133
|
-
}
|
|
15
|
+
import { freezeWindowConflict, batchRecordWriteConflict } from "./write-gate.mjs"
|
|
16
|
+
// 门面谓词 ∕ 记账(2026-09-28 拆分外提——见头注):动作级四谓词 + logToolError 住 ./dispatch-gates.mjs。
|
|
17
|
+
import {
|
|
18
|
+
logToolError, isSubagentConsumeDesignAction, isSubagentEscalateAction,
|
|
19
|
+
readonlyActionOf, controlActionOf, // E1(§2.5):工具面动作谓词钩子优先 ∕ 核名面谓词回落
|
|
20
|
+
} from "./dispatch-gates.mjs"
|
|
21
|
+
// 单条执行体(2026-09-28 拆分外提——见头注):runPreparedItem(原 runOne 闭包;本档两处调用点改指)。
|
|
22
|
+
import { runPreparedItem } from "./dispatch-run.mjs"
|
|
23
|
+
// B7 3a:作用域规则 JIT 注入(核单源——`../rules.mjs`;相位一末打点见下方缝注释)。
|
|
24
|
+
import { injectScopedRules } from "../rules.mjs"
|
|
134
25
|
|
|
135
26
|
/**
|
|
136
27
|
* Two-phase execution:
|
|
@@ -143,7 +34,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
143
34
|
// ---- Phase 1: serial preparation ----
|
|
144
35
|
// Pre-gates run per tool (parse/planMode/engineering gates); non-readonly tools
|
|
145
36
|
// that REACH the permission stage are collected into one batch — a single merged
|
|
146
|
-
// ask covers the whole toolCalls array (
|
|
37
|
+
// ask covers the whole toolCalls array (D-B1, "approve all / one by one /
|
|
147
38
|
// deny"). Tools stopped by a pre-gate never join the batch (review #7).
|
|
148
39
|
const prepared = []
|
|
149
40
|
const permPending = [] // { toolCall, tool, args } — reached the permission stage
|
|
@@ -164,7 +55,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
164
55
|
continue
|
|
165
56
|
}
|
|
166
57
|
|
|
167
|
-
if (agent.planMode && !tool.readonly && !
|
|
58
|
+
if (agent.planMode && !tool.readonly && !readonlyActionOf(tool, toolCall.name, args) && !controlActionOf(tool, toolCall.name, args)) {
|
|
168
59
|
prepared.push({ toolCall, tool, denied: true, reason: "plan mode" })
|
|
169
60
|
continue
|
|
170
61
|
}
|
|
@@ -186,11 +77,11 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
186
77
|
// before the design review passed. Signaled by a live design slot (design-review
|
|
187
78
|
// approval — persists in the session slot, survives across turns; _engDesignReviewed
|
|
188
79
|
// is eng-coder-only and reset per run). Exemptions = the non-code classes of
|
|
189
|
-
//
|
|
80
|
+
// thincoder-core/conventions.mjs (documentation and temp scratch files) — writing a design
|
|
190
81
|
// document IS the design step. Anything inside a declared code segment (default:
|
|
191
82
|
// src — incl. src/prompts/*.md) is product code, not documentation, and needs a
|
|
192
|
-
// design token. The project can declare its own code paths (
|
|
193
|
-
//
|
|
83
|
+
// design token. The project can declare its own code paths (the manifest's
|
|
84
|
+
// `codePaths`) so a non-src layout is not silently exempted. Mechanically
|
|
194
85
|
// blocks "talk then code".
|
|
195
86
|
// DESIGN-TOKEN-SETTLEMENT D3(2026-09-08):资格判据 = 权威槽"任一活槽存在"
|
|
196
87
|
// (anyLiveDesignSlot——查内存 Map,miss 回读槽文件——单值镜像 `_engDesignToken`
|
|
@@ -198,8 +89,8 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
198
89
|
if (agent.config?.agent?.engineering && depth === 0
|
|
199
90
|
&& !anyLiveDesignSlot(agent)
|
|
200
91
|
&& FILE_MUTATORS.has(toolCall.name)) {
|
|
201
|
-
const paths = tool
|
|
202
|
-
const conv =
|
|
92
|
+
const paths = toolTouchPaths(tool, args)
|
|
93
|
+
const conv = loadProjectDeclaration(agent.cwd)
|
|
203
94
|
// Unknown/missing paths (non-string, e.g. no path argument) are treated
|
|
204
95
|
// as code — cannot tell what they touch, so block conservatively. Known
|
|
205
96
|
// paths go through the single shared classifier: a declared code segment
|
|
@@ -209,7 +100,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
209
100
|
// Undeclared project → point at the declaration file (§4.3 降级可见契约).
|
|
210
101
|
const convNote = conv.declared
|
|
211
102
|
? ""
|
|
212
|
-
: ` — this path was classified as product code by the default conventions (code paths: ${conv.codePaths.join(", ")}); declare project conventions in .
|
|
103
|
+
: ` — this path was classified as product code by the default conventions (code paths: ${conv.codePaths.join(", ")}); declare project conventions in PROJECT-MANIFEST.json to adjust.`
|
|
213
104
|
prepared.push({
|
|
214
105
|
toolCall, tool, denied: true,
|
|
215
106
|
reason: "engineering design gate",
|
|
@@ -219,20 +110,28 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
219
110
|
}
|
|
220
111
|
}
|
|
221
112
|
|
|
222
|
-
// E(第 11 批·F17
|
|
113
|
+
// E(第 11 批·F17/ADVISOR-GUARDS.md §5 E-3d):D5 冻结窗口写前拦截——设计评审在途(点火 → 结算)期间,
|
|
223
114
|
// 父侧对被审文件集(声明文档集 + 批次档)的写入会被拒绝:在途写使本轮结算 stale——pass 轮
|
|
224
|
-
// = token 直接丢失(实证:第 10 批 id=20 整轮作废)。工具面 = FILE_MUTATORS
|
|
225
|
-
//
|
|
115
|
+
// = token 直接丢失(实证:第 10 批 id=20 整轮作废)。工具面 = FILE_MUTATORS ∪ `file_ops`
|
|
116
|
+
// (E2——冻结腿覆盖面;批次档腿仍 = FILE_MUTATORS:与变更记账同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
|
|
226
117
|
// agent/write-gate.mjs 的 freezeWindowConflict(声明文档集腿 = inflightDesignReviewConflict
|
|
227
118
|
// 同 docAbs / 同 normAbs;批次档腿 = run.batchDoc——M4 合流点,仅扫 running 未取消的设计条目)。
|
|
228
119
|
// 位置:只读 / autoApprove 短路之前——审批不得绕过冻结;拒绝 = 可见 denied + 逃生门
|
|
229
120
|
// (先 cancel → 改 → 重发)。
|
|
230
|
-
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
121
|
+
if (FILE_MUTATORS.has(toolCall.name) || toolCall.name === "file_ops") {
|
|
122
|
+
// #327:单源谓词(同集合内两处消费共用);#309 批次档写门与 D5 冻结窗同区、共用同一路径集。
|
|
123
|
+
// E2(parity-b1 §2.5——缺省取核形):外门扩 `file_ops`(冻结窗覆盖面与端壳对齐——`file_ops`
|
|
124
|
+
// 的 move ∕ copy ∕ rename 亦为写面);**批次档写门判据集不变**(= `FILE_MUTATORS`,file_ops 不入)。
|
|
125
|
+
const absPaths = toolTouchPaths(tool, args)
|
|
234
126
|
.filter((p) => typeof p === "string" && p)
|
|
235
127
|
.map((p) => resolve(agent.cwd, p))
|
|
128
|
+
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
129
|
+
const crossBatch = batchRecordWriteConflict(agent, depth, absPaths)
|
|
130
|
+
if (crossBatch) {
|
|
131
|
+
prepared.push({ toolCall, tool, denied: true, reason: "cross-batch record write", hint: crossBatch.message })
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
}
|
|
236
135
|
const conflict = freezeWindowConflict(agent, absPaths)
|
|
237
136
|
if (conflict) {
|
|
238
137
|
prepared.push({
|
|
@@ -246,7 +145,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
246
145
|
|
|
247
146
|
// Readonly tools (and autoApprove — the short-circuit, unchanged for the
|
|
248
147
|
// whole batch too) skip the permission stage entirely.
|
|
249
|
-
// §
|
|
148
|
+
// AGENT-LOOP-SUBAGENT.md §6.7.6 D-E3 task-domain authorization (spawn-time): an eng-coder child's
|
|
250
149
|
// tools skip the permission ASK stage exactly like autoApprove — granted by
|
|
251
150
|
// the parent spawn (approved design + task = authorization; subagent.mjs
|
|
252
151
|
// sets _engTaskAuthorized on the child). Everything EARLIER in Phase 1
|
|
@@ -254,7 +153,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
254
153
|
// — the exemption never widens what reaches this stage (round4 #3, T-E14).
|
|
255
154
|
// PreToolUse hooks still run below. Non-eng-coder children keep the manual
|
|
256
155
|
// parent ask (human in the loop).
|
|
257
|
-
if (tool.readonly ||
|
|
156
|
+
if (tool.readonly || readonlyActionOf(tool, toolCall.name, args) || controlActionOf(tool, toolCall.name, args) || isSubagentConsumeDesignAction(toolCall.name, args) || agent.autoApprove || agent._engTaskAuthorized) {
|
|
258
157
|
if (!(await runHooks("PreToolUse", { agent, toolName: toolCall.name, toolArgs: args }))) {
|
|
259
158
|
prepared.push({ toolCall, tool, denied: true, reason: "blocked by PreToolUse hook" })
|
|
260
159
|
continue
|
|
@@ -267,7 +166,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
267
166
|
permPending.push({ toolCall, tool, args })
|
|
268
167
|
}
|
|
269
168
|
|
|
270
|
-
// ---- Permission stage: one merged ask for the whole batch (
|
|
169
|
+
// ---- Permission stage: one merged ask for the whole batch (D-B1) ----
|
|
271
170
|
// >1 non-readonly tools in the same toolCalls array → a single
|
|
272
171
|
// onBatchPermissionRequest({ tools, count }) ask; verdicts:
|
|
273
172
|
// "approveAll" → batch-scope allowance (autoApprove style, NOT persistent)
|
|
@@ -292,7 +191,7 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
292
191
|
else if (batchAllowed === false) allowed = false
|
|
293
192
|
else if (callbacks.onPermissionRequest) {
|
|
294
193
|
allowed = await (async () => {
|
|
295
|
-
// D2 (AGENT-LOOP.md §7.2): announce the wait BEFORE prompting — the TUI
|
|
194
|
+
// D2 (AGENT-LOOP-SUBAGENT.md §6.7.2): announce the wait BEFORE prompting — the TUI
|
|
296
195
|
// subagent block header flips to "等待审批" so a waiting child is visibly
|
|
297
196
|
// different from a stalled one. Depth>0 only (the parent TUI shows its own
|
|
298
197
|
// permission panel). turn n/max = the child's live turn counters.
|
|
@@ -322,161 +221,20 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
322
221
|
}
|
|
323
222
|
}
|
|
324
223
|
|
|
325
|
-
// ----
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
? "Error: plan mode is active — only read-only tools are allowed. Exit plan mode first."
|
|
331
|
-
: item.reason === "engineering design gate"
|
|
332
|
-
? `Error: design review required before any file modification. ${item.hint}`
|
|
333
|
-
: item.reason === "d5 freeze window"
|
|
334
|
-
? `Error: ${item.hint}`
|
|
335
|
-
: item.reason === "denied by user"
|
|
336
|
-
? "Error: permission denied by user"
|
|
337
|
-
: item.reason === "blocked by PreToolUse hook"
|
|
338
|
-
? "Error: blocked by PreToolUse hook"
|
|
339
|
-
: "Error: no permission handler configured — this tool requires user approval but the current context doesn't support interaction (e.g. subagent or non-TUI mode)"
|
|
340
|
-
return { ...item, result: reason, ok: false }
|
|
341
|
-
}
|
|
342
|
-
// 2026-08-31 工具顺手度(用户批准"做吧"):dispatch 拦截工具执行期间的
|
|
343
|
-
// console.log/console.error——工具的探查/调试输出(原本只到终端、模型看不到)
|
|
344
|
-
// 收集后附在工具结果后回显给模型。bash 工具的输出走子进程回显(onOutput),
|
|
345
|
-
// 不走 dispatch console——拦截安全。嵌套 dispatch(subagent)各自拦截/恢复,
|
|
346
|
-
// 捕获分离(父恢复原始后子的拦截期间父捕获停止、子恢复后父继续)——正确。
|
|
347
|
-
// 声明在 try 之外:catch 块(异常路径)也要访问(报错前的探查输出回显)。
|
|
348
|
-
const capturedConsole = []
|
|
349
|
-
// LOGGING(LOGGING.md):tool:* 事件——仅真实执行(pre-gate 拦截项在下方早退分支不入事件)。
|
|
350
|
-
// 参数值永不落盘(NF-L3——工具事件不记 args);child=子代理 id(agent._logId,spawn 时 stamp)。
|
|
351
|
-
const toolT0 = Date.now()
|
|
352
|
-
const toolName = item.toolCall.name
|
|
353
|
-
logEvent("tool:call", { tool: toolName, child: agent?._logId })
|
|
354
|
-
try {
|
|
355
|
-
// R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3b / §4.4.4):结构化写工具执行前查冲突与认领
|
|
356
|
-
// 命中(软提示——决策⑥ A 不阻止;一次目录 stat——N3 度量);足迹累积(D-L3a——"检测+
|
|
357
|
-
// 记录一次完成")与认领登记延后到执行成功(实际写过的文件)。
|
|
358
|
-
const isPeerWriteTool = PEER_WRITE_TOOLS.has(toolName)
|
|
359
|
-
const peerNote = isPeerWriteTool ? peerCollabNote(agent, item.tool, item.args) : null
|
|
360
|
-
// Snapshot for undo before side-effect tools (setupOutputPanel already fired in Phase 1)
|
|
361
|
-
if (!item.tool?.readonly && item.args) {
|
|
362
|
-
snapshotForUndo(agent, item.toolCall.name, item.args, agent.cwd)
|
|
363
|
-
}
|
|
364
|
-
// M2 ACP: route fs tools through the client (IDE buffer / diff review).
|
|
365
|
-
// toolRouter returns { handled: true, result } to short-circuit execution.
|
|
366
|
-
if (callbacks.toolRouter) {
|
|
367
|
-
const routed = await callbacks.toolRouter(item.toolCall.name, item.args)
|
|
368
|
-
if (routed?.handled) {
|
|
369
|
-
const routedOk = !String(routed.result).startsWith("Error:")
|
|
370
|
-
const routedResult = peerNote && routedOk ? `${routed.result}\n${peerNote.text}` : routed.result
|
|
371
|
-
if (peerNote && routedOk) markClaimNoted(agent, peerNote.keys) // 提示已附加 ⇒ 落去重标记
|
|
372
|
-
if (isPeerWriteTool && routedOk) recordPeerWrites(agent, item.tool, item.args)
|
|
373
|
-
// §29 fix A:routed 写成功(客户端执行)同样执行期即刻记账(唯一记账点)
|
|
374
|
-
if (routedOk && FILE_MUTATORS.has(toolName)) noteExecutedMutation(agent, item.tool, item.args)
|
|
375
|
-
callbacks.onToolResult?.(item.toolCall.name, routedResult, item.toolCall.id)
|
|
376
|
-
logEvent("tool:done", { tool: toolName, ms: Date.now() - toolT0, head: headText(routedResult, 200), child: agent?._logId })
|
|
377
|
-
return { ...item, result: routedResult, ok: true }
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
const origConsoleLog = console.log
|
|
381
|
-
const origConsoleErr = console.error
|
|
382
|
-
console.log = (...a) => capturedConsole.push(a.map(String).join(" "))
|
|
383
|
-
console.error = (...a) => capturedConsole.push("[err] " + a.map(String).join(" "))
|
|
384
|
-
let rawResult
|
|
385
|
-
// ctx 对象提升为变量(§7.2.3):subagent 阻塞 execute 返回前在 ctx 上留
|
|
386
|
-
// _subagentKey(relayPrefix 去尾)——runOne 在 execute 返回后读它作 onToolResult
|
|
387
|
-
// 第 4 参(普通工具/错误路径无此字段——undefined 兼容既有签名)。每次工具调用
|
|
388
|
-
// 独立 ctx——并行同名工具(批并行 runOne)各自带自己的 key,互不串扰。
|
|
389
|
-
const toolCtx = {
|
|
390
|
-
cwd: agent.cwd,
|
|
391
|
-
agent,
|
|
392
|
-
depth,
|
|
393
|
-
signal,
|
|
394
|
-
callbacks,
|
|
395
|
-
// §11.2 D-24b: per-call id — the advisor tool marker-keys its launch so
|
|
396
|
-
// recordToolResults can split async-ack accounting from sync settles.
|
|
397
|
-
_toolCallId: item.toolCall.id,
|
|
398
|
-
onOutput: (chunk) => callbacks.onToolOutput?.(item.toolCall.name, chunk, item.toolCall.id),
|
|
399
|
-
onQuestion: callbacks.onQuestion,
|
|
400
|
-
onPermissionRequest: callbacks.onPermissionRequest,
|
|
401
|
-
}
|
|
402
|
-
try {
|
|
403
|
-
// SUBAGENT-OBSERVE-SEND D1(评审 #1):in-flight 当前工具记账——工具执行期间在
|
|
404
|
-
// agent 上留 _inflightTools Set(子代理 observe 从 dispatch 状态读——卡在长工具
|
|
405
|
-
// 调用时 history 无新回合、恰需此信号);finally 清除。批并行工具同入 Set(observe
|
|
406
|
-
// 如实返回多个在跑工具)。主会话同样记账——无害(无人读)。
|
|
407
|
-
const inflight = agent._inflightTools ?? (agent._inflightTools = new Set())
|
|
408
|
-
inflight.add(toolName)
|
|
409
|
-
try {
|
|
410
|
-
rawResult = await item.tool.execute(item.args, toolCtx)
|
|
411
|
-
} finally {
|
|
412
|
-
inflight.delete(toolName)
|
|
413
|
-
}
|
|
414
|
-
} finally {
|
|
415
|
-
console.log = origConsoleLog
|
|
416
|
-
console.error = origConsoleErr
|
|
417
|
-
}
|
|
418
|
-
if (rawResult === undefined) throw new Error(`Tool "${item.toolCall.name}" returned undefined — all tools must return a string value`)
|
|
419
|
-
const raw = String(rawResult)
|
|
420
|
-
// 写成功(非 "Error:" 字符串结果)→ 足迹计入本回合集合(flush 在 finalizeAgentTurn)
|
|
421
|
-
if (isPeerWriteTool && !raw.startsWith("Error:")) {
|
|
422
|
-
recordPeerWrites(agent, item.tool, item.args)
|
|
423
|
-
}
|
|
424
|
-
// §29 fix A:FILE_MUTATORS 执行成功即刻记账(唯一记账点——取代 record-results 批后
|
|
425
|
-
// 段 + agent.mjs 中断分支——不双计)——同批 launch 前的写在 launchSeq 之前落地 →
|
|
426
|
-
// async advisor settle 不误判 stale(同批 launch 后写仍保守 stale——T-A2/T-24b9)。
|
|
427
|
-
if (FILE_MUTATORS.has(toolName) && !raw.startsWith("Error:")) {
|
|
428
|
-
noteExecutedMutation(agent, item.tool, item.args)
|
|
429
|
-
}
|
|
430
|
-
// Multimodal tools keep the raw result (base64 images ride the multimodal
|
|
431
|
-
// channel); everything else offloads oversized text to disk. Flag-driven, not
|
|
432
|
-
// name-driven (consult P3, 2026-08-30).
|
|
433
|
-
const result = item.tool?.multimodal ? raw : await offloadToolResult(raw, item.toolCall.id)
|
|
434
|
-
// 2026-08-31:工具执行期间捕获的 console 输出附在结果后回显(模型视野)
|
|
435
|
-
const resultWithConsole = capturedConsole.length > 0
|
|
436
|
-
? `${result}\n[console during ${item.toolCall.name}]\n${capturedConsole.join("\n")}`
|
|
437
|
-
: result
|
|
438
|
-
// R10 L3:冲突 / 认领软提示附在工具结果末尾(模型可见——不阻止写;提示附加成功才落去重标记)
|
|
439
|
-
const notedOk = Boolean(peerNote) && !raw.startsWith("Error:")
|
|
440
|
-
if (notedOk) markClaimNoted(agent, peerNote.keys)
|
|
441
|
-
const resultForModel = notedOk
|
|
442
|
-
? `${resultWithConsole}\n${peerNote.text}`
|
|
443
|
-
: resultWithConsole
|
|
444
|
-
callbacks.onToolResult?.(item.toolCall.name, resultForModel, item.toolCall.id, toolCtx._subagentKey)
|
|
445
|
-
// PostToolUse hooks: fire-and-forget (result not awaited on hook failure)
|
|
446
|
-
runHooks("PostToolUse", { agent, toolName: item.toolCall.name, toolArgs: item.args, result: raw }).catch(() => {})
|
|
447
|
-
logEvent("tool:done", { tool: toolName, ms: Date.now() - toolT0, head: headText(resultForModel, 200), child: agent?._logId })
|
|
448
|
-
return { ...item, result: resultForModel, ok: true }
|
|
449
|
-
} catch (error) {
|
|
450
|
-
// Persist to ~/.thincoder/tool-errors/ for post-mortem; only pass message to the model (stack traces confuse LLMs and may leak paths)
|
|
451
|
-
logToolError(item.toolCall.name, item.args, error)
|
|
452
|
-
// User interrupt (Ctrl+C / Ctrl+I) must propagate, not become a tool error:
|
|
453
|
-
// swallowing it here would make the parent keep looping while the user
|
|
454
|
-
// asked to stop — worst case with subagents, where the child runs its
|
|
455
|
-
// whole turn budget and the interrupt appears to do nothing.
|
|
456
|
-
if (signal?.aborted) throw error
|
|
457
|
-
// LOGGING(2026-09-03 code review #4):中止先于事件——用户停不落 tool:error
|
|
458
|
-
//(vscode execute-tools parity;阻塞子代理 child:error 同款抑制)
|
|
459
|
-
logEvent("tool:error", { tool: toolName, ms: Date.now() - toolT0, err: errText(error, 200), child: agent?._logId })
|
|
460
|
-
runHooks("PostToolUseFailure", { agent, toolName: item.toolCall.name, toolArgs: item.args, error }).catch(() => {})
|
|
461
|
-
// Build contextual error: tool name + key args so the model can reason about what went wrong
|
|
462
|
-
const ctxParts = []
|
|
463
|
-
if (item.args.path) ctxParts.push(`path=${item.args.path}`)
|
|
464
|
-
if (item.args.pattern) ctxParts.push(`pattern=${item.args.pattern}`)
|
|
465
|
-
if (item.args.command) ctxParts.push(`cmd=${item.args.command.slice(0, 80)}`)
|
|
466
|
-
const ctx = ctxParts.length > 0 ? ` [${ctxParts.join(", ")}]` : ""
|
|
467
|
-
// 2026-08-31:异常路径同样回显捕获的 console(工具报错前的探查输出最有价值)
|
|
468
|
-
const consolePart = capturedConsole.length > 0
|
|
469
|
-
? `\n[console during ${item.toolCall.name}]\n${capturedConsole.join("\n")}`
|
|
470
|
-
: ""
|
|
471
|
-
return { ...item, result: `Error: ${error.message}${ctx}${consolePart}`, ok: false }
|
|
472
|
-
}
|
|
224
|
+
// ---- B7 3a JIT 缝(相位一末 ∕ 相位二前——原 VSC「派发前」语义;`depth === 0` 门)--------
|
|
225
|
+
// 作用域规则注入:打点 = `prepared` 非拒项 `{tool, args}`(即将执行集);注入源 = 本 run 尾块
|
|
226
|
+
// 读点缓存 `agent._rules.scoped`(核 `prepareRun` 同点写入),去重域 = 会话。子代不注入。
|
|
227
|
+
if (depth === 0) {
|
|
228
|
+
injectScopedRules(agent, agent.history, prepared.filter((p) => p.tool && p.args).map(({ tool, args }) => ({ tool, args })))
|
|
473
229
|
}
|
|
474
230
|
|
|
231
|
+
// ---- Phase 2: order-preserving execution ----
|
|
232
|
+
|
|
475
233
|
const results = []
|
|
476
234
|
let batch = []
|
|
477
235
|
const flush = async () => {
|
|
478
236
|
if (batch.length === 0) return
|
|
479
|
-
results.push(...await Promise.all(batch.map(
|
|
237
|
+
results.push(...await Promise.all(batch.map((item) => runPreparedItem(item, agent, depth, signal, callbacks))))
|
|
480
238
|
batch = []
|
|
481
239
|
}
|
|
482
240
|
for (const item of prepared) {
|
|
@@ -486,11 +244,11 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
486
244
|
if (item.tool && !item.tool.readonly
|
|
487
245
|
&& (!item.tool.parallel || isSubagentEscalateAction(item.tool.name, item.args))) {
|
|
488
246
|
await flush()
|
|
489
|
-
results.push(await
|
|
247
|
+
results.push(await runPreparedItem(item, agent, depth, signal, callbacks))
|
|
490
248
|
} else {
|
|
491
249
|
batch.push(item)
|
|
492
250
|
}
|
|
493
251
|
}
|
|
494
252
|
await flush()
|
|
495
253
|
return results
|
|
496
|
-
}
|
|
254
|
+
}
|
package/agent/family-tools.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 登记册**动态**载入:`agent-tools.mjs` 静态图经 consult/subagent 族可达核 agent 栈
|
|
15
15
|
* (`../agent/setup.mjs` → `../memory.mjs` → `node:sqlite`)——静态引入会破端壳 W8 契约②
|
|
16
|
-
*
|
|
16
|
+
* (W8 契约②判据(现载体 = 批件 `docs/batches/2026-09-29-residuals-round2.test.mjs`;单测树重建时回迁端侧单测档):端壳静态链不得到达 `node:sqlite`)。
|
|
17
17
|
* 核内先例 = 登记册头注「Loaded from agent.mjs via dynamic import to avoid ESM circular
|
|
18
18
|
* dependencies」(本档先于 `agent/setup.mjs` 的调用点载入,同一语义)。
|
|
19
19
|
*/
|
|
@@ -77,7 +77,7 @@ export async function assembleFamilyTools({
|
|
|
77
77
|
},
|
|
78
78
|
} : subagentTool
|
|
79
79
|
|
|
80
|
-
// §
|
|
80
|
+
// AGENT-LOOP-SUBAGENT.md §6.7.6 D-E3 + ENGINEERING-MODE.md §2.15 D(第 2 批——参数化复用,不并列第二个 IIFE):
|
|
81
81
|
// 工程子代理(depth>0;eng-coder = 偏差审计 / eng-designer = 自己勘察)get a restricted
|
|
82
82
|
// spawn channel — role enum limited to explore, NO async parameter (sync only) and action
|
|
83
83
|
// pinned to spawn(AGENT-LOOP-SUBAGENT.md §6.7 D-M3 restricted-variant action gate——escalate/check/status are
|
|
@@ -132,7 +132,7 @@ export async function assembleFamilyTools({
|
|
|
132
132
|
// consult 工具仅在配置时注册(consultModels 空池时注册会让模型调用后吃一个错误回合)——
|
|
133
133
|
// AGENT-LOOP-SUBAGENT.md §6.7: escalate 已并入常驻 subagent 的 action:"escalate"(无空池注册问题——动作在
|
|
134
134
|
// 池空时返回既有错误语义,工程模式 fail-closed 在 execute 内拒绝)。
|
|
135
|
-
// §
|
|
135
|
+
// CONSULTATION.md §6.2 D-R17a: consult_check 已退役(digest 自动注入是唯一消费通道)——consult 家族
|
|
136
136
|
// 只剩 2 工具(consult_start/consult_stop——setup 注册点与描述面同步清零)。
|
|
137
137
|
const consultTools = consultModels.length
|
|
138
138
|
? [decorate?.consultStart ?? withPool(consultStartTool), decorate?.consultStop ?? consultStopTool]
|
|
@@ -155,12 +155,12 @@ export async function assembleFamilyTools({
|
|
|
155
155
|
// the system prompt names verify (system.md) and advisor (discipline.md) — without them an
|
|
156
156
|
// escalate hit "unknown tool" and fell back to bash node --check / npm test to
|
|
157
157
|
// self-verify (2026-08-16 deepseek escalate diagnosis; plugin parity).
|
|
158
|
-
// eng-coder: advisor + verify + the §
|
|
158
|
+
// eng-coder: advisor + verify + the AGENT-LOOP-SUBAGENT.md §6.7.6 audit-only subagent channel (D-E3).
|
|
159
159
|
// eng-designer (§2.15 D): the survey-only subagent channel alone — no advisor
|
|
160
160
|
// (it does not fire reviews) and no verify (its deliverable is documents, not code).
|
|
161
161
|
// BATCH-RECORD §4.3 挂载表(批次档生命周期工具化批):eng 两分支各挂主名 `batch`
|
|
162
162
|
// (绑定段 append/status;目标 = spawn 绑定 `child._batchDoc`)——BATCH-RECORD.md §4.2 的 batch_segment
|
|
163
|
-
//
|
|
163
|
+
// 挂载形态已随单名化收口退役;depth-0 主 agent 同表挂载
|
|
164
164
|
// `batch`(D-BR18 扩权——create/close + append §1/§4/§6 + status §1(轮 2 裁定②:§4/§6
|
|
165
165
|
// 状态面走普通文档写),目标走可选 path / 在飞扫描)。
|
|
166
166
|
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 装配接线):子代理上行通道
|
package/agent/helpers.mjs
CHANGED
|
@@ -85,6 +85,38 @@ const MAX_GIT_CHANGES_DISPLAY = 20
|
|
|
85
85
|
export const OUTLINE_INJECT_PREFIX = "[System reminder: project dependency outline:"
|
|
86
86
|
export const FILE_MUTATORS = new Set(["write", "edit", "insert_after", "apply_patch", "delete", "hashline_edit"])
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* 工具自报触达面提取**单源谓词**(`docs/core/design/TOOLS.md` §6.17 / D-TO12——批 GUARD-SCHEDULER ·
|
|
90
|
+
* 台账 #327):与 `FILE_MUTATORS` 同址(门禁 / 变更记账 / L3 足迹 / 作用域规则四条面的公共输入)。
|
|
91
|
+
* 契约(恒数组 · 恒零抛):
|
|
92
|
+
* - `args` 为 null **或非对象** ⇒ 一律规范化为 `{}` 再交钩子;
|
|
93
|
+
* - 有钩子 ⇒ **钩子裁决**;无钩子 ⇒ `file_ops` **动作感知**(D-TO13 · 台账 #333)——`action === "copy"`
|
|
94
|
+
* ⇒ `[args.dest]`(源仅读取,不属写域);`move` / `rename` / 未知或缺失 `action` ⇒ `[args.source,
|
|
95
|
+
* args.dest]`(保守双算);其余工具单参兜底(形态零变——缺 path 同样返含 `undefined` 的单元素
|
|
96
|
+
* 数组,由消费方按「未知路径 ⇒ 保守」自行判定);
|
|
97
|
+
* - 钩子 throw / 返回非数组 ⇒ `[]`;
|
|
98
|
+
* - **不过滤非字符串项**(未知路径的保守判据归门禁自身——`thincoder-cli/test/portability-classification.test.mjs`
|
|
99
|
+
* T-22 语义零变)。
|
|
100
|
+
* @param {Object|undefined} tool — 工具对象(可选钩子 `touchedPaths(args)`)
|
|
101
|
+
* @param {unknown} args — 工具入参(畸形入参不抛)
|
|
102
|
+
* @returns {unknown[]} 触达路径候选(可能含非字符串项——消费方过滤)
|
|
103
|
+
*/
|
|
104
|
+
export function toolTouchPaths(tool, args) {
|
|
105
|
+
const a = args && typeof args === "object" ? args : {}
|
|
106
|
+
const hook = tool?.touchedPaths
|
|
107
|
+
if (typeof hook !== "function") {
|
|
108
|
+
if (tool?.name === "file_ops") return a.action === "copy" ? [a.dest] : [a.source, a.dest]
|
|
109
|
+
return [a.path]
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const out = hook(a)
|
|
113
|
+
return Array.isArray(out) ? out : []
|
|
114
|
+
} catch {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
88
120
|
/** Escape XML special characters in a string for safe embedding in XML/HTML */
|
|
89
121
|
export function escapeXml(s) {
|
|
90
122
|
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'")
|
|
@@ -342,9 +374,18 @@ export function readonlyToolNames(tools) {
|
|
|
342
374
|
* the repo: the shell-side depth>0 filter consumes this set (design = TOOLS.md §6.16 / D-TO11). */
|
|
343
375
|
export const SUBAGENT_TOOL_EXCLUSIONS = new Set(["question"])
|
|
344
376
|
|
|
345
|
-
/** Apply the depth>0 exclusion set — always returns a NEW array (parent table untouched).
|
|
377
|
+
/** Apply the depth>0 exclusion set — always returns a NEW array (parent table untouched).
|
|
378
|
+
* #9(TOOLS.md §6.19):`bash` 的 `async` 参仅 depth-0——子代副本删参(照 `family-tools.mjs:98`
|
|
379
|
+
* 先例;返回值仍恒新数组、未命中条目原对象直传)。运行期第二道在 `tools/bash.mjs` execute
|
|
380
|
+
* (照 `read_image`「schema 门 + 运行期第二道」双线先例)。 */
|
|
346
381
|
export function excludeSubagentTools(tools) {
|
|
347
|
-
return tools
|
|
382
|
+
return tools
|
|
383
|
+
.filter((t) => !SUBAGENT_TOOL_EXCLUSIONS.has(t.name))
|
|
384
|
+
.map((t) => {
|
|
385
|
+
if (t.name !== "bash" || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
|
|
386
|
+
const { async: _asyncDrop, ...restProps } = t.parameters.properties
|
|
387
|
+
return { ...t, parameters: { ...t.parameters, properties: restProps } }
|
|
388
|
+
})
|
|
348
389
|
}
|
|
349
390
|
|
|
350
391
|
const MAX_INSTRUCTION_CHARS = 32_000
|
|
@@ -384,7 +425,7 @@ export async function loadProjectInstructions(cwd) {
|
|
|
384
425
|
|
|
385
426
|
/** Engineering mode reminder — shared with eng.mjs tool. */
|
|
386
427
|
export const ENG_ON_REMINDER =
|
|
387
|
-
"[System reminder: engineering mode is ON — design-before-code enforced. " +
|
|
428
|
+
"[System reminder: engineering mode is ON (session-scoped) — design-before-code enforced. " +
|
|
388
429
|
"Workflow: Requirements doc → Design doc → advisor(type='design') → " +
|
|
389
430
|
"user approval → eng-coder implementation. Code changes go through eng-coder " +
|
|
390
431
|
"subagents only. Advisor calls are NOT per-turn-mandatory — call only at " +
|
|
@@ -418,6 +459,15 @@ export const AUTO_TURN_DIGEST_DOMAIN_ENG =
|
|
|
418
459
|
export const UPSTREAM_TURN_DOMAIN =
|
|
419
460
|
"[System reminder: auto-turn — a running subagent sent you an in-flight message (shown below). No user message is waiting. Decide it now and reply with subagent action:'send' (id + message) — the child consumes the reply at its next turn boundary and keeps working on the unaffected parts; if the message needs no answer, say so in one line and move on. If finished subagent reports are also present above, summarize them as usual in the same turn. Do not start new work: FORBIDDEN this turn (mechanically enforced): modifying files, bash/execute/verify, spawning subagents, asking questions — those need a real user message. End the turn once the reply is sent.]"
|
|
420
461
|
|
|
462
|
+
/** Timer wake-turn domain (AGENT-LOOP-ASYNC-POOL.md §6.30.3 D-TW4): a pending timer
|
|
463
|
+
* expired while the session was idle and the end's latch opened this turn automatically —
|
|
464
|
+
* neither the digest domain ("no one is waiting", organize-only) nor the up-stream domain
|
|
465
|
+
* (a subagent waiting for a reply) fits: the timer's own semantics is "act on what the
|
|
466
|
+
* timer was for". Verbatim from the parent-side final text (single line — no newlines).
|
|
467
|
+
* Content authority = parent side. */
|
|
468
|
+
export const TIMER_TURN_DOMAIN =
|
|
469
|
+
"[System reminder: auto-turn — a timer you set earlier has expired (reminder above). No user message is waiting. Act on what the timer was for now: pull the data or run the check rather than continuing to reason in the abstract. Stay within the work that was in flight when the timer was set; do not open unrelated new work. Wrap up as soon as the action is done or blocked.]"
|
|
470
|
+
|
|
421
471
|
/** Engineering-mode status injection — one reminder on EVERY transition (2026-08-25:
|
|
422
472
|
* OFF is announced too — the model must know the gates lifted; silence after /eng-off
|
|
423
473
|
* left it guessing. Covers TUI /eng, resume, and any path bypassing the eng tool.) */
|