@molecule/api-ai-tools 1.14.3 → 1.14.5

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.
Files changed (45) hide show
  1. package/README.md +243 -189
  2. package/dist/browser-probe.d.ts +4 -4
  3. package/dist/browser-probe.d.ts.map +1 -1
  4. package/dist/browser-probe.js +5 -5
  5. package/dist/command-position.d.ts +15 -16
  6. package/dist/command-position.d.ts.map +1 -1
  7. package/dist/command-position.js +21 -23
  8. package/dist/cut-run.d.ts +9 -9
  9. package/dist/cut-run.js +6 -6
  10. package/dist/failure-kind.d.ts +6 -6
  11. package/dist/failure-kind.js +6 -6
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +2 -2
  14. package/dist/inline-script.d.ts +5 -5
  15. package/dist/inline-script.js +6 -6
  16. package/dist/line-diff.d.ts +2 -3
  17. package/dist/line-diff.d.ts.map +1 -1
  18. package/dist/line-diff.js +2 -3
  19. package/dist/no-test-run.d.ts +5 -5
  20. package/dist/no-test-run.d.ts.map +1 -1
  21. package/dist/no-test-run.js +9 -8
  22. package/dist/output-filter.d.ts +2 -2
  23. package/dist/output-filter.js +2 -2
  24. package/dist/pipeline-exit-note.d.ts +1 -1
  25. package/dist/pipeline-exit-note.js +1 -1
  26. package/dist/playwright-report.d.ts +1 -1
  27. package/dist/playwright-report.js +1 -1
  28. package/dist/server-command.d.ts +2 -2
  29. package/dist/server-command.js +2 -2
  30. package/dist/sleep-peek.d.ts +1 -1
  31. package/dist/sleep-peek.js +1 -1
  32. package/dist/suite-overlap.d.ts +24 -25
  33. package/dist/suite-overlap.d.ts.map +1 -1
  34. package/dist/suite-overlap.js +41 -43
  35. package/dist/tools.d.ts +26 -28
  36. package/dist/tools.d.ts.map +1 -1
  37. package/dist/tools.js +114 -72
  38. package/dist/trace-artifacts.d.ts +73 -65
  39. package/dist/trace-artifacts.d.ts.map +1 -1
  40. package/dist/trace-artifacts.js +76 -77
  41. package/dist/types.d.ts +5 -5
  42. package/dist/utilities.d.ts +128 -92
  43. package/dist/utilities.d.ts.map +1 -1
  44. package/dist/utilities.js +298 -143
  45. 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 — X1
