@molecule/api-ai-tools 1.6.2 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +231 -3
- package/dist/command-position.d.ts +1 -1
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +55 -21
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/suite-overlap.d.ts +132 -0
- package/dist/suite-overlap.d.ts.map +1 -0
- package/dist/suite-overlap.js +208 -0
- package/dist/tools.d.ts +43 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +288 -42
- package/dist/utilities.d.ts +38 -2
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +82 -12
- package/package.json +2 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE rule that decides whether a suite run may start while another is already walking, and
|
|
3
|
+
* the command readings both sides of it need.
|
|
4
|
+
*
|
|
5
|
+
* Two sites ask "may a second run start now?" about the same pair of runs — the mid-run note that
|
|
6
|
+
* tells the executor what to do next while the suite it is waiting on is still going
|
|
7
|
+
* ({@link ./utilities.js} `repeatingFailureNote`), and the hold that refuses the run when it
|
|
8
|
+
* arrives (molecule-dev's `suite-overlap-hold.ts`) — and until xr-molecule-flash-m89 they answered
|
|
9
|
+
* it separately. That run's +19:59 `wait_for_task` handed over a repeating cause with "the test
|
|
10
|
+
* alone reproduces the first one … start on them now, while the run keeps walking"; 23 s later the
|
|
11
|
+
* executor sent exactly that re-run, and the overlap hold refused it — "A playwright suite is
|
|
12
|
+
* already running against this app and database … Wait for it." Two orders, one run, and the round
|
|
13
|
+
* trip between them was the whole cost.
|
|
14
|
+
*
|
|
15
|
+
* So the rule lives here ({@link suiteRunInTheWay}) and both of them call it: a run may start only
|
|
16
|
+
* when nothing in flight is already measuring the same app and database. What "the same app and
|
|
17
|
+
* database" MEANS lives here too ({@link suiteCommandTarget}) — the note and the hold have to key
|
|
18
|
+
* a command the same way before they can agree on whether two of them collide, and the re-run a
|
|
19
|
+
* note hands over is built from the same reading of the running command
|
|
20
|
+
* ({@link scopedRerunCommand}).
|
|
21
|
+
*
|
|
22
|
+
* The same treatment for the other pair that answered one question twice: whether a run walks the
|
|
23
|
+
* WHOLE suite or a part of it. The rewriter that decides whether a background Playwright run gets
|
|
24
|
+
* `--retries=0` ({@link ./utilities.js} `streamingBackgroundCommand`) and the platform's own notes
|
|
25
|
+
* (molecule-dev's `suite-cli-override.ts`) asked it separately — a regex there, a predicate here —
|
|
26
|
+
* until xr-molecule-flash-m89 showed the two answers were never the same question
|
|
27
|
+
* ({@link isDiagnosisRun}).
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
import { commandSegments } from './command-position.js';
|
|
32
|
+
import { commandRunsNoTests } from './no-test-run.js';
|
|
33
|
+
/**
|
|
34
|
+
* A command with its heredoc bodies removed: the runs, kills and hosts it names in a file it
|
|
35
|
+
* WRITES are not what the command itself does.
|
|
36
|
+
*
|
|
37
|
+
* The header is kept (`cat > run.sh <<'EOF' …EOF` leaves `cat > run.sh`), which is what the
|
|
38
|
+
* readers here want: everything before the body is still readable as the command's own words.
|
|
39
|
+
*
|
|
40
|
+
* @param command - The shell command.
|
|
41
|
+
* @returns The command without its heredoc bodies.
|
|
42
|
+
*/
|
|
43
|
+
export function withoutHeredocs(command) {
|
|
44
|
+
return command.replace(/<<-?\s*(['"]?)(\w+)\1[\s\S]*?\n\s*\2\b/g, ' ');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The server a suite command points at, as a comparable key: the host it names (`HOST=`,
|
|
48
|
+
* `BASE_URL=`, `PLAYWRIGHT_BASE_URL=`, `--variable host=`, or a bare URL), or `local` for a run
|
|
49
|
+
* that names none — a suite run in the sandbox goes to the sandbox's own app unless the command
|
|
50
|
+
* says otherwise. Every localhost spelling collapses to `local` (one sandbox, one app, one
|
|
51
|
+
* database).
|
|
52
|
+
*
|
|
53
|
+
* @param command - The suite command.
|
|
54
|
+
* @returns The target key.
|
|
55
|
+
*/
|
|
56
|
+
export function suiteCommandTarget(command) {
|
|
57
|
+
const text = withoutHeredocs(command);
|
|
58
|
+
const named = /(?:\b(?:HOST|BASE_URL|PLAYWRIGHT_BASE_URL|E2E_BASE_URL)|--variable\s+host)\s*=\s*['"]?([^\s'"|;&]+)/i.exec(text)?.[1] ?? /https?:\/\/[^\s'"|;&]+/i.exec(text)?.[0];
|
|
59
|
+
if (!named)
|
|
60
|
+
return 'local';
|
|
61
|
+
const value = named.trim();
|
|
62
|
+
let host;
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `http://${value}`);
|
|
65
|
+
host = url.hostname.toLowerCase();
|
|
66
|
+
if (!/^(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1|\[::1\])$/.test(host))
|
|
67
|
+
return url.port ? `${host}:${url.port}` : host;
|
|
68
|
+
}
|
|
69
|
+
catch (_error) {
|
|
70
|
+
// Not a URL at all (a shell variable the command does not expand): compare
|
|
71
|
+
// the token itself, so the same spelling still reads as the same target.
|
|
72
|
+
return value.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
return 'local';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
78
|
+
* invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
79
|
+
* suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
80
|
+
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
81
|
+
*
|
|
82
|
+
* @param command - The whole-suite run's command.
|
|
83
|
+
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
|
|
84
|
+
* for a run that started outside the suite, the absolute form Playwright matches each file
|
|
85
|
+
* argument against.
|
|
86
|
+
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
87
|
+
*/
|
|
88
|
+
export function scopedRerunCommand(command, locations) {
|
|
89
|
+
const at = command.search(/\bplaywright\s+test\b/);
|
|
90
|
+
if (at === -1)
|
|
91
|
+
return command;
|
|
92
|
+
const head = command.slice(0, at);
|
|
93
|
+
const rest = command.slice(at);
|
|
94
|
+
// Only the first statement runs the suite; the rest (`| tail -50`, `&& echo done`)
|
|
95
|
+
// is left exactly as it was.
|
|
96
|
+
const end = rest.search(/&&|\|\||;|\|/);
|
|
97
|
+
const statement = end === -1 ? rest : rest.slice(0, end);
|
|
98
|
+
const tail = end === -1 ? '' : rest.slice(end);
|
|
99
|
+
const scoped = statement
|
|
100
|
+
.replace(/\s*--last-failed\b/g, '')
|
|
101
|
+
.replace(/\s*--retries(?:[= ]\d+)?/g, '')
|
|
102
|
+
.replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0`);
|
|
103
|
+
return `${head}${scoped}${tail}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
107
|
+
*
|
|
108
|
+
* This is the ONE rule the mid-run note and the suite-overlap hold both answer with: a suite run
|
|
109
|
+
* may start only when this returns null, because two suites against one app and database share its
|
|
110
|
+
* rows — the failures one causes the other cannot be told from real ones. A run that invokes no
|
|
111
|
+
* suite is never in the way, and neither is one the candidate command stops first.
|
|
112
|
+
*
|
|
113
|
+
* @param candidate - The run about to start, by the app and database it would measure
|
|
114
|
+
* ({@link suiteCommandTarget}).
|
|
115
|
+
* @param inFlight - The suite runs already going, in the order the caller found them.
|
|
116
|
+
* @returns The first run in the way, or null.
|
|
117
|
+
*/
|
|
118
|
+
export function suiteRunInTheWay(candidate, inFlight) {
|
|
119
|
+
return (inFlight.find((run) => run.runsSuite && run.target === candidate.target && !run.stoppedByCommand) ?? null);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The Playwright invocation of a command, as its own command-position text (`playwright test …`),
|
|
123
|
+
* or null when the command starts no Playwright run.
|
|
124
|
+
*
|
|
125
|
+
* The runner's own word has to END where the sub-command starts (`(?=\s|$)`, not a `\b`): a
|
|
126
|
+
* segment can be a fragment beginning at a `playwright test` printed INSIDE a string
|
|
127
|
+
* (`echo "then npx playwright test" && npx playwright test`), and a match that ends against the
|
|
128
|
+
* closing quote is that echo. A version-pinned `playwright@1.44.0 test` is the same run.
|
|
129
|
+
*/
|
|
130
|
+
const PLAYWRIGHT_RUN = /^(?:@playwright\/test|playwright)(?:@\S+)?\s+test(?=\s|$)/;
|
|
131
|
+
/**
|
|
132
|
+
* What narrows a Playwright run to a part of the suite: a named file or line, a title filter, or
|
|
133
|
+
* the tests the run before it printed (`--last-failed`, `--only-changed`).
|
|
134
|
+
*/
|
|
135
|
+
const NARROWED_RUN = /\.(?:spec|test)\.[cm]?[jt]sx?\b|\.[cm]?[jt]sx?:\d+|(?:^|\s)(?:-g|--grep(?:-invert)?|--last-failed|--only-changed)\b/;
|
|
136
|
+
/**
|
|
137
|
+
* The Playwright invocation's text when the command starts one, or null.
|
|
138
|
+
*
|
|
139
|
+
* @param command - The command as the executor sent it.
|
|
140
|
+
* @returns The invocation's command-position text, or null.
|
|
141
|
+
*/
|
|
142
|
+
function playwrightRun(command) {
|
|
143
|
+
return (commandSegments(withoutHeredocs(command)).find((segment) => PLAYWRIGHT_RUN.test(segment.text))
|
|
144
|
+
?.text ?? null);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Whether a `--max-failures` BOUNDS the walk: the flag is given a count above zero.
|
|
148
|
+
*
|
|
149
|
+
* Playwright's own spelling of "no cap" is zero — `maxFailures: 0` "disables the feature" — and
|
|
150
|
+
* that is the flag the platform's `testRunNote` asks for on the uncapped whole-suite run ("run the
|
|
151
|
+
* whole suite once with `--max-failures=0`"), which is why this is not a plain flag test.
|
|
152
|
+
*
|
|
153
|
+
* @param command - The command as the executor sent it.
|
|
154
|
+
* @returns True when a failure cap would stop the walk.
|
|
155
|
+
*/
|
|
156
|
+
function boundsFailures(command) {
|
|
157
|
+
const flag = /(?:^|[\s;&|(])--max-failures(?![-\w])/.exec(command);
|
|
158
|
+
if (!flag)
|
|
159
|
+
return false;
|
|
160
|
+
const value = /(?:^|[\s;&|(])--max-failures(?:=|\s+)(['"]?)(\d[\d_]*)/.exec(command)?.[2];
|
|
161
|
+
// A `--max-failures` with no count at all is not the uncapped spelling of anything: it is read
|
|
162
|
+
// as a cap, the way Playwright's own default reading of the flag is a count of failures.
|
|
163
|
+
if (value === undefined)
|
|
164
|
+
return true;
|
|
165
|
+
return Number(value.replace(/_/g, '')) !== 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Whether a command walks a PART of a suite instead of all of it: a cap that stops it at a failure
|
|
169
|
+
* count, the failures the last run printed (`--last-failed`, `--only-changed`), a title filter, or
|
|
170
|
+
* named files/lines.
|
|
171
|
+
*
|
|
172
|
+
* THE one reading of that question, in one place. Two sites act on the answer and must never
|
|
173
|
+
* disagree:
|
|
174
|
+
*
|
|
175
|
+
* - the command rewriter that adds `--retries=0` to a background Playwright run
|
|
176
|
+
* ({@link ./utilities.js} `streamingBackgroundCommand`) — a diagnosis walks each failure once,
|
|
177
|
+
* while the run the suite is judged by keeps the suite's own retries; and
|
|
178
|
+
* - the platform's own notes (molecule-dev's `suite-cli-override.ts`), which must not tell the
|
|
179
|
+
* executor to drop the `--retries=0` the platform itself has just added — the contradiction X1
|
|
180
|
+
* flash-m74 and m75 filed, and m76 filed a third time.
|
|
181
|
+
*
|
|
182
|
+
* Until xr-molecule-flash-m89 they were a regex and a predicate that only LOOKED alike, and the
|
|
183
|
+
* gap was exactly the run the platform had asked for: its `testRunNote` says "run the whole suite
|
|
184
|
+
* once with `--max-failures=0`", and the rewriter read that cap flag as a capped diagnosis and
|
|
185
|
+
* stripped the suite's retries from the uncapped proof run that follows a deploy — the one run the
|
|
186
|
+
* graded run is compared against (+25:03, +31:39, +36:40 and +49:57, the last two against the
|
|
187
|
+
* deployed URL, where a network blip is likeliest and the retries it dropped are the ones that
|
|
188
|
+
* absorb it).
|
|
189
|
+
*
|
|
190
|
+
* `--max-failures=0` is Playwright's own "no cap" ({@link boundsFailures}), so it is not a
|
|
191
|
+
* diagnosis. A command that starts no Playwright run is not one either (false): the question is
|
|
192
|
+
* about a run already read as a Playwright suite.
|
|
193
|
+
*
|
|
194
|
+
* @param command - The command as the executor sent it.
|
|
195
|
+
* @returns True when the run is a diagnosis or a re-run, not the whole suite.
|
|
196
|
+
*/
|
|
197
|
+
export function isDiagnosisRun(command) {
|
|
198
|
+
if (boundsFailures(command))
|
|
199
|
+
return true;
|
|
200
|
+
const run = playwrightRun(command);
|
|
201
|
+
if (run === null)
|
|
202
|
+
return false;
|
|
203
|
+
// A run that only collects and prints (`playwright test --list`, `--version`) walks no part of
|
|
204
|
+
// the suite either, and is not the whole-suite run the retries belong on.
|
|
205
|
+
if (commandRunsNoTests(run))
|
|
206
|
+
return true;
|
|
207
|
+
return NARROWED_RUN.test(run);
|
|
208
|
+
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -7,6 +7,24 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import type { AITool } from '@molecule/api-ai';
|
|
10
|
+
/**
|
|
11
|
+
* The command that stops a running background task, read from the sandbox's own pid files.
|
|
12
|
+
*
|
|
13
|
+
* THE one builder for it, so every note that hands the executor a way to stop a run hands over the
|
|
14
|
+
* same pid-only form: a `pkill -f <something>` matches the shell running it (its argv IS the
|
|
15
|
+
* command), and `molecule-dev/api/src/ai/pattern-kill-guard.ts` holds exactly that — X1
|
|
16
|
+
* xr-molecule-flash-m87's stuck note suggested `kill $(cat /tmp/<id>.pid) 2>/dev/null; pkill -f
|
|
17
|
+
* <id>.sh`, which the platform's own guard then refused, and the same shape cost a refused call at
|
|
18
|
+
* +21:57. The pid tree is the whole of what the pattern was for: the detached shell and every
|
|
19
|
+
* process under it.
|
|
20
|
+
*
|
|
21
|
+
* @param backend - The execution backend.
|
|
22
|
+
* @param taskId - The task's `mol-bg-…` handle.
|
|
23
|
+
* @returns The command to hand the executor — `kill <pids>` — falling back to the task's own pid
|
|
24
|
+
* file when the walk comes back empty (a sandbox without `pgrep`, or a pid file already gone,
|
|
25
|
+
* in which case the pid-file form is still the one the platform's guard names).
|
|
26
|
+
*/
|
|
27
|
+
export declare function taskStopCommand(backend: ExecutionBackend, taskId: string): Promise<string>;
|
|
10
28
|
import type { ExecutionBackend, ToolBuildConfig } from './types.js';
|
|
11
29
|
/**
|
|
12
30
|
* The steer attached to a SUCCESSFUL read of a package's built output. The ENOENT
|
|
@@ -21,6 +39,31 @@ import type { ExecutionBackend, ToolBuildConfig } from './types.js';
|
|
|
21
39
|
* @returns The note, or null when the path is not inside a package's `dist`.
|
|
22
40
|
*/
|
|
23
41
|
export declare function moleculeDistReadNote(path: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* The refusal for a write whose target is inside an installed package, or null
|
|
44
|
+
* when the path is not under a `node_modules` directory.
|
|
45
|
+
*
|
|
46
|
+
* `node_modules` is not the project's code: nothing written there survives the
|
|
47
|
+
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
48
|
+
* shared (the pre-installed superset) breaks every other run through it. X0
|
|
49
|
+
* xr-molecule-flash-m91 (+04:43) called `write_file` on
|
|
50
|
+
* `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts` while reading
|
|
51
|
+
* the package's declarations and clobbered its runtime module a call later
|
|
52
|
+
* (`module.exports = {}` over the ESM handler its own code imports) — the write
|
|
53
|
+
* answered `ok: true`, and 18 s went to noticing, `npm pack`-ing the package and
|
|
54
|
+
* putting the file back. Reading these files stays allowed (that is how the
|
|
55
|
+
* declarations above are read at all).
|
|
56
|
+
*
|
|
57
|
+
* The restore route is named because the model that has ALREADY clobbered a file
|
|
58
|
+
* (this refusal is too late for it) is the one who needs it, and because the
|
|
59
|
+
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
60
|
+
* sandbox. `npm pack` + a `tar` extraction over the package directory is what
|
|
61
|
+
* m91 used, and what both guards leave alone.
|
|
62
|
+
*
|
|
63
|
+
* @param path - An absolute path a tool is about to write.
|
|
64
|
+
* @returns The one-line refusal naming the package, or null when nothing is installed there.
|
|
65
|
+
*/
|
|
66
|
+
export declare function installedPackageWriteRefusal(path: string): string | null;
|
|
24
67
|
/**
|
|
25
68
|
* Build a complete set of AI agent tools bound to an execution backend.
|
|
26
69
|
*
|
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;AAiF9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAwHD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAoEnE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBxE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAgjExF"}
|