acuvo-code 0.2.0
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 +328 -0
- package/ENTERPRISE.md +927 -0
- package/LICENSE +120 -0
- package/README.md +1245 -0
- package/ROADMAP.md +556 -0
- package/bin/acuvo-mcp.mjs +208 -0
- package/bin/acuvo.mjs +3108 -0
- package/lib/acceptance-consent.mjs +168 -0
- package/lib/acceptance.mjs +859 -0
- package/lib/account.mjs +226 -0
- package/lib/acuvo-dir.mjs +72 -0
- package/lib/acuvo-models.mjs +141 -0
- package/lib/apply-patch.mjs +570 -0
- package/lib/ask-user.mjs +173 -0
- package/lib/audit.mjs +530 -0
- package/lib/auto-lease.mjs +174 -0
- package/lib/background.mjs +842 -0
- package/lib/best-of.mjs +334 -0
- package/lib/board.mjs +232 -0
- package/lib/breaker.mjs +93 -0
- package/lib/budget.mjs +1375 -0
- package/lib/builtin-skills.mjs +135 -0
- package/lib/cache-floor.mjs +204 -0
- package/lib/chain.mjs +303 -0
- package/lib/changed-paths.mjs +84 -0
- package/lib/chat.mjs +434 -0
- package/lib/checkpoint.mjs +637 -0
- package/lib/child-lifetime.mjs +71 -0
- package/lib/cli-args.mjs +1255 -0
- package/lib/code-review.mjs +1382 -0
- package/lib/colour.mjs +82 -0
- package/lib/command.mjs +2847 -0
- package/lib/compact.mjs +1151 -0
- package/lib/completion.mjs +515 -0
- package/lib/creative-engines.mjs +779 -0
- package/lib/db-inspect.mjs +1624 -0
- package/lib/delete.mjs +111 -0
- package/lib/design-loop.mjs +570 -0
- package/lib/diff-preview.mjs +1044 -0
- package/lib/doctor.mjs +2139 -0
- package/lib/dropped.mjs +216 -0
- package/lib/edit-diagnostics.mjs +277 -0
- package/lib/edit.mjs +460 -0
- package/lib/env-file.mjs +250 -0
- package/lib/escalate.mjs +702 -0
- package/lib/evaluate.mjs +284 -0
- package/lib/fetch-text.mjs +952 -0
- package/lib/fleet-budget.mjs +256 -0
- package/lib/gh.mjs +1536 -0
- package/lib/git.mjs +1341 -0
- package/lib/github.mjs +261 -0
- package/lib/h2.mjs +194 -0
- package/lib/handoff.mjs +417 -0
- package/lib/hooks.mjs +626 -0
- package/lib/http-probe.mjs +907 -0
- package/lib/image-director.mjs +322 -0
- package/lib/image-edit.mjs +522 -0
- package/lib/imagegen.mjs +998 -0
- package/lib/interrupt.mjs +234 -0
- package/lib/learned.mjs +353 -0
- package/lib/lease-watch.mjs +115 -0
- package/lib/lease.mjs +868 -0
- package/lib/localize.mjs +834 -0
- package/lib/log-tail.mjs +1052 -0
- package/lib/login.mjs +157 -0
- package/lib/lsp.mjs +1613 -0
- package/lib/mcp-consent.mjs +377 -0
- package/lib/mcp-defaults.mjs +780 -0
- package/lib/mcp-server.mjs +1343 -0
- package/lib/mcp.mjs +1263 -0
- package/lib/media.mjs +1283 -0
- package/lib/memory-workspace.mjs +179 -0
- package/lib/model-json.mjs +132 -0
- package/lib/model-tier.mjs +171 -0
- package/lib/model.mjs +1445 -0
- package/lib/parallel.mjs +144 -0
- package/lib/perchance.mjs +210 -0
- package/lib/plan-coherence.mjs +1461 -0
- package/lib/plan-ledger.mjs +981 -0
- package/lib/plan.mjs +461 -0
- package/lib/policy.mjs +783 -0
- package/lib/prefix-order.mjs +38 -0
- package/lib/project-memory.mjs +127 -0
- package/lib/prompt.mjs +109 -0
- package/lib/python.mjs +862 -0
- package/lib/rcfile.mjs +853 -0
- package/lib/read-window.mjs +743 -0
- package/lib/refute-tools.mjs +34 -0
- package/lib/refute.mjs +806 -0
- package/lib/repl-driver.mjs +264 -0
- package/lib/repl.mjs +324 -0
- package/lib/replay.mjs +1218 -0
- package/lib/repo-map.mjs +1101 -0
- package/lib/report.mjs +419 -0
- package/lib/search-rank.mjs +99 -0
- package/lib/search.mjs +659 -0
- package/lib/secret-paths.mjs +54 -0
- package/lib/session.mjs +1017 -0
- package/lib/skills.mjs +703 -0
- package/lib/slash.mjs +356 -0
- package/lib/spawn-argv.mjs +1151 -0
- package/lib/spend.mjs +250 -0
- package/lib/steer.mjs +280 -0
- package/lib/stream.mjs +253 -0
- package/lib/stuck.mjs +712 -0
- package/lib/subagent.mjs +749 -0
- package/lib/terminal-graphics.mjs +171 -0
- package/lib/tool-prefix.mjs +226 -0
- package/lib/tool-shortlist.mjs +162 -0
- package/lib/tools.mjs +2333 -0
- package/lib/tsserver.mjs +423 -0
- package/lib/turn.mjs +5672 -0
- package/lib/untrusted-block.mjs +271 -0
- package/lib/verify-claim.mjs +299 -0
- package/lib/vision.mjs +330 -0
- package/lib/voice-task.mjs +561 -0
- package/lib/warm-provider.mjs +255 -0
- package/lib/websearch.mjs +401 -0
- package/lib/workspace.mjs +928 -0
- package/lib/write-approval.mjs +235 -0
- package/lib/write-many.mjs +162 -0
- package/package.json +62 -0
- package/scripts/bundle.mjs +768 -0
- package/scripts/cache-floor.mjs +176 -0
- package/scripts/machine.mjs +226 -0
- package/scripts/test.mjs +139 -0
- package/skills/accessibility.md +87 -0
- package/skills/acuvo-design-system.md +123 -0
- package/skills/animation.md +84 -0
- package/skills/api-design.md +82 -0
- package/skills/auth-and-sessions.md +78 -0
- package/skills/build-with-a-framework.md +101 -0
- package/skills/colour-and-contrast.md +112 -0
- package/skills/creative-engines.md +81 -0
- package/skills/css-layout.md +85 -0
- package/skills/data-and-charts.md +77 -0
- package/skills/debugging.md +76 -0
- package/skills/designing-by-looking.md +84 -0
- package/skills/error-handling.md +78 -0
- package/skills/forms-and-validation.md +93 -0
- package/skills/nextjs-app-router.md +75 -0
- package/skills/page-composition.md +103 -0
- package/skills/performance.md +77 -0
- package/skills/plan-before-building.md +52 -0
- package/skills/planning-and-delegating.md +72 -0
- package/skills/refactoring.md +70 -0
- package/skills/security-basics.md +76 -0
- package/skills/state-management.md +73 -0
- package/skills/supabase-multitenant.md +72 -0
- package/skills/typescript-strict.md +90 -0
- package/skills/typography.md +135 -0
- package/skills/verify-your-own-work.md +62 -0
- package/skills/web-app-quality.md +62 -0
- package/skills/working-in-the-background.md +64 -0
package/lib/evaluate.mjs
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ── ⭐⭐ THE ROUND TAX, REMOVED STRUCTURALLY ─────────────────────────────────
|
|
3
|
+
*
|
|
4
|
+
* Measured across five consecutive live runs and then again on the bench: the
|
|
5
|
+
* model reaches for `node -e "import('./x.js').then(m => console.log(...))"` to
|
|
6
|
+
* check a value. The double quotes die on `command.mjs`'s character whitelist,
|
|
7
|
+
* the round is spent, the refusal is read, and the NEXT round writes a temp file
|
|
8
|
+
* and runs that — which is what it should have done first.
|
|
9
|
+
*
|
|
10
|
+
* That is a ~20% tax on a five-round budget, and on the `git` bench task it was
|
|
11
|
+
* the difference between committing and running out of rounds.
|
|
12
|
+
*
|
|
13
|
+
* ── ⚠️ I ALREADY TRIED THE PROMPT FIX AND IT DID NOT HOLD ───────────────────
|
|
14
|
+
* An explicit rule was added naming the refusal and saying to write a file
|
|
15
|
+
* instead. It still happened on the very next run. That is the third time in
|
|
16
|
+
* this project a prompt rule has been obeyed narrowly or not at all, and the
|
|
17
|
+
* lesson is consistent: **when a model keeps reaching for something, give it the
|
|
18
|
+
* thing rather than another sentence telling it not to.**
|
|
19
|
+
*
|
|
20
|
+
* ⭐ So `evaluate` is exactly what `node -e` is for — run a snippet, see the
|
|
21
|
+
* value — with the quoting problem removed because the source arrives as a JSON
|
|
22
|
+
* string argument instead of inside a shell-shaped command line.
|
|
23
|
+
*
|
|
24
|
+
* ── ⚠️ WHY THIS DOES NOT WIDEN THE SECURITY BOUNDARY ────────────────────────
|
|
25
|
+
* It cannot do anything `write_file` + `run_command` could not already do in two
|
|
26
|
+
* calls, which is the honest test for any new capability. It writes into the
|
|
27
|
+
* workspace through the SAME path rules, runs through the SAME bounded spawn
|
|
28
|
+
* with the SAME scrubbed environment, and removes the file afterwards.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ `command.mjs` still refuses `node --eval` and that refusal STAYS. The
|
|
31
|
+
* objection there was never "evaluating is dangerous" — it was that code passed
|
|
32
|
+
* on a command line is never written to disk, so nothing it did can be reviewed
|
|
33
|
+
* afterwards. Here the snippet IS a file for the duration of the run, and it is
|
|
34
|
+
* echoed in the result, so the audit trail the flag protects is intact.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { unlinkSync, readdirSync, lstatSync } from 'node:fs';
|
|
38
|
+
import { join } from 'node:path';
|
|
39
|
+
import { spawnBounded, scrubEnvironment, childEnvironment, clampOutput, DEFAULT_COMMAND_TIMEOUT_MS } from './command.mjs';
|
|
40
|
+
import { resolveInWorkspace } from './workspace.mjs';
|
|
41
|
+
|
|
42
|
+
/** A snippet longer than this is a program, and programs get written properly. */
|
|
43
|
+
export const MAX_SNIPPET_CHARS = 4_000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* ── ⚠️ THE `finally` BELOW DOES NOT RUN WHEN THE PROCESS IS KILLED ──────────
|
|
47
|
+
*
|
|
48
|
+
* `turn.mjs` installs `process.on('SIGINT', () => { …; process.exit(130); })`
|
|
49
|
+
* so that Ctrl-C during an interactive session exits deliberately. That exit
|
|
50
|
+
* tears the process down mid-await, and a `finally` in a suspended async
|
|
51
|
+
* function is simply never reached. Measured, against the code as it stood:
|
|
52
|
+
*
|
|
53
|
+
* driver exit code : 130
|
|
54
|
+
* reached finally? : false
|
|
55
|
+
* workspace root : [ '.acuvo-eval-25072-1786406877209.mjs' ]
|
|
56
|
+
*
|
|
57
|
+
* The scratch file is at the WORKSPACE ROOT — the user's repo. It dirties
|
|
58
|
+
* `git status` and it is exactly the kind of file that ends up committed.
|
|
59
|
+
*
|
|
60
|
+
* ⭐ `process.exit()` DOES fire the `exit` event, even though a raw signal does
|
|
61
|
+
* not (turn.mjs measured that separately and relies on the same fact). So one
|
|
62
|
+
* `exit` hook over a registry of in-flight snippets closes the interrupt path,
|
|
63
|
+
* and it is the same shape turn.mjs already uses for MCP connections.
|
|
64
|
+
*/
|
|
65
|
+
const pendingSnippets = new Set();
|
|
66
|
+
let exitHookInstalled = false;
|
|
67
|
+
|
|
68
|
+
/** Remove every snippet still in flight. Synchronous — an `exit` handler cannot await. */
|
|
69
|
+
export function flushPendingSnippets() {
|
|
70
|
+
for (const abs of [...pendingSnippets]) {
|
|
71
|
+
try { unlinkSync(abs); } catch { /* already gone, or never landed */ }
|
|
72
|
+
pendingSnippets.delete(abs);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function installExitHook() {
|
|
77
|
+
// ⚠️ ONCE, not per call. `evaluate` runs many times per session and a
|
|
78
|
+
// listener per call is how you get MaxListenersExceededWarning printed at a
|
|
79
|
+
// user mid-conversation — the exact bug turn.mjs's registry was written for.
|
|
80
|
+
if (exitHookInstalled) return;
|
|
81
|
+
exitHookInstalled = true;
|
|
82
|
+
process.once('exit', flushPendingSnippets);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ── ⚠️⚠️ REAPING IS DELETING SOMEONE ELSE'S FILES ───────────────────────────
|
|
87
|
+
*
|
|
88
|
+
* `exit` covers an interrupt, but not a SIGKILL, a crashed terminal or a power
|
|
89
|
+
* cut — and those leave the same litter. The next run sweeps it, which means
|
|
90
|
+
* this code deletes files in a repo it did not write. So it is bounded three
|
|
91
|
+
* ways, ALL of which must hold:
|
|
92
|
+
*
|
|
93
|
+
* 1. the name matches the generated pattern EXACTLY — `.acuvo-eval-<digits>-
|
|
94
|
+
* <digits>.mjs` and nothing else. Not `.acuvo-eval-notes.mjs`, not a
|
|
95
|
+
* `.bak`, not the same name without the leading dot;
|
|
96
|
+
* 2. it is a plain FILE in the workspace ROOT. Never a directory, never a
|
|
97
|
+
* symlink, and this never recurses — a match one level down is untouched;
|
|
98
|
+
* 3. it is OLDER than the threshold. A snippet from a live run in another
|
|
99
|
+
* terminal must survive, and since `spawnBounded` is capped at 120s a
|
|
100
|
+
* live snippet can never approach five minutes.
|
|
101
|
+
*
|
|
102
|
+
* It also skips anything this process still has in flight, and every failure is
|
|
103
|
+
* swallowed: a reaper that can break `evaluate` is not worth having.
|
|
104
|
+
*/
|
|
105
|
+
export const STALE_SNIPPET_MS = 5 * 60_000;
|
|
106
|
+
|
|
107
|
+
/** The generated name, and only the generated name. */
|
|
108
|
+
export const SNIPPET_NAME_PATTERN = /^\.acuvo-eval-\d+-\d+\.mjs$/;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {string} root workspace root — never recursed into
|
|
112
|
+
* @returns {string[]} the names actually removed
|
|
113
|
+
*/
|
|
114
|
+
export function reapStaleSnippets(root, { now = Date.now(), olderThanMs = STALE_SNIPPET_MS } = {}) {
|
|
115
|
+
const reaped = [];
|
|
116
|
+
if (!root || typeof root !== 'string') return reaped;
|
|
117
|
+
|
|
118
|
+
let entries;
|
|
119
|
+
try {
|
|
120
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
121
|
+
} catch {
|
|
122
|
+
return reaped; // missing or unreadable root is not this function's problem
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (!SNIPPET_NAME_PATTERN.test(entry.name)) continue;
|
|
127
|
+
if (!entry.isFile()) continue; // a directory or symlink wearing the name
|
|
128
|
+
const abs = join(root, entry.name);
|
|
129
|
+
if (pendingSnippets.has(abs)) continue; // ours, and still running
|
|
130
|
+
try {
|
|
131
|
+
const st = lstatSync(abs); // belt and braces: never follow a link
|
|
132
|
+
if (!st.isFile()) continue;
|
|
133
|
+
if (now - st.mtimeMs < olderThanMs) continue; // could belong to a live run
|
|
134
|
+
unlinkSync(abs);
|
|
135
|
+
reaped.push(entry.name);
|
|
136
|
+
} catch { /* raced with someone else, or not ours to delete */ }
|
|
137
|
+
}
|
|
138
|
+
return reaped;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* ── ⚠️ AT THE WORKSPACE ROOT, AND THE FIRST VERSION WAS NOT ─────────────────
|
|
143
|
+
*
|
|
144
|
+
* I staged this in `.acuvo/` and the tool description promised "relative
|
|
145
|
+
* imports work". They did not. Node resolves a relative specifier against the
|
|
146
|
+
* IMPORTING FILE, so `./src/math.mjs` from `.acuvo/eval-*.mjs` looked for
|
|
147
|
+
* `.acuvo/src/math.mjs`:
|
|
148
|
+
*
|
|
149
|
+
* Error [ERR_MODULE_NOT_FOUND]: Cannot find module …/.acuvo/src/math.mjs
|
|
150
|
+
*
|
|
151
|
+
* ⭐ Found by RUNNING it, not by a test — the unit tests stubbed the executor
|
|
152
|
+
* and never resolved a real import. The description was the bug: it made a
|
|
153
|
+
* promise about module resolution that the file's location contradicted, and a
|
|
154
|
+
* model reading it would have written correct code and been told it was wrong.
|
|
155
|
+
*
|
|
156
|
+
* So the snippet lives at the ROOT, where `./src/x.mjs` means what the model
|
|
157
|
+
* thinks it means. The leading dot and the pid/timestamp keep it out of the way,
|
|
158
|
+
* and the `finally` below removes it either way.
|
|
159
|
+
*/
|
|
160
|
+
function snippetPath() {
|
|
161
|
+
return `.acuvo-eval-${process.pid}-${Date.now()}.mjs`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Run a JavaScript snippet in the workspace and return what it printed.
|
|
166
|
+
*
|
|
167
|
+
* @param {{ executor: any, source: unknown, timeoutMs?: number, spawnImpl?: Function }} args
|
|
168
|
+
*/
|
|
169
|
+
export async function evaluateSnippet({ executor, source, timeoutMs = DEFAULT_COMMAND_TIMEOUT_MS, spawnImpl }) {
|
|
170
|
+
if (executor.dryRun) {
|
|
171
|
+
return { ok: false, error: 'this is a --dry-run, so nothing is executed (evaluate writes a file and runs it)' };
|
|
172
|
+
}
|
|
173
|
+
if (typeof source !== 'string' || !source.trim()) {
|
|
174
|
+
return { ok: false, error: 'source is required — the JavaScript to run' };
|
|
175
|
+
}
|
|
176
|
+
if (source.length > MAX_SNIPPET_CHARS) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
error: `the snippet is ${source.length} characters, over the ${MAX_SNIPPET_CHARS} limit — write it to a real file and run that instead`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Sweep anything a previous run was killed too hard to clean up. Bounded and
|
|
184
|
+
// fail-safe (see reapStaleSnippets); it runs AFTER the dry-run guard above,
|
|
185
|
+
// because a dry run must not delete anything either.
|
|
186
|
+
try { reapStaleSnippets(executor.root); } catch { /* never break evaluate over litter */ }
|
|
187
|
+
|
|
188
|
+
const rel = snippetPath();
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* ⚠️ RESOLVED BEFORE IT IS WRITTEN. The old order wrote the file first and
|
|
192
|
+
* resolved second, so a resolve failure left a staged file with no path to
|
|
193
|
+
* unlink it by — a leak on the one path that was supposed to be the safe one.
|
|
194
|
+
*/
|
|
195
|
+
const target = resolveInWorkspace(executor.root, rel, 'read');
|
|
196
|
+
if (!target.ok) return { ok: false, error: target.reason };
|
|
197
|
+
|
|
198
|
+
// Registered BEFORE the write, so even a crash mid-write is covered.
|
|
199
|
+
pendingSnippets.add(target.absolute);
|
|
200
|
+
installExitHook();
|
|
201
|
+
|
|
202
|
+
const written = executor.writeFile(rel, source);
|
|
203
|
+
if (!written.ok) {
|
|
204
|
+
pendingSnippets.delete(target.absolute);
|
|
205
|
+
try { unlinkSync(target.absolute); } catch { /* never landed */ }
|
|
206
|
+
return { ok: false, error: `could not stage the snippet: ${written.error}` };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const run = await spawnBounded({
|
|
211
|
+
file: process.execPath,
|
|
212
|
+
args: [target.absolute],
|
|
213
|
+
cwd: executor.root,
|
|
214
|
+
timeoutMs: Math.min(Math.max(1_000, timeoutMs), 120_000),
|
|
215
|
+
spawnImpl,
|
|
216
|
+
/**
|
|
217
|
+
* ⚠️ THIS IS THE `node <a file the model wrote>` ROAD IN ITS PUREST FORM —
|
|
218
|
+
* the snippet is staged and node is spawned on it. So it is the first
|
|
219
|
+
* place an install launched from inside model-written code would reach
|
|
220
|
+
* npm with lifecycle scripts ENABLED, while the gated `npm install` road
|
|
221
|
+
* had them off. `childEnvironment` closes that asymmetry; it never
|
|
222
|
+
* closes the road, and `validateInstallSpec`'s paragraph says so plainly.
|
|
223
|
+
*/
|
|
224
|
+
env: childEnvironment({ file: process.execPath, args: [target.absolute] }, process.env),
|
|
225
|
+
});
|
|
226
|
+
if (!run.ok) return { ok: false, error: run.error };
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
// ⭐ ECHOED BACK. The snippet is gone from disk by the time the model reads
|
|
231
|
+
// this, and a result whose cause cannot be inspected is how a wrong
|
|
232
|
+
// conclusion gets built on. It is also what keeps the --eval audit
|
|
233
|
+
// argument satisfied.
|
|
234
|
+
source: clampOutput(source, 1_500).text,
|
|
235
|
+
exitCode: run.exitCode,
|
|
236
|
+
timedOut: run.timedOut,
|
|
237
|
+
durationMs: run.durationMs,
|
|
238
|
+
stdout: run.stdout,
|
|
239
|
+
stderr: run.stderr,
|
|
240
|
+
/**
|
|
241
|
+
* ⚠️ `ok` MEANS IT RAN, `passed` MEANS IT SUCCEEDED. The same distinction
|
|
242
|
+
* run_command makes, for the same reason: conflating them is exactly how a
|
|
243
|
+
* loop reports success on a throwing snippet.
|
|
244
|
+
*/
|
|
245
|
+
passed: run.exitCode === 0 && !run.timedOut,
|
|
246
|
+
};
|
|
247
|
+
} finally {
|
|
248
|
+
/**
|
|
249
|
+
* ⚠️ REMOVED IN A `finally`, INCLUDING ON TIMEOUT. A scratch file left in
|
|
250
|
+
* someone's repo is the thing that ends up committed — this project already
|
|
251
|
+
* watched the agent burn four rounds unable to delete one, and watched
|
|
252
|
+
* `git status` go dirty because of it.
|
|
253
|
+
*/
|
|
254
|
+
try { unlinkSync(target.absolute); } catch { /* already gone, or never landed */ }
|
|
255
|
+
pendingSnippets.delete(target.absolute);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function evaluateToolSchema() {
|
|
260
|
+
return {
|
|
261
|
+
type: 'function',
|
|
262
|
+
function: {
|
|
263
|
+
name: 'evaluate',
|
|
264
|
+
description: [
|
|
265
|
+
'Run a short piece of JavaScript in the workspace and see what it prints.',
|
|
266
|
+
'USE THIS INSTEAD OF `node -e` — that is refused, because a command line here cannot',
|
|
267
|
+
'contain quotes. This is the supported way to check a value, import one of your own',
|
|
268
|
+
'modules and call it, or verify a function returns what you expect.',
|
|
269
|
+
'The snippet runs as an ES module from the workspace root, so relative imports work',
|
|
270
|
+
'(e.g. `import { sum } from "./src/math.mjs"`). It is deleted afterwards.',
|
|
271
|
+
].join(' '),
|
|
272
|
+
parameters: {
|
|
273
|
+
type: 'object',
|
|
274
|
+
properties: {
|
|
275
|
+
source: {
|
|
276
|
+
type: 'string',
|
|
277
|
+
description: 'The JavaScript to run. Use console.log to see anything.',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
required: ['source'],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|