16
- * xr-molecule-flash-m87's stuck note suggested `kill $(cat /tmp/<id>.pid) 2>/dev/null; pkill -f
17
- * <id>.sh`, which the platform's own guard then refused, and the same shape cost a refused call at
18
- * +21:57. The pid tree is the whole of what the pattern was for: the detached shell and every
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
- * X1 flash-m36 asked for this half and the message kept saying "run a smaller unit"
40
- * for two months. It is the wrong direction for a suite, and measurably: a run
41
- * stopped here reports NO tally, so nothing in the result says which test or request
42
- * hung — the run has to be sent again in the background (off this budget, its output
43
- * written to a log that can be read mid-run) or, when a consumer knows the runner's
44
- * per-request limit, with each request bounded. xr-molecule-flash-m93 (+06:22,
45
- * 2026-10-16) lost 4.8 minutes to a Hurl run whose ONE unanswered request had hurl's
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 (X0 flash-m70: 24 such reads in 57 s, no note on
54
- * any of them). Carried as `distNote` rather than merged into `note` so a host that
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
- * (xr-molecule-flash-l07: the wait's DISPLAY was diagnosed as if it were the log).
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. X0 xr-molecule-flash-m106 (+07:58) made exactly that link, found
85
- * it dangling (everything was hoisted to `/workspace/node_modules`), and was
86
- * refused three times for trying to remove it — 40 s and a "3rd time this turn
87
- * … ask the user" escalation, in an unattended run, over a broken symlink in
88
- * the run's own scratch directory. So the guard asks what is there.
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. X0
123
- * xr-molecule-flash-m91 (+04:43) called `write_file` on
124
- * `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts` while reading
125
- * the package's declarations and clobbered its runtime module a call later
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 (m106 above). Three
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 what
148
- * m91 used, and what both guards leave alone.
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.
@@ -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;AAgS9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAudD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAiInE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AA2DlF;;;;;;;;;;;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,CAwjGxF"}
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 (X1 run
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 (the m99 class, recurring at xr-molecule-flash-m173
94
- * +17:07, +39:14). The command's shape cannot answer that question — every background launch is
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 (xr-molecule-flash-m191: a wrapper-launched run's cause reached the model 7
97
- * minutes late because nothing read the log for it).
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 (xr-molecule-flash-m208 +12:00, waiting on m207's walk).
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 — X1
244
- * xr-molecule-flash-m87's stuck note suggested `kill $(cat /tmp/<id>.pid) 2>/dev/null; pkill -f
245
- * <id>.sh`, which the platform's own guard then refused, and the same shape cost a refused call at
246
- * +21:57. The pid tree is the whole of what the pattern was for: the detached shell and every
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 (m182: 281 s on a walk that ended in 20).
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 (xr-molecule-flash-m303: 13/13 Hurl files green,
307
- * `exitCode: null`, after a wait that returned on the tick the run ended).
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. X1 xr-molecule-flash-m182 (+30:25) waited 281 s — its whole remaining ceiling — on a walk
370
- * that had ended in 20 s, and a `sleep 240; tail` on the same handle was answered "is live". The
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. X1
481
- * xr-molecule-flash-m157 (+14:02) waited out 4.5 minutes of a duplicate walk that measured nothing:
482
- * the first walk had already printed `4 failed / 65 skipped / 70 passed (4.5m)`.
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
- * X1 flash-m62 (2026-10-16) spent three 290 s waits on one 8.3-minute suite and
516
- * every answer read only "Still running after 280s": the run could not tell a
517
- * suite with 30 s left from one with 6 minutes left, so the wait cadence — not
518
- * the suite — set the pace, and the run learned nothing for its 14 minutes of
519
- * waiting. The elapsed time comes from the task id, the progress from the log
520
- * this wait already read, and the time left is the rate so far, said as such.
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
- * X1 flash-m87 §+47:20 (2026-10-16): that suite's answer read "Still running
523
- * after 342s of waiting, and its output is still growing (71839 bytes) — slow,
524
- * not stuck." and then this sentence, which opens with the same growth clause
525
- * and gives the same elapsed time in its other format — one answer, two facts,
526
- * each said twice. The clauses ARE the facts, and {@link runningNote} is the
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
- * X1 xr-molecule-flash-m105 (+08:49 → +10:53, 2026-10-17) hand-polled with `sleep N; tail …`
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
@@ -702,8 +701,8 @@ async function backgroundTaskState(backend, command, seconds, ceilingMs) {
702
701
  };
703
702
  }
704
703
  import { isPlatformProofLaunch, suiteCommandTarget } from './suite-overlap.js';
705
- import { diagnosisTraceCommand } 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';
704
+ import { diagnosisTraceCommand, namesNoRetries } from './trace-artifacts.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
- * X1 flash-m36 asked for this half and the message kept saying "run a smaller unit"
759
- * for two months. It is the wrong direction for a suite, and measurably: a run
760
- * stopped here reports NO tally, so nothing in the result says which test or request
761
- * hung — the run has to be sent again in the background (off this budget, its output
762
- * written to a log that can be read mid-run) or, when a consumer knows the runner's
763
- * per-request limit, with each request bounded. xr-molecule-flash-m93 (+06:22,
764
- * 2026-10-16) lost 4.8 minutes to a Hurl run whose ONE unanswered request had hurl's
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 read as a warning about work that had already
817
- * finished (xr-molecule-flash-m83 +53:11: a Hurl run of 25 s against a 290 s
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 (X0 flash-m70: 24 such reads in 57 s, no note on
826
- * any of them). Carried as `distNote` rather than merged into `note` so a host that
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
- * (xr-molecule-flash-l07: the wait's DISPLAY was diagnosed as if it were the log).
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. X0
912
- * xr-molecule-flash-m91 (+04:43) called `write_file` on
913
- * `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts` while reading
914
- * the package's declarations and clobbered its runtime module a call later
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 (m106 above). Three
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 what
937
- * m91 used, and what both guards leave alone.
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
- * cost flash-m98 two full re-sends of a six-replacement batch — the second bad
990
- * hunk was not reported until the first was fixed, 7 s later.
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 (xr-molecule-flash-m120 +00:29). A plain slow
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 — X0 xr-molecule-flash-m151 at
1057
- * +01:54) or the `node_modules` root above it.
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`; X1 run sol-m04).
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
- ? `${path} is outside the workspace (${root}), so it cannot be the working directory — ` +
1165
- `start the command with \`cd ${path} && \` instead.`
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 (xr-molecule-flash-m155 +09:58). A known binary extension is
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" (xr-molecule-flash-m141 +09:56, a
1461
- * one-line rewrite of `ArticlePreview.tsx`). A write that leaves the content
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
  *
