@thincoder/core 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abort-provenance.mjs +116 -0
- package/advisor/citations.mjs +139 -0
- package/advisor/compaction.mjs +174 -0
- package/advisor/convergence.mjs +80 -0
- package/advisor/history.mjs +77 -0
- package/advisor/loop.mjs +288 -0
- package/advisor/messages.mjs +299 -0
- package/advisor/notice.mjs +141 -0
- package/advisor/project-context.mjs +197 -0
- package/advisor/repos.mjs +150 -0
- package/advisor/run.mjs +190 -0
- package/advisor/truncate.mjs +57 -0
- package/advisor.mjs +281 -0
- package/agent/child-marks.mjs +24 -0
- package/agent/completion.mjs +145 -0
- package/agent/dispatch.mjs +493 -0
- package/agent/family-tools.mjs +174 -0
- package/agent/helpers.mjs +412 -0
- package/agent/post-turn.mjs +70 -0
- package/agent/record-results.mjs +174 -0
- package/agent/relay-prefix.mjs +39 -0
- package/agent/run-stages.mjs +244 -0
- package/agent/setup-reminders.mjs +199 -0
- package/agent/setup.mjs +234 -0
- package/agent/spawn-child.mjs +258 -0
- package/agent/suspension.mjs +240 -0
- package/agent/write-gate.mjs +87 -0
- package/agent-tools/advisor-async.mjs +481 -0
- package/agent-tools/advisor-settle.mjs +240 -0
- package/agent-tools/advisor.mjs +280 -0
- package/agent-tools/async-discard.mjs +143 -0
- package/agent-tools/async-settle.mjs +299 -0
- package/agent-tools/batch-segment.mjs +265 -0
- package/agent-tools/child-permission.mjs +45 -0
- package/agent-tools/consult.mjs +471 -0
- package/agent-tools/design-token.mjs +117 -0
- package/agent-tools/digest-budget.mjs +76 -0
- package/agent-tools/eng.mjs +102 -0
- package/agent-tools/escalate-async.mjs +302 -0
- package/agent-tools/goal.mjs +119 -0
- package/agent-tools/panel-blocks.mjs +24 -0
- package/agent-tools/parent-channel.mjs +231 -0
- package/agent-tools/plan.mjs +86 -0
- package/agent-tools/read-history.mjs +309 -0
- package/agent-tools/recent-changes.mjs +24 -0
- package/agent-tools/review-facts.mjs +31 -0
- package/agent-tools/settings.mjs +268 -0
- package/agent-tools/skill.mjs +63 -0
- package/agent-tools/spawn-gates.mjs +109 -0
- package/agent-tools/subagent-actions.mjs +496 -0
- package/agent-tools/subagent-async.mjs +456 -0
- package/agent-tools/subagent-panel.mjs +160 -0
- package/agent-tools/subagent-run.mjs +208 -0
- package/agent-tools/subagent-scheduler.mjs +446 -0
- package/agent-tools/subagent-spawn.mjs +478 -0
- package/agent-tools/subagent.mjs +419 -0
- package/agent-tools/task.mjs +87 -0
- package/agent-tools/timer.mjs +46 -0
- package/agent-tools/verify.mjs +295 -0
- package/agent-tools.mjs +23 -0
- package/agent.mjs +430 -0
- package/auto-think.mjs +115 -0
- package/compress-form.mjs +24 -0
- package/config-io.mjs +277 -0
- package/config-migrate.mjs +178 -0
- package/config-presets.mjs +49 -0
- package/config.mjs +419 -0
- package/context.mjs +495 -0
- package/conventions.mjs +223 -0
- package/embedding.mjs +120 -0
- package/escape.mjs +152 -0
- package/expand-home.mjs +16 -0
- package/explore-distill.mjs +152 -0
- package/generate-title.mjs +123 -0
- package/git/checkpoint.mjs +448 -0
- package/git/gitmem.mjs +100 -0
- package/history-window.mjs +179 -0
- package/hooks.mjs +108 -0
- package/i18n.mjs +106 -0
- package/index-bin.mjs +48 -0
- package/index-discover.mjs +176 -0
- package/ledger-cmd.mjs +209 -0
- package/ledger-db.mjs +86 -0
- package/ledger-surface.mjs +76 -0
- package/ledger.mjs +202 -0
- package/log.mjs +195 -0
- package/manifest.mjs +338 -0
- package/markdown.mjs +106 -0
- package/mcp/helpers.mjs +51 -0
- package/mcp/transport-http.mjs +248 -0
- package/mcp/transport-stdio.mjs +140 -0
- package/mcp/transport-ws.mjs +122 -0
- package/mcp.mjs +295 -0
- package/memory/code-index.mjs +219 -0
- package/memory/code-sync.mjs +427 -0
- package/memory/core.mjs +318 -0
- package/memory/delete.mjs +242 -0
- package/memory/docs.mjs +431 -0
- package/memory/file-walk.mjs +109 -0
- package/memory/origin.mjs +24 -0
- package/memory/scan.mjs +177 -0
- package/memory/schema.mjs +460 -0
- package/memory.mjs +21 -0
- package/model-ref.mjs +66 -0
- package/model-specs.mjs +277 -0
- package/package.json +32 -0
- package/peer-domains.mjs +265 -0
- package/peer-instances.mjs +178 -0
- package/permission.mjs +79 -0
- package/process-probe.mjs +315 -0
- package/prompt-files.mjs +113 -0
- package/prompt-overlays.mjs +78 -0
- package/prompts/advisor-design.md +43 -0
- package/prompts/advisor-round1.md +41 -0
- package/prompts/advisor-round2.md +46 -0
- package/prompts/advisor-round3.md +42 -0
- package/prompts/common.md +158 -0
- package/prompts/consult-base.md +19 -0
- package/prompts/discipline-engineering.md +123 -0
- package/prompts/discipline-normal.md +206 -0
- package/prompts/persona-coder.md +21 -0
- package/prompts/persona-eng-coder.md +41 -0
- package/prompts/persona-eng-designer.md +80 -0
- package/prompts/persona-engineering.md +160 -0
- package/prompts/persona-explore.md +15 -0
- package/prompts/persona-normal.md +35 -0
- package/prompts/persona-plan.md +27 -0
- package/provider/anthropic.mjs +225 -0
- package/provider/core.mjs +491 -0
- package/provider/errors.mjs +101 -0
- package/provider/google.mjs +257 -0
- package/provider/index.mjs +7 -0
- package/provider/list-models.mjs +163 -0
- package/provider/normalize.mjs +81 -0
- package/provider/rate.mjs +168 -0
- package/provider/responses.mjs +495 -0
- package/provider/retry.mjs +88 -0
- package/provider/sse.mjs +264 -0
- package/provider/wait-status.mjs +59 -0
- package/proxy.mjs +274 -0
- package/rules.mjs +53 -0
- package/session-gc.mjs +248 -0
- package/session-guard.mjs +59 -0
- package/session-lifecycle.mjs +305 -0
- package/session-migrate.mjs +48 -0
- package/session-rename.mjs +38 -0
- package/session-segments.mjs +99 -0
- package/session-slot-write.mjs +168 -0
- package/session-slots-manifest.mjs +264 -0
- package/session-slots.mjs +298 -0
- package/session-store.mjs +441 -0
- package/session.mjs +244 -0
- package/skills.mjs +234 -0
- package/text-budget.mjs +79 -0
- package/token-ttl.mjs +285 -0
- package/tool-docs/apply_patch.md +15 -0
- package/tool-docs/bash.md +38 -0
- package/tool-docs/delete.md +13 -0
- package/tool-docs/edit.md +30 -0
- package/tool-docs/execute.md +21 -0
- package/tool-docs/fetch.md +12 -0
- package/tool-docs/file_ops.md +17 -0
- package/tool-docs/get_current_time.md +8 -0
- package/tool-docs/git.md +54 -0
- package/tool-docs/glob.md +11 -0
- package/tool-docs/grep.md +19 -0
- package/tool-docs/hashline_edit.md +14 -0
- package/tool-docs/insert_after.md +15 -0
- package/tool-docs/lint.md +10 -0
- package/tool-docs/ls.md +12 -0
- package/tool-docs/lsp.md +10 -0
- package/tool-docs/process.md +10 -0
- package/tool-docs/question.md +16 -0
- package/tool-docs/read.md +20 -0
- package/tool-docs/read_image.md +8 -0
- package/tool-docs/tree.md +14 -0
- package/tool-docs/wait_for.md +22 -0
- package/tool-docs/websearch.md +16 -0
- package/tool-docs/write.md +11 -0
- package/tools/bash.mjs +276 -0
- package/tools/edit-batch.mjs +204 -0
- package/tools/edit-diff.mjs +388 -0
- package/tools/exec-run.mjs +43 -0
- package/tools/execute.mjs +243 -0
- package/tools/file.mjs +464 -0
- package/tools/git-checkpoint.mjs +143 -0
- package/tools/git-ext.mjs +173 -0
- package/tools/git.mjs +415 -0
- package/tools/glob-dialect.mjs +130 -0
- package/tools/index.mjs +76 -0
- package/tools/linter.mjs +120 -0
- package/tools/lsp.mjs +335 -0
- package/tools/ops.mjs +293 -0
- package/tools/patch.mjs +290 -0
- package/tools/question.mjs +26 -0
- package/tools/repomap.mjs +314 -0
- package/tools/search.mjs +248 -0
- package/tools/shared.mjs +467 -0
- package/tools/tree.mjs +81 -0
- package/tools/web.mjs +224 -0
- package/tools/write-path.mjs +191 -0
- package/traces/trace-store.mjs +303 -0
- package/undo-stack.mjs +47 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools/execute.mjs — JavaScript execution tool
|
|
3
|
+
*
|
|
4
|
+
* Gives the model an `execute` tool that runs JS in a child `node
|
|
5
|
+
* --input-type=module --eval` process — NOT the in-process vm sandbox it used
|
|
6
|
+
* to be. The vm route could not support dynamic `import()` (needs the
|
|
7
|
+
* --experimental-vm-modules flag) or await it, which pushed every real JS run
|
|
8
|
+
* back to `bash node -e`. A child node process gives top-level await, dynamic
|
|
9
|
+
* `import()` of the project's own .mjs modules, native `console`/`fetch`, AND a
|
|
10
|
+
* killable timeout (an in-process infinite loop would freeze the CLI; a child
|
|
11
|
+
* process is killed like bash).
|
|
12
|
+
*
|
|
13
|
+
* The child runs PURE node ESM — no helpers are injected (exec-prelude.mjs
|
|
14
|
+
* retired 2026-09-03, TOOLS.md §12: preloaded readFile/writeFile/glob/grep/log
|
|
15
|
+
* helpers made execute look like a file tool, bypassing the dedicated
|
|
16
|
+
* read/ls/glob/grep/write/edit tools). Scripts that need fs/path import the
|
|
17
|
+
* node: modules themselves — same boundary as bash, no fake sandbox.
|
|
18
|
+
*
|
|
19
|
+
* Parameters:
|
|
20
|
+
* code — JS to run inline (top-level await and import() supported). Use this OR scriptFile.
|
|
21
|
+
* scriptFile — run a .mjs/.js file with node (self-contained, imports what it needs). Use this OR code.
|
|
22
|
+
* nodeArgs — (scriptFile) extra node flags before the script (e.g. --test, --check); eval-like flags rejected
|
|
23
|
+
* workdir — run in this sub-directory (no directory restriction)
|
|
24
|
+
* filter — return only output lines matching this regex (case-insensitive)
|
|
25
|
+
* timeoutMs — timeout (default 30s, max 600000ms)
|
|
26
|
+
*/
|
|
27
|
+
import { spawn, execFileSync } from "node:child_process"
|
|
28
|
+
import { resolve } from "node:path"
|
|
29
|
+
import { DESC } from "./shared.mjs"
|
|
30
|
+
|
|
31
|
+
const MAX_SCRIPT = 50_000
|
|
32
|
+
const MAX_OUTPUT = 50_000
|
|
33
|
+
const DEFAULT_TIMEOUT = 30_000
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 超时错误文本——带重试引导(TOOLS.md §14.1 D14.1.2——"下一跳"):数字 = 实际生效的
|
|
37
|
+
* timeoutMs(Math.min(t, 600_000) 或默认 30s)——上限 600000 与 schema/头注/execute.md 一致。
|
|
38
|
+
*/
|
|
39
|
+
const timeoutErrorText = (timeoutMs) =>
|
|
40
|
+
`Error: script timed out after ${timeoutMs}ms — retry with a larger timeoutMs (up to 600000) for long scripts, or use bash (default 120s) for shell commands`
|
|
41
|
+
|
|
42
|
+
/** Resolve workdir relative to cwd — no boundary assertion
|
|
43
|
+
* (§10.1 2026-09-02: workspace confinement removed; the child node process is
|
|
44
|
+
* not directory-limited — same boundary as bash). */
|
|
45
|
+
function resolveBaseDir(cwd, workdir) {
|
|
46
|
+
if (!workdir || typeof workdir !== "string") return cwd
|
|
47
|
+
return resolve(cwd, workdir)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Keep only output lines matching a regex (execute filter, case-insensitive). */
|
|
51
|
+
function applyFilter(output, filter) {
|
|
52
|
+
try {
|
|
53
|
+
const re = new RegExp(filter, "i")
|
|
54
|
+
const lines = output.split("\n").filter((l) => re.test(l))
|
|
55
|
+
return lines.length ? lines.join("\n") : `(no output lines matched filter "${filter}")`
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return `Error: filter regex invalid: ${e.message}`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Platform-aware process tree kill — mirror of system.mjs/verify.mjs killProcessTree.
|
|
62
|
+
* Timeout/abort must reach grandchildren: a script that spawned children keeps the
|
|
63
|
+
* pipes open otherwise — "close" never fires and the tool stalls until the 3s kick
|
|
64
|
+
* while the orphan keeps running (2026-09-05 advisor 🟡#4). */
|
|
65
|
+
function killProcessTree(child) {
|
|
66
|
+
if (process.platform === "win32") {
|
|
67
|
+
try { execFileSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { stdio: "ignore" }) } catch {}
|
|
68
|
+
} else {
|
|
69
|
+
try { process.kill(-child.pid, "SIGKILL") } catch {}
|
|
70
|
+
try { child.kill("SIGKILL") } catch {}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export { killProcessTree }
|
|
74
|
+
|
|
75
|
+
// ─── 树杀注入缝(#57——「树杀实现按端注入」,形态参 §2.13.5 注入缝)───────────────
|
|
76
|
+
/**
|
|
77
|
+
* 超时 / abort 树杀注入位(**缺省不覆盖** = 核内 `killProcessTree`——CLI 语义,零行为变;
|
|
78
|
+
* 端装配层可覆盖为本端树杀实现)。核内零端名分支(契约 5——本档只认 `killTree` 函数名)。
|
|
79
|
+
* 契约:`killTree(child) → void`(尽力而为——调用点已包 try/catch,抛错不阻断收尾)。
|
|
80
|
+
*/
|
|
81
|
+
let injectedKill = null
|
|
82
|
+
export function configureProcessTreeKill(impl) {
|
|
83
|
+
injectedKill = impl && typeof impl.killTree === "function" ? impl.killTree : null
|
|
84
|
+
}
|
|
85
|
+
/** 撤销注入(测试与端装配生命周期用——缺省态 = 核内 killProcessTree)。 */
|
|
86
|
+
export function resetProcessTreeKill() { injectedKill = null }
|
|
87
|
+
|
|
88
|
+
/** Spawn node with the given args, capture stdout/stderr, enforce timeout/abort.
|
|
89
|
+
* Resolves { text, ok } — ok=false on non-zero exit / timeout / abort. */
|
|
90
|
+
function runNode(childArgs, baseDir, timeoutMs, signal) {
|
|
91
|
+
return new Promise((resolvePromise) => {
|
|
92
|
+
const child = spawn(process.execPath, childArgs, {
|
|
93
|
+
cwd: baseDir,
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
// POSIX detached → 子进程为组首——killProcessTree 的 -pid 组杀可达孙进程(win 用
|
|
97
|
+
// taskkill /T 不需 detached)——2026-09-05 advisor 🟡#4
|
|
98
|
+
detached: process.platform !== "win32",
|
|
99
|
+
// 双保险(2026-09-05——裸 spawn 无 signal 教训——system.mjs 同款):abort 时
|
|
100
|
+
// Node 自动杀直接子进程(第一道)——onAbort 手动 kill 兜底(SIGKILL 防信号陷阱
|
|
101
|
+
// 脚本——见 kill 注释)——AbortError 在 error 分支让路(close 必随——走 mode 收尾)
|
|
102
|
+
...(signal ? { signal } : {}),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
let outBuf = "", errBuf = "", truncated = false, settled = false, mode = null
|
|
106
|
+
let timer = null, kickTimer = null
|
|
107
|
+
|
|
108
|
+
const settle = (text, ok) => {
|
|
109
|
+
if (settled) return
|
|
110
|
+
settled = true
|
|
111
|
+
clearTimeout(timer)
|
|
112
|
+
clearTimeout(kickTimer)
|
|
113
|
+
if (signal) signal.removeEventListener("abort", onAbort)
|
|
114
|
+
resolvePromise({ text, ok })
|
|
115
|
+
}
|
|
116
|
+
// Tree kill(SIGKILL/taskkill /T——signal-trapping 脚本躲不开 watchdog;孙进程持管道
|
|
117
|
+
// 时直接 kill 不达——close 不触发拖到 kick——2026-09-05 advisor 🟡#4 对齐 bash/verify)。
|
|
118
|
+
// #57:树杀实现按端注入(缺省 = 核内 killProcessTree);注入实现抛错不阻断收尾(尽力而为)。
|
|
119
|
+
const kill = () => { try { (injectedKill ?? killProcessTree)(child) } catch { /* kill is best-effort */ } }
|
|
120
|
+
// After kill, wait for "close" (child fully reaped) before settling — settling
|
|
121
|
+
// early races the caller deleting the cwd dir while the child still holds it.
|
|
122
|
+
const armKick = () => { kickTimer = setTimeout(() => settle(mode === "abort" ? "(stopped)" : timeoutErrorText(timeoutMs), false), 3000) }
|
|
123
|
+
const onAbort = () => { if (mode) return; mode = "abort"; kill(); armKick() }
|
|
124
|
+
|
|
125
|
+
timer = setTimeout(() => { if (!mode) { mode = "timeout"; kill(); armKick() } }, timeoutMs)
|
|
126
|
+
|
|
127
|
+
if (signal) {
|
|
128
|
+
if (signal.aborted) onAbort()
|
|
129
|
+
else signal.addEventListener("abort", onAbort, { once: true })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const cap = (buf, d) => {
|
|
133
|
+
if (buf.length < MAX_OUTPUT) return buf + d
|
|
134
|
+
if (!truncated) { truncated = true; return buf + "\n...[output truncated]" }
|
|
135
|
+
return buf
|
|
136
|
+
}
|
|
137
|
+
child.stdout.on("data", (d) => { outBuf = cap(outBuf, d.toString()) })
|
|
138
|
+
child.stderr.on("data", (d) => { errBuf = cap(errBuf, d.toString()) })
|
|
139
|
+
child.on("error", (e) => {
|
|
140
|
+
// signal 双保险(2026-09-05):abort 时 Node signal option 杀子进程 → 本事件以
|
|
141
|
+
// AbortError 先触发——让路(close 必随——close 分支 mode==="abort" →
|
|
142
|
+
// settle("(stopped)"))——不把中止误报为启动失败
|
|
143
|
+
if (e.name === "AbortError") return
|
|
144
|
+
settle(`Error: failed to start node: ${e.message}`, false)
|
|
145
|
+
})
|
|
146
|
+
child.on("close", (code) => {
|
|
147
|
+
if (mode === "abort") return settle("(stopped)", false)
|
|
148
|
+
if (mode === "timeout") return settle(timeoutErrorText(timeoutMs), false)
|
|
149
|
+
const out = outBuf.trimEnd()
|
|
150
|
+
const err = errBuf.trim()
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
settle(out || "(no output)", true)
|
|
153
|
+
} else {
|
|
154
|
+
settle(err ? (out ? `${out}\n\n[stderr]:\n${err}` : err) : `${out}\n(exit code ${code})`.trim(), false)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Validate nodeArgs (extra node flags for scriptFile mode, e.g. --test / --check). Forbids
|
|
161
|
+
* eval-like flags that would conflict with scriptFile mode or re-open inline injection. */
|
|
162
|
+
function validateNodeArgs(nodeArgs) {
|
|
163
|
+
if (!nodeArgs) return []
|
|
164
|
+
const arr = Array.isArray(nodeArgs) ? nodeArgs : String(nodeArgs).split(/\s+/).filter(Boolean)
|
|
165
|
+
const forbidden = /^(--eval|-e|--input-type|--print|-p|--inspect|--inspect-brk)(=|$)/i
|
|
166
|
+
for (const a of arr) {
|
|
167
|
+
if (forbidden.test(a)) throw new Error(`nodeArgs flag not allowed: ${a}`)
|
|
168
|
+
}
|
|
169
|
+
return arr
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const executeTool = {
|
|
173
|
+
name: "execute",
|
|
174
|
+
description: DESC("execute"),
|
|
175
|
+
parameters: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
code: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "JavaScript code to execute (top-level await and dynamic import() supported). Pure node ESM — no preloaded globals; import node: modules (fs/path) yourself when needed. File reads/writes go through the dedicated read/ls/glob/grep/write/edit tools. Use this OR scriptFile.",
|
|
181
|
+
},
|
|
182
|
+
scriptFile: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "Run a .mjs/.js file with node (self-contained — the file imports what it needs). Path relative to workdir — no directory restriction. Use this OR code. For `node <script>` / `node --test <file>` / `node --check <file>`.",
|
|
185
|
+
},
|
|
186
|
+
nodeArgs: {
|
|
187
|
+
type: "array",
|
|
188
|
+
items: { type: "string" },
|
|
189
|
+
description: "(scriptFile) Extra node flags before the script, e.g. [\"--test\"], [\"--check\"]. Eval-like flags (--eval/--input-type/--inspect) are rejected.",
|
|
190
|
+
},
|
|
191
|
+
workdir: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Run in this directory (relative to cwd — no directory restriction; default cwd)",
|
|
194
|
+
},
|
|
195
|
+
filter: {
|
|
196
|
+
type: "string",
|
|
197
|
+
description: "Optional: only return output lines matching this regex (case-insensitive)",
|
|
198
|
+
},
|
|
199
|
+
timeoutMs: {
|
|
200
|
+
type: "integer",
|
|
201
|
+
minimum: 1,
|
|
202
|
+
maximum: 600000,
|
|
203
|
+
description: `Timeout in milliseconds (default ${DEFAULT_TIMEOUT}, max 600000)`,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
required: [],
|
|
207
|
+
},
|
|
208
|
+
readonly: false,
|
|
209
|
+
|
|
210
|
+
async execute(args, ctx) {
|
|
211
|
+
let baseDir
|
|
212
|
+
try { baseDir = resolveBaseDir(ctx.cwd, args.workdir) }
|
|
213
|
+
catch (e) { return `Error: ${e.message}` }
|
|
214
|
+
|
|
215
|
+
const t = Number(args.timeoutMs)
|
|
216
|
+
const timeoutMs = Number.isFinite(t) && t > 0 ? Math.min(t, 600_000) : DEFAULT_TIMEOUT
|
|
217
|
+
|
|
218
|
+
let childArgs
|
|
219
|
+
if (args.scriptFile) {
|
|
220
|
+
if (args.code?.trim()) return "Error: pass code OR scriptFile, not both"
|
|
221
|
+
// scriptFile mode: run a .mjs/.js file with node [nodeArgs...]. Self-contained —
|
|
222
|
+
// a real node process imports what it needs. No directory restriction.
|
|
223
|
+
const scriptAbs = resolve(baseDir, args.scriptFile)
|
|
224
|
+
let nodeArgs
|
|
225
|
+
try { nodeArgs = validateNodeArgs(args.nodeArgs) }
|
|
226
|
+
catch (e) { return `Error: ${e.message}` }
|
|
227
|
+
childArgs = [...nodeArgs, scriptAbs]
|
|
228
|
+
} else {
|
|
229
|
+
const code = args.code ?? ""
|
|
230
|
+
if (!code.trim()) return "Error: either code or scriptFile is required"
|
|
231
|
+
if (code.length > MAX_SCRIPT) {
|
|
232
|
+
return `Error: script too large (${code.length} > ${MAX_SCRIPT} bytes). Split into smaller scripts or use individual tools.`
|
|
233
|
+
}
|
|
234
|
+
// inline mode: pure node ESM — no prelude, nothing injected (TOOLS.md §12).
|
|
235
|
+
childArgs = ["--input-type=module", "--eval", code]
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { text, ok } = await runNode(childArgs, baseDir, timeoutMs, ctx.signal)
|
|
239
|
+
// Only filter successful output — never swallow an error report behind a filter.
|
|
240
|
+
if (!ok) return text
|
|
241
|
+
return args.filter ? applyFilter(text, args.filter) : text
|
|
242
|
+
},
|
|
243
|
+
}
|