@thincoder/core 0.9.4 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -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 +14 -14
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +23 -13
- 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 +55 -294
- package/agent/family-tools.mjs +11 -11
- package/agent/helpers.mjs +63 -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 +19 -15
- 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 +90 -30
- 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 +11 -11
- package/agent-tools/advisor-settle.mjs +17 -17
- package/agent-tools/advisor.mjs +38 -43
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +23 -20
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +65 -34
- package/agent-tools/batch-paths.mjs +186 -0
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +22 -119
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +58 -58
- 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 +18 -13
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +3 -3
- 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 +29 -273
- package/agent-tools/subagent-async.mjs +36 -31
- package/agent-tools/subagent-panel.mjs +38 -9
- package/agent-tools/subagent-run.mjs +44 -29
- package/agent-tools/subagent-scheduler.mjs +29 -28
- package/agent-tools/subagent-spawn.mjs +57 -25
- package/agent-tools/subagent.mjs +46 -66
- 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 +20 -351
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +12 -7
- package/config-watch.mjs +83 -0
- package/config.mjs +17 -4
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/git/checkpoint.mjs +4 -8
- 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 +35 -7
- package/ledger-migrate.mjs +299 -0
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +11 -5
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-specs.mjs +131 -66
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +263 -0
- package/peer-domains.mjs +100 -71
- 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 +19 -7
- 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 +1 -2
- package/prompts/persona-engineering.md +41 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +10 -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-gc.mjs +67 -6
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +90 -18
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -236
- 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/question.md +1 -0
- 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 +10 -11
- package/tools/edit-diff.mjs +33 -25
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +11 -12
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/glob-dialect.mjs +3 -3
- 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/search.mjs +2 -2
- package/tools/shared.mjs +9 -7
- 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/tools/ops.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ops.mjs — operational tools: file_ops (move/copy/rename), process (list),
|
|
2
|
+
* ops.mjs — operational tools: file_ops (move/copy/rename), process (list/kill),
|
|
3
3
|
* get_current_time, wait_for (condition wait). Each exists so the model reaches
|
|
4
4
|
* for a dedicated tool instead of shelling out to `bash` for the same operation
|
|
5
5
|
* (parity with thinworker).
|
|
6
|
+
* #9:process 收编 `kill`(靶 = pid ∥ 后台 bash 任务 id——欠面收正:原须绕 bash taskkill);
|
|
7
|
+
* wait_for 条件族 +`bash id:N done`。list ∥ 既有条件面零变。
|
|
6
8
|
*/
|
|
7
9
|
import { DESC, resolveInCwd, truncate } from "./shared.mjs"
|
|
8
10
|
// 单一写路径点(编辑器编辑径注入缝——CORE-UNIFICATION §2.13.5):路径操作也经它落盘
|
|
@@ -51,11 +53,21 @@ export const processTool = {
|
|
|
51
53
|
parameters: {
|
|
52
54
|
type: "object",
|
|
53
55
|
properties: {
|
|
54
|
-
|
|
56
|
+
action: { type: "string", enum: ["list", "kill"], description: "list (default) — show running processes; kill — terminate a process tree (target: pid) or a background bash task (target: id)" },
|
|
57
|
+
name: { type: "string", description: "Optional name substring filter for list (case-insensitive)" },
|
|
58
|
+
pid: { type: "number", description: "kill target: process id — the whole tree is terminated (taskkill /T /F on win32; POSIX group kill)" },
|
|
59
|
+
id: { type: "number", description: "kill target: background bash task id (bash#N from the bash async ack)" },
|
|
55
60
|
},
|
|
56
61
|
},
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
// #9:动作分级——list 保持只读分类(dispatch-gates `isSubagentReadonlyAction`:planMode 放行/
|
|
63
|
+
// 免审批);kill 是破坏性动作(审批门)。`parallel` 保既有调度形(list 恒可批并行)。
|
|
64
|
+
readonly: false,
|
|
65
|
+
parallel: true,
|
|
66
|
+
async execute(args = {}, ctx = {}) {
|
|
67
|
+
const action = args?.action ?? "list"
|
|
68
|
+
if (action === "kill") return await executeKill(args, ctx)
|
|
69
|
+
if (action !== "list") return `Error: action must be list | kill (got "${action}")`
|
|
70
|
+
const { name } = args
|
|
59
71
|
const filter = typeof name === "string" && name.trim() ? name.trim().toLowerCase() : null
|
|
60
72
|
let rows
|
|
61
73
|
try {
|
|
@@ -69,6 +81,24 @@ export const processTool = {
|
|
|
69
81
|
},
|
|
70
82
|
}
|
|
71
83
|
|
|
84
|
+
/** #9 kill 执行体(靶 = pid ∥ 后台任务 id 二选一;两靶皆树杀)。动态 import(W8 契约②:
|
|
85
|
+
* agent-tools 链静态达 node:sqlite——端壳静态闭包须保持零命中)。 */
|
|
86
|
+
async function executeKill({ pid, id }, ctx) {
|
|
87
|
+
const hasPid = pid !== undefined && pid !== null && String(pid).trim() !== ""
|
|
88
|
+
const hasId = id !== undefined && id !== null && String(id).trim() !== ""
|
|
89
|
+
if (hasPid === hasId) {
|
|
90
|
+
return "Error: kill requires exactly one target — pid (process id) or id (background bash task id)"
|
|
91
|
+
}
|
|
92
|
+
const { killBgTask, killBgPid } = await import("../agent-tools/bash-async.mjs")
|
|
93
|
+
if (hasPid) {
|
|
94
|
+
if (!killBgPid(pid)) return `Error: kill pid: "${String(pid)}" is not a valid process id`
|
|
95
|
+
return `killed process tree ${Number(pid)} (taskkill /T /F on win32; POSIX group kill)`
|
|
96
|
+
}
|
|
97
|
+
const res = killBgTask(ctx?.agent, id)
|
|
98
|
+
if (res.status === "error") return `Error: ${res.error}`
|
|
99
|
+
return `killed background bash#${res.id} — its process tree was terminated; no digest will arrive${res.logPath ? ` — log so far: ${res.logPath}` : ""}`
|
|
100
|
+
}
|
|
101
|
+
|
|
72
102
|
function listWindows() {
|
|
73
103
|
// tasklist /FO CSV /NH → lines: "name.exe","1234","Console","1","12,345 K"
|
|
74
104
|
const out = execFileSync("tasklist", ["/FO", "CSV", "/NH"], { encoding: "utf8", timeout: 10000 })
|
|
@@ -112,7 +142,7 @@ export const getCurrentTimeTool = {
|
|
|
112
142
|
|
|
113
143
|
// ─── wait_for ───────────────────────────────────────────────────
|
|
114
144
|
|
|
115
|
-
/** wait_for bounds
|
|
145
|
+
/** wait_for bounds: a BUILT-IN ceiling replaces the unbounded
|
|
116
146
|
* sleep-then-wait — timeout_ms defaults to 30s (config.json agent.waitForTimeoutMs
|
|
117
147
|
* overrides), capped at WAIT_FOR_MAX_TIMEOUT_MS (600s — the execute-tool timeout
|
|
118
148
|
* convention, Math.min(t, 600_000)); interval_ms defaults to 1s with a 100ms
|
|
@@ -122,7 +152,7 @@ export const WAIT_FOR_MAX_TIMEOUT_MS = 600_000
|
|
|
122
152
|
export const WAIT_FOR_DEFAULT_INTERVAL_MS = 1_000
|
|
123
153
|
export const WAIT_FOR_MIN_INTERVAL_MS = 100
|
|
124
154
|
|
|
125
|
-
// Condition-source seam (
|
|
155
|
+
// Condition-source seam (评审 #4): production uses the real evaluator
|
|
126
156
|
// (evaluateWaitForCondition); tests inject a deterministic fake source via
|
|
127
157
|
// setWaitForConditionSource (default null — production path unchanged).
|
|
128
158
|
let injectedConditionSource = null
|
|
@@ -139,10 +169,11 @@ export function parseWaitForCondition(condition) {
|
|
|
139
169
|
if (c === "advisor settled") return { kind: "advisor", arg: null }
|
|
140
170
|
if (c === "consult done") return { kind: "consult", arg: null }
|
|
141
171
|
if ((m = c.match(/^subagent id:\s*(\d+) done$/))) return { kind: "subagent", arg: String(Number(m[1])) }
|
|
172
|
+
if ((m = c.match(/^bash id:\s*(\d+) done$/))) return { kind: "bash", arg: String(Number(m[1])) }
|
|
142
173
|
if ((m = c.match(/^file exists:\s*(.+)$/))) return { kind: "file", arg: m[1].trim() }
|
|
143
174
|
if ((m = c.match(/^port open:\s*(\d+)$/))) return { kind: "port", arg: Number(m[1]) }
|
|
144
175
|
throw new Error(
|
|
145
|
-
`wait_for: unsupported condition "${c}" — supported: "advisor settled", "subagent id:N done", "consult done", "file exists:path", "port open:N"`,
|
|
176
|
+
`wait_for: unsupported condition "${c}" — supported: "advisor settled", "subagent id:N done", "consult done", "bash id:N done", "file exists:path", "port open:N"`,
|
|
146
177
|
)
|
|
147
178
|
}
|
|
148
179
|
|
|
@@ -170,7 +201,7 @@ function asyncEntryDone(agent, id) {
|
|
|
170
201
|
return entry.done === true || entry.status === "done"
|
|
171
202
|
}
|
|
172
203
|
|
|
173
|
-
/** 评审池双载体(第 10 批 §
|
|
204
|
+
/** 评审池双载体(第 10 批 AGENT-LOOP-ASYNC-POOL.md §6.11 #2):agent ∪ history——未初始化的载体不进集合。 */
|
|
174
205
|
function advisorPools(agent) {
|
|
175
206
|
return [agent?._asyncAdvisors, agent?.history?._asyncAdvisors].filter((m) => m instanceof Map)
|
|
176
207
|
}
|
|
@@ -183,8 +214,8 @@ function hasRunningIn(pool, pred) {
|
|
|
183
214
|
return false
|
|
184
215
|
}
|
|
185
216
|
|
|
186
|
-
/** "advisor settled" 真判据(第 10 批 §
|
|
187
|
-
* (agent._asyncAdvisors ∪ history._asyncAdvisors)无 running/queued 条目(与 §
|
|
217
|
+
/** "advisor settled" 真判据(第 10 批 AGENT-LOOP-ASYNC-POOL.md §6.11 #2 修正):**评审池真实态**——双载体
|
|
218
|
+
* (agent._asyncAdvisors ∪ history._asyncAdvisors)无 running/queued 条目(与 AGENT-LOOP-ASYNC-POOL.md §6.10 的
|
|
188
219
|
* 未决评审判定 advisorReviewPending 同源语义)。修前读**子代理池**的 role==="advisor"
|
|
189
220
|
* 条目——评审条目只在 _asyncAdvisors → 恒无命中 → 恒真 0ms 秒过(用户实证)。 */
|
|
190
221
|
function advisorSettled(agent) {
|
|
@@ -228,12 +259,18 @@ export async function evaluateWaitForCondition(condition, ctx) {
|
|
|
228
259
|
const agent = ctx?.agent
|
|
229
260
|
switch (parsed.kind) {
|
|
230
261
|
case "advisor":
|
|
231
|
-
// 第 10
|
|
232
|
-
// 不再读子代理池的 role==="advisor" 条目(该池永无此类条目);语义与 §
|
|
262
|
+
// 第 10 批修正(AGENT-LOOP-ASYNC-POOL.md §6.11 #2——修前恒真 0ms 秒过):判据 = 评审池真实态(双载体)——
|
|
263
|
+
// 不再读子代理池的 role==="advisor" 条目(该池永无此类条目);语义与 §6.10 未决
|
|
233
264
|
// 评审判定同源(advisorReviewPending)。
|
|
234
265
|
return advisorSettled(agent)
|
|
235
266
|
case "subagent":
|
|
236
267
|
return asyncEntryDone(agent, parsed.arg)
|
|
268
|
+
case "bash": {
|
|
269
|
+
// #9:后台 bash 任务判据(池内 done ∥ 出池即 done——`bash-async.mjs` 单点)。
|
|
270
|
+
// 动态 import(W8 契约②:agent-tools 链静态达 node:sqlite——端壳静态闭包零命中)。
|
|
271
|
+
const { bgTaskDone } = await import("../agent-tools/bash-async.mjs")
|
|
272
|
+
return bgTaskDone(agent, parsed.arg)
|
|
273
|
+
}
|
|
237
274
|
case "consult":
|
|
238
275
|
return consultAllDone(agent)
|
|
239
276
|
case "file":
|
|
@@ -256,7 +293,7 @@ export const waitForTool = {
|
|
|
256
293
|
parameters: {
|
|
257
294
|
type: "object",
|
|
258
295
|
properties: {
|
|
259
|
-
condition: { type: "string", description: 'Condition expression — "advisor settled", "subagent id:N done", "consult done", "file exists:path", "port open:N"' },
|
|
296
|
+
condition: { type: "string", description: 'Condition expression — "advisor settled", "subagent id:N done", "consult done", "bash id:N done", "file exists:path", "port open:N"' },
|
|
260
297
|
interval_ms: { type: "integer", description: "Poll interval in ms (default 1000, floor 100)" },
|
|
261
298
|
timeout_ms: { type: "integer", description: "Overall timeout in ms (default 30000, cap 600000; config.json agent.waitForTimeoutMs overrides the default)" },
|
|
262
299
|
},
|
package/tools/patch.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
majorityEol
|
|
8
8
|
} from "./shared.mjs";
|
|
9
9
|
import { writeThroughPath, TMP_SUFFIX, markDirty } from "./write-path.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { spawnGit } from "./git-run.mjs";
|
|
11
11
|
import { mkdir, readFile, lstat, unlink } from "node:fs/promises";
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import { relative, dirname } from "node:path";
|
|
@@ -275,7 +275,9 @@ export const deleteTool = {
|
|
|
275
275
|
const rel = relative(ctx.cwd, abs).replace(/\\/g, "/")
|
|
276
276
|
let tracked = false
|
|
277
277
|
try {
|
|
278
|
-
|
|
278
|
+
// #208①(2026-09-25):表外同步 spawn 收口——探针转 `spawnGit` 单点(GIT_ENV 加固 + 超时 + 树杀);
|
|
279
|
+
// 失败语义零变(未跟踪 / 非仓 ⇒ tracked 保持 false)。
|
|
280
|
+
await spawnGit(ctx.cwd, ["ls-files", "--error-unmatch", "--", rel])
|
|
279
281
|
tracked = true
|
|
280
282
|
} catch {
|
|
281
283
|
// untracked / non-git repo
|
package/tools/repomap.mjs
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { existsSync } from "node:fs"
|
|
7
7
|
import { readFile, stat } from "node:fs/promises"
|
|
8
8
|
import { join } from "node:path"
|
|
9
|
-
import { normalizeEOL } from "./shared.mjs"
|
|
9
|
+
import { DESC, normalizeEOL } from "./shared.mjs" // #15 描述外置:文本单点 = tool-docs/repo_outline.md(DESC 单解析面)
|
|
10
|
+
import { INDEX_ORIGIN_ROW_WARN } from "../memory/schema.mjs" // §6.14 B4 文件数上界(WARN 值复用)
|
|
10
11
|
|
|
11
12
|
/** Extract JS/TS file import paths (normalize by stripping .ts/.js/.mjs suffixes) */
|
|
12
13
|
function parseImports(lines, ext) {
|
|
@@ -111,13 +112,22 @@ function normalizeExt(p) {
|
|
|
111
112
|
return p.replace(/\.(m?js|jsx|tsx?)$/i, "")
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
/** B4 越界提示行(跳过大纲构建;指路 = 声明 ∕ 剪枝)——两入口(buildSummary ∕ buildOutline)同文;
|
|
116
|
+
* `origin` 给出时才缀「for this origin」(repo_outline 工具缝不传 origin——跨 origin 行不假称本 origin)。 */
|
|
117
|
+
function outlineOverLimit(origin = null) {
|
|
118
|
+
return `(code index too large for an outline: > ${INDEX_ORIGIN_ROW_WARN} files${origin ? " for this origin" : ""} — declare index.excludePaths in PROJECT-MANIFEST.json, or prune: thincoder memory sweep --origin <o> --path <sub>)`
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
/**
|
|
115
122
|
* Internal: scan all files, build forward dependency graph + reverse reference graph.
|
|
116
123
|
* Returns { deps, importers, fileCount } shared by buildOutline / buildSummary.
|
|
117
124
|
*/
|
|
118
|
-
async function _buildDepGraph(db, cwd) {
|
|
119
|
-
|
|
125
|
+
async function _buildDepGraph(db, cwd, { origin = null } = {}) {
|
|
126
|
+
// §6.14 B4:origin 限定 + 文件数上界(文件行 `LIMIT = WARN+1` 形——越界不构建大纲,返回信号)
|
|
127
|
+
const limit = INDEX_ORIGIN_ROW_WARN + 1
|
|
128
|
+
const allFiles = db.prepare(`SELECT DISTINCT path FROM code_chunks ${origin ? "WHERE origin = ? " : ""}ORDER BY path LIMIT ${limit}`).all(...(origin ? [origin] : [])).map((r) => r.path)
|
|
120
129
|
if (allFiles.length === 0) return null
|
|
130
|
+
if (allFiles.length >= limit) return { overLimit: true, fileCount: allFiles.length }
|
|
121
131
|
|
|
122
132
|
const deps = new Map() // path → { imports: Set, exports: Set, size: number, dir: string }
|
|
123
133
|
const importers = new Map() // importee → Set<importer>
|
|
@@ -182,8 +192,9 @@ async function _buildDepGraph(db, cwd) {
|
|
|
182
192
|
* 3. Entry points (files with no importers — startup/top-level entry points)
|
|
183
193
|
* Output is naturally bounded (~1000-2000 chars), no more OUTLINE_INJECT_MAX hard truncation.
|
|
184
194
|
*/
|
|
185
|
-
export async function buildSummary(db, cwd) {
|
|
186
|
-
const graph = await _buildDepGraph(db, cwd)
|
|
195
|
+
export async function buildSummary(db, cwd, { origin = null } = {}) {
|
|
196
|
+
const graph = await _buildDepGraph(db, cwd, { origin })
|
|
197
|
+
if (graph?.overLimit) return outlineOverLimit(origin)
|
|
187
198
|
if (!graph) return "(no indexed source files; run codeSync or /reindex first)"
|
|
188
199
|
const { deps, importers, fileCount } = graph
|
|
189
200
|
|
|
@@ -257,8 +268,9 @@ export async function buildSummary(db, cwd) {
|
|
|
257
268
|
}
|
|
258
269
|
|
|
259
270
|
/** Get known file list from code_chunks (reuse index), parse by path to generate outline text */
|
|
260
|
-
export async function buildOutline(db, cwd, focusPath) {
|
|
261
|
-
const graph = await _buildDepGraph(db, cwd)
|
|
271
|
+
export async function buildOutline(db, cwd, focusPath, { origin = null } = {}) {
|
|
272
|
+
const graph = await _buildDepGraph(db, cwd, { origin })
|
|
273
|
+
if (graph?.overLimit) return outlineOverLimit(origin)
|
|
262
274
|
if (!graph) return "(no indexed source files; run codeSync or /reindex first)"
|
|
263
275
|
const { deps, importers } = graph
|
|
264
276
|
|
|
@@ -296,8 +308,7 @@ export async function buildOutline(db, cwd, focusPath) {
|
|
|
296
308
|
export function repoOutlineTool(db, cwd) {
|
|
297
309
|
return {
|
|
298
310
|
name: "repo_outline",
|
|
299
|
-
description:
|
|
300
|
-
"Show the project's file dependency outline: which files import/export from which, and what symbols they export. Use when you need to understand the project structure, find where a function is defined, or see what files depend on a module. Pass a path to focus on a single file's relationships. Find code by keyword or snippet with code_search.",
|
|
311
|
+
description: DESC("repo_outline"), // #15 外置:文本单点 = tool-docs/repo_outline.md(工具名逐字对核)
|
|
301
312
|
parameters: {
|
|
302
313
|
type: "object",
|
|
303
314
|
properties: {
|
package/tools/search.mjs
CHANGED
|
@@ -32,7 +32,7 @@ export const globTool = {
|
|
|
32
32
|
readonly: true,
|
|
33
33
|
async execute(args, ctx) {
|
|
34
34
|
const base = resolveInCwd(ctx, args.path ?? ".")
|
|
35
|
-
// §
|
|
35
|
+
// TOOLS.md §6.7 调用侧拆分(评审 #5 职责分层):空格分隔 include !exclude 多模式在这里拆;
|
|
36
36
|
// globToRegex 只收单个模式(数组由 compileGlobMatchers 收)。
|
|
37
37
|
let match
|
|
38
38
|
try {
|
|
@@ -115,7 +115,7 @@ export const grepTool = {
|
|
|
115
115
|
} catch {
|
|
116
116
|
return `grep error: path not found: ${base}`
|
|
117
117
|
}
|
|
118
|
-
// §
|
|
118
|
+
// TOOLS.md §6.7 调用侧拆分:glob 参数支持空格分隔 include !exclude 多模式(include/exclude 求交)。
|
|
119
119
|
let fileTest = null
|
|
120
120
|
if (args.glob) {
|
|
121
121
|
try {
|
package/tools/shared.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Imported by tools/file.mjs / system.mjs / web.mjs / git.mjs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { execFile } from "node:child_process"
|
|
7
7
|
import { readFileSync, existsSync, realpathSync, readdirSync, statSync, openSync, readSync, closeSync } from "node:fs"
|
|
8
8
|
import { dirname, join, resolve } from "node:path"
|
|
9
9
|
import { fileURLToPath } from "node:url"
|
|
@@ -217,12 +217,14 @@ export function makeDecoder() {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
/** Single-file git diff. Silently returns empty on failure. Large diffs that exceed maxBuffer are truncated rather than swallowed.
|
|
221
|
-
|
|
220
|
+
/** Single-file git diff. Silently returns empty on failure. Large diffs that exceed maxBuffer are truncated rather than swallowed.
|
|
221
|
+
* #208①(2026-09-25):体转 async 薄壳委托 `spawnGit` 单点(GIT_ENV 加固 + 两档超时 + 树杀 + 非阻塞);
|
|
222
|
+
* 形保真 = trim / 200 行截断 / 10MB 溢出取部分输出 / 失败静默回 `""`(调用面 5 处 `await` 化)。 */
|
|
223
|
+
export async function gitDiffOne(cwd, abs) {
|
|
222
224
|
try {
|
|
223
|
-
const diff =
|
|
224
|
-
|
|
225
|
-
}).trim()
|
|
225
|
+
const diff = (await spawnGit(cwd, ["--no-pager", "diff", "--no-color", "--", abs], {
|
|
226
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
227
|
+
})).trim()
|
|
226
228
|
if (!diff) return ""
|
|
227
229
|
const lines = diff.split("\n")
|
|
228
230
|
if (lines.length <= 200) return diff
|
|
@@ -373,7 +375,7 @@ export function detectDanger(command) {
|
|
|
373
375
|
}
|
|
374
376
|
|
|
375
377
|
|
|
376
|
-
/** Glob dialect (TOOLS.md §
|
|
378
|
+
/** Glob dialect (TOOLS.md §6.7) lives in glob-dialect.mjs — single authority for
|
|
377
379
|
* brace expansion / exclusion / explicit syntax errors (extracted 2026-09-06 —
|
|
378
380
|
* advisor #5: shared.mjs exceeded 500 lines). Re-exported so existing importers
|
|
379
381
|
* (system.mjs tools, ls filter, tests) keep their import paths unchanged. */
|
package/tools/web.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { DESC, truncate, stripTags, htmlToText, isPrivateHost } from "./shared.mjs";
|
|
2
2
|
import { URL } from "node:url";
|
|
3
3
|
import { proxyFetch } from "../proxy.mjs";
|
|
4
|
+
import { resolveEnvRefs } from "../env-ref.mjs";
|
|
4
5
|
|
|
5
6
|
export const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
6
7
|
const FETCH_TIMEOUT = 15_000
|
|
7
|
-
//
|
|
8
|
+
// D-TF3:网络失败错误文本追加 proxy 提示——纯文本提示,不自动路由(2026-08-31 裁定:proxy 显式传不自动应用)
|
|
8
9
|
const PROXY_HINT = "network failure — retry with proxy: 'http://host:port' if the target is blocked"
|
|
9
10
|
|
|
10
11
|
// ── Web search (Bing; direct by default, through proxy when configured and web toggle on) ──
|
|
@@ -46,7 +47,9 @@ const ENGINE_NAMES = ENGINES.map(e => e.name)
|
|
|
46
47
|
* { engine, results } or null to fall back to Bing HTML scraping.
|
|
47
48
|
* proxyUri: explicit per-call proxy (args.proxy) — never the config.json one. */
|
|
48
49
|
async function fetchTavily(query, limit, ctx, proxyUri) {
|
|
49
|
-
|
|
50
|
+
// #57(provider-config-family 批):值位 `${env:VAR}` 消费侧解析(CONFIG.md §6.3)——未设 ∕ 空串 ∕
|
|
51
|
+
// 畸形 ⇒ 抛错(不静默字面透传);未配置 key 仍走 Bing 回落。
|
|
52
|
+
const apiKey = resolveEnvRefs(ctx?.agent?.config?.websearch?.apiKey)
|
|
50
53
|
if (!apiKey) return null
|
|
51
54
|
const ctrl = new AbortController()
|
|
52
55
|
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT)
|
package/tools/write-path.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 写入又与用户随后的保存互相竞写(split-brain 数据丢失)。核内原先写盘直调
|
|
7
7
|
* `node:fs/promises`、散在 5 个档的 8 个写点上 ⇒ **没有可注入的一跳** ⇒ 端侧接核后会
|
|
8
8
|
* 丢掉编辑器径。本模块就是那一跳:核内**工具面的 8 个编辑写点**全改调 `writeThroughPath`
|
|
9
|
-
* (模型面直写 `agent-tools/batch
|
|
9
|
+
* (模型面直写 `agent-tools/batch.mjs` = 设计登记的 S2 前置门——不在本缝覆盖面;
|
|
10
10
|
* 口径与 `CORE-UNIFICATION.md` §2.13.5 落地收正同源)。
|
|
11
11
|
*
|
|
12
12
|
* 默认径 = CLI 语义(**零行为变**):`writeFile` + 记账。端侧
|
package/traces/trace-store.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* trace-store.mjs —
|
|
2
|
+
* trace-store.mjs — 完整轨迹存档(TRACES.md §6 机制 + §7 决策表 D-TR1–4 / D-TR6 / D-TR10–13 权威规格)。
|
|
3
3
|
*
|
|
4
4
|
* 完整请求-响应轨迹落盘:每次 chat() 调用一个 JSONL 文件到
|
|
5
5
|
* ~/.thincoder/traces/YYYY-MM-DD/<sessionKey>-<seq>.jsonl。采集点唯一 = core.mjs
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* 写盘异步(node:fs/promises——不 await);落盘失败静默降级(不抛错、不阻塞
|
|
12
12
|
* chat() 返回)。recordChatTrace 返回落盘 promise——仅供测试/显式消费方 await。
|
|
13
13
|
* - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
|
|
14
|
-
* 轨迹目录(既有测试跑真实 agent 管线会产生数百次 chat
|
|
15
|
-
*
|
|
16
|
-
* 与 log.mjs 的 THINCODER_LOG_DIR 同惯例)。
|
|
14
|
+
* 轨迹目录(既有测试跑真实 agent 管线会产生数百次 chat 调用);显式测试缝
|
|
15
|
+
* (`_setTracesRootForTest`)强制写入该目录(与 log.mjs 同惯例——CONFIG.md §6.2)。
|
|
17
16
|
* - 脱敏(D-TR2):复用 log.mjs 字段名黑名单(apikey/designtoken/password/secret/
|
|
18
17
|
* token/authorization/proxyuri/proxy)+ SECRET_FORM 形态扫描(redactSecret)——对
|
|
19
18
|
* messages/content/reasoning/toolCalls/error 全字段应用;不发明新遮蔽模式。
|
|
@@ -24,7 +23,7 @@
|
|
|
24
23
|
* 与写盘同在 fire-and-forget 异步体内)。2026-09-21(TRACES.md §6.4):判据改**目录级三段梯**
|
|
25
24
|
* + 每写 prune **节流**;清理实现外提 `trace-cleanup.mjs`(本档包装保既有 import 面)。
|
|
26
25
|
* - seq = 当日目录内最大已有 seq + 1(D-TR3——跨会话/进程重启不覆写既有旧轨迹——
|
|
27
|
-
* 18.6.1 评审 #3);**进程内 seqCache
|
|
26
|
+
* 18.6.1 评审 #3);**进程内 seqCache(TRACES.md §6.2——消除逐调用同步扫目录)**:首次
|
|
28
27
|
* readdirSync 后进程内递增预留,目录被清理(retention)后取下界重扫一次(防御)。
|
|
29
28
|
* 多进程语义(修正轮 #10——登记):同 cwd 多实例各自进程内缓存——seq 可撞、同
|
|
30
29
|
* sessionKey 记录并入同名文件(append 不覆写);**可容忍**(本机 traces = 诊断面、
|
|
@@ -44,16 +43,20 @@ import { configDir, loadConfig } from "../config.mjs"
|
|
|
44
43
|
import { redactSecret, errText, classifyErr } from "../log.mjs"
|
|
45
44
|
import { normalizeCwd } from "../session-slots.mjs"
|
|
46
45
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
46
|
+
/** Test seam: override the traces root (process-internal seam — CONFIG.md §6.2 ③ 通道). */
|
|
47
|
+
let _tracesRoot = null
|
|
48
|
+
export function _setTracesRootForTest(p) { _tracesRoot = p }
|
|
49
|
+
export function _resetTracesRootForTest() { _tracesRoot = null }
|
|
50
|
+
|
|
51
|
+
/** 轨迹根目录:显式测试缝 > ~/.thincoder/traces(configDir——D-TR3——与 sessions/ 同域)。 */
|
|
49
52
|
export function tracesRoot() {
|
|
50
|
-
return
|
|
53
|
+
return _tracesRoot ?? join(configDir, "traces")
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/** 写门(测试隔离):test runner 进程(NODE_TEST_CONTEXT)默认跳过——除显式
|
|
54
|
-
*
|
|
57
|
+
* 测试缝 override(轨迹测试隔离临时目录)。 */
|
|
55
58
|
function writeEnabled() {
|
|
56
|
-
if (process.env.NODE_TEST_CONTEXT &&
|
|
59
|
+
if (process.env.NODE_TEST_CONTEXT && _tracesRoot === null) return false
|
|
57
60
|
return true
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -93,7 +96,7 @@ export function tracesDirFor(dateStr) {
|
|
|
93
96
|
// 完整落盘:不截断、不丢行(无单消息/单记录额度,无在途丢弃);清理 = 每写一次 prune
|
|
94
97
|
// (写盘成功后顺带清理——与写盘同在 fire-and-forget 异步体内,永不阻塞 chat)。
|
|
95
98
|
|
|
96
|
-
// 进程内 seq
|
|
99
|
+
// 进程内 seq 缓存(TRACES.md §6.2:dir → 已预留 max;首次读盘后递增预留,目录被清理后取下界重扫一次)。
|
|
97
100
|
const _seqCache = new Map()
|
|
98
101
|
// 每写 prune 节流(D-TR12 · §6.4):窗 = 10 分钟 + 在飞合并(同窗 3 连写 ⇒ 扫描 ≤1 次)。
|
|
99
102
|
export const PRUNE_THROTTLE_MS = 10 * 60_000
|
|
@@ -132,7 +135,7 @@ function scanMaxSeq(dir) {
|
|
|
132
135
|
|
|
133
136
|
/** seq = max(当日目录最大已有 seq, 进程内已预留) + 1(D-TR3——跨会话/进程重启
|
|
134
137
|
* 不覆写——T-TR8/T-TR10;同步预留 = 异步写盘启动前的原子号位分配)。
|
|
135
|
-
*
|
|
138
|
+
* TRACES.md §6.2:seqCache 进程内递增——不逐调用 readdirSync(目录被清理/缺失 → 重扫一次)。 */
|
|
136
139
|
export function nextTraceSeq(dateStr) {
|
|
137
140
|
const dir = tracesDirFor(dateStr)
|
|
138
141
|
let max = _seqCache.get(dir)
|
|
@@ -152,7 +155,7 @@ export function tracesEnabled(logCtx) {
|
|
|
152
155
|
return logCtx?.traces !== false
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
// ───
|
|
158
|
+
// ─── 单遍序列化(TRACES.md §6.2 D-TR1——脱敏内联、零复制图、字段名/次序同构)────────
|
|
156
159
|
|
|
157
160
|
const OMIT = Symbol("omit") // JSON.stringify 语义:对象属性省略(数组元素 → null)
|
|
158
161
|
|
|
@@ -292,7 +295,7 @@ export function recordChatTrace(provider, opts = {}, result = null, error = null
|
|
|
292
295
|
}
|
|
293
296
|
|
|
294
297
|
/** D-TR10 清理面(D-TR11 目录级三段梯)——实现外提 `traces/trace-cleanup.mjs`;此处包装保
|
|
295
|
-
* 既有 import 面与缺省 `dir`(`tracesRoot()` 单源——测试经 `
|
|
298
|
+
* 既有 import 面与缺省 `dir`(`tracesRoot()` 单源——测试经 `_setTracesRootForTest` 隔离)。
|
|
296
299
|
* 调用点 = 每写 prune(经 `maybePruneTraces`)+ 启动清理(壳侧);返回删除文件数。 */
|
|
297
300
|
export function cleanupTraces({ dir = tracesRoot(), retentionHours = 24 } = {}) {
|
|
298
301
|
return cleanupTracesImpl({ dir, retentionHours })
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vision-reader.mjs — 非多模态降级读图跑者(上提核件 —— parity-b4 W0 · 2026-09-29)。
|
|
3
|
+
*
|
|
4
|
+
* 上提源 = VSC `thincoder-vscode/src/extension/vision-channel.mjs:14`(`findVisionChannel` 逐字)
|
|
5
|
+
* + `image-handler.mjs:70-95`(`runVisionReader` 逐字);VSC 自持跑者 ∕ 渠道副本已随迁移轮(parity-b4)删。
|
|
6
|
+
* 消费面 = VSC `image-handler.mjs`(已改指本档)· 桌面 `src/main/attachments.mjs`
|
|
7
|
+
* (降级适配器缺省 = 本档 —— `visionReader` 缝的核默认实现)。
|
|
8
|
+
*
|
|
9
|
+
* `findVisionChannel`(纯函数):渠道表 ⇒ 视觉读图渠道 —— 「优先同名(当前主)渠道的视觉默认模型,
|
|
10
|
+
* else 全渠道首个视觉默认模型者」;判据 = `specForModel(p.model).multimodal`
|
|
11
|
+
* (与 `read_image` 注册门 ∕ `isNonVisionModel` 同源)。
|
|
12
|
+
*
|
|
13
|
+
* `runVisionReader`:一次性 depth-1 只读 explore 子代理读图返回文本描述。
|
|
14
|
+
* 渠道 = 视觉渠道(`resolveProviders()` 现读);provider = 核 `resolveChildProvider`(`<渠道>:<模型>`);
|
|
15
|
+
* 工具 = 按**该渠道模型**装配的只读族(`read_image` 随 multimodal 注册 —— 主模型非视觉时其工具表
|
|
16
|
+
* 本无该件,故按子代理模型重装,不沿用主代理工具表);`config ∕ memory` = 主代理同源。
|
|
17
|
+
* 失败径全 `null`(原样兜底,不静默丢):无渠道 ∕ 渠道未知 ∕ 渠道无 key(spawn 期失败回落)∕
|
|
18
|
+
* spawn 失败 ∕ 超时 ∕ 空返;外部 `signal` 已停 ⇒ 快速失败,否则桥接内部控制器。
|
|
19
|
+
*
|
|
20
|
+
* W8 契约②(2026-09-29 断链):`agent.mjs` ∕ `agent-tools/subagent-async.mjs` 静态链达 `node:sqlite`
|
|
21
|
+
* (`agent.mjs` → `agent/setup.mjs` → `memory.mjs` → `memory/schema.mjs`)⇒ 两端一律**函数体内动态 import**
|
|
22
|
+
* (约定源 = `ledger.mjs:16`;实例 = `tools/index.mjs:63`);本档静态边只留闭包净件(`config-io.mjs` ∕
|
|
23
|
+
* `model-specs.mjs` ∕ `tools/index.mjs`)。
|
|
24
|
+
*/
|
|
25
|
+
import { resolveProviders } from "./config-io.mjs"
|
|
26
|
+
import { specForModel } from "./model-specs.mjs"
|
|
27
|
+
import { assembleBuiltinTools } from "./tools/index.mjs"
|
|
28
|
+
|
|
29
|
+
/** 读图墙钟上限(VSC `image-handler.mjs:70` 同值——一次性子代理的成功径上限)。 */
|
|
30
|
+
export const VISION_READ_TIMEOUT_MS = 60_000
|
|
31
|
+
|
|
32
|
+
/** 找可跑视觉读图的渠道:`{ provider, model }` ∥ `null`——优先同名(当前主)渠道的视觉默认模型,
|
|
33
|
+
* else 全渠道首个视觉默认模型者("优先同名渠道视觉模型 → 首视觉渠道")。 */
|
|
34
|
+
export function findVisionChannel(providers, currentName = "") {
|
|
35
|
+
const first = []
|
|
36
|
+
for (const p of providers ?? []) {
|
|
37
|
+
const model = typeof p?.model === "string" && p.model && specForModel(p.model).multimodal ? p.model : ""
|
|
38
|
+
if (!model) continue
|
|
39
|
+
if (p.name === currentName) return { provider: p.name, model }
|
|
40
|
+
first.push({ provider: p.name, model })
|
|
41
|
+
}
|
|
42
|
+
return first[0] ?? null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 降级读图跑者(缝契约 = `visionReader({ paths, signal }) ⇒ { ok: true, description }` ∥ `null`
|
|
46
|
+
* ——见 `attachments.mjs` `downgradeNonVisionImages`)。 */
|
|
47
|
+
export async function runVisionReader({ paths, providerName, cwd, parentAgent, signal }) {
|
|
48
|
+
// 取消缝(VSC A12 同款):已停 ⇒ 快速失败(不复用 60s 超时路径);否则桥接内部 controller。
|
|
49
|
+
if (signal?.aborted) return null
|
|
50
|
+
let providers = []
|
|
51
|
+
try { providers = resolveProviders().providers } catch { return null }
|
|
52
|
+
const vc = findVisionChannel(providers, providerName)
|
|
53
|
+
if (!vc) return null
|
|
54
|
+
let provider = null
|
|
55
|
+
try {
|
|
56
|
+
const { resolveChildProvider } = await import("./agent-tools/subagent-async.mjs") // 动态 import(该链静态达 node:sqlite——W8 契约②)
|
|
57
|
+
provider = resolveChildProvider(parentAgent, `${vc.provider}:${vc.model}`)
|
|
58
|
+
} catch { return null }
|
|
59
|
+
const ac = new AbortController()
|
|
60
|
+
const timer = setTimeout(() => ac.abort(), VISION_READ_TIMEOUT_MS)
|
|
61
|
+
const onAbort = () => ac.abort()
|
|
62
|
+
if (signal) signal.addEventListener("abort", onAbort, { once: true })
|
|
63
|
+
try {
|
|
64
|
+
const { createAgent, runAgent, readonlyToolNames, excludeSubagentTools } = await import("./agent.mjs") // 动态 import(agent 链静态达 node:sqlite——W8 契约②)
|
|
65
|
+
const builtins = await assembleBuiltinTools({ memory: parentAgent?.memory, cwd, model: vc.model })
|
|
66
|
+
const allowed = readonlyToolNames(builtins)
|
|
67
|
+
const tools = excludeSubagentTools(builtins.filter((t) => allowed.has(t.name)))
|
|
68
|
+
const child = createAgent({ provider, tools, config: parentAgent?.config, cwd, memory: parentAgent?.memory, role: "explore" })
|
|
69
|
+
const task = paths.map((f) => `用 read_image 读 ${f} 返回图像内容描述`).join("\n")
|
|
70
|
+
const desc = await runAgent(child, task, {}, { depth: 1, maxTurns: 10, signal: ac.signal })
|
|
71
|
+
return typeof desc === "string" && desc.trim() ? { ok: true, description: desc.trim() } : null
|
|
72
|
+
} catch { return null } finally {
|
|
73
|
+
clearTimeout(timer)
|
|
74
|
+
if (signal) signal.removeEventListener("abort", onAbort)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* agent-tools/batch-segment.mjs — **过渡 shim**(batch 工具单名化收口,BATCH-RECORD §4.14):
|
|
3
|
-
* 本档仅 re-export `batch.mjs` 主档导出面(旧 import 面 = VSC `setup-tooltable.mjs` 经此 import
|
|
4
|
-
* `configureBatchSegment` / 旧测试消费——导出全超集,行为零变)。权威实现 = batch.mjs
|
|
5
|
-
* (batchTool 四 action + append 迁移面 + 过渡别名 batchSegmentTool);撤除判据 = §4.14
|
|
6
|
-
* (消费面清零后删本档——先例债:删档需同步 tooltable 装配 import,随批登记)。
|
|
7
|
-
*/
|
|
8
|
-
export {
|
|
9
|
-
MAX_TEXT_CHARS,
|
|
10
|
-
SEGMENT_BY_ROLE,
|
|
11
|
-
batchDocBases,
|
|
12
|
-
batchDocForReview,
|
|
13
|
-
batchSegmentTool,
|
|
14
|
-
batchTool,
|
|
15
|
-
configureBatchSegment,
|
|
16
|
-
resetBatchSegment,
|
|
17
|
-
resolveBatchDocPath,
|
|
18
|
-
} from "./batch.mjs"
|