@molecule/api-ai-tools 1.7.0 → 1.8.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.
@@ -64,6 +64,122 @@ const PACKAGE_RUNNER_HEADS = new Set(['bun', 'npm', 'pnpm', 'yarn']);
64
64
  * a script of the project's, and nothing is stripped from them.
65
65
  */
66
66
  const PACKAGE_RUNNER_SUBCOMMANDS = new Set(['dlx', 'exec', 'x']);
67
+ /**
68
+ * npm's value-taking options: `npm exec`'s own (`-p/--package`, `-c/--call`, `-w/--workspace`,
69
+ * `--prefix`) and the string-valued configs `npm config ls -l` lists in the sandbox image, which
70
+ * npm accepts on the command line of any invocation.
71
+ */
72
+ const NPM_VALUE_FLAGS = [
73
+ '-c',
74
+ '--call',
75
+ '-p',
76
+ '--package',
77
+ '-w',
78
+ '--workspace',
79
+ '--prefix',
80
+ '--registry',
81
+ '--cache',
82
+ '--userconfig',
83
+ '--globalconfig',
84
+ '--loglevel',
85
+ '--logs-dir',
86
+ '--script-shell',
87
+ '--shell',
88
+ '--tag',
89
+ '--otp',
90
+ '--scope',
91
+ '--location',
92
+ '--user-agent',
93
+ '--which',
94
+ '--viewer',
95
+ '--proxy',
96
+ '--https-proxy',
97
+ '--cert',
98
+ '--key',
99
+ '--ca',
100
+ '--cafile',
101
+ '--editor',
102
+ '--message',
103
+ '--node-options',
104
+ '--save-prefix',
105
+ '--preid',
106
+ '--access',
107
+ '--also',
108
+ '--audit-level',
109
+ '--auth-type',
110
+ '--before',
111
+ '--browser',
112
+ '--cidr',
113
+ '--cpu',
114
+ '--depth',
115
+ '--expect-result-count',
116
+ '--expect-results',
117
+ '--install-strategy',
118
+ '--libc',
119
+ '--local-address',
120
+ '--lockfile-version',
121
+ '--only',
122
+ '--os',
123
+ '--pack-destination',
124
+ '--provenance-file',
125
+ '--replace-registry-host',
126
+ '--sbom-format',
127
+ '--sbom-type',
128
+ '--searchexclude',
129
+ '--searchopts',
130
+ '--tag-version-prefix',
131
+ ];
132
+ /** pnpm dlx's value-taking options (`pnpm dlx --help`); its `-c` is the boolean shell-mode. */
133
+ const PNPM_DLX_VALUE_FLAGS = ['--allow-build', '--package', '--reporter'];
134
+ /**
135
+ * The package manager's OWN options that take their value from the next word — read from the
136
+ * versions the sandbox image (`node:22-trixie-slim`) ships: npm 10.9.9 (`npm exec --help`, and
137
+ * every string-valued key of `npm config ls -l`), pnpm 10.34.7 (`pnpm help exec`,
138
+ * `pnpm dlx --help`) and Yarn 4.18.1 (`yarn dlx --help`). A flag absent here is skipped as a
139
+ * boolean, which is what every documented switch is.
140
+ *
141
+ * The table is keyed per manager because the same short flag means different things in two of
142
+ * them: `-c` is npm's `--call <command>` (a value, and a command of its own —
143
+ * {@link PACKAGE_RUNNER_CALL_FLAGS}) but pnpm's `--shell-mode` (a switch), and `-w` is npm's
144
+ * `--workspace <name>` (a value) but pnpm's `--workspace-root` (a switch).
145
+ */
146
+ const PACKAGE_RUNNER_VALUE_FLAGS = {
147
+ // npm exec's documented options, plus the config flags npm accepts on any invocation.
148
+ npx: NPM_VALUE_FLAGS,
149
+ 'npm exec': NPM_VALUE_FLAGS,
150
+ 'npm x': NPM_VALUE_FLAGS,
151
+ // `npx --prefix /app playwright test` is how flash-m101's run reached the app's own Playwright.
152
+ bunx: ['-p', '--package'],
153
+ 'bun x': ['-p', '--package'],
154
+ pnpx: PNPM_DLX_VALUE_FLAGS,
155
+ 'pnpm dlx': PNPM_DLX_VALUE_FLAGS,
156
+ 'pnpm x': PNPM_DLX_VALUE_FLAGS,
157
+ 'pnpm exec': [
158
+ '-C',
159
+ '--dir',
160
+ '--loglevel',
161
+ '--resume-from',
162
+ '--reporter',
163
+ '--filter',
164
+ '--filter-prod',
165
+ '--changed-files-ignore-pattern',
166
+ '--workspace-concurrency',
167
+ '--prefix',
168
+ ],
169
+ 'yarn dlx': ['-p', '--package'],
170
+ 'yarn exec': [],
171
+ 'yarn x': [],
172
+ };
173
+ /**
174
+ * The package-runner options whose VALUE is itself a command (`npm exec -c '<command>'`), so it is
175
+ * parsed as one: `npx -c 'playwright test'` runs Playwright, and its program is not the word after
176
+ * the flag.
177
+ */
178
+ const PACKAGE_RUNNER_CALL_FLAGS = {
179
+ npx: ['-c', '--call'],
180
+ 'npm exec': ['-c', '--call'],
181
+ 'npm x': ['-c', '--call'],
182
+ };
67
183
  /** Wrapper flags that are followed by their own value (`sudo -u root`, `timeout -k 5`). */
