@molecule/api-ai-tools 1.6.6 → 1.7.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 +290 -28
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +213 -70
- package/dist/trace-artifacts.d.ts +147 -0
- package/dist/trace-artifacts.d.ts.map +1 -0
- package/dist/trace-artifacts.js +467 -0
- package/dist/utilities.d.ts +77 -18
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +112 -26
- package/package.json +2 -2
package/dist/utilities.js
CHANGED
|
@@ -327,6 +327,20 @@ function lastReporterFlag(step) {
|
|
|
327
327
|
* carries every result twice.
|
|
328
328
|
*/
|
|
329
329
|
const PRINTING_REPORTERS = new Set(['list', 'line', 'dot', 'basic', 'spec']);
|
|
330
|
+
/**
|
|
331
|
+
* Playwright's switch for printing each failure's own error as that test fails, instead of only in
|
|
332
|
+
* the summary at the end: `list` reads it in its constructor (`ListReporterOptions.printFailuresInline`,
|
|
333
|
+
* `playwright/lib/reporters/list.js` — 1.63, the version the toolchain pins), and when it prints
|
|
334
|
+
* inline the end-of-run repeat is suppressed with it (`epilogue(!printFailuresInline)`), so one
|
|
335
|
+
* reporter still prints each failure exactly once.
|
|
336
|
+
*
|
|
337
|
+
* A background suite is put on `list` (see {@link streamingBackgroundCommand}) and this with it, so
|
|
338
|
+
* the log names its causes WHILE it walks. Without it the log's only failure lines are `✘` rows:
|
|
339
|
+
* X1 xr-molecule-flash-m99 (+19:27 → +21:00, 2026-10-16) was told to `grep -n -A 20 '<test name>'`
|
|
340
|
+
* the log, got nothing back through four greps, inferred causes from the spec prose instead, and
|
|
341
|
+
* edited `app/src` while the run was still walking — which voids the run.
|
|
342
|
+
*/
|
|
343
|
+
export const PLAYWRIGHT_FAILURES_INLINE_ENV = 'PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE';
|
|
330
344
|
/**
|
|
331
345
|
* Rewrite a command about to run in the background so its log grows while it
|
|
332
346
|
* runs. Two shapes left the log empty until exit, and an empty log is what
|
|
@@ -352,6 +366,11 @@ const PRINTING_REPORTERS = new Set(['list', 'line', 'dot', 'basic', 'spec']);
|
|
|
352
366
|
* `junit`) is the
|
|
353
367
|
* executor's own and is left exactly as it is — so is any reporter this build does not know.
|
|
354
368
|
*
|
|
369
|
+
* A run put on `list` is also given {@link PLAYWRIGHT_FAILURES_INLINE_ENV}, so each failure's own
|
|
370
|
+
* error is in the log as that test fails rather than only at the end: a suite that is still walking
|
|
371
|
+
* is exactly when the executor reads the log, and without it a running log carries `✘` rows and
|
|
372
|
+
* nothing else (xr-molecule-flash-m99 spent ~90 s on greps of one, then fixed what it guessed).
|
|
373
|
+
*
|
|
355
374
|
* Only the command's last step is touched, and only its reporter, and only when that step runs
|
|
356
375
|
* something: `npx playwright test --list` takes two seconds and writes nothing per test, so
|
|
357
376
|
* neither reporter nor `--retries=0` is added to it ({@link isPlaywrightRunCommand}).
|
|
@@ -441,6 +460,20 @@ export function streamingBackgroundCommand(command) {
|
|
|
441
460
|
changes.push('Added `--reporter=list` so each test result is written to the log as it finishes ' +
|
|
442
461
|
'(the config reporter may print nothing until the end).');
|
|
443
462
|
}
|
|
463
|
+
// The run's failures, in the log as they fail (see PLAYWRIGHT_FAILURES_INLINE_ENV). Only for a
|
|
464
|
+
// run that ends on `list` — a run whose only reporters are machine formats has no failure lines
|
|
465
|
+
// to make inline, and this is inert for one — and set as an `export` at the head of the whole
|
|
466
|
+
// command, never mid-command: the last step may be a wrapper (`timeout 280 npx playwright test`)
|
|
467
|
+
// or start a line of its own (a multi-line command's last line), and an assignment written in
|
|
468
|
+
// front of either would be read as the program to run.
|
|
469
|
+
const endsOnListReporter = finalPlaywrightRun &&
|
|
470
|
+
(reporters === null || reporters.values.some((v) => PRINTING_REPORTERS.has(v)));
|
|
471
|
+
if (endsOnListReporter) {
|
|
472
|
+
out = `export ${PLAYWRIGHT_FAILURES_INLINE_ENV}=1; ${out}`;
|
|
473
|
+
changes.push('Each failure’s own error is now written into the log as that test fails, not only in the ' +
|
|
474
|
+
'summary at the end — so a run that is still walking already says what its failures are, ' +
|
|
475
|
+
'and you need not wait for it to end to start on them.');
|
|
476
|
+
}
|
|
444
477
|
if (finalPlaywrightRun && diagnosis && !/--retries\b/.test(lastStep)) {
|
|
445
478
|
out = `${out.trimEnd()} --retries=0`;
|
|
446
479
|
changes.push('Added `--retries=0`: a diagnosis or re-run walks each failure once; keep the suite’s retries for the proof run.');
|
|
@@ -1280,10 +1313,12 @@ export const RUNNING_FAILURE_LINE_RE = '^[[:space:]]*(✘|✗|×|✖|✕|[0-9]+\
|
|
|
1280
1313
|
* The shell command whose stdout is the interesting part of a log that is still being written:
|
|
1281
1314
|
* the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
1282
1315
|
*
|
|
1283
|
-
* A Playwright `list` run prints one line per test as it finishes
|
|
1284
|
-
*
|
|
1285
|
-
*
|
|
1286
|
-
*
|
|
1316
|
+
* A Playwright `list` run prints one line per test as it finishes, so the LAST bytes of a running
|
|
1317
|
+
* log are its least informative part — in X1 flash-m61 the last 3 000 bytes of a 139-test walk
|
|
1318
|
+
* were the skip list, and all 61 failures were above them, which cost the run the grep it had to
|
|
1319
|
+
* make anyway. A background run also carries each failure's own error as it fails
|
|
1320
|
+
* ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}), and {@link RUNNING_FAILURE_LINE_RE} already matches
|
|
1321
|
+
* those blocks' headers and their `Error:` lines, so this slice shows the causes too.
|
|
1287
1322
|
*
|
|
1288
1323
|
* @param log - The task log path.
|
|
1289
1324
|
* @param own - A file the command redirects its own output to, if any.
|
|
@@ -1529,9 +1564,11 @@ function distinctTests(tests) {
|
|
|
1529
1564
|
* The cause repeating among a running suite's failures, or null.
|
|
1530
1565
|
*
|
|
1531
1566
|
* Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
|
|
1532
|
-
* path and how long it ran — and
|
|
1533
|
-
*
|
|
1534
|
-
*
|
|
1567
|
+
* path and how long it ran — and, on a run the platform started in the background, each failure's
|
|
1568
|
+
* own error with it ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}); a `list` run with no error bodies in
|
|
1569
|
+
* its log (an older Playwright, a log the bodies have been cut from) carries only the duration as
|
|
1570
|
+
* a signature. Where the log carries the failure bodies (either inline, or in a finished run's
|
|
1571
|
+
* summary) the signature is the
|
|
1535
1572
|
* error itself — {@link failureMessage}: the error class plus the locator or the values that
|
|
1536
1573
|
* differ — which is stronger (it needs no assumption about how long a failure should take), so
|
|
1537
1574
|
* the message is looked for first and the duration is then left with the tests whose bodies the
|
|
@@ -1714,6 +1751,42 @@ export function universalCause(log, found) {
|
|
|
1714
1751
|
return null;
|
|
1715
1752
|
return { count: found.count, files };
|
|
1716
1753
|
}
|
|
1754
|
+
/**
|
|
1755
|
+
* The ONE finding that ends a wait early, and the only one a note may hand a stop command over
|
|
1756
|
+
* for: a PROVEN cause that is failing the whole run.
|
|
1757
|
+
*
|
|
1758
|
+
* Both halves are load-bearing, each of them learned from a run that lost minutes to the other.
|
|
1759
|
+
*
|
|
1760
|
+
* - **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across
|
|
1761
|
+
* {@link UNIVERSAL_CAUSE_FILES} spec files or more, with nothing passing since the first — the
|
|
1762
|
+
* dont-stop-runs rule. X1 flash-m83 (2026-10-16) stopped eight local runs at their first
|
|
1763
|
+
* repeating cause in 14.5 minutes and never finished one, and a run that still has other causes
|
|
1764
|
+
* left to name is the only thing that will name them.
|
|
1765
|
+
* - **A PROVEN cause** is an error repeated VERBATIM. A shared DURATION is a LEAD, never a cause:
|
|
1766
|
+
* a suite whose config sets a timeout spends that timeout on every failure that waits for
|
|
1767
|
+
* anything, so three unrelated tests timing out together look exactly like one broken run
|
|
1768
|
+
* (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}). X1
|
|
1769
|
+
* xr-molecule-flash-m99 (2026-10-16) was told `status: 'repeating_failure'` twice on that
|
|
1770
|
+
* shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
|
|
1771
|
+
* — read the early return as "stop waiting, edit now", and voided a run that was still walking;
|
|
1772
|
+
* the second cost a 224 s wait that ended with no result at all.
|
|
1773
|
+
*
|
|
1774
|
+
* Neither half alone is enough, and {@link repeatingFailure} finding a signature is not either:
|
|
1775
|
+
* that answers "is a cause repeating among this run's failures", which is worth telling the
|
|
1776
|
+
* executor mid-run as a NOTE while the wait goes on. This answers the narrower question the wait
|
|
1777
|
+
* needs — is the run over as a measuring instrument — and it is THE one predicate behind both
|
|
1778
|
+
* that decision and the note's wording, so the two cannot come to different verdicts.
|
|
1779
|
+
*
|
|
1780
|
+
* @param found - What {@link repeatingFailure} found mid-run.
|
|
1781
|
+
* @param universal - What {@link universalCause} found for it, if anything.
|
|
1782
|
+
* @returns The cause to stop the run over, or null — in which case nothing may be stopped, the
|
|
1783
|
+
* wait keeps going, and the finding rides the run's result as a note.
|
|
1784
|
+
*/
|
|
1785
|
+
export function earlyReturnCause(found, universal) {
|
|
1786
|
+
if (found.message === null)
|
|
1787
|
+
return null;
|
|
1788
|
+
return universal ?? null;
|
|
1789
|
+
}
|
|
1717
1790
|
/**
|
|
1718
1791
|
* Every failure a run names, grouped by the cause behind it — one group per cause, with the
|
|
1719
1792
|
* tests it hit.
|
|
@@ -1723,11 +1796,11 @@ export function universalCause(log, found) {
|
|
|
1723
1796
|
* bodies to read; X1 flash-m83 (2026-10-16) spent three deploys (6.7 minutes) each fixing a
|
|
1724
1797
|
* single cause, two of them found by runs stopped before they walked the rest.
|
|
1725
1798
|
*
|
|
1726
|
-
* The signature is the cause itself where the log carries the failures' bodies (a finished run,
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
* why a duration group is labelled as a duration and not as a
|
|
1730
|
-
* printed twice, or failed twice, is one test.
|
|
1799
|
+
* The signature is the cause itself where the log carries the failures' bodies (a finished run, a
|
|
1800
|
+
* background `list` run — whose failures print as they fail — or a `line` reporter) —
|
|
1801
|
+
* {@link failureMessage}: the error class plus the locator or the values that differ — and how long
|
|
1802
|
+
* they took where it does not, which is why a duration group is labelled as a duration and not as a
|
|
1803
|
+
* proven cause. A test the log printed twice, or failed twice, is one test.
|
|
1731
1804
|
*
|
|
1732
1805
|
* @param log - The log of a finished or running run.
|
|
1733
1806
|
* @returns The causes, the one with the most tests first.
|
|
@@ -1753,8 +1826,9 @@ export function failuresByCause(log) {
|
|
|
1753
1826
|
if (tests.length > 0)
|
|
1754
1827
|
causes.push({ message, seconds: null, tests });
|
|
1755
1828
|
}
|
|
1756
|
-
// The failures with no body of their own —
|
|
1757
|
-
// grouped by how long they took: a band is opened by its
|
|
1829
|
+
// The failures with no body of their own — a `list` log with no error blocks in it, from a run
|
|
1830
|
+
// not started in the background — are grouped by how long they took: a band is opened by its
|
|
1831
|
+
// first test and takes the ones that
|
|
1758
1832
|
// then ran the same length, the same band {@link repeatingFailure} uses.
|
|
1759
1833
|
const claimed = new Set(causes.flatMap((c) => c.tests.map(testKey)));
|
|
1760
1834
|
const unclaimed = distinctTests(failedTests(text)).filter((t) => t.seconds !== null && !claimed.has(testKey(t)));
|
|
@@ -1841,11 +1915,15 @@ export function rerunOfRunningSuite(run, locations) {
|
|
|
1841
1915
|
* tests, so the note never names one or two tests as a cause.
|
|
1842
1916
|
*
|
|
1843
1917
|
* The note names the cause early so the executor can START FIXING while the run keeps walking,
|
|
1844
|
-
* and it
|
|
1845
|
-
*
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
*
|
|
1918
|
+
* and it hands a stop command over only when {@link earlyReturnCause} says so — the SAME
|
|
1919
|
+
* predicate the wait uses to decide whether to return early, so a finding the wait keeps waiting
|
|
1920
|
+
* on is a finding this note never asks to stop for. X1 flash-m76 (2026-10-16) spent 4.0 minutes
|
|
1921
|
+
* walking 15 tests whose one cause was certain at the third, 47 s in, and the note answers
|
|
1922
|
+
* exactly that; X1 flash-m83 then stopped eight runs at their first repeating cause in 14.5
|
|
1923
|
+
* minutes and never finished one, so the note must not say to stop when the run still has other
|
|
1924
|
+
* causes to name. X1 xr-molecule-flash-m99 (2026-10-16) is the third shape: a shared duration is
|
|
1925
|
+
* not a cause at all, so a lead reaches the executor as a note on a result it is still waiting
|
|
1926
|
+
* for — never as a verdict with a stop command in it.
|
|
1849
1927
|
*
|
|
1850
1928
|
* The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
|
|
1851
1929
|
* re-run of one of its tests is a second run against the same app and database, and the platform
|
|
@@ -1859,7 +1937,7 @@ export function rerunOfRunningSuite(run, locations) {
|
|
|
1859
1937
|
*
|
|
1860
1938
|
* @param found - What {@link repeatingFailure} found.
|
|
1861
1939
|
* @param o - The run's facts: its `taskId`, its `log` path, `stop` — the shell command that
|
|
1862
|
-
* stops it, printed only for
|
|
1940
|
+
* stops it, printed only for an {@link earlyReturnCause} — `universal`, what
|
|
1863
1941
|
* {@link universalCause} found (absent means the run is not failing whole), and `run`, how the
|
|
1864
1942
|
* run was started ({@link rerunOfRunningSuite}) when the caller knows it.
|
|
1865
1943
|
* @returns The note.
|
|
@@ -1917,8 +1995,11 @@ export function repeatingFailureNote(found, o) {
|
|
|
1917
1995
|
// directory the run started in. Without that record it says the timing and nothing more.
|
|
1918
1996
|
const thenRun = aRunIsInTheWay && rerun ? `\nSend this once the run ends:\n ${rerun}` : '';
|
|
1919
1997
|
const where = `task ${o.taskId}, log ${o.log}`;
|
|
1920
|
-
|
|
1921
|
-
|
|
1998
|
+
// The ONE predicate behind this wording and the wait's own early return (`earlyReturnCause`), so
|
|
1999
|
+
// a finding the wait keeps waiting on can never be handed a stop command here.
|
|
2000
|
+
const stop = earlyReturnCause(found, o.universal);
|
|
2001
|
+
if (stop) {
|
|
2002
|
+
const files = stop.files.map(shortFile);
|
|
1922
2003
|
const shown = files.slice(0, 4).join(', ');
|
|
1923
2004
|
const moreFiles = files.length > 4 ? `, … and ${files.length - 4} more` : '';
|
|
1924
2005
|
return (`Every test this run has walked so far has failed the same way — all ${found.count} of ` +
|
|
@@ -1928,15 +2009,20 @@ export function repeatingFailureNote(found, o) {
|
|
|
1928
2009
|
`repeats this one cause. Stop it and fix that cause first: \`exec_command: ${o.stop}\`, ` +
|
|
1929
2010
|
`then run the whole suite again. It is still going (${where}).${reproduce}`);
|
|
1930
2011
|
}
|
|
2012
|
+
// Everything else is a note on a run the wait is still waiting for, and both its shapes end the
|
|
2013
|
+
// same way: the run finishes, because it is the only thing that names the causes this finding is
|
|
2014
|
+
// not. What it does NOT carry is the "Stop it if every test fails this way" clause — that one
|
|
2015
|
+
// belongs to a PROVEN cause the run is still walking (the sentence below), never to a lead:
|
|
2016
|
+
// xr-molecule-flash-m99's executor read it on a shared duration and voided the run.
|
|
1931
2017
|
const keepGoing = `Let the rest of the run finish — it is the only thing that will name the causes that are NOT ` +
|
|
1932
2018
|
`this one, and a run stopped at its first repeating cause costs a second whole run to find ` +
|
|
1933
|
-
`them.
|
|
1934
|
-
`every request, the same selector timing out on every test of every file so far). ` +
|
|
1935
|
-
`It is still going (${where}).`;
|
|
2019
|
+
`them. It is still going (${where}).`;
|
|
1936
2020
|
if (isCause)
|
|
1937
2021
|
return (`${found.count} different tests in this run have already failed the same way — ${signature} — ` +
|
|
1938
2022
|
`so one cause is repeating:\n${rows}${more}\n` +
|
|
1939
|
-
`Start fixing it now, while the run keeps walking
|
|
2023
|
+
`Start fixing it now, while the run keeps walking. Stop it only if every test it walks ` +
|
|
2024
|
+
`fails this same way (the app unreachable, a 5xx on every request, the same selector timing ` +
|
|
2025
|
+
`out on every test of every file so far).${reproduce} ${keepGoing}${thenRun}`);
|
|
1940
2026
|
return (`${found.count} different tests in this run each failed after ~${found.seconds}s — the same ` +
|
|
1941
2027
|
`duration to within half a second. A shared duration is a lead, not a cause: a suite whose ` +
|
|
1942
2028
|
`config sets a timeout spends that timeout on every failure that waits for anything, so ` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-ai-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@molecule/api-ai": "^1.0.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@molecule/api-ai": "1.3.
|
|
34
|
+
"@molecule/api-ai": "1.3.4",
|
|
35
35
|
"@types/node": "26.1.2",
|
|
36
36
|
"esbuild": "0.28.2",
|
|
37
37
|
"typescript": "6.0.3",
|