@molecule/api-ai-tools 1.8.2 → 1.9.1
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 +413 -31
- package/dist/command-position.d.ts +26 -2
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +221 -24
- package/dist/failure-kind.d.ts +66 -0
- package/dist/failure-kind.d.ts.map +1 -0
- package/dist/failure-kind.js +134 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/sleep-peek.d.ts +51 -0
- package/dist/sleep-peek.d.ts.map +1 -0
- package/dist/sleep-peek.js +76 -0
- package/dist/suite-overlap.d.ts +35 -1
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +57 -2
- package/dist/tools.d.ts +56 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +285 -48
- package/dist/trace-artifacts.d.ts +77 -7
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +344 -47
- package/dist/utilities.d.ts +63 -13
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +277 -64
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A leading `sleep` in an executor's command: the seconds it would have waited, and the command
|
|
3
|
+
* that follows it.
|
|
4
|
+
*
|
|
5
|
+
* X1 xr-molecule-flash-m105 (+08:49 → +10:53, 2026-10-17) spent 225 s of its 60-minute clock
|
|
6
|
+
* inside `exec_command` calls of the shape `sleep N; tail <task log>`. The platform's own launch
|
|
7
|
+
* note said "peek with exec_command: tail <log>" beside "never sleep", and the only concrete
|
|
8
|
+
* instruction was the peek, so the executor built the poll by hand. Two of its three sleeps were
|
|
9
|
+
* provably dead — the task had ended 0.1 s in (`playwright: not found`, exit 127) and another had
|
|
10
|
+
* already printed `Error: No tests found` — and the third spent 120 s asleep beside a live
|
|
11
|
+
* 139-test run whose failures were already in the log.
|
|
12
|
+
*
|
|
13
|
+
* The platform does not have to answer "what is the task doing" from a sleep: `wait_for_task`
|
|
14
|
+
* already returns the moment the task ends, early on a proven repeating cause, and with the
|
|
15
|
+
* progress and failures the log holds. This module answers only the parsing question — is the
|
|
16
|
+
* command's FIRST statement a `sleep` of {@link MIN_PEEK_SLEEP_SECONDS} seconds or more, and what
|
|
17
|
+
* follows it — from the package's own shell parse ({@link locatedCommandSegments}), so a `sleep`
|
|
18
|
+
* named in an argument (`grep -n 'sleep 60' notes.md`) or written after another statement
|
|
19
|
+
* (`cd x && sleep 60`) is not one.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The shortest leading sleep the platform drops: below it the wall clock it costs is noise, and a
|
|
25
|
+
* short settle (`sleep 2; curl …`) is a legitimate thing to run.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MIN_PEEK_SLEEP_SECONDS = 5;
|
|
28
|
+
/** The leading `sleep` a command opens with, and the command that follows it. */
|
|
29
|
+
export interface LeadingSleep {
|
|
30
|
+
/** How many seconds the statement would have slept (GNU sleep sums its operands). */
|
|
31
|
+
seconds: number;
|
|
32
|
+
/** What follows the leading statement, trimmed — '' when the sleep was the whole command. */
|
|
33
|
+
rest: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The leading `sleep` of a command, when the first statement is one and it asks for at least
|
|
37
|
+
* {@link MIN_PEEK_SLEEP_SECONDS}.
|
|
38
|
+
*
|
|
39
|
+
* The statement is read by {@link locatedCommandSegments} — the same parse every other command
|
|
40
|
+
* classifier uses — so only a `sleep` in command position of the FIRST statement counts. The end
|
|
41
|
+
* of that statement is then found by walking forward from its own start: the statement is
|
|
42
|
+
* `sleep <duration>`, whose words carry no quotes and open no substitution, so the first unquoted
|
|
43
|
+
* `;`, newline or `&` ends it. A `|` is refused: dropping the left side of a pipeline would hand
|
|
44
|
+
* the right side its stdin instead, and a `||` fallback would run on a success that never did.
|
|
45
|
+
*
|
|
46
|
+
* @param command - The shell command the executor sent.
|
|
47
|
+
* @returns The seconds and the remaining command, or null when the command does not open with a
|
|
48
|
+
* long `sleep`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function leadingSleepPeek(command: string): LeadingSleep | null;
|
|
51
|
+
//# sourceMappingURL=sleep-peek.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep-peek.d.ts","sourceRoot":"","sources":["../src/sleep-peek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAA;IACf,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAyBrE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A leading `sleep` in an executor's command: the seconds it would have waited, and the command
|
|
3
|
+
* that follows it.
|
|
4
|
+
*
|
|
5
|
+
* X1 xr-molecule-flash-m105 (+08:49 → +10:53, 2026-10-17) spent 225 s of its 60-minute clock
|
|
6
|
+
* inside `exec_command` calls of the shape `sleep N; tail <task log>`. The platform's own launch
|
|
7
|
+
* note said "peek with exec_command: tail <log>" beside "never sleep", and the only concrete
|
|
8
|
+
* instruction was the peek, so the executor built the poll by hand. Two of its three sleeps were
|
|
9
|
+
* provably dead — the task had ended 0.1 s in (`playwright: not found`, exit 127) and another had
|
|
10
|
+
* already printed `Error: No tests found` — and the third spent 120 s asleep beside a live
|
|
11
|
+
* 139-test run whose failures were already in the log.
|
|
12
|
+
*
|
|
13
|
+
* The platform does not have to answer "what is the task doing" from a sleep: `wait_for_task`
|
|
14
|
+
* already returns the moment the task ends, early on a proven repeating cause, and with the
|
|
15
|
+
* progress and failures the log holds. This module answers only the parsing question — is the
|
|
16
|
+
* command's FIRST statement a `sleep` of {@link MIN_PEEK_SLEEP_SECONDS} seconds or more, and what
|
|
17
|
+
* follows it — from the package's own shell parse ({@link locatedCommandSegments}), so a `sleep`
|
|
18
|
+
* named in an argument (`grep -n 'sleep 60' notes.md`) or written after another statement
|
|
19
|
+
* (`cd x && sleep 60`) is not one.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { durationSeconds, locatedCommandSegments } from './command-position.js';
|
|
24
|
+
/**
|
|
25
|
+
* The shortest leading sleep the platform drops: below it the wall clock it costs is noise, and a
|
|
26
|
+
* short settle (`sleep 2; curl …`) is a legitimate thing to run.
|
|
27
|
+
*/
|
|
28
|
+
export const MIN_PEEK_SLEEP_SECONDS = 5;
|
|
29
|
+
/**
|
|
30
|
+
* The leading `sleep` of a command, when the first statement is one and it asks for at least
|
|
31
|
+
* {@link MIN_PEEK_SLEEP_SECONDS}.
|
|
32
|
+
*
|
|
33
|
+
* The statement is read by {@link locatedCommandSegments} — the same parse every other command
|
|
34
|
+
* classifier uses — so only a `sleep` in command position of the FIRST statement counts. The end
|
|
35
|
+
* of that statement is then found by walking forward from its own start: the statement is
|
|
36
|
+
* `sleep <duration>`, whose words carry no quotes and open no substitution, so the first unquoted
|
|
37
|
+
* `;`, newline or `&` ends it. A `|` is refused: dropping the left side of a pipeline would hand
|
|
38
|
+
* the right side its stdin instead, and a `||` fallback would run on a success that never did.
|
|
39
|
+
*
|
|
40
|
+
* @param command - The shell command the executor sent.
|
|
41
|
+
* @returns The seconds and the remaining command, or null when the command does not open with a
|
|
42
|
+
* long `sleep`.
|
|
43
|
+
*/
|
|
44
|
+
export function leadingSleepPeek(command) {
|
|
45
|
+
if (typeof command !== 'string')
|
|
46
|
+
return null;
|
|
47
|
+
const first = locatedCommandSegments(command)[0];
|
|
48
|
+
if (!first || first.start === null || first.segment === null)
|
|
49
|
+
return null;
|
|
50
|
+
if (first.segment.program !== 'sleep')
|
|
51
|
+
return null;
|
|
52
|
+
// GNU sleep sums its operands (`sleep 45 15`); every one of them has to be a duration.
|
|
53
|
+
let seconds = 0;
|
|
54
|
+
for (const arg of first.segment.args) {
|
|
55
|
+
const part = durationSeconds(arg);
|
|
56
|
+
if (part === null)
|
|
57
|
+
return null;
|
|
58
|
+
seconds += part;
|
|
59
|
+
}
|
|
60
|
+
if (seconds < MIN_PEEK_SLEEP_SECONDS)
|
|
61
|
+
return null;
|
|
62
|
+
let end = command.length;
|
|
63
|
+
for (let i = first.start; i < command.length; i++) {
|
|
64
|
+
const char = command[i] ?? '';
|
|
65
|
+
if (char === '|')
|
|
66
|
+
return null;
|
|
67
|
+
if (char === ';' || char === '\n' || char === '&') {
|
|
68
|
+
end = i;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let at = end;
|
|
73
|
+
while (at < command.length && /[;&\s]/.test(command[at] ?? ''))
|
|
74
|
+
at += 1;
|
|
75
|
+
return { seconds, rest: command.slice(at).trim() };
|
|
76
|
+
}
|
package/dist/suite-overlap.d.ts
CHANGED
|
@@ -50,12 +50,44 @@ export declare function withoutHeredocs(command: string): string;
|
|
|
50
50
|
* @returns The target key.
|
|
51
51
|
*/
|
|
52
52
|
export declare function suiteCommandTarget(command: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
|
|
55
|
+
* {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
|
|
56
|
+
*
|
|
57
|
+
* @param command - A command, or the Playwright statement inside it.
|
|
58
|
+
* @returns True when the statement loads a config of its own.
|
|
59
|
+
*/
|
|
60
|
+
export declare function namesSuiteConfig(command: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* The command with a `--config` added to the Playwright invocation it starts — or unchanged when
|
|
63
|
+
* it already names one, or starts no Playwright run.
|
|
64
|
+
*
|
|
65
|
+
* The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
|
|
66
|
+
* it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
|
|
67
|
+
*
|
|
68
|
+
* xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
|
|
69
|
+
* `--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
|
|
70
|
+
* NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
|
|
71
|
+
* that says nothing about the app. The platform composes commands back to the executor too (the
|
|
72
|
+
* narrowed re-run of a finished run's failures), and a command it hands back has to carry the
|
|
73
|
+
* config the run was measured under, never the shape that caused that finding.
|
|
74
|
+
*
|
|
75
|
+
* @param command - The command as the executor sent it.
|
|
76
|
+
* @param config - The config the run loads, when the caller knows it.
|
|
77
|
+
* @returns The command with the flag, or the command unchanged.
|
|
78
|
+
*/
|
|
79
|
+
export declare function withSuiteConfig(command: string, config: string | null | undefined): string;
|
|
53
80
|
/**
|
|
54
81
|
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
55
82
|
* invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
56
83
|
* suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
57
84
|
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
58
85
|
*
|
|
86
|
+
* `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
|
|
87
|
+
* carries it whenever the command itself does not, so a run that dropped `--config` cannot be
|
|
88
|
+
* handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
|
|
89
|
+
* `Cannot navigate to invalid URL` because the re-run loaded no base URL).
|
|
90
|
+
*
|
|
59
91
|
* @param command - The whole-suite run's command.
|
|
60
92
|
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
|
|
61
93
|
* for a run that started outside the suite, the absolute form Playwright matches each file
|
|
@@ -64,9 +96,11 @@ export declare function suiteCommandTarget(command: string): string;
|
|
|
64
96
|
* (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
|
|
65
97
|
* xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
|
|
66
98
|
* a test the request had already said does not count.
|
|
99
|
+
* @param config - The config the run loaded, when the caller knows it; added when the command
|
|
100
|
+
* does not name one.
|
|
67
101
|
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
68
102
|
*/
|
|
69
|
-
export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[]): string;
|
|
103
|
+
export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null): string;
|
|
70
104
|
/**
|
|
71
105
|
* One suite run, as the rule that decides whether a second may start beside it reads it.
|
|
72
106
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED
|
|
1
|
+
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;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;;;;;;;;;;;;;;;;;;;;;;GAsBG;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,GACrB,MAAM,CA8BR;AAED;;;;;;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
|
@@ -74,12 +74,62 @@ export function suiteCommandTarget(command) {
|
|
|
74
74
|
}
|
|
75
75
|
return 'local';
|
|
76
76
|
}
|
|
77
|
+
/** Whether a Playwright statement already names its own config (`-c`, `--config`, `--config=`). */
|
|
78
|
+
const NAMES_CONFIG = /(?:^|\s)(?:-c|--config)(?:\s+|=)/;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
|
|
81
|
+
* {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
|
|
82
|
+
*
|
|
83
|
+
* @param command - A command, or the Playwright statement inside it.
|
|
84
|
+
* @returns True when the statement loads a config of its own.
|
|
85
|
+
*/
|
|
86
|
+
export function namesSuiteConfig(command) {
|
|
87
|
+
return NAMES_CONFIG.test(command);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The command with a `--config` added to the Playwright invocation it starts — or unchanged when
|
|
91
|
+
* it already names one, or starts no Playwright run.
|
|
92
|
+
*
|
|
93
|
+
* The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
|
|
94
|
+
* it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
|
|
95
|
+
*
|
|
96
|
+
* xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
|
|
97
|
+
* `--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
|
|
98
|
+
* NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
|
|
99
|
+
* that says nothing about the app. The platform composes commands back to the executor too (the
|
|
100
|
+
* narrowed re-run of a finished run's failures), and a command it hands back has to carry the
|
|
101
|
+
* config the run was measured under, never the shape that caused that finding.
|
|
102
|
+
*
|
|
103
|
+
* @param command - The command as the executor sent it.
|
|
104
|
+
* @param config - The config the run loads, when the caller knows it.
|
|
105
|
+
* @returns The command with the flag, or the command unchanged.
|
|
106
|
+
*/
|
|
107
|
+
export function withSuiteConfig(command, config) {
|
|
108
|
+
if (!config)
|
|
109
|
+
return command;
|
|
110
|
+
const at = command.search(/\bplaywright\s+test\b/);
|
|
111
|
+
if (at === -1)
|
|
112
|
+
return command;
|
|
113
|
+
const head = command.slice(0, at);
|
|
114
|
+
const rest = command.slice(at);
|
|
115
|
+
const end = rest.search(/&&|\|\||;|\|/);
|
|
116
|
+
const statement = end === -1 ? rest : rest.slice(0, end);
|
|
117
|
+
const tail = end === -1 ? '' : rest.slice(end);
|
|
118
|
+
if (namesSuiteConfig(statement))
|
|
119
|
+
return command;
|
|
120
|
+
return `${head}${statement.replace(/^(playwright\s+test)\b/, `$1 --config ${shellQuote(config)}`)}${tail}`;
|
|
121
|
+
}
|
|
77
122
|
/**
|
|
78
123
|
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
79
124
|
* invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
80
125
|
* suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
81
126
|
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
82
127
|
*
|
|
128
|
+
* `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
|
|
129
|
+
* carries it whenever the command itself does not, so a run that dropped `--config` cannot be
|
|
130
|
+
* handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
|
|
131
|
+
* `Cannot navigate to invalid URL` because the re-run loaded no base URL).
|
|
132
|
+
*
|
|
83
133
|
* @param command - The whole-suite run's command.
|
|
84
134
|
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
|
|
85
135
|
* for a run that started outside the suite, the absolute form Playwright matches each file
|
|
@@ -88,9 +138,11 @@ export function suiteCommandTarget(command) {
|
|
|
88
138
|
* (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
|
|
89
139
|
* xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
|
|
90
140
|
* a test the request had already said does not count.
|
|
141
|
+
* @param config - The config the run loaded, when the caller knows it; added when the command
|
|
142
|
+
* does not name one.
|
|
91
143
|
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
92
144
|
*/
|
|
93
|
-
export function scopedRerunCommand(command, locations, excluded = []) {
|
|
145
|
+
export function scopedRerunCommand(command, locations, excluded = [], config) {
|
|
94
146
|
const at = command.search(/\bplaywright\s+test\b/);
|
|
95
147
|
if (at === -1)
|
|
96
148
|
return command;
|
|
@@ -110,10 +162,13 @@ export function scopedRerunCommand(command, locations, excluded = []) {
|
|
|
110
162
|
.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
111
163
|
.join('|'))}`
|
|
112
164
|
: '';
|
|
165
|
+
// The run's own config: the command's `--config` when it has one, else the config the caller
|
|
166
|
+
// recorded for the run. A re-run without it measures nothing (m113).
|
|
167
|
+
const carry = config && !namesSuiteConfig(statement) ? ` --config ${shellQuote(config)}` : '';
|
|
113
168
|
const scoped = statement
|
|
114
169
|
.replace(/\s*--last-failed\b/g, '')
|
|
115
170
|
.replace(/\s*--retries(?:[= ]\d+)?/g, '')
|
|
116
|
-
.replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0${invert}`);
|
|
171
|
+
.replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0${carry}${invert}`);
|
|
117
172
|
return `${head}${scoped}${tail}`;
|
|
118
173
|
}
|
|
119
174
|
/**
|
package/dist/tools.d.ts
CHANGED
|
@@ -60,8 +60,47 @@ export declare const SUITE_OVER_BUDGET_ADVICE: string;
|
|
|
60
60
|
*/
|
|
61
61
|
export declare function moleculeDistReadNote(path: string): string | null;
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* What the filesystem says about a write target under `node_modules` — the half
|
|
64
|
+
* of the decision a path's spelling cannot make.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* A path that reads `node_modules` is not necessarily an install: a run links
|
|
68
|
+
* `node_modules` beside its e2e copy to borrow the hoisted tree, and that link
|
|
69
|
+
* is its own. X0 xr-molecule-flash-m106 (+07:58) made exactly that link, found
|
|
70
|
+
* it dangling (everything was hoisted to `/workspace/node_modules`), and was
|
|
71
|
+
* refused three times for trying to remove it — 40 s and a "3rd time this turn
|
|
72
|
+
* … ask the user" escalation, in an unattended run, over a broken symlink in
|
|
73
|
+
* the run's own scratch directory. So the guard asks what is there.
|
|
74
|
+
*/
|
|
75
|
+
export interface InstalledPackageFacts {
|
|
76
|
+
/** Whether the `node_modules` entry the path goes through is a symbolic link. */
|
|
77
|
+
readonly symlink: boolean;
|
|
78
|
+
/** Whether a package is really installed there: a `package.json` resolves under the entry. */
|
|
79
|
+
readonly installed: boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Looks at the filesystem for {@link InstalledPackageFacts}.
|
|
83
|
+
*
|
|
84
|
+
* The guard cannot `stat` the path itself: the tools act through a backend (a
|
|
85
|
+
* sandbox over the network for the fleet), so the caller — which holds that
|
|
86
|
+
* backend — supplies the look. {@link shellInstalledPackageProbe} builds one
|
|
87
|
+
* from any shell runner.
|
|
88
|
+
*/
|
|
89
|
+
export type InstalledPackageProbe = (path: string) => Promise<InstalledPackageFacts>;
|
|
90
|
+
/**
|
|
91
|
+
* Build an {@link InstalledPackageProbe} from a shell runner (a backend's
|
|
92
|
+
* `run`, or a sandbox's `exec`): one command reports whether the entry is a
|
|
93
|
+
* link and whether a `package.json` resolves under it.
|
|
94
|
+
*
|
|
95
|
+
* @param run - Runs a command where the path lives.
|
|
96
|
+
* @returns The probe.
|
|
97
|
+
*/
|
|
98
|
+
export declare function shellInstalledPackageProbe(run: (command: string) => Promise<{
|
|
99
|
+
stdout?: string;
|
|
100
|
+
} | null | undefined>): InstalledPackageProbe;
|
|
101
|
+
/**
|
|
102
|
+
* The refusal for a write whose target is inside an INSTALLED package, or null
|
|
103
|
+
* when the path is not under `node_modules` or nothing is installed there.
|
|
65
104
|
*
|
|
66
105
|
* `node_modules` is not the project's code: nothing written there survives the
|
|
67
106
|
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
@@ -74,6 +113,19 @@ export declare function moleculeDistReadNote(path: string): string | null;
|
|
|
74
113
|
* putting the file back. Reading these files stays allowed (that is how the
|
|
75
114
|
* declarations above are read at all).
|
|
76
115
|
*
|
|
116
|
+
* What is protected is the INSTALL, not the spelling: the decision comes from
|
|
117
|
+
* {@link InstalledPackageFacts}, never from the path alone (m106 above). Three
|
|
118
|
+
* things are the run's own to remove or replace —
|
|
119
|
+
*
|
|
120
|
+
* - a `node_modules` that is a SYMLINK: `rm`/`unlink` removes the link, never the
|
|
121
|
+
* tree it points at, so a link the run made cannot clobber anything;
|
|
122
|
+
* - anything under the run's own `.mol-scratch/` directory;
|
|
123
|
+
* - a `node_modules` with no `package.json` under it (nothing was ever installed).
|
|
124
|
+
*
|
|
125
|
+
* A path INSIDE the link is still decided by what resolves there: a link into a
|
|
126
|
+
* real tree is refused, because removing or writing through it reaches the
|
|
127
|
+
* install.
|
|
128
|
+
*
|
|
77
129
|
* The restore route is named because the model that has ALREADY clobbered a file
|
|
78
130
|
* (this refusal is too late for it) is the one who needs it, and because the
|
|
79
131
|
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
@@ -81,9 +133,10 @@ export declare function moleculeDistReadNote(path: string): string | null;
|
|
|
81
133
|
* m91 used, and what both guards leave alone.
|
|
82
134
|
*
|
|
83
135
|
* @param path - An absolute path a tool is about to write.
|
|
136
|
+
* @param probe - Looks at the filesystem where the path lives.
|
|
84
137
|
* @returns The one-line refusal naming the package, or null when nothing is installed there.
|
|
85
138
|
*/
|
|
86
|
-
export declare function installedPackageWriteRefusal(path: string): string | null
|
|
139
|
+
export declare function installedPackageWriteRefusal(path: string, probe?: InstalledPackageProbe): Promise<string | null>;
|
|
87
140
|
/**
|
|
88
141
|
* Build a complete set of AI agent tools bound to an execution backend.
|
|
89
142
|
*
|
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;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAgJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAiOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmEnE;;;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;AAgDD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAu2ExF"}
|