68
184
  const WRAPPER_VALUE_FLAGS = {
69
185
  env: ['-S', '--split-string', '-u', '--unset', '-C', '--chdir'],
@@ -149,6 +265,44 @@ function flagWords(words, from, wrapper) {
149
265
  }
150
266
  return count;
151
267
  }
268
+ /**
269
+ * How many words a package runner (`npx`, `npm exec`, `pnpm dlx`, `yarn dlx`) owns before the
270
+ * program it runs — its own options, and each value flag's value — plus the command string of a
271
+ * `-c '<command>'`, which is a command of its own rather than the program word.
272
+ *
273
+ * A `--` ends the options: `npm exec --prefix x -- playwright test` runs `playwright` with `test`,
274
+ * so the terminator is consumed and the walk stops there.
275
+ *
276
+ * @param words - The segment's words.
277
+ * @param from - The index just after the runner (or after `npm exec`).
278
+ * @param key - The runner's table key (`npm`, `npx`, `pnpm exec`, `yarn dlx`, …).
279
+ * @returns The count of words the runner owns, and the `-c` command string when it carries one.
280
+ */
281
+ function packageRunnerOptions(words, from, key) {
282
+ const withValue = PACKAGE_RUNNER_VALUE_FLAGS[key] ?? [];
283
+ const callFlags = PACKAGE_RUNNER_CALL_FLAGS[key] ?? [];
284
+ let count = 0;
285
+ while (from + count < words.length) {
286
+ const word = words[from + count] ?? '';
287
+ if (!word.startsWith('-'))
288
+ break;
289
+ // `--` ends the runner's options; the program is the next word.
290
+ if (word === '--')
291
+ return { count: count + 1, nested: null };
292
+ const flag = unquoted(word);
293
+ // `--flag=value` carries its value inside the same word.
294
+ if (word.includes('=')) {
295
+ count += 1;
296
+ continue;
297
+ }
298
+ if (callFlags.includes(flag)) {
299
+ const value = words[from + count + 1];
300
+ return { count: count + 2, nested: value === undefined ? null : unquoted(value) };
301
+ }
302
+ count += withValue.includes(flag) ? 2 : 1;
303
+ }
304
+ return { count, nested: null };
305
+ }
152
306
  /**
153
307
  * The `-c '<command>'` string a shell was handed, or null when the shell was given a FILE
154
308
  * (`bash run.sh`) — the wrapper walk then steps past its flags instead.
@@ -201,12 +355,19 @@ function commandStart(words) {
201
355
  continue;
202
356
  }
203
357
  if (PACKAGE_RUNNERS.has(name)) {
204
- at += 1 + flagWords(words, at + 1, name);
358
+ const runner = packageRunnerOptions(words, at + 1, name);
359
+ if (runner.nested !== null)
360
+ return { at, nested: runner.nested, timeoutSeconds };
361
+ at += 1 + runner.count;
205
362
  continue;
206
363
  }
207
364
  if (PACKAGE_RUNNER_HEADS.has(name) &&
208
365
  PACKAGE_RUNNER_SUBCOMMANDS.has(unquoted(words[at + 1] ?? ''))) {
209
- at += 2 + flagWords(words, at + 2, name);
366
+ const subcommand = unquoted(words[at + 1] ?? '');
367
+ const runner = packageRunnerOptions(words, at + 2, `${name} ${subcommand}`);
368
+ if (runner.nested !== null)
369
+ return { at, nested: runner.nested, timeoutSeconds };
370
+ at += 2 + runner.count;
210
371
  continue;
211
372
  }
212
373
  if (!LAUNCHERS.has(name))
package/dist/cut-run.d.ts CHANGED
@@ -24,6 +24,12 @@
24
24
  * `backends/sandbox.ts`), which reports 124 whatever the pipeline did, and returns everything
25
25
  * printed until then. The budget is the tool's own, so its number belongs to the tool.
26
26
  *
27
+ * A runner's closing summary is what separates "used the bound and finished" from "stopped at the
28
+ * bound", but it is only half the evidence: Playwright prints one when its process is stopped too,
29
+ * listing just the tests it reached. So a summary is a finish only when it accounts for the total
30
+ * the runner announced before it started ({@link suiteSummary}) — a summary that does not
31
+ * reconcile is a stopped run like any other, and the result says so.
32
+ *
27
33
  * @module
28
34
  */
