@molecule/api-ai-tools 1.14.4 → 1.14.6
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/README.md +236 -190
- package/dist/browser-probe.d.ts +4 -4
- package/dist/browser-probe.d.ts.map +1 -1
- package/dist/browser-probe.js +5 -5
- package/dist/command-position.d.ts +15 -16
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +21 -23
- package/dist/cut-run.d.ts +9 -9
- package/dist/cut-run.js +6 -6
- package/dist/failure-kind.d.ts +6 -6
- package/dist/failure-kind.js +6 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/inline-script.d.ts +5 -5
- package/dist/inline-script.js +6 -6
- package/dist/line-diff.d.ts +2 -3
- package/dist/line-diff.d.ts.map +1 -1
- package/dist/line-diff.js +2 -3
- package/dist/no-test-run.d.ts +5 -5
- package/dist/no-test-run.d.ts.map +1 -1
- package/dist/no-test-run.js +9 -8
- package/dist/output-filter.d.ts +2 -2
- package/dist/output-filter.js +2 -2
- package/dist/pipeline-exit-note.d.ts +1 -1
- package/dist/pipeline-exit-note.js +1 -1
- package/dist/playwright-report.d.ts +1 -1
- package/dist/playwright-report.js +1 -1
- package/dist/server-command.d.ts +2 -2
- package/dist/server-command.js +2 -2
- package/dist/sleep-peek.d.ts +1 -1
- package/dist/sleep-peek.js +1 -1
- package/dist/suite-overlap.d.ts +24 -25
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +41 -43
- package/dist/tools.d.ts +26 -28
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +98 -69
- package/dist/trace-artifacts.d.ts +64 -65
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +73 -75
- package/dist/types.d.ts +5 -5
- package/dist/utilities.d.ts +107 -91
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +191 -143
- package/package.json +2 -2
package/dist/tools.d.ts
CHANGED
|
@@ -12,11 +12,10 @@ import type { AITool } from '@molecule/api-ai';
|
|
|
12
12
|
*
|
|
13
13
|
* THE one builder for it, so every note that hands the executor a way to stop a run hands over the
|
|
14
14
|
* same pid-only form: a `pkill -f <something>` matches the shell running it (its argv IS the
|
|
15
|
-
* command), and `molecule-dev/api/src/ai/pattern-kill-guard.ts` holds exactly that —
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* process under it.
|
|
15
|
+
* command), and `molecule-dev/api/src/ai/pattern-kill-guard.ts` holds exactly that — a stuck note
|
|
16
|
+
* that suggested `kill $(cat /tmp/<id>.pid) 2>/dev/null; pkill -f <id>.sh` was refused by the
|
|
17
|
+
* platform's own guard, and the same shape costs a refused call every time. The pid tree is the
|
|
18
|
+
* whole of what the pattern was for: the detached shell and every process under it.
|
|
20
19
|
*
|
|
21
20
|
* @param backend - The execution backend.
|
|
22
21
|
* @param taskId - The task's `mol-bg-…` handle.
|
|
@@ -36,22 +35,21 @@ export declare const SPLIT_WORK_ADVICE: string;
|
|
|
36
35
|
* (Playwright, Vitest, a `hurl` run — {@link isSuiteRunCommand}): its files are not
|
|
37
36
|
* a bigger unit to break up.
|
|
38
37
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* 300 s default per request holding the file, ten seconds past this tool's budget.
|
|
38
|
+
* The wrong advice here is "run a smaller unit": it is the wrong direction for a
|
|
39
|
+
* suite, and measurably so — a run stopped here reports NO tally, so nothing in
|
|
40
|
+
* the result says which test or request hung. The run has to be sent again in the
|
|
41
|
+
* background (off this budget, its output written to a log that can be read
|
|
42
|
+
* mid-run) or, when a consumer knows the runner's per-request limit, with each
|
|
43
|
+
* request bounded. One Hurl run lost 4.8 minutes to a single unanswered request
|
|
44
|
+
* held by hurl's 300 s default per request, ten seconds past this tool's budget.
|
|
47
45
|
*/
|
|
48
46
|
export declare const SUITE_OVER_BUDGET_ADVICE: string;
|
|
49
47
|
/**
|
|
50
48
|
* The steer attached to a SUCCESSFUL read of a package's built output. The ENOENT
|
|
51
49
|
* redirect below fires only when the read FAILS, so a model that knows the hoisted
|
|
52
50
|
* layout reads `node_modules/@molecule/<pkg>/dist/*.js` happily and never learns the
|
|
53
|
-
* documented API is one call away (
|
|
54
|
-
*
|
|
51
|
+
* documented API is one call away (24 such reads in 57 s, with no note on any of
|
|
52
|
+
* them). Carried as `distNote` rather than merged into `note` so a host that
|
|
55
53
|
* has read_molecule_doc (molecule-dev's cadence note, which also names the functions)
|
|
56
54
|
* can supersede it without string-matching.
|
|
57
55
|
*
|
|
@@ -68,7 +66,7 @@ export declare function moleculeDistReadNote(path: string): string | null;
|
|
|
68
66
|
* the log this platform's launcher writes for it, and the redirect target is the file this
|
|
69
67
|
* process's own launch registered for that id — never a path parsed out of a command by the
|
|
70
68
|
* caller. A diagnosis that reads a running walk must not build a `/tmp` path of its own
|
|
71
|
-
* (
|
|
69
|
+
* (a wait's DISPLAY gets diagnosed as if it were the log).
|
|
72
70
|
*
|
|
73
71
|
* @param taskId - The `mol-bg-…` handle the wait returned.
|
|
74
72
|
* @returns The read command, or null when the handle is not a task id this platform minted.
|
|
@@ -81,11 +79,11 @@ export declare function registeredTaskLogCommand(taskId: string): string | null;
|
|
|
81
79
|
* @remarks
|
|
82
80
|
* A path that reads `node_modules` is not necessarily an install: a run links
|
|
83
81
|
* `node_modules` beside its e2e copy to borrow the hoisted tree, and that link
|
|
84
|
-
* is its own.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
82
|
+
* is its own. A run that made exactly that link found it dangling (everything was
|
|
83
|
+
* hoisted to `/workspace/node_modules`) and was refused three times for trying to
|
|
84
|
+
* remove it — 40 s and a "3rd time this turn … ask the user" escalation, in an
|
|
85
|
+
* unattended run, over a broken symlink in the run's own scratch directory. So the
|
|
86
|
+
* guard asks what is there.
|
|
89
87
|
*/
|
|
90
88
|
export interface InstalledPackageFacts {
|
|
91
89
|
/** Whether the `node_modules` entry the path goes through is a symbolic link. */
|
|
@@ -119,17 +117,17 @@ export declare function shellInstalledPackageProbe(run: (command: string) => Pro
|
|
|
119
117
|
*
|
|
120
118
|
* `node_modules` is not the project's code: nothing written there survives the
|
|
121
119
|
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
122
|
-
* shared (the pre-installed superset) breaks every other run through it.
|
|
123
|
-
*
|
|
124
|
-
* `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts
|
|
125
|
-
* the package's declarations
|
|
120
|
+
* shared (the pre-installed superset) breaks every other run through it. A
|
|
121
|
+
* `write_file` on
|
|
122
|
+
* `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts`, made while
|
|
123
|
+
* reading the package's declarations, clobbered its runtime module a call later
|
|
126
124
|
* (`module.exports = {}` over the ESM handler its own code imports) — the write
|
|
127
125
|
* answered `ok: true`, and 18 s went to noticing, `npm pack`-ing the package and
|
|
128
126
|
* putting the file back. Reading these files stays allowed (that is how the
|
|
129
127
|
* declarations above are read at all).
|
|
130
128
|
*
|
|
131
129
|
* What is protected is the INSTALL, not the spelling: the decision comes from
|
|
132
|
-
* {@link InstalledPackageFacts}, never from the path alone (
|
|
130
|
+
* {@link InstalledPackageFacts}, never from the path alone (see the symlink case above). Three
|
|
133
131
|
* things are the run's own to remove or replace —
|
|
134
132
|
*
|
|
135
133
|
* - a `node_modules` that is a SYMLINK: `rm`/`unlink` removes the link, never the
|
|
@@ -144,8 +142,8 @@ export declare function shellInstalledPackageProbe(run: (command: string) => Pro
|
|
|
144
142
|
* The restore route is named because the model that has ALREADY clobbered a file
|
|
145
143
|
* (this refusal is too late for it) is the one who needs it, and because the
|
|
146
144
|
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
147
|
-
* sandbox. `npm pack` + a `tar` extraction over the package directory is
|
|
148
|
-
*
|
|
145
|
+
* sandbox. `npm pack` + a `tar` extraction over the package directory is the
|
|
146
|
+
* repair both guards leave alone.
|
|
149
147
|
*
|
|
150
148
|
* @param path - An absolute path a tool is about to write.
|
|
151
149
|
* @param probe - Looks at the filesystem where the path lives.
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AA+R9C;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAwdD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkInE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AA0DlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AA2GD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAwmGxF"}
|
package/dist/tools.js
CHANGED
|
@@ -29,8 +29,7 @@ const waitedByTask = new Map();
|
|
|
29
29
|
/**
|
|
30
30
|
* Each waited task's log size at the end of its last wait. A command whose log
|
|
31
31
|
* keeps growing is slow, not stuck: a serial 139-test suite was called stuck at
|
|
32
|
-
* 560 s and the executor was told to kill it while its log still grew
|
|
33
|
-
* m21, 2026-10-15).
|
|
32
|
+
* 560 s and the executor was told to kill it while its log still grew.
|
|
34
33
|
*/
|
|
35
34
|
const logBytesByTask = new Map();
|
|
36
35
|
/** The file a background command writes its own output to, when it redirects it. */
|
|
@@ -90,11 +89,11 @@ const detachedByTask = new Set();
|
|
|
90
89
|
*
|
|
91
90
|
* The wait note may promise that each failure's own error sits under its ✘ line only when the
|
|
92
91
|
* log actually carries one: a run printing its failures only in the end-of-run summary would send
|
|
93
|
-
* the model grepping a file they were not in (
|
|
94
|
-
*
|
|
92
|
+
* the model grepping a file they were not in (a recurring class). The command's shape cannot
|
|
93
|
+
* answer that question — every background launch is
|
|
95
94
|
* given the switch, and the executor's own wrapper passes it on — so the evidence is the log, read
|
|
96
|
-
* during the wait
|
|
97
|
-
*
|
|
95
|
+
* during the wait: nothing else notices a wrapper-launched run's cause, which otherwise reaches
|
|
96
|
+
* the model minutes late.
|
|
98
97
|
*/
|
|
99
98
|
const inlineFailuresByTask = new Set();
|
|
100
99
|
/**
|
|
@@ -112,7 +111,7 @@ const runByTask = new Map();
|
|
|
112
111
|
* shared by every project this process serves, so a task is only ever read as THIS project's when
|
|
113
112
|
* this backend started it or its files are in this project's sandbox ({@link ownedTaskIds}).
|
|
114
113
|
* Without that, a sleep in one project was held for a walk running in another's sandbox, and its
|
|
115
|
-
* note carried that project's command
|
|
114
|
+
* note carried that project's command.
|
|
116
115
|
*/
|
|
117
116
|
const launchedByBackend = new WeakMap();
|
|
118
117
|
/** A task id this tool minted — the only shape a /tmp path is ever built from. */
|
|
@@ -240,11 +239,10 @@ const taskPidsCommand = (taskId) => `R=$(cat /tmp/${taskId}.pid 2>/dev/null); `
|
|
|
240
239
|
*
|
|
241
240
|
* THE one builder for it, so every note that hands the executor a way to stop a run hands over the
|
|
242
241
|
* same pid-only form: a `pkill -f <something>` matches the shell running it (its argv IS the
|
|
243
|
-
* command), and `molecule-dev/api/src/ai/pattern-kill-guard.ts` holds exactly that —
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* process under it.
|
|
242
|
+
* command), and `molecule-dev/api/src/ai/pattern-kill-guard.ts` holds exactly that — a stuck note
|
|
243
|
+
* that suggested `kill $(cat /tmp/<id>.pid) 2>/dev/null; pkill -f <id>.sh` was refused by the
|
|
244
|
+
* platform's own guard, and the same shape costs a refused call every time. The pid tree is the
|
|
245
|
+
* whole of what the pattern was for: the detached shell and every process under it.
|
|
248
246
|
*
|
|
249
247
|
* @param backend - The execution backend.
|
|
250
248
|
* @param taskId - The task's `mol-bg-…` handle.
|
|
@@ -270,7 +268,8 @@ export async function taskStopCommand(backend, taskId) {
|
|
|
270
268
|
/**
|
|
271
269
|
* How often a wait re-asks whether a task whose exit file has not appeared has ended. The exit
|
|
272
270
|
* file is read every tick; this probe is a process test plus the task's own output, and 3 s is
|
|
273
|
-
* close enough that a run which ended is never waited out (
|
|
271
|
+
* close enough that a run which ended is never waited out (281 s were once spent on a walk that
|
|
272
|
+
* ended in 20).
|
|
274
273
|
*/
|
|
275
274
|
const TASK_END_LOOK_MS = 3_000;
|
|
276
275
|
/** How much of a task's own output the end test reads — a runner's tally is its last lines. */
|
|
@@ -303,8 +302,8 @@ function exitCodeIn(raw) {
|
|
|
303
302
|
* The exit file is read in THIS shell, beside the pid test, because the two are ONE observation.
|
|
304
303
|
* Read as two round trips — a wait's tick reading the file, then this probe asking the pid — a
|
|
305
304
|
* wrapper that finished and wrote its code between them was reported "ended without writing an
|
|
306
|
-
* exit code" while its code was on disk (
|
|
307
|
-
*
|
|
305
|
+
* exit code" while its code was on disk (13/13 Hurl files green, `exitCode: null`, after a wait
|
|
306
|
+
* that returned on the tick the run ended).
|
|
308
307
|
*
|
|
309
308
|
* The pid test runs BEFORE the exit file is read, and that order is the point: the launcher writes
|
|
310
309
|
* the code and then exits, and `kill -0` answers "gone" only once the process is reaped — so a
|
|
@@ -366,8 +365,8 @@ function outputEnding(text) {
|
|
|
366
365
|
*
|
|
367
366
|
* A missing exit file alone is not "running": the launcher writes that file from the wrapper it
|
|
368
367
|
* started, and a command that backgrounds its real work leaves the file missing after the run is
|
|
369
|
-
* over.
|
|
370
|
-
*
|
|
368
|
+
* over. A wait can spend its whole remaining ceiling (281 s in one case) on a walk that had ended
|
|
369
|
+
* in 20 s, and a `sleep 240; tail` on the same handle is answered "is live". The
|
|
371
370
|
* end is read the way `molecule-dev/api/src/ai/background-tasks.ts` reads it — exit file, then
|
|
372
371
|
* the pid — plus the one signal this package has and that tracker does not: the task's own output
|
|
373
372
|
* file already holding its ending.
|
|
@@ -477,9 +476,9 @@ const FINISHED_TALLY = /\b\d+[ \t]+(?:passed|failed)[ \t]+\(\d+(?:\.\d+)?(?:ms|s
|
|
|
477
476
|
* fresh `Running N tests` header — and what the finished walk's tally said.
|
|
478
477
|
*
|
|
479
478
|
* A command that chained two suite runs starts the second walk the moment the first ends, and the
|
|
480
|
-
* wait used to read that log as one slow run ("slow, not stuck") for the whole second walk.
|
|
481
|
-
*
|
|
482
|
-
*
|
|
479
|
+
* wait used to read that log as one slow run ("slow, not stuck") for the whole second walk — 4.5
|
|
480
|
+
* minutes of a duplicate walk that measured nothing, while the first walk had already printed
|
|
481
|
+
* `4 failed / 65 skipped / 70 passed (4.5m)`.
|
|
483
482
|
*
|
|
484
483
|
* @param slice - The log slice this wait read.
|
|
485
484
|
* @returns The finished walk's tally lines, or null when the log shows one walk (or none).
|
|
@@ -512,18 +511,18 @@ function secondSuiteWalk(slice) {
|
|
|
512
511
|
* says it has got, and how much longer that implies — one fact per clause, built
|
|
513
512
|
* here so the note carrying them cannot state one of them twice.
|
|
514
513
|
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
514
|
+
* Three 290 s waits on one 8.3-minute suite can each answer only "Still running
|
|
515
|
+
* after 280s": the run cannot tell a suite with 30 s left from one with 6 minutes
|
|
516
|
+
* left, so the wait cadence — not the suite — sets the pace, and fourteen minutes
|
|
517
|
+
* of waiting learn nothing. The elapsed time comes from the task id, the progress
|
|
518
|
+
* from the log this wait already read, and the time left is the rate so far, said
|
|
519
|
+
* as such.
|
|
521
520
|
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
521
|
+
* One such answer read "Still running after 342s of waiting, and its output is
|
|
522
|
+
* still growing (71839 bytes) — slow, not stuck." and then this sentence, which
|
|
523
|
+
* opens with the same growth clause and gives the same elapsed time in its other
|
|
524
|
+
* format — one answer, two facts, each said twice. The clauses ARE the facts, and
|
|
525
|
+
* {@link runningNote} is the
|
|
527
526
|
* only place they become a sentence, so a lead the caller writes itself cannot
|
|
528
527
|
* repeat one (the way `deployOwedNote` returns one sentence rather than a join).
|
|
529
528
|
*
|
|
@@ -588,7 +587,7 @@ function runningNote(clauses) {
|
|
|
588
587
|
* command has no task of this process's to report, in which case the sleep runs as written (the
|
|
589
588
|
* tool's own budget bounds it, as before).
|
|
590
589
|
*
|
|
591
|
-
*
|
|
590
|
+
* One run hand-polled with `sleep N; tail …`
|
|
592
591
|
* three times. The state the executor wanted was always one the platform already held: two tasks
|
|
593
592
|
* had ended 0.1 s in (exit 127, and `Error: No tests found`) and the third was live at 72 of 139
|
|
594
593
|
* with failures the log already carried. So a command that opens with a long sleep is answered
|
|
@@ -703,7 +702,7 @@ async function backgroundTaskState(backend, command, seconds, ceilingMs) {
|
|
|
703
702
|
}
|
|
704
703
|
import { isPlatformProofLaunch, suiteCommandTarget } from './suite-overlap.js';
|
|
705
704
|
import { diagnosisTraceCommand, namesNoRetries } from './trace-artifacts.js';
|
|
706
|
-
import { addedRedactionMask, BRACE_GLOB_HINT, checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, earlyReturnCause, emptyCallError, expandBraceGlob, failureCausesNote, findPathArgs, findPatternArgs, fusedLine, htmlOutline, isCommandTimeout, isEnvFilePath, isOutsideRoot, isPlaywrightRunCommand, isReadOnlyInspection, isSuiteRunCommand, linesWithinSerialized, literalDirPrefix, logShowsInlineFailureBodies, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_HTML_INLINE_CHARS, MAX_OUTPUT_SIZE, MAX_READ_CALL_CHARS, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, READ_RESULT_ENVELOPE_CHARS, redactSecrets, redactSecretsInCode, repeatedSuiteRunError, repeatingFailure, repeatingFailureNote, resolvePath, RUNNING_FAILURE_LINES, runningCauseCommand, runningLogReadCommand, runningOutputSliceCommand, searchTimeoutError, serializedChars, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, suiteRunBudgetCommand, suiteRunConfigCommand, suiteRunPipelinePlacement, suiteRunPriorityLaunch, TOOL_RESULT_CHARS, truncateMiddle, universalCause, whitespaceTolerantReplace, withoutSuiteTimeout, withPlaywrightRunReport, } from './utilities.js';
|
|
705
|
+
import { addedRedactionMask, BRACE_GLOB_HINT, checkBlockedCommand, commandStartsWithCdInto, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, earlyReturnCause, emptyCallError, expandBraceGlob, failureCausesNote, findPathArgs, findPatternArgs, fusedLine, htmlOutline, isCommandTimeout, isEnvFilePath, isOutsideRoot, isPlaywrightRunCommand, isReadOnlyInspection, isSuiteRunCommand, linesWithinSerialized, literalDirPrefix, logShowsInlineFailureBodies, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_HTML_INLINE_CHARS, MAX_OUTPUT_SIZE, MAX_READ_CALL_CHARS, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, READ_RESULT_ENVELOPE_CHARS, redactSecrets, redactSecretsInCode, repeatedSuiteRunError, repeatingFailure, repeatingFailureNote, resolvePath, RUNNING_FAILURE_LINES, runningCauseCommand, runningLogReadCommand, runningOutputSliceCommand, searchTimeoutError, serializedChars, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, suiteRunBudgetCommand, suiteRunConfigCommand, suiteRunPipelinePlacement, suiteRunPriorityLaunch, TOOL_RESULT_CHARS, truncateMiddle, universalCause, whitespaceTolerantReplace, withoutSuiteTimeout, withPlaywrightRunReport, } from './utilities.js';
|
|
707
706
|
/**
|
|
708
707
|
* How long exec_command waits for a command it backgrounded only because the
|
|
709
708
|
* command asked for more than the tool's ceiling, before handing back a task id.
|
|
@@ -755,14 +754,13 @@ export const SPLIT_WORK_ADVICE = 'Run a smaller unit per command (one test file,
|
|
|
755
754
|
* (Playwright, Vitest, a `hurl` run — {@link isSuiteRunCommand}): its files are not
|
|
756
755
|
* a bigger unit to break up.
|
|
757
756
|
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
* 300 s default per request holding the file, ten seconds past this tool's budget.
|
|
757
|
+
* The wrong advice here is "run a smaller unit": it is the wrong direction for a
|
|
758
|
+
* suite, and measurably so — a run stopped here reports NO tally, so nothing in
|
|
759
|
+
* the result says which test or request hung. The run has to be sent again in the
|
|
760
|
+
* background (off this budget, its output written to a log that can be read
|
|
761
|
+
* mid-run) or, when a consumer knows the runner's per-request limit, with each
|
|
762
|
+
* request bounded. One Hurl run lost 4.8 minutes to a single unanswered request
|
|
763
|
+
* held by hurl's 300 s default per request, ten seconds past this tool's budget.
|
|
766
764
|
*/
|
|
767
765
|
export const SUITE_OVER_BUDGET_ADVICE = 'A test suite is not a bigger unit to split: stopped here it reports no tally at all, so nothing ' +
|
|
768
766
|
'in this result says which test or request never finished — run it in the background ' +
|
|
@@ -813,17 +811,16 @@ function joinFindArgs(args) {
|
|
|
813
811
|
/**
|
|
814
812
|
* The share of its budget a command must use before the "you asked for more than
|
|
815
813
|
* this tool's ceiling" note is worth attaching. Below it the clamp changed nothing
|
|
816
|
-
* about the run, and the line
|
|
817
|
-
* finished (
|
|
818
|
-
* ceiling carried the note).
|
|
814
|
+
* about the run, and the line reads as a warning about work that had already
|
|
815
|
+
* finished (a Hurl run of 25 s against a 290 s ceiling carried the note).
|
|
819
816
|
*/
|
|
820
817
|
const CEILING_NOTE_MIN_USED = 0.5;
|
|
821
818
|
/**
|
|
822
819
|
* The steer attached to a SUCCESSFUL read of a package's built output. The ENOENT
|
|
823
820
|
* redirect below fires only when the read FAILS, so a model that knows the hoisted
|
|
824
821
|
* layout reads `node_modules/@molecule/<pkg>/dist/*.js` happily and never learns the
|
|
825
|
-
* documented API is one call away (
|
|
826
|
-
*
|
|
822
|
+
* documented API is one call away (24 such reads in 57 s, with no note on any of
|
|
823
|
+
* them). Carried as `distNote` rather than merged into `note` so a host that
|
|
827
824
|
* has read_molecule_doc (molecule-dev's cadence note, which also names the functions)
|
|
828
825
|
* can supersede it without string-matching.
|
|
829
826
|
*
|
|
@@ -849,7 +846,7 @@ export function moleculeDistReadNote(path) {
|
|
|
849
846
|
* the log this platform's launcher writes for it, and the redirect target is the file this
|
|
850
847
|
* process's own launch registered for that id — never a path parsed out of a command by the
|
|
851
848
|
* caller. A diagnosis that reads a running walk must not build a `/tmp` path of its own
|
|
852
|
-
* (
|
|
849
|
+
* (a wait's DISPLAY gets diagnosed as if it were the log).
|
|
853
850
|
*
|
|
854
851
|
* @param taskId - The `mol-bg-…` handle the wait returned.
|
|
855
852
|
* @returns The read command, or null when the handle is not a task id this platform minted.
|
|
@@ -908,17 +905,17 @@ export function shellInstalledPackageProbe(run) {
|
|
|
908
905
|
*
|
|
909
906
|
* `node_modules` is not the project's code: nothing written there survives the
|
|
910
907
|
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
911
|
-
* shared (the pre-installed superset) breaks every other run through it.
|
|
912
|
-
*
|
|
913
|
-
* `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts
|
|
914
|
-
* the package's declarations
|
|
908
|
+
* shared (the pre-installed superset) breaks every other run through it. A
|
|
909
|
+
* `write_file` on
|
|
910
|
+
* `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts`, made while
|
|
911
|
+
* reading the package's declarations, clobbered its runtime module a call later
|
|
915
912
|
* (`module.exports = {}` over the ESM handler its own code imports) — the write
|
|
916
913
|
* answered `ok: true`, and 18 s went to noticing, `npm pack`-ing the package and
|
|
917
914
|
* putting the file back. Reading these files stays allowed (that is how the
|
|
918
915
|
* declarations above are read at all).
|
|
919
916
|
*
|
|
920
917
|
* What is protected is the INSTALL, not the spelling: the decision comes from
|
|
921
|
-
* {@link InstalledPackageFacts}, never from the path alone (
|
|
918
|
+
* {@link InstalledPackageFacts}, never from the path alone (see the symlink case above). Three
|
|
922
919
|
* things are the run's own to remove or replace —
|
|
923
920
|
*
|
|
924
921
|
* - a `node_modules` that is a SYMLINK: `rm`/`unlink` removes the link, never the
|
|
@@ -933,8 +930,8 @@ export function shellInstalledPackageProbe(run) {
|
|
|
933
930
|
* The restore route is named because the model that has ALREADY clobbered a file
|
|
934
931
|
* (this refusal is too late for it) is the one who needs it, and because the
|
|
935
932
|
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
936
|
-
* sandbox. `npm pack` + a `tar` extraction over the package directory is
|
|
937
|
-
*
|
|
933
|
+
* sandbox. `npm pack` + a `tar` extraction over the package directory is the
|
|
934
|
+
* repair both guards leave alone.
|
|
938
935
|
*
|
|
939
936
|
* @param path - An absolute path a tool is about to write.
|
|
940
937
|
* @param probe - Looks at the filesystem where the path lives.
|
|
@@ -986,8 +983,8 @@ const MAX_REPORTED_REPLACEMENT_FAILURES = 5;
|
|
|
986
983
|
* A batch is applied all-or-nothing, so one bad `old_string` means none of the
|
|
987
984
|
* others were applied either: they are not "the rest, which worked", they are
|
|
988
985
|
* unchecked work the model will otherwise re-send as-is. Naming one per round trip
|
|
989
|
-
*
|
|
990
|
-
*
|
|
986
|
+
* costs two full re-sends of a six-replacement batch — the second bad hunk is not
|
|
987
|
+
* reported until the first is fixed, 7 s later.
|
|
991
988
|
*
|
|
992
989
|
* @param path - The file the batch targets (nothing was written to it).
|
|
993
990
|
* @param total - How many replacements the batch carried.
|
|
@@ -1014,7 +1011,7 @@ function batchFailureError(path, total, failures) {
|
|
|
1014
1011
|
* Delays between retries of the symlink guard while a sandbox's command channel is
|
|
1015
1012
|
* still attaching: four attempts over roughly 2.6 s (plus the commands' own time),
|
|
1016
1013
|
* which covers the span a freshly claimed E2B sandbox answers `SandboxNotFoundError`
|
|
1017
|
-
* before its exec channel is usable
|
|
1014
|
+
* before its exec channel is usable. A plain slow
|
|
1018
1015
|
* answer keeps the single 300 ms retry instead — a sandbox that is gone does not
|
|
1019
1016
|
* come back inside five seconds.
|
|
1020
1017
|
*/
|
|
@@ -1053,8 +1050,8 @@ const MOLECULE_CATALOG_POINTER = 'use find_package(query) to search the @molecul
|
|
|
1053
1050
|
'read_molecule_doc(name) for one package';
|
|
1054
1051
|
/**
|
|
1055
1052
|
* Which package catalog a `list_files` path is, when it is one: the `@molecule` scope (1,032
|
|
1056
|
-
* package directories, ~53 KB of names in one plan-phase call
|
|
1057
|
-
*
|
|
1053
|
+
* package directories, ~53 KB of names in one plan-phase call) or the `node_modules` root
|
|
1054
|
+
* above it.
|
|
1058
1055
|
*
|
|
1059
1056
|
* @param path - A resolved (absolute, normalized) directory path.
|
|
1060
1057
|
* @returns `'molecule'` for the `@molecule` scope, `'packages'` for a `node_modules` root, or null.
|
|
@@ -1102,7 +1099,7 @@ export function buildTools(backend, config) {
|
|
|
1102
1099
|
* The excluded dirs for a search rooted at `path`. Inside an installed
|
|
1103
1100
|
* package the build output IS the code: excluding `dist` there returned
|
|
1104
1101
|
* nothing for `IconName` in `node_modules/@molecule/app-icons` (it lives in
|
|
1105
|
-
* `dist/types.d.ts
|
|
1102
|
+
* `dist/types.d.ts`).
|
|
1106
1103
|
*
|
|
1107
1104
|
* @param path - The resolved search root.
|
|
1108
1105
|
* @returns The dir names to skip below it.
|
|
@@ -1156,18 +1153,41 @@ export function buildTools(backend, config) {
|
|
|
1156
1153
|
* Resolve a workspace-relative path using optional path guards.
|
|
1157
1154
|
*
|
|
1158
1155
|
* @param path - Relative or absolute path requested by the tool input.
|
|
1156
|
+
* @param use - Whether the path is a file target or the command's working directory.
|
|
1159
1157
|
* @returns A normalized path honoring `pathGuards` and the backend root.
|
|
1160
1158
|
*/
|
|
1161
1159
|
function resolve(path, use = 'file') {
|
|
1162
1160
|
if (pathGuards && typeof path === 'string' && isOutsideRoot(path, root)) {
|
|
1163
1161
|
throw new ToolInputError(use === 'cwd'
|
|
1164
|
-
?
|
|
1165
|
-
`start the command with \`cd ${path} &&
|
|
1162
|
+
? `\`cwd\` must be inside the workspace (${root}); ${path} is outside it — ` +
|
|
1163
|
+
`leave \`cwd\` unset and start the command with \`cd ${path} && \`.`
|
|
1166
1164
|
: `${path} is outside the workspace (${root}). The file tools only reach files inside it — ` +
|
|
1167
1165
|
`read or list anything else with exec_command (e.g. \`cat ${path}\` or \`ls ${path}\`).`);
|
|
1168
1166
|
}
|
|
1169
1167
|
return pathGuards ? resolvePath(path, root) : path;
|
|
1170
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* The working directory an `exec_command` runs in, from its optional `cwd`.
|
|
1171
|
+
*
|
|
1172
|
+
* A `cwd` outside the workspace is refused — unless the command itself
|
|
1173
|
+
* already opens with `cd <that same directory>`, in which case the command
|
|
1174
|
+
* needs no `cwd` and runs from the root exactly as the refusal would have
|
|
1175
|
+
* advised (an executor told to start a command with a `cd` its command already started with).
|
|
1176
|
+
*
|
|
1177
|
+
* @param requested - The `cwd` the tool input carried.
|
|
1178
|
+
* @param command - The command the tool input carried.
|
|
1179
|
+
* @returns The directory to run in.
|
|
1180
|
+
*/
|
|
1181
|
+
function resolveExecCwd(requested, command) {
|
|
1182
|
+
if (pathGuards &&
|
|
1183
|
+
typeof requested === 'string' &&
|
|
1184
|
+
isOutsideRoot(requested, root) &&
|
|
1185
|
+
typeof command === 'string' &&
|
|
1186
|
+
commandStartsWithCdInto(command, requested, root)) {
|
|
1187
|
+
return root;
|
|
1188
|
+
}
|
|
1189
|
+
return resolve(requested, 'cwd');
|
|
1190
|
+
}
|
|
1171
1191
|
/**
|
|
1172
1192
|
* Resolve the path a WRITING tool was given, refusing a target inside an
|
|
1173
1193
|
* installed package ({@link installedPackageWriteRefusal}). Reads are not sent
|
|
@@ -1329,7 +1349,7 @@ export function buildTools(backend, config) {
|
|
|
1329
1349
|
* `read_file` asks the backend for a string, so a PNG comes back as UTF-8
|
|
1330
1350
|
* replacement characters and used to be handed to the model whole — 21,191
|
|
1331
1351
|
* chars of unreadable bytes it cannot look at, ~12k tokens per read, asked for
|
|
1332
|
-
* twice in one run
|
|
1352
|
+
* twice in one run. A known binary extension is
|
|
1333
1353
|
* refused by name; a file without one is caught from the bytes themselves, by
|
|
1334
1354
|
* a NUL byte or by the density of replacement characters in its first
|
|
1335
1355
|
* {@link BINARY_SAMPLE_CHARS} chars.
|
|
@@ -1457,8 +1477,8 @@ export function buildTools(backend, config) {
|
|
|
1457
1477
|
* The counts are a real line diff ({@link diffLineCount}), not the change in
|
|
1458
1478
|
* line count: replacing one line with another leaves the file the same
|
|
1459
1479
|
* length, and a length delta reported that edit as `0/0` — reading to the
|
|
1460
|
-
* model as "your write changed nothing" (
|
|
1461
|
-
*
|
|
1480
|
+
* model as "your write changed nothing" (a one-line rewrite of `ArticlePreview.tsx`).
|
|
1481
|
+
* A write that leaves the content
|
|
1462
1482
|
* identical is `unchanged` rather than a `modified` with `0/0`, so the two
|
|
1463
1483
|
* are distinguishable.
|
|
1464
1484
|
*
|
|
@@ -2684,7 +2704,7 @@ export function buildTools(backend, config) {
|
|
|
2684
2704
|
},
|
|
2685
2705
|
async exec_command(input) {
|
|
2686
2706
|
const rawCommand = input.command;
|
|
2687
|
-
const cwd = input.cwd ?
|
|
2707
|
+
const cwd = input.cwd ? resolveExecCwd(input.cwd, rawCommand) : root;
|
|
2688
2708
|
// A model that sent the command under another name (`cmd`, `script`, …)
|
|
2689
2709
|
// has already been normalized by guardToolExecute; reaching here with
|
|
2690
2710
|
// nothing means there is genuinely no command. Say so in the terms that
|
|
@@ -3632,9 +3652,18 @@ export function buildTools(backend, config) {
|
|
|
3632
3652
|
status: 'running',
|
|
3633
3653
|
waitedMs: earlyMs,
|
|
3634
3654
|
outputSoFar: sanitizeOutput(truncateMiddle(causeLog, MAX_OUTPUT_SIZE)),
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3655
|
+
// This yield fires only on a FINAL failure (the `finalOnly` read above), so the
|
|
3656
|
+
// deployed run it belongs to can no longer pass and a local edit cannot void a green
|
|
3657
|
+
// proof. The note therefore spends its words on what to do with that: start the fix
|
|
3658
|
+
// while the walk continues, and only talk to the app once the run has ended. Wording it
|
|
3659
|
+
// as patience alone cost 4 min 45 s of idle on an already-red run (xr-molecule-flash-
|
|
3660
|
+
// hl01), against ≈4.5 min saved when the same yield was acted on (l69).
|
|
3661
|
+
note: 'A test in this run has failed on its final attempt, so this deployed run will not ' +
|
|
3662
|
+
'pass. This is partial log output, not a completed result: leave the task running — ' +
|
|
3663
|
+
"its end names the run's other failures. Local edits do not change this deployed " +
|
|
3664
|
+
'build, so start on this failure now: read the failing spec and the code it drives, ' +
|
|
3665
|
+
'and fix it while the run keeps walking. Send nothing against this app until the run ' +
|
|
3666
|
+
'ends, then collect its final result with `wait_for_task`.',
|
|
3638
3667
|
};
|
|
3639
3668
|
}
|
|
3640
3669
|
// A lead: this wait goes on, and the finding rides its result as a note — the executor has
|