@molecule/api-ai-tools 1.5.15 → 1.5.16
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 +70 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +31 -9
- package/dist/utilities.d.ts +43 -2
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +142 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-16T07:29:43.225Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -572,6 +572,22 @@ function redactSecretsInCode(s: string): string
|
|
|
572
572
|
|
|
573
573
|
**Returns:** A redacted copy that preserves ordinary code verbatim.
|
|
574
574
|
|
|
575
|
+
#### `printsOnlySource(command)`
|
|
576
|
+
|
|
577
|
+
Whether a shell command only PRINTS source files (`cat a.tsx`, `sed -n '1,40p' x.ts`,
|
|
578
|
+
`head -50 b.hurl`): its output is file content and gets the code-safe redaction, like
|
|
579
|
+
read_file. Through the env-dump grade a printed `type AuthState = 'authenticated' | …`
|
|
580
|
+
came back as `'[REDACTED]'`, and the executor wrote the mask into a new file (X1
|
|
581
|
+
flash-m58). Any segment that is not such a print, or any env file named, keeps the full grade.
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
function printsOnlySource(command: string): boolean
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
- `command` — The shell command.
|
|
588
|
+
|
|
589
|
+
**Returns:** True when every segment prints source files only.
|
|
590
|
+
|
|
575
591
|
#### `isEnvFilePath(path)`
|
|
576
592
|
|
|
577
593
|
Whether a path is an env file, for which {@link redactSecrets}' full env-dump
|
|
@@ -646,9 +662,16 @@ killed (X1 run flash-m15 lost 20 of its 60 minutes this way, 2026-10-15):
|
|
|
646
662
|
- a trailing `| tail -N`, which holds everything back until the command ends.
|
|
647
663
|
It is dropped: `wait_for_task` already returns the end of the log.
|
|
648
664
|
- `playwright test` with no `--reporter`, which runs the config's reporter —
|
|
649
|
-
often `html`, which prints nothing per test. `--reporter=
|
|
665
|
+
often `html`, which prints nothing per test. `--reporter=list` is added
|
|
650
666
|
when the playwright run is the command's last step and nothing is piped or
|
|
651
|
-
redirected from it
|
|
667
|
+
redirected from it: it writes each result as it finishes, and it is the
|
|
668
|
+
shape the platform reads a background suite back in (`suite-fail-fast.ts`'s
|
|
669
|
+
fail-fast note and the Tests bar both parse `list` lines). A run that names
|
|
670
|
+
`--reporter=line` is switched to `list` for the same reason — `line` writes
|
|
671
|
+
`[n/N] <title>` with no ✓/✘ mark and no duration, so on X1 flash-m62 an
|
|
672
|
+
8.3-minute walk of one suite could never trip the fail-fast note.
|
|
673
|
+
|
|
674
|
+
Only the command's last step is touched, and only its reporter.
|
|
652
675
|
|
|
653
676
|
```typescript
|
|
654
677
|
function streamingBackgroundCommand(command: string): { command: string; changes: string[] }
|
|
@@ -827,6 +850,25 @@ function failureBlocks(s: string): string[]
|
|
|
827
850
|
|
|
828
851
|
**Returns:** Each block, in order (empty when there are none).
|
|
829
852
|
|
|
853
|
+
#### `runningOutputSliceCommand(log, own)`
|
|
854
|
+
|
|
855
|
+
The shell command whose stdout is the interesting part of a log that is still being written:
|
|
856
|
+
the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
857
|
+
|
|
858
|
+
A Playwright `list` run prints one line per test as it finishes and its error blocks only at
|
|
859
|
+
the end, so the LAST bytes of a running log are its least informative part — in X1 flash-m61
|
|
860
|
+
the last 3 000 bytes of a 139-test walk were the skip list, and all 61 failures were above
|
|
861
|
+
them, which cost the run the grep it had to make anyway.
|
|
862
|
+
|
|
863
|
+
```typescript
|
|
864
|
+
function runningOutputSliceCommand(log: string, own?: string): string
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
- `log` — The task log path.
|
|
868
|
+
- `own` — A file the command redirects its own output to, if any.
|
|
869
|
+
|
|
870
|
+
**Returns:** The shell command, safe to hand to the execution backend as-is.
|
|
871
|
+
|
|
830
872
|
#### `whitespaceTolerantReplace(content, oldString, newString)`
|
|
831
873
|
|
|
832
874
|
Attempt a whitespace-tolerant replacement when an exact `old_string` match
|
|
@@ -1031,6 +1073,31 @@ const DEFAULT_SEARCH_EXCLUDED_DIRS: readonly [
|
|
|
1031
1073
|
]
|
|
1032
1074
|
```
|
|
1033
1075
|
|
|
1076
|
+
#### `RUNNING_FAILURE_LINES`
|
|
1077
|
+
|
|
1078
|
+
Failure lines shown from a log that is still being written.
|
|
1079
|
+
|
|
1080
|
+
```typescript
|
|
1081
|
+
const RUNNING_FAILURE_LINES: 40
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
#### `RUNNING_TAIL_BYTES`
|
|
1085
|
+
|
|
1086
|
+
Bytes of a running log's end appended after its failure lines.
|
|
1087
|
+
|
|
1088
|
+
```typescript
|
|
1089
|
+
const RUNNING_TAIL_BYTES: 1000
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
#### `RUNNING_FAILURE_LINE_RE`
|
|
1093
|
+
|
|
1094
|
+
The lines worth showing from a log that is still being written: a failed test's mark or
|
|
1095
|
+
numbered block header, and a runner's own error lines. Matched with `grep -E`.
|
|
1096
|
+
|
|
1097
|
+
```typescript
|
|
1098
|
+
const RUNNING_FAILURE_LINE_RE: '^[[:space:]]*(✘|✗|×|✖|✕|[0-9]+\\)|[0-9]+ (passed|failed|flaky|skipped|did not run)|FAIL|FAILED|ERROR|Error:|error:|Traceback|AssertionError)'
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1034
1101
|
#### `PARSE_CHECKABLE`
|
|
1035
1102
|
|
|
1036
1103
|
Files a parse check can vouch for cheaply right after a write.
|
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;AA8B9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,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;AA8B9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6CnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAupDxF"}
|
package/dist/tools.js
CHANGED
|
@@ -25,7 +25,7 @@ const waitedByTask = new Map();
|
|
|
25
25
|
const logBytesByTask = new Map();
|
|
26
26
|
/** The file a background command writes its own output to, when it redirects it. */
|
|
27
27
|
const outputFileByTask = new Map();
|
|
28
|
-
import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, fusedLine, isEnvFilePath, isOutsideRoot, isValidGlob, MAX_BATCH_READ_BYTES, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_OUTPUT_SIZE, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, redactSecrets, redactSecretsInCode, resolvePath, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
|
|
28
|
+
import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, fusedLine, isEnvFilePath, isOutsideRoot, isValidGlob, MAX_BATCH_READ_BYTES, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_OUTPUT_SIZE, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, printsOnlySource, redactSecrets, redactSecretsInCode, resolvePath, RUNNING_FAILURE_LINES, runningOutputSliceCommand, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
|
|
29
29
|
/**
|
|
30
30
|
* How long exec_command waits for a command it backgrounded only because the
|
|
31
31
|
* command asked for more than the tool's ceiling, before handing back a task id.
|
|
@@ -921,7 +921,15 @@ export function buildTools(backend, config) {
|
|
|
921
921
|
...(fusedNote
|
|
922
922
|
? { syntaxError: fusedNote }
|
|
923
923
|
: {
|
|
924
|
-
|
|
924
|
+
// The evidence for this note is `located`, not `dropped`: a drop joins
|
|
925
|
+
// two lines only when something other than a newline (or EOF) follows
|
|
926
|
+
// the replaced text. A weaker model writes one line's change as two
|
|
927
|
+
// replacements — the line including its trailing newline, then the
|
|
928
|
+
// line's own text — and the newline it consumed ended that line with a
|
|
929
|
+
// BLANK line after it, so nothing joined while the model was told it
|
|
930
|
+
// had: it spent a new round trip putting the blank line back
|
|
931
|
+
// (flash-m62). Say nothing when nothing was joined.
|
|
932
|
+
...(dropped.length > 0 && located.length > 0
|
|
925
933
|
? {
|
|
926
934
|
newlineNote: `${dropped.length === 1 ? 'A replacement' : `${dropped.length} replacements`} removed the line break after the replaced text (old_string ended with a newline, new_string does not), so the next line now continues on the same line. Check the excerpt; add the newline back if that was not intended.`,
|
|
927
935
|
}
|
|
@@ -966,7 +974,9 @@ export function buildTools(backend, config) {
|
|
|
966
974
|
// search found nothing (X1 run sol-m07). A pattern that is not a valid
|
|
967
975
|
// extended regex (`foo(` — an unbalanced paren) makes grep exit 2; it is
|
|
968
976
|
// then searched as the plain text it was meant as.
|
|
969
|
-
|
|
977
|
+
// -H: a search scoped to one file printed `line:content` with no file, and every match
|
|
978
|
+
// came back `{file: "", line: 0}` (X1 flash-m62).
|
|
979
|
+
const grepArgs = `-rnH ${globArg} ${contextArg} ${grepExcludeArgs(path)} --max-count=${MAX_SEARCH_RESULTS} -- ${shellQuote(pattern)} ${shellQuote(path)}`;
|
|
970
980
|
const result = await backend.run(`{ grep -E ${grepArgs}; s=$?; if [ "$s" -eq 2 ]; then grep -F ${grepArgs}; fi; } 2>/dev/null || true`, { timeout: 10000 });
|
|
971
981
|
// grep emits `<file>:<line>:<content>`, so redaction runs PER MATCH on the
|
|
972
982
|
// content alone: the file prefix would otherwise hide an env assignment from
|
|
@@ -1268,7 +1278,12 @@ export function buildTools(backend, config) {
|
|
|
1268
1278
|
// truncateMiddle (not truncate): a failing build/test/migration puts its
|
|
1269
1279
|
// error at the TAIL, so keep the head AND the tail — head-only truncation
|
|
1270
1280
|
// strands the executor with passing progress and no failure reason.
|
|
1271
|
-
|
|
1281
|
+
// A command that only prints source files returns file content: the code-safe grade,
|
|
1282
|
+
// as read_file uses (utilities.ts printsOnlySource).
|
|
1283
|
+
const grade = printsOnlySource(command)
|
|
1284
|
+
? (s) => sanitizeFileContent(s, 'printed.ts')
|
|
1285
|
+
: sanitizeOutput;
|
|
1286
|
+
const stdout = grade(truncateMiddle(result.stdout, MAX_OUTPUT_SIZE));
|
|
1272
1287
|
const stderr = sanitizeOutput(truncateMiddle(result.stderr, MAX_OUTPUT_SIZE));
|
|
1273
1288
|
if (budgetSeconds && result.exitCode === 124) {
|
|
1274
1289
|
return {
|
|
@@ -1456,15 +1471,17 @@ export function buildTools(backend, config) {
|
|
|
1456
1471
|
};
|
|
1457
1472
|
}
|
|
1458
1473
|
// The output so far rides the result: a wait that shows nothing was waited blind
|
|
1459
|
-
// (X1 flash-m50: 4m42s across two waits on an 11-minute run).
|
|
1474
|
+
// (X1 flash-m50: 4m42s across two waits on an 11-minute run). The slice leads with the
|
|
1475
|
+
// failure lines, not the last bytes — a suite's tail is its progress lines
|
|
1476
|
+
// (X1 flash-m61: the last 3 000 bytes of a 139-test walk were the skip list).
|
|
1460
1477
|
const own = outputFileByTask.get(taskId);
|
|
1461
1478
|
let soFar;
|
|
1462
1479
|
try {
|
|
1463
|
-
const r = await backend.run(
|
|
1480
|
+
const r = await backend.run(runningOutputSliceCommand(log, own), { timeout: 5_000 });
|
|
1464
1481
|
soFar = r.stdout ?? '';
|
|
1465
1482
|
}
|
|
1466
1483
|
catch (_error) {
|
|
1467
|
-
// No
|
|
1484
|
+
// No slice: the note still says how to peek.
|
|
1468
1485
|
soFar = '';
|
|
1469
1486
|
}
|
|
1470
1487
|
return {
|
|
@@ -1473,8 +1490,13 @@ export function buildTools(backend, config) {
|
|
|
1473
1490
|
waitedMs,
|
|
1474
1491
|
...(soFar.trim() ? { outputSoFar: sanitizeOutput(soFar) } : {}),
|
|
1475
1492
|
note: `Still running after ${Math.round(waitedMs / 1000)}s. Call wait_for_task again to ` +
|
|
1476
|
-
`keep waiting — its result carries the log output
|
|
1477
|
-
|
|
1493
|
+
`keep waiting — its result carries the log output. ` +
|
|
1494
|
+
(soFar.trim()
|
|
1495
|
+
? `outputSoFar is the failure lines so far (up to ${RUNNING_FAILURE_LINES}) and then the end of ` +
|
|
1496
|
+
`the log, not the whole run — \`grep -n '✘' ${log} | head -40\` lists every failure, ` +
|
|
1497
|
+
`\`grep -n -A 20 '<test name>' ${log}\` reads one failure's error. `
|
|
1498
|
+
: `Nothing has been printed yet — \`grep -n '✘' ${log} | head -40\` lists failures as they appear. `) +
|
|
1499
|
+
`Meanwhile do work that leaves the code under test alone (read the failures so far, plan the fixes): editing app/src or api/src now changes the code the run is testing, and its result no longer counts.`,
|
|
1478
1500
|
};
|
|
1479
1501
|
}
|
|
1480
1502
|
let output;
|
package/dist/utilities.d.ts
CHANGED
|
@@ -55,6 +55,17 @@ export declare function redactSecrets(s: string): string;
|
|
|
55
55
|
* @returns A redacted copy that preserves ordinary code verbatim.
|
|
56
56
|
*/
|
|
57
57
|
export declare function redactSecretsInCode(s: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Whether a shell command only PRINTS source files (`cat a.tsx`, `sed -n '1,40p' x.ts`,
|
|
60
|
+
* `head -50 b.hurl`): its output is file content and gets the code-safe redaction, like
|
|
61
|
+
* read_file. Through the env-dump grade a printed `type AuthState = 'authenticated' | …`
|
|
62
|
+
* came back as `'[REDACTED]'`, and the executor wrote the mask into a new file (X1
|
|
63
|
+
* flash-m58). Any segment that is not such a print, or any env file named, keeps the full grade.
|
|
64
|
+
*
|
|
65
|
+
* @param command - The shell command.
|
|
66
|
+
* @returns True when every segment prints source files only.
|
|
67
|
+
*/
|
|
68
|
+
export declare function printsOnlySource(command: string): boolean;
|
|
58
69
|
/**
|
|
59
70
|
* Whether a path is an env file, for which {@link redactSecrets}' full env-dump
|
|
60
71
|
* treatment is appropriate rather than {@link redactSecretsInCode}. Matches
|
|
@@ -108,9 +119,16 @@ export declare function outputRedirectTarget(command: string): string | null;
|
|
|
108
119
|
* - a trailing `| tail -N`, which holds everything back until the command ends.
|
|
109
120
|
* It is dropped: `wait_for_task` already returns the end of the log.
|
|
110
121
|
* - `playwright test` with no `--reporter`, which runs the config's reporter —
|
|
111
|
-
* often `html`, which prints nothing per test. `--reporter=
|
|
122
|
+
* often `html`, which prints nothing per test. `--reporter=list` is added
|
|
112
123
|
* when the playwright run is the command's last step and nothing is piped or
|
|
113
|
-
* redirected from it
|
|
124
|
+
* redirected from it: it writes each result as it finishes, and it is the
|
|
125
|
+
* shape the platform reads a background suite back in (`suite-fail-fast.ts`'s
|
|
126
|
+
* fail-fast note and the Tests bar both parse `list` lines). A run that names
|
|
127
|
+
* `--reporter=line` is switched to `list` for the same reason — `line` writes
|
|
128
|
+
* `[n/N] <title>` with no ✓/✘ mark and no duration, so on X1 flash-m62 an
|
|
129
|
+
* 8.3-minute walk of one suite could never trip the fail-fast note.
|
|
130
|
+
*
|
|
131
|
+
* Only the command's last step is touched, and only its reporter.
|
|
114
132
|
*
|
|
115
133
|
* @param command - The command as the executor sent it.
|
|
116
134
|
* @returns The command to run and one sentence per change made (empty when none).
|
|
@@ -296,6 +314,29 @@ export declare function truncateMiddle(s: string, maxLength: number): string;
|
|
|
296
314
|
* @returns Each block, in order (empty when there are none).
|
|
297
315
|
*/
|
|
298
316
|
export declare function failureBlocks(s: string): string[];
|
|
317
|
+
/** Failure lines shown from a log that is still being written. */
|
|
318
|
+
export declare const RUNNING_FAILURE_LINES = 40;
|
|
319
|
+
/** Bytes of a running log's end appended after its failure lines. */
|
|
320
|
+
export declare const RUNNING_TAIL_BYTES = 1000;
|
|
321
|
+
/**
|
|
322
|
+
* The lines worth showing from a log that is still being written: a failed test's mark or
|
|
323
|
+
* numbered block header, and a runner's own error lines. Matched with `grep -E`.
|
|
324
|
+
*/
|
|
325
|
+
export declare const RUNNING_FAILURE_LINE_RE = "^[[:space:]]*(\u2718|\u2717|\u00D7|\u2716|\u2715|[0-9]+\\)|[0-9]+ (passed|failed|flaky|skipped|did not run)|FAIL|FAILED|ERROR|Error:|error:|Traceback|AssertionError)";
|
|
326
|
+
/**
|
|
327
|
+
* The shell command whose stdout is the interesting part of a log that is still being written:
|
|
328
|
+
* the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
329
|
+
*
|
|
330
|
+
* A Playwright `list` run prints one line per test as it finishes and its error blocks only at
|
|
331
|
+
* the end, so the LAST bytes of a running log are its least informative part — in X1 flash-m61
|
|
332
|
+
* the last 3 000 bytes of a 139-test walk were the skip list, and all 61 failures were above
|
|
333
|
+
* them, which cost the run the grep it had to make anyway.
|
|
334
|
+
*
|
|
335
|
+
* @param log - The task log path.
|
|
336
|
+
* @param own - A file the command redirects its own output to, if any.
|
|
337
|
+
* @returns The shell command, safe to hand to the execution backend as-is.
|
|
338
|
+
*/
|
|
339
|
+
export declare function runningOutputSliceCommand(log: string, own?: string): string;
|
|
299
340
|
/**
|
|
300
341
|
* Attempt a whitespace-tolerant replacement when an exact `old_string` match
|
|
301
342
|
* failed. Finds a contiguous run of lines in `content` whose per-line
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAEE,CAAA;
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|
package/dist/utilities.js
CHANGED
|
@@ -98,9 +98,15 @@ const NON_SECRET_VALUES = new Set([
|
|
|
98
98
|
* @param quote - The quote character the value uses (`"` or `'`).
|
|
99
99
|
* @returns A String.replace replacer preserving allowlisted values.
|
|
100
100
|
*/
|
|
101
|
-
const jsonValueReplacer = (quote) => (match, prefix) => {
|
|
101
|
+
const jsonValueReplacer = (quote) => (match, prefix, offset, whole) => {
|
|
102
102
|
const value = match.slice(prefix.length);
|
|
103
103
|
const inner = value.slice(1, -1);
|
|
104
|
+
// A TypeScript type literal is never a credential: `type AuthState = 'authenticated' | …`
|
|
105
|
+
// (X1 flash-m58). A union member is followed by `|`; a declaration starts `type`.
|
|
106
|
+
if (/^\s*\|/.test(whole.slice(offset + match.length)))
|
|
107
|
+
return match;
|
|
108
|
+
if (/\btype\s+$/.test(whole.slice(Math.max(0, offset - 16), offset)))
|
|
109
|
+
return match;
|
|
104
110
|
// An empty value hides nothing, and a suite fixture's `"password": ""` is the fact a
|
|
105
111
|
// failing assertion turns on (X1 flash-m52).
|
|
106
112
|
// A version (`"jsonwebtoken": "9.0.2"`, a package name that holds a keyword) is not a
|
|
@@ -153,6 +159,61 @@ export function redactSecrets(s) {
|
|
|
153
159
|
export function redactSecretsInCode(s) {
|
|
154
160
|
return s.replace(SECRET_ENV_ASSIGNMENT, '$1=[REDACTED]');
|
|
155
161
|
}
|
|
162
|
+
/** Source-file extensions a printing command's output is read as code for. */
|
|
163
|
+
const SOURCE_FILE_RE = /\.(?:[cm]?[jt]sx?|css|scss|html|vue|svelte|md|sql|hurl)$/;
|
|
164
|
+
/**
|
|
165
|
+
* Whether a shell command only PRINTS source files (`cat a.tsx`, `sed -n '1,40p' x.ts`,
|
|
166
|
+
* `head -50 b.hurl`): its output is file content and gets the code-safe redaction, like
|
|
167
|
+
* read_file. Through the env-dump grade a printed `type AuthState = 'authenticated' | …`
|
|
168
|
+
* came back as `'[REDACTED]'`, and the executor wrote the mask into a new file (X1
|
|
169
|
+
* flash-m58). Any segment that is not such a print, or any env file named, keeps the full grade.
|
|
170
|
+
*
|
|
171
|
+
* @param command - The shell command.
|
|
172
|
+
* @returns True when every segment prints source files only.
|
|
173
|
+
*/
|
|
174
|
+
export function printsOnlySource(command) {
|
|
175
|
+
if (/\.env\b/.test(command))
|
|
176
|
+
return false;
|
|
177
|
+
const segments = command
|
|
178
|
+
.split(/\s*(?:&&|;|\n)\s*/)
|
|
179
|
+
.map((s) => s.trim())
|
|
180
|
+
.filter((s) => s && !/^cd\s/.test(s));
|
|
181
|
+
if (segments.length === 0)
|
|
182
|
+
return false;
|
|
183
|
+
return segments.every((seg) => {
|
|
184
|
+
// Words, quotes kept whole: a grep pattern like "sign\\|verify" is one word, not a pipe.
|
|
185
|
+
const words = seg.match(/"(?:\\.|[^"\\])*"|'[^']*'|\S+/g) ?? [];
|
|
186
|
+
const stages = [[]];
|
|
187
|
+
for (const w of words) {
|
|
188
|
+
if (w === '|')
|
|
189
|
+
stages.push([]);
|
|
190
|
+
else if (!/^\d?>&\d$|^2>\/dev\/null$/.test(w))
|
|
191
|
+
stages[stages.length - 1].push(w);
|
|
192
|
+
}
|
|
193
|
+
const [first, ...rest] = stages;
|
|
194
|
+
const head = first[0];
|
|
195
|
+
// A print of named source files: cat/head/tail/nl/sed -n, or a non-recursive grep/rg
|
|
196
|
+
// (X1 flash-m61 read a fixture's password masked through `grep -n … auth.hurl`).
|
|
197
|
+
let args = first.slice(1);
|
|
198
|
+
if (head === 'sed' && args[0] !== '-n')
|
|
199
|
+
return false;
|
|
200
|
+
if (head === 'grep' || head === 'rg') {
|
|
201
|
+
if (args.some((a) => /^-[a-zA-Z]*[rR]/.test(a) || a === '--recursive'))
|
|
202
|
+
return false;
|
|
203
|
+
const flags = args.filter((a) => a.startsWith('-'));
|
|
204
|
+
const rest2 = args.filter((a) => !a.startsWith('-'));
|
|
205
|
+
args = flags.includes('-e') ? rest2 : rest2.slice(1);
|
|
206
|
+
}
|
|
207
|
+
else if (!/^(?:cat|head|tail|nl|sed)$/.test(head ?? ''))
|
|
208
|
+
return false;
|
|
209
|
+
const files = args.filter((t) => !t.startsWith('-') && !/^'[^']*'$|^"[^"]*"$|^\d+$|^\d+,\d+p$/.test(t));
|
|
210
|
+
if (files.length === 0 || !files.every((f) => SOURCE_FILE_RE.test(f)))
|
|
211
|
+
return false;
|
|
212
|
+
// After it, only text filters that read no file of their own.
|
|
213
|
+
return rest.every((stage) => /^(?:head|tail|sort|uniq|wc|cut|nl|grep)$/.test(stage[0] ?? '') &&
|
|
214
|
+
!stage.slice(1).some((a) => !a.startsWith('-') && /\.[a-z]+$/i.test(a) && !/^['"]/.test(a)));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
156
217
|
/**
|
|
157
218
|
* Whether a path is an env file, for which {@link redactSecrets}' full env-dump
|
|
158
219
|
* treatment is appropriate rather than {@link redactSecretsInCode}. Matches
|
|
@@ -226,6 +287,8 @@ export function outputRedirectTarget(command) {
|
|
|
226
287
|
}
|
|
227
288
|
/** A trailing `| tail -N` / `| tail -n N` (stderr redirects before it stay). */
|
|
228
289
|
const TRAILING_TAIL = /\s*\|\s*tail(?:\s+-n)?\s+[-+]?\d+\s*$/;
|
|
290
|
+
/** `--reporter=line` / `--reporter line`, the one reporter the platform cannot read back. */
|
|
291
|
+
const LINE_REPORTER_FLAG = /--reporter([= ])line\b/;
|
|
229
292
|
/**
|
|
230
293
|
* Rewrite a command about to run in the background so its log grows while it
|
|
231
294
|
* runs. Two shapes left the log empty until exit, and an empty log is what
|
|
@@ -235,9 +298,16 @@ const TRAILING_TAIL = /\s*\|\s*tail(?:\s+-n)?\s+[-+]?\d+\s*$/;
|
|
|
235
298
|
* - a trailing `| tail -N`, which holds everything back until the command ends.
|
|
236
299
|
* It is dropped: `wait_for_task` already returns the end of the log.
|
|
237
300
|
* - `playwright test` with no `--reporter`, which runs the config's reporter —
|
|
238
|
-
* often `html`, which prints nothing per test. `--reporter=
|
|
301
|
+
* often `html`, which prints nothing per test. `--reporter=list` is added
|
|
239
302
|
* when the playwright run is the command's last step and nothing is piped or
|
|
240
|
-
* redirected from it
|
|
303
|
+
* redirected from it: it writes each result as it finishes, and it is the
|
|
304
|
+
* shape the platform reads a background suite back in (`suite-fail-fast.ts`'s
|
|
305
|
+
* fail-fast note and the Tests bar both parse `list` lines). A run that names
|
|
306
|
+
* `--reporter=line` is switched to `list` for the same reason — `line` writes
|
|
307
|
+
* `[n/N] <title>` with no ✓/✘ mark and no duration, so on X1 flash-m62 an
|
|
308
|
+
* 8.3-minute walk of one suite could never trip the fail-fast note.
|
|
309
|
+
*
|
|
310
|
+
* Only the command's last step is touched, and only its reporter.
|
|
241
311
|
*
|
|
242
312
|
* @param command - The command as the executor sent it.
|
|
243
313
|
* @returns The command to run and one sentence per change made (empty when none).
|
|
@@ -259,12 +329,20 @@ export function streamingBackgroundCommand(command) {
|
|
|
259
329
|
changes.push(`Dropped the trailing \`${head[0].trim()}\`: Playwright prints its pass/fail count last, and it would be cut.`);
|
|
260
330
|
}
|
|
261
331
|
const lastStep = out.split(/&&|\|\||;/).at(-1) ?? '';
|
|
262
|
-
|
|
263
|
-
!/--reporter\b/.test(lastStep) &&
|
|
332
|
+
const finalPlaywrightRun = /\bplaywright\s+test\b/.test(lastStep) &&
|
|
264
333
|
// `2>&1` only joins stderr to the log; any other pipe or redirect sends the output elsewhere.
|
|
265
|
-
!/[|>]/.test(lastStep.replace(/\d?>&\d/g, ''))
|
|
266
|
-
|
|
267
|
-
|
|
334
|
+
!/[|>]/.test(lastStep.replace(/\d?>&\d/g, ''));
|
|
335
|
+
if (finalPlaywrightRun && LINE_REPORTER_FLAG.test(lastStep)) {
|
|
336
|
+
// Only the last step is rewritten, and `lastStep` is a suffix of `out`.
|
|
337
|
+
out =
|
|
338
|
+
out.slice(0, out.length - lastStep.length) +
|
|
339
|
+
lastStep.replace(LINE_REPORTER_FLAG, '--reporter$1list');
|
|
340
|
+
changes.push('Switched `--reporter=line` to `--reporter=list`: it also writes each test result as it ' +
|
|
341
|
+
'finishes, and it is the shape the platform reads back from a background run.');
|
|
342
|
+
}
|
|
343
|
+
else if (finalPlaywrightRun && !/--reporter\b/.test(lastStep)) {
|
|
344
|
+
out = `${out.trimEnd()} --reporter=list`;
|
|
345
|
+
changes.push('Added `--reporter=list` so each test result is written to the log as it finishes ' +
|
|
268
346
|
'(the config reporter may print nothing until the end).');
|
|
269
347
|
}
|
|
270
348
|
return { command: out, changes };
|
|
@@ -283,7 +361,8 @@ export function streamingBackgroundCommand(command) {
|
|
|
283
361
|
* a keyless build still succeeds, and blocking those sent it looking for
|
|
284
362
|
* workarounds (X0 rehearsal 14).
|
|
285
363
|
*/
|
|
286
|
-
|
|
364
|
+
// `(?<!\\)`: a backslash-escaped `\|` is a literal in the shell (grep "a\|env"), not a pipe (X1 flash-m58).
|
|
365
|
+
const BLOCKED_COMMANDS = /(?<!\\)(?:^|[;&|`]\s*|(?:sh|bash|zsh|dash)\s+-c\s+['"]?\s*)(?:\/usr\/bin\/)?(?:\benv(?:\s+(?:-0|--null|--))?\s*(?:$|[;&|>)`'"])|\bprintenv\b|\bexport\s*$|\bset\s*$|\bdeclare\s+-x|cat\s+\/etc\/environment|cat\s+\/root\/\.bashrc|cat\s+\/proc\/\d+\/environ|cat\s+\/proc\/self\/environ|strings\s+\/proc|xargs[^;&|\n]*\/proc\/[^;&|\n]*environ|less\s+\/proc|head\s+\/proc|tail\s+\/proc|xxd\s+\/proc|od\s+\/proc|base64\s+\/proc|dd\s[^\n]*\/proc|sed\s[^\n]*\/proc\/[^\n]*environ|awk\s[^\n]*\/proc\/[^\n]*environ|cp\s[^\n]*\/proc\/[^\n]*environ|(?:cat|less|head|tail|xxd|od|base64|strings|dd|cp|sed|awk)\s[^\n;&|]*\/etc\/mol(?:\/|\b))/i;
|
|
287
366
|
/** Block shell redirects from /proc environ. */
|
|
288
367
|
const BLOCKED_PROC_REDIRECT = /(?:<\s*\/proc\/(?:\d+|self)\/environ)/i;
|
|
289
368
|
/** Block shell redirects from the molecule platform secrets dir (`/etc/mol`). */
|
|
@@ -554,6 +633,33 @@ export function truncate(s, maxLength) {
|
|
|
554
633
|
export function truncateMiddle(s, maxLength) {
|
|
555
634
|
if (s.length <= maxLength)
|
|
556
635
|
return s;
|
|
636
|
+
// Room for the tally line it may append, so the result stays near the cap.
|
|
637
|
+
return withRunTally(s, truncateMiddleBody(s, Math.max(Math.floor(maxLength / 2), maxLength - 200)));
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* A test run's tally lines (` 3 failed`, ` 71 passed (4.6m)`) the cut text lost, put back at
|
|
641
|
+
* its end: Playwright lists every failed test after its "N failed" line, so a long list pushed
|
|
642
|
+
* the line itself into the cut middle and the run read as having no failures (X1 flash-m60).
|
|
643
|
+
*
|
|
644
|
+
* @param original - The whole output.
|
|
645
|
+
* @param cut - The truncated output.
|
|
646
|
+
* @returns The cut output, with any lost tally lines appended.
|
|
647
|
+
*/
|
|
648
|
+
function withRunTally(original, cut) {
|
|
649
|
+
const tally = [
|
|
650
|
+
...original.matchAll(/^\s*\d+ (?:failed|passed|flaky|skipped|did not run|interrupted)\b[^\n]{0,40}$/gm),
|
|
651
|
+
].map((m) => m[0].trim());
|
|
652
|
+
const lost = tally.filter((line) => !cut.includes(line));
|
|
653
|
+
return lost.length ? `${cut}\n[run tally] ${lost.join(' · ')}` : cut;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* {@link truncateMiddle} without the tally guard.
|
|
657
|
+
*
|
|
658
|
+
* @param s - The text.
|
|
659
|
+
* @param maxLength - The budget.
|
|
660
|
+
* @returns The cut text.
|
|
661
|
+
*/
|
|
662
|
+
function truncateMiddleBody(s, maxLength) {
|
|
557
663
|
// Reserve room for the elision notice so head + notice + tail stays WITHIN
|
|
558
664
|
// maxLength — the cap is a real token budget the caller relies on (a head-only
|
|
559
665
|
// truncate that overshoots by its suffix length breaks that contract). 140 =
|
|
@@ -624,6 +730,33 @@ export function failureBlocks(s) {
|
|
|
624
730
|
blocks.push(current.join('\n').trimEnd());
|
|
625
731
|
return blocks;
|
|
626
732
|
}
|
|
733
|
+
/** Failure lines shown from a log that is still being written. */
|
|
734
|
+
export const RUNNING_FAILURE_LINES = 40;
|
|
735
|
+
/** Bytes of a running log's end appended after its failure lines. */
|
|
736
|
+
export const RUNNING_TAIL_BYTES = 1000;
|
|
737
|
+
/**
|
|
738
|
+
* The lines worth showing from a log that is still being written: a failed test's mark or
|
|
739
|
+
* numbered block header, and a runner's own error lines. Matched with `grep -E`.
|
|
740
|
+
*/
|
|
741
|
+
export const RUNNING_FAILURE_LINE_RE = '^[[:space:]]*(✘|✗|×|✖|✕|[0-9]+\\)|[0-9]+ (passed|failed|flaky|skipped|did not run)|FAIL|FAILED|ERROR|Error:|error:|Traceback|AssertionError)';
|
|
742
|
+
/**
|
|
743
|
+
* The shell command whose stdout is the interesting part of a log that is still being written:
|
|
744
|
+
* the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
745
|
+
*
|
|
746
|
+
* A Playwright `list` run prints one line per test as it finishes and its error blocks only at
|
|
747
|
+
* the end, so the LAST bytes of a running log are its least informative part — in X1 flash-m61
|
|
748
|
+
* the last 3 000 bytes of a 139-test walk were the skip list, and all 61 failures were above
|
|
749
|
+
* them, which cost the run the grep it had to make anyway.
|
|
750
|
+
*
|
|
751
|
+
* @param log - The task log path.
|
|
752
|
+
* @param own - A file the command redirects its own output to, if any.
|
|
753
|
+
* @returns The shell command, safe to hand to the execution backend as-is.
|
|
754
|
+
*/
|
|
755
|
+
export function runningOutputSliceCommand(log, own) {
|
|
756
|
+
const read = `cat ${log}${own ? ` ${shellQuote(own)}` : ''} 2>/dev/null`;
|
|
757
|
+
return (`{ ${read} | grep -aE ${shellQuote(RUNNING_FAILURE_LINE_RE)} | head -${RUNNING_FAILURE_LINES} | cut -c1-300; ` +
|
|
758
|
+
`echo; ${read} | tail -c ${RUNNING_TAIL_BYTES}; }`);
|
|
759
|
+
}
|
|
627
760
|
/**
|
|
628
761
|
* Attempt a whitespace-tolerant replacement when an exact `old_string` match
|
|
629
762
|
* failed. Finds a contiguous run of lines in `content` whose per-line
|