29
35
  /**
@@ -46,14 +52,82 @@ export type CommandRunState = 'ran' | typeof CUT_AT_BUDGET;
46
52
  */
47
53
  export declare function commandRunState(result: unknown): CommandRunState;
48
54
  /**
49
- * Whether text carries a runner's closing summary ({@link SUITE_TALLY}).
55
+ * What a runner's own closing summary reports, and whether it accounts for the whole run.
56
+ *
57
+ * A summary is not by itself proof that the run finished. Playwright prints one when its process
58
+ * is stopped too (SIGTERM/SIGINT), listing only the tests it reached — so a run killed at 36 of
59
+ * 139 tests ends with the same `36 passed (2.5m)` line as a run that walked all 139. The only
60
+ * thing that separates them is the total the runner announced before it started
61
+ * (`Running 139 tests using 1 worker`), which {@link suiteSummary} reconciles the summary against.
62
+ */
63
+ export interface SuiteSummary {
64
+ /**
65
+ * The total the runner announced before its first test, or null when it announced none (Hurl
66
+ * announces nothing; Vitest's own summary line carries its total in parentheses).
67
+ */
68
+ announced: number | null;
69
+ /**
70
+ * Every test the summary accounts for — the ones it passed, failed, marked flaky, timed out,
71
+ * interrupted, skipped or left "did not run". This is how far the walk got, which is what a
72
+ * stopped run's sentence says; a summary that accounts for fewer than {@link announced} was
73
+ * printed by a process that stopped early.
74
+ */
75
+ accounted: number;
76
+ /** Whether the summary accounts for every test the runner announced. */
77
+ finished: boolean;
78
+ }
79
+ /**
80
+ * What the runner's closing summary accounts for, or null when it printed none.
81
+ *
82
+ * Three runners' summaries are read, each by its own wording: Playwright's per-status lines
83
+ * (reconciled against its `Running N tests` announcement), Vitest's single `Tests …` line (its own
84
+ * parenthesised total), and Hurl's `Executed files:` block (which it prints only at the end, and
85
+ * which announces no total — so it is a finish by construction).
86
+ *
87
+ * @param text - The run's stdout and stderr, RAW (a truncated copy can lose the announcement).
88
+ * @returns The summary, or null when the text carries none.
89
+ */
90
+ export declare function suiteSummary(text: string): SuiteSummary | null;
91
+ /**
92
+ * Whether text carries a runner's closing summary.
93
+ *
94
+ * A summary is NOT a finish on its own — a stopped Playwright run prints one too. Use
95
+ * {@link suiteSummary} and its `finished` for that.
50
96
  *
51
97
  * @param text - The run's stdout and stderr.
52
98
  * @returns True when the runner's own end-of-run summary is there.
53
99
  */
