@molecule/api-ai-tools 1.12.0 → 1.13.0
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 +352 -4
- package/dist/command-position.d.ts +33 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +66 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/playwright-report.d.ts +210 -0
- package/dist/playwright-report.d.ts.map +1 -0
- package/dist/playwright-report.js +406 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +94 -5
- package/dist/trace-artifacts.d.ts +12 -0
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +1 -0
- package/dist/utilities.d.ts +41 -4
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +144 -26
- package/package.json +2 -2
package/dist/utilities.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { posix } from 'path';
|
|
8
|
-
import { commandSegments, executedCommands, locatedCommandSegments, statementArgumentEnd, withoutTimeoutWrapper, } from './command-position.js';
|
|
8
|
+
import { commandSegments, executedCommands, locatedCommandSegments, statementArgumentEnd, statementBoundary, withoutTimeoutWrapper, } from './command-position.js';
|
|
9
9
|
import { classifyRunFailure } from './failure-kind.js';
|
|
10
10
|
import { commandRunsNoTests } from './no-test-run.js';
|
|
11
|
+
import { PLAYWRIGHT_REPORT_ENV } from './playwright-report.js';
|
|
11
12
|
import { isDiagnosisRun, scopedRerunCommand, suiteCommandTarget, suiteRunInTheWay, withoutHeredocs, withSuiteConfig, } from './suite-overlap.js';
|
|
12
13
|
/**
|
|
13
14
|
* Shell-safe quoting using single quotes. Unlike JSON.stringify (double quotes),
|
|
@@ -612,10 +613,17 @@ function isStatusOnlyStep(step) {
|
|
|
612
613
|
* excludes by name ({@link DiagnosisRunContext}). A `--grep-invert` of exactly those is the
|
|
613
614
|
* request's own scope, not a diagnosis, so it keeps the suite's retries on a deployed proof
|
|
614
615
|
* (xr-molecule-flash-m170 +33:16).
|
|
615
|
-
* @
|
|
616
|
-
*
|
|
617
|
-
|
|
618
|
-
|
|
616
|
+
* @param reportPath - Where this run should write Playwright's standard `json` report, when the
|
|
617
|
+
* caller keeps one ({@link ./playwright-report.js}). The reporter is added to the run's own
|
|
618
|
+
* reporter set, never in place of one, and its output goes to that file through
|
|
619
|
+
* `PLAYWRIGHT_JSON_OUTPUT_FILE` — so the log, the executors' own reporters and every verdict
|
|
620
|
+
* stay exactly as they are. Omitted, or a run whose reporter set is not this function's to place
|
|
621
|
+
* (already names `json`, sets the variable itself, or whose runner cannot be located), leaves
|
|
622
|
+
* the command untouched and the result's `reportPath` unset.
|
|
623
|
+
* @returns The command to run, one sentence per change made (empty when none), and the report path
|
|
624
|
+
* when one was actually wired — or `error` when the command starts the suite more than once.
|
|
625
|
+
*/
|
|
626
|
+
export function streamingBackgroundCommand(command, context, reportPath) {
|
|
619
627
|
// A command that runs no test is not a run to keep a growing log for: nothing about it is
|
|
620
628
|
// rewritten (flash-m79's `--list` was read as a whole-suite run end to end, and got a ~4 KB
|
|
621
629
|
// refusal to send itself again with `--max-failures=15`).
|
|
@@ -743,14 +751,26 @@ export function streamingBackgroundCommand(command, context) {
|
|
|
743
751
|
// The last step's reporters: one printer, and it is `list`. Every branch below only ever moves
|
|
744
752
|
// the command TO that shape — a reporter set is never left with two printers in it.
|
|
745
753
|
const reporters = finalPlaywrightRun ? lastReporterFlag(lastStep) : null;
|
|
754
|
+
// The standard machine report this run may carry (`playwright-report.ts`). It is never taken from
|
|
755
|
+
// the executor: a reporter set already naming `json`, or a command that sets
|
|
756
|
+
// `PLAYWRIGHT_JSON_OUTPUT_FILE` itself, is the executor's own artifact and is left untouched.
|
|
757
|
+
const reportWanted = reportPath !== undefined &&
|
|
758
|
+
finalPlaywrightRun &&
|
|
759
|
+
!/(?:^|[\s;])PLAYWRIGHT_JSON_OUTPUT_FILE=/.test(out) &&
|
|
760
|
+
!(reporters?.values.includes('json') ?? false);
|
|
761
|
+
let report = null;
|
|
746
762
|
if (reporters) {
|
|
747
763
|
const printing = reporters.values.filter((v) => PRINTING_REPORTERS.has(v));
|
|
748
764
|
// Already exactly `list`: the shape, and nothing printing beside it. Left verbatim — the
|
|
749
765
|
// executor's own spelling of the command is not rewritten for nothing.
|
|
750
|
-
|
|
766
|
+
const printerRewrite = printing.length > 0 && !(printing.length === 1 && printing[0] === 'list');
|
|
767
|
+
const addReport = reportWanted && !reporters.values.includes('json');
|
|
768
|
+
if (printerRewrite || addReport) {
|
|
751
769
|
// The printing reporters become ONE `list`, in the place of the first of them; every other
|
|
752
770
|
// reporter (a `json`/`html`/`junit` file the executor asked for, or one this build does not
|
|
753
|
-
// know) is left where it was.
|
|
771
|
+
// know) is left where it was. The machine report, when this run carries one, is appended to
|
|
772
|
+
// the same set: it writes the runner's own per-test identities and attachment paths to a
|
|
773
|
+
// file and prints nothing into the log.
|
|
754
774
|
let placed = false;
|
|
755
775
|
const kept = reporters.values
|
|
756
776
|
.map((v) => {
|
|
@@ -762,6 +782,11 @@ export function streamingBackgroundCommand(command, context) {
|
|
|
762
782
|
return 'list';
|
|
763
783
|
})
|
|
764
784
|
.filter((v) => v !== null);
|
|
785
|
+
// The sentence below is about the reporTERS this run prints with, so it reads the set as it
|
|
786
|
+
// was rewritten — the machine report is written to a file and is no part of that sentence.
|
|
787
|
+
const printers = kept.join(',');
|
|
788
|
+
if (addReport)
|
|
789
|
+
kept.push('json');
|
|
765
790
|
// Only the last step is rewritten, and its offsets are the parse's own. The punctuation
|
|
766
791
|
// after the step (`; }`, or the `)` attached to it) is kept verbatim.
|
|
767
792
|
out =
|
|
@@ -771,26 +796,39 @@ export function streamingBackgroundCommand(command, context) {
|
|
|
771
796
|
lastStep.slice(reporters.end) +
|
|
772
797
|
rawLastStep.slice(lastStep.length) +
|
|
773
798
|
out.slice(stepStart + rawLastStep.length);
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
799
|
+
if (printerRewrite) {
|
|
800
|
+
changes.push(
|
|
801
|
+
// `list` was already there and a second printer sat beside it: that one is dropped. When
|
|
802
|
+
// no `list` was named at all, the printer is SWITCHED to it (flash-m62's `line`).
|
|
803
|
+
printing.includes('list')
|
|
804
|
+
? `Dropped \`${printing
|
|
805
|
+
.filter((v) => v !== 'list')
|
|
806
|
+
.join(',')}\` from your \`--reporter=${reporters.values.join(',')}\`: ` +
|
|
807
|
+
'two reporters that both print each failure write every test result into the log ' +
|
|
808
|
+
'twice — the whole log doubled for nothing (X1 flash-m83) — so this run keeps ' +
|
|
809
|
+
'`list` alone.'
|
|
810
|
+
: `Switched the test reporters to \`--reporter${reporters.separator}${printers}\`: ` +
|
|
811
|
+
'`list` writes each test result as it finishes and is the shape the platform reads ' +
|
|
812
|
+
'back from a background run, and it is the only printing reporter — two of them ' +
|
|
813
|
+
'write every result into the log twice (X1 flash-m83).');
|
|
814
|
+
}
|
|
815
|
+
if (addReport)
|
|
816
|
+
report = reportPath;
|
|
788
817
|
}
|
|
789
818
|
}
|
|
790
|
-
else if (finalPlaywrightRun &&
|
|
819
|
+
else if (finalPlaywrightRun &&
|
|
820
|
+
addToRun(reportWanted ? '--reporter=list,json' : '--reporter=list')) {
|
|
821
|
+
if (reportWanted)
|
|
822
|
+
report = reportPath;
|
|
791
823
|
changes.push('Added `--reporter=list` so each test result is written to the log as it finishes ' +
|
|
792
824
|
'(the config reporter may print nothing until the end).');
|
|
793
825
|
}
|
|
826
|
+
// The report's own file option, set once at the head of the whole command — the json reporter
|
|
827
|
+
// reads it from the environment, resolved against the runner's directory, so an absolute path is
|
|
828
|
+
// used and the file lands beside the task's own log whatever `cd` the run opens with.
|
|
829
|
+
if (report !== null) {
|
|
830
|
+
out = `export ${PLAYWRIGHT_REPORT_ENV}=${shellQuote(report)}; ${out}`;
|
|
831
|
+
}
|
|
794
832
|
// The run's failures, in the log as they fail (see PLAYWRIGHT_FAILURES_INLINE_ENV), on EVERY
|
|
795
833
|
// background command that is not a no-test invocation — not only the ones whose text this
|
|
796
834
|
// reader can place the runner in. The executor starts the suite through its own wrapper often
|
|
@@ -825,7 +863,58 @@ export function streamingBackgroundCommand(command, context) {
|
|
|
825
863
|
if (addToRun('--retries=0'))
|
|
826
864
|
changes.push(`Added \`--retries=0\`: ${why}.`);
|
|
827
865
|
}
|
|
828
|
-
return { command: out, changes };
|
|
866
|
+
return { command: out, changes, ...(report === null ? {} : { reportPath: report }) };
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Put Playwright's standard `json` report on a FOREGROUND run that already names its own reporters.
|
|
870
|
+
*
|
|
871
|
+
* A background run is rewritten onto the platform's reporter anyway ({@link
|
|
872
|
+
* streamingBackgroundCommand}), so adding the machine report there costs nothing. A foreground run
|
|
873
|
+
* is not: its reporters are either the command's own flags or the suite's config, and replacing a
|
|
874
|
+
* config reporter would stop writing the artifact the executor configured. So this only ever
|
|
875
|
+
* APPENDS `json` to a `--reporter` set the command already wrote — the log keeps every printing
|
|
876
|
+
* reporter it had, the executor's file reporters keep their place, and the standard report goes to
|
|
877
|
+
* {@link PLAYWRIGHT_REPORT_ENV}'s file.
|
|
878
|
+
*
|
|
879
|
+
* A command that names no reporter at all is left exactly as written: its reporters are the suite
|
|
880
|
+
* config's, and this tool does not rewrite those. A command that already names `json`, or sets
|
|
881
|
+
* `PLAYWRIGHT_JSON_OUTPUT_FILE` itself, already has a report of its own — a path the executor chose
|
|
882
|
+
* is never overwritten.
|
|
883
|
+
*
|
|
884
|
+
* @param command - The command the executor sent, after every other rewrite.
|
|
885
|
+
* @param reportPath - The file this run's machine report should be written to.
|
|
886
|
+
* @returns The command to run and the report path when one was wired; `reportPath` is null when the
|
|
887
|
+
* run was left untouched and no report may be expected.
|
|
888
|
+
*/
|
|
889
|
+
export function withPlaywrightRunReport(command, reportPath) {
|
|
890
|
+
const unchanged = { command, reportPath: null };
|
|
891
|
+
if (reportPath === '')
|
|
892
|
+
return unchanged;
|
|
893
|
+
if (!isPlaywrightRunCommand(command))
|
|
894
|
+
return unchanged;
|
|
895
|
+
if (/(?:^|[\s;])PLAYWRIGHT_JSON_OUTPUT_FILE=/.test(command))
|
|
896
|
+
return unchanged;
|
|
897
|
+
const run = lastSuiteRun(command);
|
|
898
|
+
const runner = run ? run.located[run.at] : undefined;
|
|
899
|
+
if (!runner || runner.start === null || runner.end === null)
|
|
900
|
+
return unchanged;
|
|
901
|
+
const at = statementArgumentEnd(command, runner.start, runner.end);
|
|
902
|
+
if (at === null)
|
|
903
|
+
return unchanged;
|
|
904
|
+
// The reporter flag the RUN's own statement names — not one an argument of another program
|
|
905
|
+
// carries (`grep --reporter x file`): the slice starts at the runner's own program word.
|
|
906
|
+
const flag = lastReporterFlag(command.slice(runner.start, at));
|
|
907
|
+
if (flag === null || flag.values.includes('json'))
|
|
908
|
+
return unchanged;
|
|
909
|
+
const start = runner.start + flag.start;
|
|
910
|
+
const end = runner.start + flag.end;
|
|
911
|
+
const rewritten = command.slice(0, start) +
|
|
912
|
+
`--reporter${flag.separator}${[...flag.values, 'json'].join(',')}` +
|
|
913
|
+
command.slice(end);
|
|
914
|
+
return {
|
|
915
|
+
command: `export ${PLAYWRIGHT_REPORT_ENV}=${shellQuote(reportPath)}; ${rewritten}`,
|
|
916
|
+
reportPath,
|
|
917
|
+
};
|
|
829
918
|
}
|
|
830
919
|
/**
|
|
831
920
|
* How a shell command starts each runner whose whole-suite run saturates the
|
|
@@ -1248,6 +1337,10 @@ function lastSuiteRun(command) {
|
|
|
1248
1337
|
* tail, and a 95-second walk's log held only the tally while the platform's own hold had handed
|
|
1249
1338
|
* that command back).
|
|
1250
1339
|
*
|
|
1340
|
+
* The command around the dropped stages is left as written: a group they sit in keeps its closer
|
|
1341
|
+
* (`( … | tail -25)` → `( … )`, never `( …` — xr-molecule-flash-m418 +23:31), and a stage the cut
|
|
1342
|
+
* would leave a closer dangling inside is not dropped at all.
|
|
1343
|
+
*
|
|
1251
1344
|
* @param command - The shell command.
|
|
1252
1345
|
* @returns The rewritten command and the stages dropped.
|
|
1253
1346
|
*/
|
|
@@ -1281,15 +1374,40 @@ export function stripTrailingSuiteFilters(command) {
|
|
|
1281
1374
|
// A stage inside a nested `sh -c` string has no offsets in this command: nothing can be cut.
|
|
1282
1375
|
if (previous.end === null || stage.start === null || lastDropped.end === null)
|
|
1283
1376
|
return { command, dropped: '' };
|
|
1377
|
+
// Every stage dropped must be provably whole inside the command's own structure. A group
|
|
1378
|
+
// closer attached to one of them closes a group the pipeline below still runs in
|
|
1379
|
+
// (`( … | tail -5) | cat`), and a stage that OPENS a group it does not close (`| { cat; }`) has
|
|
1380
|
+
// its closer in a later statement — either cut would leave the punctuation dangling. Only the
|
|
1381
|
+
// last stage may end on an enclosing closer, and it is kept ({@link statementBoundary}).
|
|
1382
|
+
const boundaries = [];
|
|
1383
|
+
for (let i = first; i <= end; i += 1) {
|
|
1384
|
+
const droppedStage = located[i];
|
|
1385
|
+
if (!droppedStage || droppedStage.end === null)
|
|
1386
|
+
return { command, dropped: '' };
|
|
1387
|
+
const boundary = statementBoundary(command, droppedStage.start, droppedStage.end);
|
|
1388
|
+
if (boundary === null || boundary.leavesGroupOpen)
|
|
1389
|
+
return { command, dropped: '' };
|
|
1390
|
+
if (i < end && boundary.textEnd !== droppedStage.end)
|
|
1391
|
+
return { command, dropped: '' };
|
|
1392
|
+
boundaries.push(boundary);
|
|
1393
|
+
}
|
|
1394
|
+
const lastBoundary = boundaries[boundaries.length - 1];
|
|
1395
|
+
if (lastBoundary === undefined)
|
|
1396
|
+
return { command, dropped: '' };
|
|
1397
|
+
// Where the dropped stages' own text ends. The tokenizer attaches the closer of an enclosing
|
|
1398
|
+
// group to the stage's last word (`tail -25)`), so cutting at {@link LocatedSegment.end} deleted
|
|
1399
|
+
// the `)` and left `( … 2>&1` — a command no shell parses, which the background runner rejected
|
|
1400
|
+
// with `end of file unexpected` (xr-molecule-flash-m418 +23:31).
|
|
1401
|
+
const dropEnd = lastBoundary.textEnd;
|
|
1284
1402
|
const pipe = stage.between.indexOf('|');
|
|
1285
1403
|
if (pipe < 0)
|
|
1286
1404
|
return { command, dropped: '' };
|
|
1287
1405
|
const cut = previous.end + pipe;
|
|
1288
1406
|
// The stages dropped are a middle slice when a statement follows, so the text after them is
|
|
1289
|
-
// kept — `; echo done` still runs.
|
|
1407
|
+
// kept — `; echo done` still runs, and so does the punctuation closing the group they sat in.
|
|
1290
1408
|
return {
|
|
1291
|
-
command: `${command.slice(0, cut).trimEnd()}${command.slice(
|
|
1292
|
-
dropped: command.slice(cut,
|
|
1409
|
+
command: `${command.slice(0, cut).trimEnd()}${command.slice(dropEnd)}`,
|
|
1410
|
+
dropped: command.slice(cut, dropEnd).trim(),
|
|
1293
1411
|
};
|
|
1294
1412
|
}
|
|
1295
1413
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-ai-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@types/node": "26.1.2",
|
|
36
36
|
"esbuild": "0.28.2",
|
|
37
37
|
"typescript": "6.0.3",
|
|
38
|
-
"vitest": "4.1.
|
|
38
|
+
"vitest": "4.1.12"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|