@@ -2576,9 +2596,13 @@ export function buildTools(backend, config) {
2576
2596
  // the model meant answered a `list_files` instantly (xr-molecule-flash-m94).
2577
2597
  // A bare first segment (`helpers/*`) is left alone on purpose: it means "any
2578
2598
  // `helpers/` below the root" (xr-molecule-flash-m77, see literalDirPrefix).
2599
+ // An npm scope under a `node_modules` root is the exception the root enables:
2600
+ // `@molecule/app-markdown*/package.json` names `<root>/@molecule` and nothing
2601
+ // else, and un-narrowed it walked the whole dependency tree into the deadline
2602
+ // (xr-molecule-flash-l65).
2579
2603
  // Brace alternatives may each name a directory (`{src/a,src/b}/*.ts`),
2580
2604
  // so a narrowing prefix is used only when every expansion agrees on it.
2581
- const prefixes = patterns.map((p) => literalDirPrefix(p));
2605
+ const prefixes = patterns.map((p) => literalDirPrefix(p, path));
2582
2606
  const prefixDir = prefixes.every((p) => p === prefixes[0]) ? prefixes[0] : null;
2583
2607
  let findRoot = path;
2584
2608
  let findArgs = findArgsForPatterns(patterns);
@@ -2680,7 +2704,7 @@ export function buildTools(backend, config) {
2680
2704
  },
2681
2705
  async exec_command(input) {
2682
2706
  const rawCommand = input.command;
2683
- const cwd = input.cwd ? resolve(input.cwd, 'cwd') : root;
2707
+ const cwd = input.cwd ? resolveExecCwd(input.cwd, rawCommand) : root;
2684
2708
  // A model that sent the command under another name (`cmd`, `script`, …)
2685
2709
  // has already been normalized by guardToolExecute; reaching here with
2686
2710
  // nothing means there is genuinely no command. Say so in the terms that
@@ -3603,7 +3627,16 @@ export function buildTools(backend, config) {
3603
3627
  // A failure the request excludes by name is not a diagnostic to hand back — the same
3604
3628
  // matcher the finished run's causes note drops it with, so the wait keeps running and
3605
3629
  // the one yield stays available for a failure that counts (xr-molecule-flash-h13).
3606
- logShowsInlineFailureBodies(causeLog, excludedTestTitles?.() ?? [])) {
3630
+ // Only a failure the run has FINISHED with is a diagnostic: a first attempt whose retry
3631
+ // has not printed yet may still pass, and handing it back sent the builder to fix a test
3632
+ // the retry absorbed while the deployed proof went green — the edit voided that proof
3633
+ // and cost a redeploy plus a whole walk (xr-molecule-flash-l69). The yield itself stays:
3634
+ // the retry-failure shape it was written for keeps firing. A command pinned to
3635
+ // `--retries=0` has no retry to wait for, so its first failure is already final.
3636
+ logShowsInlineFailureBodies(causeLog, excludedTestTitles?.() ?? [], {
3637
+ finalOnly: true,
3638
+ noRetries: namesNoRetries(taskRecord.command),
3639
+ })) {
3607
3640
  // Completion during the log read still wins over a progress return.
3608
3641
  exitCode = await readExit();
3609
3642
  if (exitCode !== null)
@@ -3619,9 +3652,18 @@ export function buildTools(backend, config) {
3619
3652
  status: 'running',
3620
3653
  waitedMs: earlyMs,
3621
3654
  outputSoFar: sanitizeOutput(truncateMiddle(causeLog, MAX_OUTPUT_SIZE)),
3622
- note: 'A failure report is available, but this task is still running. This is partial ' +
3623
- 'log output, not a completed result. Local edits do not change this deployed ' +
3624
- 'build; leave the task running and collect its final result.',
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`.',
3625
3667
  };
3626
3668
  }
3627
3669
  // A lead: this wait goes on, and the finding rides its result as a note — the executor has