54
100
  export declare function suiteTally(text: string): boolean;
101
+ /**
102
+ * The exit code a finished suite run may report.
103
+ *
104
+ * The code in a background result is the code of the command's LAST pipeline stage. When a stage
105
+ * after the suite runner still consumes its output (`| tee run.log`, or a filter the platform could
106
+ * not prove output-only), that stage produced the code, not the run — and `… | grep … | tail -20`
107
+ * exited 0 for a run with a failure in it (xr-molecule-flash-m101 +25:54). No code is reported
108
+ * then: the log's own tally decides.
109
+ *
110
+ * The same reading covers a code of 0 beside a tally that names a failure: whatever ended the
111
+ * pipeline, a run whose log counts a failure did not pass.
112
+ *
113
+ * @param exitCode - The code the pipeline reported (null when it has none).
114
+ * @param output - The run's full log.
115
+ * @param piped - Whether a pipeline stage after the suite runner still consumes its output.
116
+ * @returns The code the run may report, or null when the code is the filter's or contradicts the tally.
117
+ */
118
+ export declare function suiteRunExitCode(exitCode: number | null, output: string, piped: boolean): number | null;
55
119
  /** What stopped a run. */
56
- export type CutBy = 'tool-budget' | 'command-timeout';
120
+ export type CutBy =
121
+ /** This tool's own budget (the backend's `timeout`, just past it). */
122
+ 'tool-budget'
123
+ /** The `timeout <N>` the command itself carried. */
124
+ | 'command-timeout'
125
+ /**
126
+ * Something outside the run: the runner's summary does not account for the total it announced,
127
+ * and neither of the two bounds above had fired — the process was killed by something this
128
+ * platform does not own (the sandbox's own process supervisor, the box going away, a signal).
129
+ */
130
+ | 'process-stop';
57
131
  /** A run a budget stopped, as its result reports it. */
