@molecule/api-ai-tools 1.10.8 → 1.10.9

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 CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-20T08:52:42.570Z
6
+ Generated: 2026-10-20T13:43:44.255Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -449,14 +449,14 @@ interface TraceRequest {
449
449
  /**
450
450
  * The response status as the trace recorded it. Playwright writes `-1` when a request was never
451
451
  * answered (its HAR entry starts at `-1` and only a response event overwrites it); `0` is the
452
- * same absence when the snapshot carried no response at all. Neither is a server failure.
452
+ * same absence when the snapshot carried no response at all. Neither alone proves a server failure.
453
453
  */
454
454
  status: number
455
455
  /**
456
456
  * Playwright's own error text for a request that failed WITHOUT a response
457
457
  * (`net::ERR_CONNECTION_REFUSED`) — a real failure. A request that got no response and recorded
458
- * no text was aborted or still in flight when the test ended (the page closed under it), so it
459
- * says nothing about the server (xr-molecule-flash-m112 +30:45).
458
+ * no text may still have been in flight when recording ended. Missing text or an explicit
459
+ * cancellation cannot establish either a server failure or server health.
460
460
  */
461
461
  failure?: string
462
462
  /**
@@ -1562,6 +1562,19 @@ function durationSeconds(word: string): number | null
1562
1562
 
1563
1563
  **Returns:** The seconds, or null when the word is not a duration.
1564
1564
 
1565
+ #### `endsInBackground(command)`
1566
+
1567
+ Whether the outer shell's final statement starts a background job.
1568
+ Inner shell strings have their own job tables and do not count.
1569
+
1570
+ ```typescript
1571
+ function endsInBackground(command: string): boolean
1572
+ ```
1573
+
1574
+ - `command` — The shell command.
1575
+
1576
+ **Returns:** Whether its final statement ends with the shell's `&` operator.
1577
+
1565
1578
  #### `commandSegments(command)`
1566
1579
 
1567
1580
  The segments of a command, each with its program and arguments — the answer every classifier
@@ -2568,11 +2581,12 @@ function isStaticAssetRequest(request: TraceRequest): boolean
2568
2581
 
2569
2582
  #### `failedRequest(request)`
2570
2583
 
2571
- Whether a trace recorded a request the server actually failed: a 4xx/5xx, or a network error.
2584
+ Whether a trace recorded a failed response or a request error other than cancellation.
2572
2585
 
2573
2586
  A 404 on a static asset is excepted: `/sw.js` and `/fonts/*` 404 on an app that never shipped
2574
2587
  them, and naming one as a failure sent m125 into writing the caching worker it had asked about.
2575
- A 5xx on an asset, or a network error, still counts — the server did fail.
2588
+ A 5xx on an asset, or a network error, still counts. A request error alone does not locate
2589
+ the cause in the server: the browser or network may have rejected the request.
2576
2590
 
2577
2591
  ```typescript
2578
2592
  function failedRequest(request: TraceRequest): boolean
@@ -2584,9 +2598,9 @@ function failedRequest(request: TraceRequest): boolean
2584
2598
 
2585
2599
  #### `unansweredRequest(request)`
2586
2600
 
2587
- Whether a trace recorded NO response and no reason for it — a request that was aborted or still
2588
- in flight when the test ended. It is not evidence that the server failed (m112 +30:45), and one
2589
- the page shell made is not evidence about the step at all (m125 +29:33).
2601
+ Whether a trace recorded no response and no error other than cancellation. The request may
2602
+ have been pending or cancelled when recording ended; the trace does not establish whether
2603
+ the endpoint stalled. A shell request is not evidence about the step (m125 +29:33).
2590
2604
 
2591
2605
  ```typescript
2592
2606
  function unansweredRequest(request: TraceRequest): boolean
@@ -2594,7 +2608,7 @@ function unansweredRequest(request: TraceRequest): boolean
2594
2608
 
2595
2609
  - `request` — One request a trace recorded.
2596
2610
 
2597
- **Returns:** True when the request is still in flight and worth naming.
2611
+ **Returns:** True when the request's missing response is inconclusive and worth naming.
2598
2612
 
2599
2613
  #### `stalledDocumentSubresource(request)`
2600
2614
 
@@ -58,6 +58,14 @@ export interface CommandSegment {
58
58
  * @returns The seconds, or null when the word is not a duration.
59
59
  */
60
60
  export declare function durationSeconds(word: string): number | null;
61
+ /**
62
+ * Whether the outer shell's final statement starts a background job.
63
+ * Inner shell strings have their own job tables and do not count.
64
+ *
65
+ * @param command - The shell command.
66
+ * @returns Whether its final statement ends with the shell's `&` operator.
67
+ */
68
+ export declare function endsInBackground(command: string): boolean;
61
69
  /**
62
70
  * The segments of a command, each with its program and arguments — the answer every classifier
63
71
  * that reads a command as "this runs the suite" is asking for.
@@ -1 +1 @@
1
- {"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AA6qBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AA4GD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
1
+ {"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAikBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AA8GD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AA4GD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
@@ -863,6 +863,16 @@ function splitSegments(command) {
863
863
  }
864
864
  return out;
865
865
  }
866
+ /**
867
+ * Whether the outer shell's final statement starts a background job.
868
+ * Inner shell strings have their own job tables and do not count.
869
+ *
870
+ * @param command - The shell command.
871
+ * @returns Whether its final statement ends with the shell's `&` operator.
872
+ */
873
+ export function endsInBackground(command) {
874
+ return splitSegments(command).at(-1)?.after === '&';
875
+ }
866
876
  /** A redirect's target word: a quoted one keeps its spaces (`tee 'out file.txt'`). */
867
877
  const REDIRECT_TARGET_WORD = String.raw `(?:'[^']*'|"[^"]*"|[^\s;&|]+)`;
868
878
  /**
@@ -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;AAgO9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAkaD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA8HnE;;;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;;;;;;;;;;;;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,CAm4FxF"}
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;AA4O9C;;;;;;;;;;;;;;;;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;AA8HnE;;;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;;;;;;;;;;;;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,CAi5FxF"}
package/dist/tools.js CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { posix } from 'path';
10
10
  import { browserProbeOverBudgetAdvice, browserProbeRefusal } from './browser-probe.js';
11
+ import { endsInBackground } from './command-position.js';
11
12
  import { commandCutRun, CUT_AT_BUDGET, cutRunSentence, suiteRunExitCode, suiteSummary, } from './cut-run.js';
12
13
  import { addChangeSpan, changedSpan, countLines, renderEditExcerpt, renderSmallFile, } from './edit-excerpt.js';
13
14
  import { INLINE_SCRIPT_BUDGET_MS, inlineLocalScriptCommand, inlineScriptRunawaySentence, } from './inline-script.js';
@@ -33,6 +34,16 @@ const waitedByTask = new Map();
33
34
  const logBytesByTask = new Map();
34
35
  /** The file a background command writes its own output to, when it redirects it. */
35
36
  const outputFileByTask = new Map();
37
+ /**
38
+ * Recovered legacy task ids whose command ENDS with a backgrounded statement (`… &`).
39
+ *
40
+ * Such a launch returns the status of the backgrounding operation itself — a launch
41
+ * acknowledgement — and its wrapper is expected to be gone while the run it started still walks, so
42
+ * neither the wrapper's code nor its death says the run ended ({@link taskEndState}). The run's own
43
+ * output is the only ending that counts. New tasks append a shell `wait` and record the actual
44
+ * child's status; only scripts recovered from earlier versions need this compatibility path.
45
+ */
46
+ const detachedByTask = new Set();
36
47
  /**
37
48
  * Task ids whose log has SHOWN a failure's own body under a `✘` row — the inline failure switch's
38
49
  * effect, read from the log itself ({@link logShowsInlineFailureBodies}).
@@ -225,22 +236,59 @@ const TASK_END_LOOK_MS = 3_000;
225
236
  /** How much of a task's own output the end test reads — a runner's tally is its last lines. */
226
237
  const TASK_OUTPUT_END_BYTES = 4_000;
227
238
  /**
228
- * The shell probe behind {@link taskEndState}: the pid the launcher wrote, and the end of the
229
- * file the command redirects its own output to.
239
+ * The verdict an exit file holds, or null when it holds none.
240
+ *
241
+ * The launcher writes the file as `echo $? > <exitFile>`, which opens it and then writes the code
242
+ * — so a reader can catch it EMPTY in between, and a wrapper killed at that instant leaves it
243
+ * empty for good. Neither is a verdict. Reading it as 0 (what this used to do) hands back success
244
+ * for a command that may have failed — the same fabrication `molecule-dev/api/src/ai/
245
+ * background-tasks.ts` refuses when it treats such a read as "no exit file yet".
246
+ *
247
+ * @param raw - The exit file's content as read.
248
+ * @returns The exit code it records, or null when it records none.
249
+ */
250
+ function exitCodeIn(raw) {
251
+ const trimmed = raw.trim();
252
+ return /^\d+$/.test(trimmed) ? Number.parseInt(trimmed, 10) : null;
253
+ }
254
+ /**
255
+ * The shell probe behind {@link taskEndState}: the code the launcher wrote, the pid it wrote, and
256
+ * the end of the file the command redirects its own output to.
230
257
  *
231
258
  * The pid test is the one `molecule-dev/api/src/ai/background-tasks.ts` applies in `liveness` —
232
259
  * a written exit code ends the task, a live pid keeps it running, and a handle the sandbox cannot
233
260
  * account for stays unknown — so this package and that one cannot disagree about whether a task
234
261
  * is still walking.
235
262
  *
263
+ * The exit file is read in THIS shell, beside the pid test, because the two are ONE observation.
264
+ * Read as two round trips — a wait's tick reading the file, then this probe asking the pid — a
265
+ * wrapper that finished and wrote its code between them was reported "ended without writing an
266
+ * exit code" while its code was on disk (xr-molecule-flash-m303: 13/13 Hurl files green,
267
+ * `exitCode: null`, after a wait that returned on the tick the run ended).
268
+ *
269
+ * The pid test runs BEFORE the exit file is read, and that order is the point: the launcher writes
270
+ * the code and then exits, and `kill -0` answers "gone" only once the process is reaped — so a
271
+ * "gone" answer means the write has already happened, and the read that follows cannot miss it.
272
+ * Read first, the same shell would still have a window between the two statements.
273
+ *
274
+ * The first TWO lines are always the two headers, in this order, and the exit header is always
275
+ * there (`unknown` when the file holds no code yet). {@link taskEndState} reads those positions
276
+ * and nothing else, so a run whose own output prints `MOL_TASK_EXIT 0` cannot have that line
277
+ * mistaken for a written code — the bytes a command can produce start at the third line.
278
+ *
236
279
  * @param taskId - The task's `mol-bg-…` handle (format already checked by the caller).
237
280
  * @param own - The file the command redirects its own output to, when the launch could tell.
238
- * @returns The command; its first line is `MOL_TASK_STATE <alive|gone|unknown>`, the rest the tail.
281
+ * @returns The command; line 1 is `MOL_TASK_STATE <alive|gone|unknown>`, line 2
282
+ * `MOL_TASK_EXIT <n|unknown>`, and the rest the tail of the run's own output.
239
283
  */
240
284
  const taskStateCommand = (taskId, own) => `P=$(cat /tmp/${taskId}.pid 2>/dev/null); ` +
241
- `if [ -z "$P" ] || [ ! -f /tmp/${taskId}.sh ]; then echo MOL_TASK_STATE unknown; ` +
242
- `elif kill -0 "$P" 2>/dev/null; then echo MOL_TASK_STATE alive; ` +
243
- `else echo MOL_TASK_STATE gone; fi; ` +
285
+ `if [ -z "$P" ] || [ ! -f /tmp/${taskId}.sh ]; then S=unknown; ` +
286
+ `elif kill -0 "$P" 2>/dev/null; then S=alive; ` +
287
+ `else S=gone; fi; ` +
288
+ `c=$(cat /tmp/${taskId}.exit 2>/dev/null); ` +
289
+ `case "$c" in ''|*[!0-9]*) X=unknown;; *) X=$c;; esac; ` +
290
+ `echo MOL_TASK_STATE "$S"; ` +
291
+ `echo MOL_TASK_EXIT "$X"; ` +
244
292
  (own ? `tail -c ${TASK_OUTPUT_END_BYTES} ${shellQuote(own)} 2>/dev/null` : 'true');
245
293
  /**
246
294
  * The ending a task's own output already records, read from the file its command redirects to.
@@ -286,10 +334,12 @@ function outputEnding(text) {
286
334
  *
287
335
  * @param backend - The execution backend the task runs on.
288
336
  * @param taskId - The task's `mol-bg-…` handle.
289
- * @returns Whether it has ended, and the code its own output recorded when it recorded one.
337
+ * @returns Whether it has ended, and the code the exit file (or the run's own output) recorded,
338
+ * or null when there is no verdict to report.
290
339
  */
291
340
  async function taskEndState(backend, taskId) {
292
341
  const own = outputFileByTask.get(taskId);
342
+ const detached = detachedByTask.has(taskId);
293
343
  let stdout;
294
344
  try {
295
345
  const r = await backend.run(taskStateCommand(taskId, own), { timeout: 5_000 });
@@ -300,18 +350,32 @@ async function taskEndState(backend, taskId) {
300
350
  // the loud one (a result handed back for a run that is still walking).
301
351
  return { ended: false, exitCode: null };
302
352
  }
303
- const [stateLine = '', ...rest] = stdout.split('\n');
304
- const state = /MOL_TASK_STATE (alive|gone|unknown)/.exec(stateLine)?.[1] ?? 'unknown';
305
- const ownEnding = outputEnding(rest.join('\n'));
353
+ // The headers are OURS, at fixed positions — a line the run's own output prints that is shaped
354
+ // like one cannot be read as one, and the bytes after line 2 are the run's tail regardless.
355
+ const lines = stdout.split('\n').map((line) => line.replace(/\r$/, ''));
356
+ const state = /^MOL_TASK_STATE (alive|gone|unknown)$/.exec(lines[0] ?? '')?.[1] ?? 'unknown';
357
+ const headerCode = /^MOL_TASK_EXIT (\d+)$/.exec(lines[1] ?? '')?.[1];
358
+ const reported = headerCode === undefined ? null : Number(headerCode);
359
+ const ownEnding = outputEnding(lines.slice(2).join('\n'));
306
360
  // The run's own output can end the task while its wrapper is still alive: the wrapper may be
307
361
  // waiting on the wrong thing (m182's was blocked) while the run it started is over. An explicit
308
- // `EXIT=<n>` is that verdict; a bare tally is trusted only once the pid is gone, because the
309
- // instant between a walk's tally and its next walk's header reads the same.
362
+ // `EXIT=<n>` is that verdict — more specific than the wrapper's own code, which in that shape is
363
+ // the status of the backgrounding statement, not of the run — and a bare tally is trusted only
364
+ // once the pid is gone, because the instant between a walk's tally and its next walk's header
365
+ // reads the same.
310
366
  if (ownEnding.ended && (ownEnding.exitCode !== null || state === 'gone'))
311
367
  return ownEnding;
368
+ // The code the launcher wrote, read in the same shell as the pid test. It is an ending only once
369
+ // the wrapper is GONE: a wrapper still alive has not reached that write yet (or is a moment away
370
+ // from exiting), so the code is not an ending for it — and a launch whose LAST statement is
371
+ // backgrounded (`… &`) returned the status of the backgrounding operation, a launch
372
+ // acknowledgement, that is never the run's verdict (see {@link detachedByTask}). Read after the
373
+ // pid test, it is found here rather than reported as never written (xr-molecule-flash-m303).
374
+ if (!detached && reported !== null && state === 'gone')
375
+ return { ended: true, exitCode: reported };
312
376
  // A pid this sandbox can account for and that is gone, with no exit file: the wrapper that
313
377
  // writes the exit code was killed or exited, so the task ended and has no verdict to invent.
314
- if (state === 'gone')
378
+ if (!detached && state === 'gone')
315
379
  return { ended: true, exitCode: null };
316
380
  return { ended: false, exitCode: null };
317
381
  }
@@ -517,9 +581,7 @@ async function backgroundTaskState(backend, command, seconds, ceilingMs) {
517
581
  const ids = recent.filter((id) => owned.has(id));
518
582
  const exitCodeOf = async (id) => {
519
583
  try {
520
- const raw = await backend.readFile(`/tmp/${id}.exit`);
521
- const code = Number.parseInt(raw.trim(), 10);
522
- return Number.isFinite(code) ? code : 0;
584
+ return exitCodeIn(await backend.readFile(`/tmp/${id}.exit`));
523
585
  }
524
586
  catch (_error) {
525
587
  // No exit file yet: the task is still running. Not an error.
@@ -537,7 +599,9 @@ async function backgroundTaskState(backend, command, seconds, ceilingMs) {
537
599
  // whose run is already over (m182 +30:25: `sleep 240; tail /tmp/full-run.log` was answered "is
538
600
  // live" for a task that had ended 20 s after it started).
539
601
  const stateOf = async (id) => {
540
- const exitCode = await exitCodeOf(id);
602
+ // A launch that ended in `&` writes its exit file at once with the launch's own status; that
603
+ // is an acknowledgement, and the task's end is read from its own output instead.
604
+ const exitCode = detachedByTask.has(id) ? null : await exitCodeOf(id);
541
605
  if (exitCode !== null)
542
606
  return { id, ended: true, exitCode };
543
607
  return { id, ...(await taskEndState(backend, id)) };
@@ -2752,6 +2816,13 @@ export function buildTools(backend, config) {
2752
2816
  // (X1 xr-molecule-flash-m157 +14:02 — two whole walks chained with `;`).
2753
2817
  if (streaming.error)
2754
2818
  return { error: streaming.error };
2819
+ // This tool already backgrounds the script. If its last statement also uses `&`,
2820
+ // let the same shell wait for that child and preserve its exit code. Inferring its
2821
+ // completion from output cannot work for silent commands such as `true &`.
2822
+ // The parser reads the outer shell: an inner `sh -c '… &'` has its own job table.
2823
+ if (endsInBackground(streaming.command)) {
2824
+ streaming.command += '\nwait "$!"';
2825
+ }
2755
2826
  // …and the trace flag this tool added (above), which the streaming rewrite never saw:
2756
2827
  // its changes are what this branch reports the run's own settings in.
2757
2828
  if (traced.change)
@@ -2796,15 +2867,14 @@ export function buildTools(backend, config) {
2796
2867
  // seconds (a Hurl suite: 37 ms). Handing it back as a task cost a
2797
2868
  // wait_for_task round trip each time (X1 flash-m20, twice in ten
2798
2869
  // minutes), so wait a short grace first and return a finished command
2799
- // exactly as a foreground run returns.
2870
+ // exactly as a foreground run returns. A redundant final `&` is followed by `wait`
2871
+ // above, so this file also carries the actual child status in that case.
2800
2872
  if (overCeiling && input.run_in_background !== true) {
2801
2873
  const graceUntil = Date.now() + overCeilingGraceMs;
2802
2874
  while (Date.now() < graceUntil) {
2803
2875
  let code = null;
2804
2876
  try {
2805
- const raw = await backend.readFile(exitFile);
2806
- const parsed = Number.parseInt(raw.trim(), 10);
2807
- code = Number.isFinite(parsed) ? parsed : 0;
2877
+ code = exitCodeIn(await backend.readFile(exitFile));
2808
2878
  }
2809
2879
  catch (_error) {
2810
2880
  // No exit file yet: still running.
@@ -3154,6 +3224,10 @@ export function buildTools(backend, config) {
3154
3224
  // already resolved and stays as written.
3155
3225
  if (target)
3156
3226
  outputFileByTask.set(taskId, posix.resolve(root, target));
3227
+ // The launch's own shape outlives this process the same way its redirect target does:
3228
+ // a command that ended in `&` is still a launch acknowledgement, not a finished run.
3229
+ if (endsInBackground(script))
3230
+ detachedByTask.add(taskId);
3157
3231
  }
3158
3232
  catch (_error) {
3159
3233
  // No script under that id: this process did not start the task, so
@@ -3225,11 +3299,17 @@ export function buildTools(backend, config) {
3225
3299
  const requested = Number(input.timeout);
3226
3300
  const budgetMs = Number.isFinite(requested) && requested > 0 ? Math.min(requested, ceiling) : ceiling;
3227
3301
  const startedAt = Date.now();
3302
+ // A task whose command ended in `&` writes its exit file the moment it launches, with the
3303
+ // launch's own status: that code is an acknowledgement, never the run's verdict, and its end
3304
+ // is read from its own output by {@link taskEndState} instead.
3305
+ const detachedRun = detachedByTask.has(taskId);
3228
3306
  const readExit = async () => {
3307
+ if (detachedRun)
3308
+ return null;
3229
3309
  try {
3230
- const raw = await backend.readFile(exitFile);
3231
- const code = Number.parseInt(raw.trim(), 10);
3232
- return Number.isFinite(code) ? code : 0;
3310
+ // A file that is there but holds no number yet is the launcher's truncate-then-write
3311
+ // instant, not a verdict — the next tick reads the code (see {@link exitCodeIn}).
3312
+ return exitCodeIn(await backend.readFile(exitFile));
3233
3313
  }
3234
3314
  catch (_error) {
3235
3315
  // No exit file yet — the command is still running. Not an error.
@@ -35,14 +35,14 @@ export interface TraceRequest {
35
35
  /**
36
36
  * The response status as the trace recorded it. Playwright writes `-1` when a request was never
37
37
  * answered (its HAR entry starts at `-1` and only a response event overwrites it); `0` is the
38
- * same absence when the snapshot carried no response at all. Neither is a server failure.
38
+ * same absence when the snapshot carried no response at all. Neither alone proves a server failure.
39
39
  */
40
40
  status: number;
41
41
  /**
42
42
  * Playwright's own error text for a request that failed WITHOUT a response
43
43
  * (`net::ERR_CONNECTION_REFUSED`) — a real failure. A request that got no response and recorded
44
- * no text was aborted or still in flight when the test ended (the page closed under it), so it
45
- * says nothing about the server (xr-molecule-flash-m112 +30:45).
44
+ * no text may still have been in flight when recording ended. Missing text or an explicit
45
+ * cancellation cannot establish either a server failure or server health.
46
46
  */
47
47
  failure?: string;
48
48
  /**
@@ -480,23 +480,24 @@ export declare function parseTraceArtifacts(stdout: string): TraceArtifact[];
480
480
  */
481
481
  export declare function isStaticAssetRequest(request: TraceRequest): boolean;
482
482
  /**
483
- * Whether a trace recorded a request the server actually failed: a 4xx/5xx, or a network error.
483
+ * Whether a trace recorded a failed response or a request error other than cancellation.
484
484
  *
485
485
  * A 404 on a static asset is excepted: `/sw.js` and `/fonts/*` 404 on an app that never shipped
486
486
  * them, and naming one as a failure sent m125 into writing the caching worker it had asked about.
487
- * A 5xx on an asset, or a network error, still counts — the server did fail.
487
+ * A 5xx on an asset, or a network error, still counts. A request error alone does not locate
488
+ * the cause in the server: the browser or network may have rejected the request.
488
489
  *
489
490
  * @param request - One request a trace recorded.
490
491
  * @returns True when the request failed in a way the note should name.
491
492
  */
492
493
  export declare function failedRequest(request: TraceRequest): boolean;
493
494
  /**
494
- * Whether a trace recorded NO response and no reason for it — a request that was aborted or still
495
- * in flight when the test ended. It is not evidence that the server failed (m112 +30:45), and one
496
- * the page shell made is not evidence about the step at all (m125 +29:33).
495
+ * Whether a trace recorded no response and no error other than cancellation. The request may
496
+ * have been pending or cancelled when recording ended; the trace does not establish whether
497
+ * the endpoint stalled. A shell request is not evidence about the step (m125 +29:33).
497
498
  *
498
499
  * @param request - One request a trace recorded.
499
- * @returns True when the request is still in flight and worth naming.
500
+ * @returns True when the request's missing response is inconclusive and worth naming.
500
501
  */
501
502
  export declare function unansweredRequest(request: TraceRequest): boolean;
502
503
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"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"}
@@ -1896,11 +1896,12 @@ export function isStaticAssetRequest(request) {
1896
1896
  return STATIC_ASSET_PATH.test(path) || STATIC_ASSET_DIR.test(path) || SHELL_FILE_PATH.test(path);
1897
1897
  }
1898
1898
  /**
1899
- * Whether a trace recorded a request the server actually failed: a 4xx/5xx, or a network error.
1899
+ * Whether a trace recorded a failed response or a request error other than cancellation.
1900
1900
  *
1901
1901
  * A 404 on a static asset is excepted: `/sw.js` and `/fonts/*` 404 on an app that never shipped
1902
1902
  * them, and naming one as a failure sent m125 into writing the caching worker it had asked about.
1903
- * A 5xx on an asset, or a network error, still counts — the server did fail.
1903
+ * A 5xx on an asset, or a network error, still counts. A request error alone does not locate
1904
+ * the cause in the server: the browser or network may have rejected the request.
1904
1905
  *
1905
1906
  * @param request - One request a trace recorded.
1906
1907
  * @returns True when the request failed in a way the note should name.
@@ -1908,20 +1909,27 @@ export function isStaticAssetRequest(request) {
1908
1909
  export function failedRequest(request) {
1909
1910
  if (isStaticAssetRequest(request) && request.status === 404)
1910
1911
  return false;
1911
- return request.status >= 400 || (request.status <= 0 && request.failure !== undefined);
1912
+ return request.status >= 400 || (request.status <= 0 && recordedRequestFailure(request));
1913
+ }
1914
+ /** A browser cancellation records no conclusion about the endpoint's availability. */
1915
+ const REQUEST_CANCELLED = /\b(?:net::ERR_ABORTED|NS_BINDING_ABORTED|AbortError)\b|^(?:aborted|cancelled|canceled)$/i;
1916
+ /** Whether the request carries a nonempty error that is not an explicit cancellation. */
1917
+ function recordedRequestFailure(request) {
1918
+ const failure = request.failure?.trim();
1919
+ return failure !== undefined && failure !== '' && !REQUEST_CANCELLED.test(failure);
1912
1920
  }
1913
1921
  /**
1914
- * Whether a trace recorded NO response and no reason for it — a request that was aborted or still
1915
- * in flight when the test ended. It is not evidence that the server failed (m112 +30:45), and one
1916
- * the page shell made is not evidence about the step at all (m125 +29:33).
1922
+ * Whether a trace recorded no response and no error other than cancellation. The request may
1923
+ * have been pending or cancelled when recording ended; the trace does not establish whether
1924
+ * the endpoint stalled. A shell request is not evidence about the step (m125 +29:33).
1917
1925
  *
1918
1926
  * @param request - One request a trace recorded.
1919
- * @returns True when the request is still in flight and worth naming.
1927
+ * @returns True when the request's missing response is inconclusive and worth naming.
1920
1928
  */
1921
1929
  export function unansweredRequest(request) {
1922
1930
  if (isStaticAssetRequest(request))
1923
1931
  return false;
1924
- return request.status <= 0 && request.failure === undefined;
1932
+ return request.status <= 0 && !recordedRequestFailure(request);
1925
1933
  }
1926
1934
  /**
1927
1935
  * Whether a trace recorded a DOCUMENT SUBRESOURCE that never finished: a stylesheet, script or
@@ -2200,14 +2208,16 @@ function navigationNeverFinished(item) {
2200
2208
  function transportFailure(items) {
2201
2209
  for (const item of items) {
2202
2210
  const document = item.requests.find((request) => request.type === 'document');
2203
- const failed = item.requests.find((request) => request.failure !== undefined && NETWORK_ERROR.test(request.failure));
2211
+ const failed = item.requests.find((request) => recordedRequestFailure(request) &&
2212
+ request.failure !== undefined &&
2213
+ NETWORK_ERROR.test(request.failure));
2204
2214
  if (failed !== undefined) {
2205
2215
  const error = NETWORK_ERROR.exec(failed.failure ?? '')?.[0] ?? failed.failure ?? '';
2206
2216
  return { error, where: failed.path };
2207
2217
  }
2208
2218
  for (const line of item.console) {
2209
2219
  const error = NETWORK_ERROR.exec(line)?.[0];
2210
- if (error !== undefined)
2220
+ if (error !== undefined && !REQUEST_CANCELLED.test(error))
2211
2221
  return {
2212
2222
  error,
2213
2223
  where: document?.path ?? item.requests[0]?.path ?? 'the page',
@@ -2249,27 +2259,22 @@ function isApiPath(path, prefixes) {
2249
2259
  });
2250
2260
  }
2251
2261
  /**
2252
- * The API requests a failing run's traces recorded with NO response, one entry per method+path.
2253
- *
2254
- * A request still in flight is not a server failure on its own ({@link unansweredRequest}), and
2255
- * the note says so. Several API paths left unanswered in one walk is a different fact: the server
2256
- * stopped answering while the tests ran, and the closing "the cause is in the page" reading is
2257
- * then false (xr-molecule-flash-m215 +36:28: three API paths got no response and two `page.goto`
2258
- * calls timed out across a platform restart, and the note sent the run at avatar components and
2259
- * token signing).
2262
+ * The API requests that recorded an error without a response, one entry per method+path.
2263
+ * Missing failure text stays inconclusive regardless of the number of pending requests
2264
+ * (m303/m305). An answer from another endpoint cannot excuse a recorded failure here.
2260
2265
  *
2261
2266
  * @param items - The traces the note is about.
2262
2267
  * @param apiPrefixes - The API prefixes the caller read, or empty.
2263
2268
  * @returns One entry per distinct method+path, in the order the traces recorded them.
2264
2269
  */
2265
- function unansweredApiRequests(items, apiPrefixes) {
2270
+ function failedApiRequests(items, apiPrefixes) {
2266
2271
  const groups = new Map();
2267
2272
  for (const item of items) {
2268
2273
  // The step's own requests are also in `item.requests` on a modern trace, but an older reader
2269
2274
  // put them only on the action: count each method+path once per trace, not once per list.
2270
2275
  const seen = new Set();
2271
2276
  for (const request of [...item.requests, ...(item.lastAction?.requests ?? [])]) {
2272
- if (!unansweredRequest(request))
2277
+ if (request.status > 0 || !failedRequest(request))
2273
2278
  continue;
2274
2279
  const path = sameOriginPath(request);
2275
2280
  if (path === null || !isApiPath(path, apiPrefixes))
@@ -2304,11 +2309,10 @@ function navigationTimedOut(item, failures) {
2304
2309
  return failure !== undefined && navigationMethod(failure.operation) !== null;
2305
2310
  }
2306
2311
  /**
2307
- * The reading a run gets when the server stopped answering: the API requests with no response and
2308
- * the page loads that never finished, named once so both the lead and the closing sentence carry
2309
- * the same fact.
2312
+ * Recorded API errors without responses and page loads that never finished. Both the lead and
2313
+ * closing sentence name the evidence without assigning a server, network, or browser cause.
2310
2314
  *
2311
- * @param requests - The API requests the traces recorded with no response.
2315
+ * @param requests - The API requests that recorded errors without a response.
2312
2316
  * @param navigations - How many traced failures were a navigation that never finished.
2313
2317
  * @param capital - True to start a sentence, false to continue one.
2314
2318
  * @returns The sentence, without a trailing period run or a next step.
@@ -2320,15 +2324,14 @@ function serverSilentReading(requests, navigations, capital) {
2320
2324
  const more = requests.length > shown.length ? ` and ${requests.length - shown.length} more` : '';
2321
2325
  const asked = requests.length === 0
2322
2326
  ? ''
2323
- : `${requests.length} API ${requests.length === 1 ? 'request' : 'requests'} got no response` +
2327
+ : `${requests.length} API ${requests.length === 1 ? 'request' : 'requests'} failed without a response` +
2324
2328
  (shown.length === 0 ? '' : ` (${shown.join(', ')}${more})`);
2325
2329
  const loads = navigations === 0
2326
2330
  ? ''
2327
2331
  : `${navigations} page ${navigations === 1 ? 'load' : 'loads'} never finished`;
2328
2332
  const what = [asked, loads].filter((part) => part !== '').join(' and ');
2329
- return (`${capital ? 'The' : 'the'} server did not answer: ${what}. That is the app — a restart, a ` +
2330
- 'cold start, or a request the server never finishes — being unavailable while these tests ' +
2331
- 'ran, not page state.');
2333
+ return (`${capital ? 'The' : 'the'} trace recorded incomplete requests or page loads: ${what}. ` +
2334
+ 'This does not identify whether the cause is in the server, network, or browser.');
2332
2335
  }
2333
2336
  /**
2334
2337
  * The path segments a request ASKS FOR a credential through — a login, a registration, a token
@@ -3397,15 +3400,13 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
3397
3400
  ...new Set(stalls.flatMap((stall) => (stall === null ? [] : [stall.reported]))),
3398
3401
  ];
3399
3402
  const anyStalled = stalls.some((stall) => stall !== null);
3400
- // The server stopped answering while the tests ran (m215): several API requests with no
3401
- // response, or a navigation that never finished. A single request still in flight is not this
3402
- // (m112), and the note must never close with "the cause is in the page" beside it — the run
3403
- // then rewrites the page while the API was the thing that never answered.
3404
- const silentApiRequests = unansweredApiRequests(traced, apiPrefixes);
3403
+ // A recorded request error or navigation deadline is evidence of a failed attempt, not proof
3404
+ // of its cause. Pending requests remain inconclusive at every count (m303/m305).
3405
+ const silentApiRequests = failedApiRequests(traced, apiPrefixes);
3406
+ const hasPendingRequests = traced.some((item) => [...item.requests, ...(item.lastAction?.requests ?? [])].some(unansweredRequest));
3405
3407
  const silentNavigations = traced.filter((item) => navigationTimedOut(item, failures)).length;
3406
3408
  const serverSilent = silentApiRequests.length >= 2 || silentNavigations > 0;
3407
- // A single unanswered request stays what it always was (m112). Two or more distinct API paths
3408
- // are a fact about the server, and it leads the note before any row.
3409
+ // Multiple recorded API failures lead the note; no response alone cannot trigger this.
3409
3410
  const serverSilentLead = silentApiRequests.length < 2
3410
3411
  ? ''
3411
3412
  : `${serverSilentReading(silentApiRequests, silentNavigations, true)}\n\n`;
@@ -3529,7 +3530,7 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
3529
3530
  if (unanswered.length > 0 && room > 0) {
3530
3531
  const shown = unanswered.slice(0, room);
3531
3532
  room -= shown.length;
3532
- parts.push(`no response recorded — still in flight when the test ended, so not a server failure; ` +
3533
+ parts.push(`no response recorded — possibly still in flight when the test ended or cancelled; ` +
3533
3534
  `that alone does not establish the cause: ${shown.map(requestLabel).join('; ')}`);
3534
3535
  }
3535
3536
  if (answered.length > 0 && room > 0) {
@@ -3755,19 +3756,23 @@ export function failureTraceNote(items, target, earlierFailures = [], apiPrefixe
3755
3756
  // a fact about the element, so it replaces the "look at the page" advice (m197).
3756
3757
  hiddenBox
3757
3758
  : serverSilent
3758
- ? // The server did not answer (m215): a page-state reading would send the run at
3759
- // the markup while nothing answered it. Same fact as the lead, and the one action.
3759
+ ? // A navigation deadline must not be presented as proof of page-state failure.
3760
+ // Name the incomplete load without claiming why it did not finish.
3760
3761
  'No listed request failed — none got a 4xx/5xx or a network error — but ' +
3761
3762
  `${serverSilentReading(silentApiRequests, silentNavigations, false)} ` +
3762
- `Re-run only these tests against ${target} (\`--last-failed\`) before changing ` +
3763
- 'any code; change the app only if they fail again with requests that answered.'
3764
- : 'No listed request failed — none got a 4xx/5xx or a network error — so the cause is in the ' +
3765
- "page, not the API. Start with what the test's last action sent (the first line of each " +
3766
- 'row) and what the page did next: a request that answered (2xx included) while the page ' +
3767
- 'still did not show the state the assertion waited on is the fact to read — its status ' +
3768
- 'and response body, not the selector. If the request is not the one the test meant — an ' +
3769
- 'unfollow sent where the test clicked follow — the state the action read was stale ' +
3770
- 'before it.') +
3763
+ `Re-run only these tests against ${target} (\`--last-failed\`) or inspect the app's ` +
3764
+ 'runtime log for the same window to distinguish the cause.'
3765
+ : hasPendingRequests
3766
+ ? 'No listed request failed with a 4xx/5xx or a network error, but some requests have no ' +
3767
+ 'recorded response. This does not establish whether the server stalled or the browser cancelled ' +
3768
+ 'the request. Read the pending paths alongside the failed action and its timeout.'
3769
+ : 'No listed request failed with a 4xx/5xx or a network error. That does not establish ' +
3770
+ "that its response body or the page state was correct. Read what the test's last action sent (the first line of each " +
3771
+ 'row) and what the page did next: a request that answered (2xx included) while the page ' +
3772
+ 'still did not show the state the assertion waited on is the fact to read — its status ' +
3773
+ 'and response body, not the selector. If the request is not the one the test meant — an ' +
3774
+ 'unfollow sent where the test clicked follow — the state the action read was stale ' +
3775
+ 'before it.') +
3771
3776
  prefixAdvice +
3772
3777
  (wrapped
3773
3778
  ? ' The API received a body wrapped in `data`: app-http’s `post(url, data, config)` / ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.10.8",
3
+ "version": "1.10.9",
4
4
  "description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",