@molecule/api-ai-tools 1.9.5 → 1.9.7
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 +98 -8
- package/dist/command-position.d.ts +12 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +111 -19
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +9 -38
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +28 -10
- package/dist/trace-artifacts.d.ts +46 -0
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +260 -39
- package/dist/utilities.d.ts +39 -5
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +134 -14
- package/package.json +1 -1
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-
|
|
6
|
+
Generated: 2026-10-18T00:28:51.925Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -133,6 +133,18 @@ interface LocatedCommandSegment {
|
|
|
133
133
|
* statements are one pipeline (`|`, never `;`) asks it instead of reading the text between them.
|
|
134
134
|
*/
|
|
135
135
|
separator: string
|
|
136
|
+
/**
|
|
137
|
+
* The separator that ENDED this statement — `&` when the shell backgrounds it (so the process
|
|
138
|
+
* writes its own output, not the command's), '' for the last statement of a command.
|
|
139
|
+
*/
|
|
140
|
+
terminator: string
|
|
141
|
+
/**
|
|
142
|
+
* The file this statement sends its own output to (`> out.log`, `>> log`, `&> run.log`,
|
|
143
|
+
* `| tee file`), or null. Only a redirect written at the command's own level counts: one
|
|
144
|
+
* inside a `( … )` / `{ … }` group writes through that group, and one that feeds a heredoc's
|
|
145
|
+
* file (`cat > cfg.ts <<'EOF'`) is writing a config (X1 flash-m44, flash-m136).
|
|
146
|
+
*/
|
|
147
|
+
redirectTarget: string | null
|
|
136
148
|
}
|
|
137
149
|
```
|
|
138
150
|
|
|
@@ -388,6 +400,16 @@ interface TraceArtifact {
|
|
|
388
400
|
line: number
|
|
389
401
|
/** The requests the page made, the interesting ones first ({@link traceArtifactsScript}). */
|
|
390
402
|
requests: TraceRequest[]
|
|
403
|
+
/**
|
|
404
|
+
* The request the failing step itself sent: the trace's LAST `fetch`/`xhr` in recording order
|
|
405
|
+
* (a click's own call), else its document request. It leads the note's row, because a click that
|
|
406
|
+
* changed nothing is explained by the request the click sent and what came back — xr-molecule-
|
|
407
|
+
* flash-m135's Follow click sent `DELETE /api/profiles/x/follow → 200` (its `following` state was
|
|
408
|
+
* stale) and the note listed only the test's earlier, answered requests.
|
|
409
|
+
*
|
|
410
|
+
* Absent when the trace recorded neither, and on traces read before this field existed.
|
|
411
|
+
*/
|
|
412
|
+
lead?: TraceRequest
|
|
391
413
|
/** The console errors and page errors the page raised. */
|
|
392
414
|
console: string[]
|
|
393
415
|
/**
|
|
@@ -728,6 +750,21 @@ interface UniversalCause {
|
|
|
728
750
|
count: number
|
|
729
751
|
/** The distinct spec files they live in, in the order the log named them. */
|
|
730
752
|
files: string[]
|
|
753
|
+
/**
|
|
754
|
+
* The cause is that the page rendered NOTHING: every failure so far is a wait for an element
|
|
755
|
+
* the page never painted, whatever the selector asked for ({@link emptyPageCause}). One cause
|
|
756
|
+
* is failing every test even though no two failures share an error signature, so the note says
|
|
757
|
+
* so in the page's terms instead of naming one locator as the signature.
|
|
758
|
+
*/
|
|
759
|
+
pageRendersNothing?: true
|
|
760
|
+
/** The page's own uncaught error, when the run printed one. */
|
|
761
|
+
pageError?: string
|
|
762
|
+
/**
|
|
763
|
+
* The distinct failures this cause covers, in log order — the rows a note lists for
|
|
764
|
+
* {@link pageRendersNothing}. Absent for a signature cause, whose rows are the repeating
|
|
765
|
+
* failure's own tests.
|
|
766
|
+
*/
|
|
767
|
+
tests?: FailedTest[]
|
|
731
768
|
}
|
|
732
769
|
```
|
|
733
770
|
|
|
@@ -1721,6 +1758,28 @@ function unansweredRequest(request: TraceRequest): boolean
|
|
|
1721
1758
|
|
|
1722
1759
|
**Returns:** True when the request is still in flight and worth naming.
|
|
1723
1760
|
|
|
1761
|
+
#### `stalledDocumentSubresource(request)`
|
|
1762
|
+
|
|
1763
|
+
Whether a trace recorded a DOCUMENT SUBRESOURCE that never finished: a stylesheet, script or
|
|
1764
|
+
other asset the page itself loads, with no response and no error text — still in flight when
|
|
1765
|
+
the navigation timed out.
|
|
1766
|
+
|
|
1767
|
+
The page shell's own incidental requests (`/sw.js`, the manifest, a favicon, a font) are
|
|
1768
|
+
excepted for the reason {@link isStaticAssetRequest} documents: m125's run was sent to write a
|
|
1769
|
+
caching service worker by an aborted `/sw.js`. A pending `/assets/*.css` is different — the
|
|
1770
|
+
navigation blocks on it, so it is the reason `page.goto` ran out of time rather than one of the
|
|
1771
|
+
app's API calls (xr-molecule-flash-m135 +34:10: three deployed `page.goto` timeouts on tests that
|
|
1772
|
+
pass locally, one 88 KB stylesheet fetch measured at 27 s, and the failing tests' traces each
|
|
1773
|
+
held the never-answered CSS while the note said "the cause is in the page").
|
|
1774
|
+
|
|
1775
|
+
```typescript
|
|
1776
|
+
function stalledDocumentSubresource(request: TraceRequest): boolean
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
- `request` — One request a trace recorded.
|
|
1780
|
+
|
|
1781
|
+
**Returns:** True when the request is a document subresource that never finished.
|
|
1782
|
+
|
|
1724
1783
|
#### `failureTraceNote(items, target, earlierFailures)`
|
|
1725
1784
|
|
|
1726
1785
|
The note a failed suite run's result carries: what each failing test's page actually sent, and
|
|
@@ -1742,8 +1801,8 @@ A 4xx request carries what the page SENT beside its status, so a wrapped body is
|
|
|
1742
1801
|
line rather than inferred from the status; when one of those bodies is an object whose only key
|
|
1743
1802
|
is `data`, the note names the call shape that produces it (xr-molecule-flash-m111 +11:36).
|
|
1744
1803
|
|
|
1745
|
-
|
|
1746
|
-
|
|
1804
|
+
Every row opens with the request the failing step ITSELF sent ({@link TraceArtifact.lead}) and
|
|
1805
|
+
its response — whatever its status — because the question a `waiting for locator` failure asks
|
|
1747
1806
|
|
|
1748
1807
|
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/trace-artifacts.d.ts`.)_
|
|
1749
1808
|
|
|
@@ -1947,13 +2006,24 @@ The file a command sends its own output to, when it redirects it (`> out.txt`,
|
|
|
1947
2006
|
this leaves its task log empty, so the log is the wrong place to look and an empty
|
|
1948
2007
|
log proves nothing (X1 flash-m29).
|
|
1949
2008
|
|
|
2009
|
+
Which redirect counts is decided by the command's own shell structure, from the same
|
|
2010
|
+
parse every other command reader uses: a redirect written in a BACKGROUNDED statement
|
|
2011
|
+
(`… &`) or inside a `( … )` / `{ … }` group writes that process's or group's output, not
|
|
2012
|
+
the command's. The platform's own preview launch is exactly that — the preview server is
|
|
2013
|
+
started with `>/tmp/mol-preview-4173.log 2>&1 &` inside the build/serve prelude, and the
|
|
2014
|
+
suite's foreground command follows it — so a reader that took the last `>` in the whole
|
|
2015
|
+
text told a run its output went to the PREVIEW's log (X1 flash-m136 +18:31).
|
|
2016
|
+
|
|
2017
|
+
The target comes back without its shell quotes: `> '/tmp/x.log'` is `/tmp/x.log`, and
|
|
2018
|
+
the callers resolve a relative one against the command's cwd.
|
|
2019
|
+
|
|
1950
2020
|
```typescript
|
|
1951
2021
|
function outputRedirectTarget(command: string): string | null
|
|
1952
2022
|
```
|
|
1953
2023
|
|
|
1954
2024
|
- `command` — The shell command.
|
|
1955
2025
|
|
|
1956
|
-
**Returns:** The target
|
|
2026
|
+
**Returns:** The target, or null (`/dev/null` and `&1`/`&2` are not files).
|
|
1957
2027
|
|
|
1958
2028
|
#### `streamingBackgroundCommand(command)`
|
|
1959
2029
|
|
|
@@ -2624,13 +2694,13 @@ alongside the others.
|
|
|
2624
2694
|
```typescript
|
|
2625
2695
|
function universalCause(
|
|
2626
2696
|
log: string,
|
|
2627
|
-
found: RepeatingFailure,
|
|
2697
|
+
found: RepeatingFailure | null,
|
|
2628
2698
|
command?: string | null,
|
|
2629
2699
|
): UniversalCause | null
|
|
2630
2700
|
```
|
|
2631
2701
|
|
|
2632
2702
|
- `log` — The log so far.
|
|
2633
|
-
- `found` — The repeating cause {@link repeatingFailure} found in it.
|
|
2703
|
+
- `found` — The repeating cause {@link repeatingFailure} found in it, or null when no two failures share a signature (a page that renders nothing fails each spec at its own first locator — {@link emptyPageCause} still reads that run).
|
|
2634
2704
|
- `command` — How the run was started, when the caller knows it: its spec-file selection scales the file threshold ({@link specFilesSelected}).
|
|
2635
2705
|
|
|
2636
2706
|
**Returns:** The cause's count and files, or null when the run is not failing whole.
|
|
@@ -2661,12 +2731,12 @@ _(description truncated — read the full text in `node_modules/@molecule/api-ai
|
|
|
2661
2731
|
|
|
2662
2732
|
```typescript
|
|
2663
2733
|
function earlyReturnCause(
|
|
2664
|
-
found: RepeatingFailure,
|
|
2734
|
+
found: RepeatingFailure | null,
|
|
2665
2735
|
universal: UniversalCause | null | undefined,
|
|
2666
2736
|
): UniversalCause | null
|
|
2667
2737
|
```
|
|
2668
2738
|
|
|
2669
|
-
- `found` — What {@link repeatingFailure} found mid-run.
|
|
2739
|
+
- `found` — What {@link repeatingFailure} found mid-run, or null when no signature repeats.
|
|
2670
2740
|
- `universal` — What {@link universalCause} found for it, if anything.
|
|
2671
2741
|
|
|
2672
2742
|
**Returns:** The cause to stop the run over, or null — in which case nothing may be stopped, the wait keeps going, and the finding rides the run's result as a note.
|
|
@@ -3046,6 +3116,16 @@ How much of the response body the note prints — the API's first error line.
|
|
|
3046
3116
|
const TRACE_NOTE_GOT_CHARS: 120
|
|
3047
3117
|
```
|
|
3048
3118
|
|
|
3119
|
+
#### `TRACE_NOTE_LEAD_GOT_CHARS`
|
|
3120
|
+
|
|
3121
|
+
How much of the FAILING STEP's response body the note prints. Longer than
|
|
3122
|
+
{@link TRACE_NOTE_GOT_CHARS}: it is the one line that answers "what did the click actually get
|
|
3123
|
+
back", and an app's own body (`{"profile":{…}}`) can carry the answer past its first clause.
|
|
3124
|
+
|
|
3125
|
+
```typescript
|
|
3126
|
+
const TRACE_NOTE_LEAD_GOT_CHARS: 200
|
|
3127
|
+
```
|
|
3128
|
+
|
|
3049
3129
|
#### `DIAGNOSIS_TRACE_FLAG`
|
|
3050
3130
|
|
|
3051
3131
|
The `--trace` flag this platform adds, and the only value it adds it with.
|
|
@@ -3363,6 +3443,16 @@ property of the run rather than of a page.
|
|
|
3363
3443
|
const UNIVERSAL_CAUSE_FILES: 3
|
|
3364
3444
|
```
|
|
3365
3445
|
|
|
3446
|
+
#### `EMPTY_PAGE_CAUSE_TESTS`
|
|
3447
|
+
|
|
3448
|
+
How many tests a run must have walked — with nothing passing since its first failure — before
|
|
3449
|
+
"the page renders nothing" can be its one cause. A handful of failures is a handful of bugs;
|
|
3450
|
+
a page that never painted fails every spec that reaches it, and m141's walk had 36 by +20:30.
|
|
3451
|
+
|
|
3452
|
+
```typescript
|
|
3453
|
+
const EMPTY_PAGE_CAUSE_TESTS: 15
|
|
3454
|
+
```
|
|
3455
|
+
|
|
3366
3456
|
#### `CONNECTION_CAUSE_TESTS`
|
|
3367
3457
|
|
|
3368
3458
|
How many tests a connection-class cause must cover before the run is stopped even when they
|
|
@@ -114,6 +114,18 @@ export interface LocatedCommandSegment {
|
|
|
114
114
|
* statements are one pipeline (`|`, never `;`) asks it instead of reading the text between them.
|
|
115
115
|
*/
|
|
116
116
|
separator: string;
|
|
117
|
+
/**
|
|
118
|
+
* The separator that ENDED this statement — `&` when the shell backgrounds it (so the process
|
|
119
|
+
* writes its own output, not the command's), '' for the last statement of a command.
|
|
120
|
+
*/
|
|
121
|
+
terminator: string;
|
|
122
|
+
/**
|
|
123
|
+
* The file this statement sends its own output to (`> out.log`, `>> log`, `&> run.log`,
|
|
124
|
+
* `| tee file`), or null. Only a redirect written at the command's own level counts: one
|
|
125
|
+
* inside a `( … )` / `{ … }` group writes through that group, and one that feeds a heredoc's
|
|
126
|
+
* file (`cat > cfg.ts <<'EOF'`) is writing a config (X1 flash-m44, flash-m136).
|
|
127
|
+
*/
|
|
128
|
+
redirectTarget: string | null;
|
|
117
129
|
}
|
|
118
130
|
/**
|
|
119
131
|
* Every statement of a command, with its own assignments and where it sits — assignment-only
|
|
@@ -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;;;OAGG;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;AAqND;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;
|
|
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;;;OAGG;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;AAqND;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAmjBD;;;;;;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;AAwFD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|
package/dist/command-position.js
CHANGED
|
@@ -541,6 +541,26 @@ function splitSegments(command) {
|
|
|
541
541
|
let buffer = '';
|
|
542
542
|
/** The offset in the command of the current buffer's first character. */
|
|
543
543
|
let bufferStart = 0;
|
|
544
|
+
/** How many `( … )` / `{ … }` groups are open at the current position. */
|
|
545
|
+
let groupDepth = 0;
|
|
546
|
+
/** The depth each character of `buffer` was written at, aligned with it (-1 when quoted). */
|
|
547
|
+
let depths = [];
|
|
548
|
+
/**
|
|
549
|
+
* Append shell text to the statement being read, recording the depth it was written at.
|
|
550
|
+
* Text inside a quote or a command substitution is recorded as -1: a `>` there is a literal
|
|
551
|
+
* character or a nested command's business, never the outer command's redirect.
|
|
552
|
+
*/
|
|
553
|
+
const put = (text, depth = groupDepth) => {
|
|
554
|
+
for (let n = 0; n < text.length; n++)
|
|
555
|
+
depths.push(depth);
|
|
556
|
+
buffer += text;
|
|
557
|
+
};
|
|
558
|
+
/** Close the statement being read at `after`. */
|
|
559
|
+
const end = (after) => {
|
|
560
|
+
parts.push({ text: buffer, start: bufferStart, after, depths });
|
|
561
|
+
buffer = '';
|
|
562
|
+
depths = [];
|
|
563
|
+
};
|
|
544
564
|
/** The nestings this position sits inside, outermost first. */
|
|
545
565
|
const stack = [];
|
|
546
566
|
for (let i = 0; i < source.length; i++) {
|
|
@@ -550,7 +570,7 @@ function splitSegments(command) {
|
|
|
550
570
|
// A single-quoted string expands nothing at all: no escape, no substitution, and no quote
|
|
551
571
|
// but its own ends it.
|
|
552
572
|
if (top === 'single') {
|
|
553
|
-
|
|
573
|
+
put(char, -1);
|
|
554
574
|
if (char === "'")
|
|
555
575
|
stack.pop();
|
|
556
576
|
continue;
|
|
@@ -559,7 +579,7 @@ function splitSegments(command) {
|
|
|
559
579
|
// inside single quotes (handled above). The `(` is consumed here so the depth is counted once.
|
|
560
580
|
if (char === '$' && next === '(') {
|
|
561
581
|
stack.push('sub');
|
|
562
|
-
|
|
582
|
+
put('$(', -1);
|
|
563
583
|
i += 1;
|
|
564
584
|
continue;
|
|
565
585
|
}
|
|
@@ -569,21 +589,21 @@ function splitSegments(command) {
|
|
|
569
589
|
if (char === '"')
|
|
570
590
|
stack.pop();
|
|
571
591
|
else if (char === '\\' && next !== undefined) {
|
|
572
|
-
|
|
592
|
+
put(char + next, -1);
|
|
573
593
|
i += 1;
|
|
574
594
|
continue;
|
|
575
595
|
}
|
|
576
|
-
|
|
596
|
+
put(char, -1);
|
|
577
597
|
continue;
|
|
578
598
|
}
|
|
579
599
|
if (char === '"') {
|
|
580
600
|
stack.push('double');
|
|
581
|
-
|
|
601
|
+
put(char, -1);
|
|
582
602
|
continue;
|
|
583
603
|
}
|
|
584
604
|
if (char === "'") {
|
|
585
605
|
stack.push('single');
|
|
586
|
-
|
|
606
|
+
put(char, -1);
|
|
587
607
|
continue;
|
|
588
608
|
}
|
|
589
609
|
// Inside a substitution or a subshell of one: its own parentheses nest, its `|`/`;`/`&&`
|
|
@@ -593,41 +613,53 @@ function splitSegments(command) {
|
|
|
593
613
|
stack.push('sub');
|
|
594
614
|
else if (char === ')')
|
|
595
615
|
stack.pop();
|
|
596
|
-
|
|
616
|
+
put(char, -1);
|
|
597
617
|
continue;
|
|
598
618
|
}
|
|
599
619
|
if (char === '\\' && next !== undefined) {
|
|
600
|
-
|
|
620
|
+
// An escaped character is a literal (`\>` writes no file).
|
|
621
|
+
put(char + next, -1);
|
|
601
622
|
i += 1;
|
|
602
623
|
continue;
|
|
603
624
|
}
|
|
625
|
+
// Grouping belongs to the shell, not to a word: a statement written inside `( … )` or
|
|
626
|
+
// `{ … }` writes through that group, so the depth travels with every character a caller
|
|
627
|
+
// reading a redirect may ask about.
|
|
628
|
+
if (char === '(' || char === '{') {
|
|
629
|
+
put(char);
|
|
630
|
+
groupDepth += 1;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (char === ')' || char === '}') {
|
|
634
|
+
groupDepth = Math.max(0, groupDepth - 1);
|
|
635
|
+
put(char);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
604
638
|
if ((char === '&' && next === '&') || (char === '|' && next === '|')) {
|
|
605
|
-
|
|
606
|
-
buffer = '';
|
|
639
|
+
end(char + next);
|
|
607
640
|
i += 1;
|
|
608
641
|
bufferStart = i + 1;
|
|
609
642
|
continue;
|
|
610
643
|
}
|
|
611
644
|
if (char === ';' || char === '|' || char === '\n') {
|
|
612
|
-
|
|
613
|
-
buffer = '';
|
|
645
|
+
end(char);
|
|
614
646
|
bufferStart = i + 1;
|
|
615
647
|
continue;
|
|
616
648
|
}
|
|
617
649
|
if (char === '&' && source[i - 1] !== '>' && next !== '>') {
|
|
618
|
-
|
|
619
|
-
buffer = '';
|
|
650
|
+
end('&');
|
|
620
651
|
bufferStart = i + 1;
|
|
621
652
|
continue;
|
|
622
653
|
}
|
|
623
|
-
|
|
654
|
+
put(char);
|
|
624
655
|
}
|
|
625
|
-
|
|
656
|
+
end('');
|
|
626
657
|
const out = [];
|
|
627
658
|
// The separator of the last part, kept across the empty ones so a statement's own separator
|
|
628
659
|
// survives the blank lines around it (`a &&\n b` and a heredoc's blanked body alike).
|
|
629
660
|
let lastAfter = null;
|
|
630
661
|
for (const part of parts) {
|
|
662
|
+
const trimStart = part.text.length - part.text.trimStart().length;
|
|
631
663
|
const text = part.text.trim();
|
|
632
664
|
if (text === '') {
|
|
633
665
|
lastAfter = part.after;
|
|
@@ -635,14 +667,63 @@ function splitSegments(command) {
|
|
|
635
667
|
}
|
|
636
668
|
out.push({
|
|
637
669
|
text,
|
|
638
|
-
start: part.start +
|
|
670
|
+
start: part.start + trimStart,
|
|
639
671
|
after: part.after,
|
|
640
672
|
before: lastAfter ?? '',
|
|
673
|
+
depths: part.depths.slice(trimStart),
|
|
641
674
|
});
|
|
642
675
|
lastAfter = part.after;
|
|
643
676
|
}
|
|
644
677
|
return out;
|
|
645
678
|
}
|
|
679
|
+
/** A redirect's target word: a quoted one keeps its spaces (`tee 'out file.txt'`). */
|
|
680
|
+
const REDIRECT_TARGET_WORD = String.raw `(?:'[^']*'|"[^"]*"|[^\s;&|]+)`;
|
|
681
|
+
/**
|
|
682
|
+
* A `>`, `>>` or `&>` redirect written at a statement's own level, with the word it writes to.
|
|
683
|
+
* The lookbehind is what keeps `2>&1` out: the `>` of a descriptor redirect follows a digit.
|
|
684
|
+
*/
|
|
685
|
+
const REDIRECT_AND_TARGET = new RegExp(String.raw `(?<![0-9&>])(?:&>|1?>>?)\s*(${REDIRECT_TARGET_WORD})`, 'g');
|
|
686
|
+
/** A word with one layer of MATCHING shell quotes removed (`'out file.txt'` → `out file.txt`). */
|
|
687
|
+
function unquotedWord(word) {
|
|
688
|
+
return word.replace(/^(['"])(.*)\1$/, '$2');
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* The file a statement sends its own output to, when it redirects it at the command's own level.
|
|
692
|
+
*
|
|
693
|
+
* Three shapes are deliberately not one: a redirect inside a `( … )` / `{ … }` group (it writes
|
|
694
|
+
* through that group, not through the command's own shell), a statement that feeds a heredoc's
|
|
695
|
+
* file (`cat > cfg.ts <<'EOF'` writes a config), and `/dev/null` or a descriptor (`2>&1`).
|
|
696
|
+
*
|
|
697
|
+
* @param text - The statement's text, as the tokenizer split it.
|
|
698
|
+
* @param depths - The depth each character of `text` was written at: the open-group count, 0 at
|
|
699
|
+
* the command's own level, -1 inside a quote or a command substitution.
|
|
700
|
+
* @param program - The word in command position (`tee` writes through its own file argument).
|
|
701
|
+
* @param args - The statement's words after the program.
|
|
702
|
+
* @returns The target with one layer of quotes removed, or null.
|
|
703
|
+
*/
|
|
704
|
+
function redirectTargetOf(text, depths, program, args) {
|
|
705
|
+
// A heredoc's own file is a config the command WRITES, never where the run's output goes
|
|
706
|
+
// (X1 flash-m44).
|
|
707
|
+
if (/(?:^|\s)<<(?![<])-?/.test(text))
|
|
708
|
+
return null;
|
|
709
|
+
// `| tee file` is a statement of its own: the pipe is a statement separator here.
|
|
710
|
+
if (program === 'tee') {
|
|
711
|
+
const target = args.find((arg) => !arg.startsWith('-'));
|
|
712
|
+
return target === undefined ? null : unquotedWord(target);
|
|
713
|
+
}
|
|
714
|
+
let last = null;
|
|
715
|
+
for (const match of text.matchAll(REDIRECT_AND_TARGET)) {
|
|
716
|
+
const word = unquotedWord(match[1] ?? '');
|
|
717
|
+
// Only the command's own level counts: not a group's redirect (depth > 0), and not one
|
|
718
|
+
// written inside a quote or a command substitution (depth -1).
|
|
719
|
+
if ((depths[match.index ?? 0] ?? 0) !== 0)
|
|
720
|
+
continue;
|
|
721
|
+
if (word === '/dev/null' || word.startsWith('&'))
|
|
722
|
+
continue;
|
|
723
|
+
last = word;
|
|
724
|
+
}
|
|
725
|
+
return last;
|
|
726
|
+
}
|
|
646
727
|
/**
|
|
647
728
|
* One segment's words: quotes kept whole, grouping parentheses dropped, and a `(` written
|
|
648
729
|
* against the first word taken off it (`(npx playwright test)`).
|
|
@@ -912,6 +993,9 @@ function locatedSegmentsOf(command, depth) {
|
|
|
912
993
|
assignmentOffset: part.start + leading.end,
|
|
913
994
|
programOffset: words[start.at] === undefined ? null : part.start + (wordOffset(part.text, start.at) ?? 0),
|
|
914
995
|
};
|
|
996
|
+
const program = words[start.at] === undefined ? '' : programName(words[start.at] ?? '');
|
|
997
|
+
const args = words.slice(start.at + 1);
|
|
998
|
+
const redirectTarget = redirectTargetOf(part.text, part.depths, program, args);
|
|
915
999
|
if (start.nested !== null) {
|
|
916
1000
|
const inner = locatedSegmentsOf(start.nested, depth + 1);
|
|
917
1001
|
for (let i = 0; i < inner.length; i += 1) {
|
|
@@ -927,6 +1011,12 @@ function locatedSegmentsOf(command, depth) {
|
|
|
927
1011
|
end: null,
|
|
928
1012
|
assignmentOffset: null,
|
|
929
1013
|
programOffset: null,
|
|
1014
|
+
// `sh -c '…' &` backgrounds the command inside the string too.
|
|
1015
|
+
terminator: part.after === '&' ? '&' : entry.terminator,
|
|
1016
|
+
// A redirect written OUTSIDE the string (`sh -c '…' > run.log`) carries the nested
|
|
1017
|
+
// command's output, so the first nested statement holds it — unless that statement
|
|
1018
|
+
// redirects on its own, which takes the output away from the outer file.
|
|
1019
|
+
redirectTarget: i === 0 && entry.redirectTarget === null ? redirectTarget : entry.redirectTarget,
|
|
930
1020
|
separator: i === 0 ? part.before : entry.separator,
|
|
931
1021
|
});
|
|
932
1022
|
}
|
|
@@ -943,11 +1033,11 @@ function locatedSegmentsOf(command, depth) {
|
|
|
943
1033
|
...placed,
|
|
944
1034
|
programOffset: null,
|
|
945
1035
|
separator: part.before,
|
|
1036
|
+
terminator: part.after,
|
|
1037
|
+
redirectTarget,
|
|
946
1038
|
});
|
|
947
1039
|
continue;
|
|
948
1040
|
}
|
|
949
|
-
const program = programName(word);
|
|
950
|
-
const args = words.slice(start.at + 1);
|
|
951
1041
|
out.push({
|
|
952
1042
|
segment: {
|
|
953
1043
|
program,
|
|
@@ -958,6 +1048,8 @@ function locatedSegmentsOf(command, depth) {
|
|
|
958
1048
|
assignments: leading.words,
|
|
959
1049
|
...placed,
|
|
960
1050
|
separator: part.before,
|
|
1051
|
+
terminator: part.after,
|
|
1052
|
+
redirectTarget,
|
|
961
1053
|
});
|
|
962
1054
|
}
|
|
963
1055
|
return out;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAClB,MAAM,CAqCR;
|
|
1
|
+
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAClB,MAAM,CAqCR;AAgHD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
|
package/dist/suite-overlap.js
CHANGED
|
@@ -262,45 +262,18 @@ function playwrightLocations(locations, command, cwd) {
|
|
|
262
262
|
/** A spec-file argument Playwright accepts as its positional filter — never a flag. */
|
|
263
263
|
const SPEC_FILE_ARGUMENT = /^(?!-)[^\s]*\.(?:spec|test)\.[cm]?[jt]sx?(?::\d+(?::\d+)?)?$/;
|
|
264
264
|
/**
|
|
265
|
-
*
|
|
266
|
-
* `url-navigation.spec.ts:13:7`, and the file is the argument that supersedes.
|
|
265
|
+
* The statement with every bare spec-file argument taken out once the re-run names failure locations.
|
|
267
266
|
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Whether two spellings name the same spec file: equal, or one the absolute (or longer) form of
|
|
276
|
-
* the other. The command and the reporter routinely spell the same file differently — the model
|
|
277
|
-
* names `url-navigation.spec.ts`, the summary prints `specs/e2e/url-navigation.spec.ts:13`, and a
|
|
278
|
-
* hold hands back `/workspace/fixtures/realworld/specs/e2e/url-navigation.spec.ts:13`.
|
|
279
|
-
*
|
|
280
|
-
* @param argument - The spec-file argument as the command wrote it.
|
|
281
|
-
* @param location - The failing location.
|
|
282
|
-
* @returns True when the location is inside that file.
|
|
283
|
-
*/
|
|
284
|
-
function sameSpecFile(argument, location) {
|
|
285
|
-
const a = locationFile(argument)
|
|
286
|
-
.replace(/^['"]|['"]$/g, '')
|
|
287
|
-
.replace(/^\.\//, '');
|
|
288
|
-
const l = locationFile(location)
|
|
289
|
-
.replace(/^['"]|['"]$/g, '')
|
|
290
|
-
.replace(/^\.\//, '');
|
|
291
|
-
return a === l || a.endsWith(`/${l}`) || l.endsWith(`/${a}`);
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* The statement with every spec-file argument its OWN failure locations already name taken out.
|
|
295
|
-
*
|
|
296
|
-
* A `file:line` inside a named file supersedes the whole file, so keeping both runs the file plus
|
|
297
|
-
* the locations — the whole suite again, with the failures duplicated inside it. xr-molecule-flash-m118
|
|
298
|
-
* (+45:24) composed just that: the model's `npx playwright test … url-navigation.spec.ts` was
|
|
299
|
-
* narrowed to the 14 `file:line`s AND the bare file, "~297s" for a file and fourteen duplicates.
|
|
267
|
+
* The re-run is "just these": Playwright matches each positional argument against every collected
|
|
268
|
+
* file's path, so a bare file left beside the `file:line`s re-admits ALL of that file's tests.
|
|
269
|
+
* xr-molecule-flash-m118 (+45:24) kept the one file its 14 failures were in ("~297s" for a file and
|
|
270
|
+
* fourteen duplicates); xr-molecule-flash-m134 (+20:0x) kept all four files of a run whose 8
|
|
271
|
+
* failures sat in two of them — 32 tests, ~3 min, instead of the 8 failures' ~30 s. A file with no
|
|
272
|
+
* failure in it has nothing to re-run, so it goes too.
|
|
300
273
|
*
|
|
301
274
|
* @param statement - The Playwright statement, starting at its own `playwright test`.
|
|
302
275
|
* @param locations - The failing `file:line`s the re-run names.
|
|
303
|
-
* @returns The statement without
|
|
276
|
+
* @returns The statement without its bare spec-file arguments.
|
|
304
277
|
*/
|
|
305
278
|
function dropSupersededSpecFiles(statement, locations) {
|
|
306
279
|
if (locations.length === 0)
|
|
@@ -312,9 +285,7 @@ function dropSupersededSpecFiles(statement, locations) {
|
|
|
312
285
|
const kept = statement
|
|
313
286
|
.slice(head.length)
|
|
314
287
|
.replace(/(^|\s+)([^\s]+)(?=\s|$)/g, (whole, _space, token) => {
|
|
315
|
-
|
|
316
|
-
return whole;
|
|
317
|
-
return locations.every((location) => sameSpecFile(token, location)) ? '' : whole;
|
|
288
|
+
return SPEC_FILE_ARGUMENT.test(token) ? '' : whole;
|
|
318
289
|
});
|
|
319
290
|
return `${head}${kept}`;
|
|
320
291
|
}
|
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;
|
|
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;AAiJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAkOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAoEnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;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;AA8ED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAi7ExF"}
|
package/dist/tools.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { posix } from 'path';
|
|
9
10
|
import { commandCutRun, CUT_AT_BUDGET, cutRunSentence, suiteRunExitCode } from './cut-run.js';
|
|
10
11
|
import { addChangeSpan, changedSpan, countLines, renderEditExcerpt, renderSmallFile, } from './edit-excerpt.js';
|
|
11
12
|
import { guardToolExecute, missingParamError, ToolInputError } from './input-normalizer.js';
|
|
@@ -2148,12 +2149,12 @@ export function buildTools(backend, config) {
|
|
|
2148
2149
|
// IO priority (see suiteRunPriorityLaunch). Foreground runs are never
|
|
2149
2150
|
// touched, and a sandbox without the tools runs it unchanged.
|
|
2150
2151
|
const prioritized = suiteRunPriorityLaunch(streaming.command, `sh ${script} > ${log} 2>&1; echo $? > ${exitFile}`);
|
|
2152
|
+
// The target comes back without its shell quotes (`> '/tmp/x.log'` is `/tmp/x.log`);
|
|
2153
|
+
// `posix.resolve` leaves an absolute one absolute and places a relative one under the
|
|
2154
|
+
// cwd the command ran in (X1 flash-m134 +31:20: `${cwd}/${target}` produced
|
|
2155
|
+
// `/workspace/'/tmp/…'`, a path nothing writes).
|
|
2151
2156
|
const ownTarget = outputRedirectTarget(streaming.command);
|
|
2152
|
-
const ownFile = ownTarget
|
|
2153
|
-
? ownTarget.startsWith('/')
|
|
2154
|
-
? ownTarget
|
|
2155
|
-
: `${cwd}/${ownTarget}`
|
|
2156
|
-
: null;
|
|
2157
|
+
const ownFile = ownTarget === null ? null : posix.resolve(cwd, ownTarget);
|
|
2157
2158
|
if (ownFile)
|
|
2158
2159
|
outputFileByTask.set(id, ownFile);
|
|
2159
2160
|
runByTask.set(id, { command: streaming.command, cwd });
|
|
@@ -2456,9 +2457,10 @@ export function buildTools(backend, config) {
|
|
|
2456
2457
|
const target = outputRedirectTarget(await backend.readFile(`/tmp/${taskId}.sh`));
|
|
2457
2458
|
// A relative target was written against the command's own cwd, which
|
|
2458
2459
|
// the handle does not carry; the project root is where a background
|
|
2459
|
-
// command runs unless the caller passed one.
|
|
2460
|
+
// command runs unless the caller passed one. An absolute target is
|
|
2461
|
+
// already resolved and stays as written.
|
|
2460
2462
|
if (target)
|
|
2461
|
-
outputFileByTask.set(taskId,
|
|
2463
|
+
outputFileByTask.set(taskId, posix.resolve(root, target));
|
|
2462
2464
|
}
|
|
2463
2465
|
catch (_error) {
|
|
2464
2466
|
// No script under that id: this process did not start the task, so
|
|
@@ -2587,12 +2589,24 @@ export function buildTools(backend, config) {
|
|
|
2587
2589
|
continue;
|
|
2588
2590
|
}
|
|
2589
2591
|
const found = repeatingFailure(causeLog);
|
|
2590
|
-
|
|
2592
|
+
// The page-renders-nothing cause needs no repeated signature: every spec waits for its
|
|
2593
|
+
// own first element, so the failures differ while the cause does not (m141).
|
|
2594
|
+
const universal = universalCause(causeLog, found, started?.command);
|
|
2595
|
+
if (!found && !universal)
|
|
2591
2596
|
continue;
|
|
2592
2597
|
// The ONE predicate behind this decision and `repeatingFailureNote`'s wording, so the two
|
|
2593
2598
|
// cannot come to different verdicts.
|
|
2594
|
-
const stopCause = earlyReturnCause(found,
|
|
2599
|
+
const stopCause = earlyReturnCause(found, universal);
|
|
2595
2600
|
if (stopCause) {
|
|
2601
|
+
// The note's rows and re-run hint are about the failures the cause covers; an
|
|
2602
|
+
// empty-page cause carries its own list, and a run with no repeated signature has no
|
|
2603
|
+
// other one.
|
|
2604
|
+
const noted = found ?? {
|
|
2605
|
+
count: stopCause.count,
|
|
2606
|
+
message: null,
|
|
2607
|
+
seconds: null,
|
|
2608
|
+
tests: stopCause.tests ?? [],
|
|
2609
|
+
};
|
|
2596
2610
|
// …and only now is the pid walk paid for.
|
|
2597
2611
|
repeatingCauseNoted.add(taskId);
|
|
2598
2612
|
const earlyMs = Date.now() - startedAt;
|
|
@@ -2601,7 +2615,7 @@ export function buildTools(backend, config) {
|
|
|
2601
2615
|
taskId,
|
|
2602
2616
|
status: 'repeating_failure',
|
|
2603
2617
|
waitedMs: earlyMs,
|
|
2604
|
-
note: repeatingFailureNote(
|
|
2618
|
+
note: repeatingFailureNote(noted, {
|
|
2605
2619
|
taskId,
|
|
2606
2620
|
log,
|
|
2607
2621
|
stop: await taskStopCommand(backend, taskId),
|
|
@@ -2614,6 +2628,10 @@ export function buildTools(backend, config) {
|
|
|
2614
2628
|
// to keep waiting for the run's own result, so it must not be told one in the status. The
|
|
2615
2629
|
// look keeps running for the same reason: an app that goes down mid-run turns a shared
|
|
2616
2630
|
// duration into a proven whole-run cause, and that one is worth returning early for.
|
|
2631
|
+
// Without a repeated signature there is no lead to note (the empty-page cause above is
|
|
2632
|
+
// the only reading that needs none).
|
|
2633
|
+
if (!found)
|
|
2634
|
+
continue;
|
|
2617
2635
|
if (!midRunLeadNoted.has(taskId)) {
|
|
2618
2636
|
midRunLeadNoted.add(taskId);
|
|
2619
2637
|
// A run against the sandbox's own app is changed by an edit while it walks — the two
|