58
132
  export interface CutRun {
59
133
  /** The bound the run was allowed, in seconds. */
@@ -65,9 +139,15 @@ export interface CutRun {
65
139
  * the pipeline's filter (`… | tail`) rather than to the run — the case that read as success.
66
140
  */
67
141
  exitCode: number | null;
68
- /** How many files or tests the runner's own output shows finishing, or null when it shows none. */
142
+ /**
143
+ * How far the run got, as its own output counts it — the files that finished, or the tests its
144
+ * summary accounts for — or null when the output says nothing.
145
+ */
69
146
  ran: number | null;
70
- /** How many the run was walking, when the runner printed it (Playwright's `[n/K]`), or null. */
147
+ /**
148
+ * How many the run was walking: the total its runner announced (`Running 139 tests`), or
149
+ * Playwright's `[n/K]`, whichever the output carries; null when neither does.
150
+ */
71
151
  total: number | null;
72
152
  /** The noun the runner counts in. */
73
153
  noun: 'files' | 'tests';
@@ -80,6 +160,9 @@ export interface CutRun {
80
160
  *
81
161
  * Evidence, in order — never a guess:
82
162
  *
163
+ * - the runner's own summary, when it printed one: it is a finish only if it accounts for the total
164
+ * the runner announced. A summary that accounts for fewer is a walk a process stopped early, and
165
+ * falls through to the branches below with its own counts ({@link suiteSummary}).
83
166
  * - `timeout`'s own 124 (the code the backend's wrapper returns whenever the TOOL's budget fired,
84
167
  * whatever the pipeline ended with) — and, when the clock says the command's own smaller bound
85
168
  * ran out first and less than the budget, that bound instead.
@@ -88,14 +171,19 @@ export interface CutRun {
88
171
  * killed at its own bound. Its exit code, when the pipeline handed one back, is the filter's.
89
172
  * - the run used this tool's whole budget behind a filter that withheld its output, and printed no
90
173
  * closing summary: the budget stopped it, and no code in the result is the run's.
174
+ * - a summary that does not account for its announced total, with neither bound fired: something
175
+ * outside this platform stopped the process ({@link CutBy} `process-stop`).
91
176
  *
92
177
  * @param command - The command the executor sent.
93
178
  * @param result - Its result.
94
179
  * @param elapsedMs - How long the command ran, in ms.
95
180
  * @param budgetSeconds - This tool's foreground budget, in seconds (0 when it has none).
181
+ * @param rawOutput - The run's output BEFORE truncation, when the caller has it: the announcement
182
+ * a summary is reconciled against is the first line a long run prints, which is exactly what a
183
+ * size cap drops. Detection reads this; the sentence's last line still comes from `result`.
96
184
  * @returns The stopped run, or null.
97
185
  */
98
- export declare function commandCutRun(command: string, result: unknown, elapsedMs: number, budgetSeconds?: number): CutRun | null;
186
+ export declare function commandCutRun(command: string, result: unknown, elapsedMs: number, budgetSeconds?: number, rawOutput?: string): CutRun | null;
99
187
  /**
100
188
  * The facts a result records about the run a budget stopped it in — what a reader needs to say
101
189
  * how far the run got WITHOUT re-reading its log or re-deriving the counts.
@@ -110,12 +198,14 @@ export declare function commandCutRun(command: string, result: unknown, elapsedM
110
198
  */
111
199
  export declare function cutRunFacts(result: unknown): CutRun | null;
112
200
  /**
113
- * The first line of a stopped run's result: how long it was allowed, what stopped it, how far it
114
- * got, and the last thing it printed.
201
+ * The first line of a stopped run's result: how far it got, what stopped it, the last thing it
202
+ * printed, and what the reader must do next.
115
203
  *
116
204
  * Written to be quotable by the model in one sentence and to leave no room for "nearly passing":
117
205
  * the counts come from the runner's own output ({@link runProgress}), and a `total` the caller
118
- * read elsewhere (the suite's own file count) fills in the "M of K" it can then state.
206
+ * read elsewhere (the suite's own file count) fills in the "of N" it can then state. The sentence
207
+ * says how many tests the stop left unrun — a number a reader cannot round up to "the suite
208
+ * passed" — and hands over the one move that survives a long run: run it in the background.
119
209
  *
120
210
  * @param run - The stopped run.
121
211
  * @returns The sentence.
@@ -1 +1 @@
1
- {"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AA0BD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAErD,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,mGAAmG;IACnG,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,gGAAgG;IAChG,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AA2CD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,GAChB,MAAM,GAAG,IAAI,CA2Df;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgB1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBlD"}
1
+ {"version":3,"file":"cut-run.d.ts","sourceRoot":"","sources":["../src/cut-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH;;;GAGG;AACH,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAE5C,kFAAkF;AAClF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,aAAa,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAGhE;AAiBD;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAA;CAClB;AAkDD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4B9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAIf;AAED,0BAA0B;AAC1B,MAAM,MAAM,KAAK;AACf,sEAAsE;AACpE,aAAa;AACf,oDAAoD;GAClD,iBAAiB;AACnB;;;;GAIG;GACD,cAAc,CAAA;AAElB,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,EAAE,KAAK,CAAA;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,qCAAqC;IACrC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,yEAAyE;IACzE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAsDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,aAAa,SAAI,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAgFf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgB1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAuBlD"}