@molecule/api-ai-tools 1.6.5 → 1.6.6
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 +311 -1
- package/dist/command-position.d.ts +26 -0
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +54 -7
- package/dist/cut-run.d.ts +124 -0
- package/dist/cut-run.d.ts.map +1 -0
- package/dist/cut-run.js +267 -0
- 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 +20 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +118 -13
- package/dist/utilities.d.ts +75 -0
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +95 -0
- 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-16T21:18:13.332Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -73,6 +73,37 @@ interface CommandSegment {
|
|
|
73
73
|
args: readonly string[]
|
|
74
74
|
/** The program and its arguments as one line — what a runner pattern is matched against. */
|
|
75
75
|
text: string
|
|
76
|
+
/**
|
|
77
|
+
* The seconds the segment's own `timeout` wrapper allows the command it runs
|
|
78
|
+
* (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
79
|
+
*/
|
|
80
|
+
timeoutSeconds: number | null
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `CutRun`
|
|
85
|
+
|
|
86
|
+
A run a budget stopped, as its result reports it.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
interface CutRun {
|
|
90
|
+
/** The bound the run was allowed, in seconds. */
|
|
91
|
+
seconds: number
|
|
92
|
+
/** Whether this tool's budget stopped it or the `timeout` the command carried. */
|
|
93
|
+
stoppedBy: CutBy
|
|
94
|
+
/**
|
|
95
|
+
* The exit code the run's own process reported, or null when the code in the result belongs to
|
|
96
|
+
* the pipeline's filter (`… | tail`) rather than to the run — the case that read as success.
|
|
97
|
+
*/
|
|
98
|
+
exitCode: number | null
|
|
99
|
+
/** How many files or tests the runner's own output shows finishing, or null when it shows none. */
|
|
100
|
+
ran: number | null
|
|
101
|
+
/** How many the run was walking, when the runner printed it (Playwright's `[n/K]`), or null. */
|
|
102
|
+
total: number | null
|
|
103
|
+
/** The noun the runner counts in. */
|
|
104
|
+
noun: 'files' | 'tests'
|
|
105
|
+
/** The last thing the run printed — the line a reader looks at first. */
|
|
106
|
+
doing: string | null
|
|
76
107
|
}
|
|
77
108
|
```
|
|
78
109
|
|
|
@@ -443,6 +474,22 @@ interface FailureCause {
|
|
|
443
474
|
|
|
444
475
|
### Types
|
|
445
476
|
|
|
477
|
+
#### `CommandRunState`
|
|
478
|
+
|
|
479
|
+
What a command's result records: a run that ended, or one a budget stopped.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
type CommandRunState = 'ran' | typeof CUT_AT_BUDGET
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
#### `CutBy`
|
|
486
|
+
|
|
487
|
+
What stopped a run.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
type CutBy = 'tool-budget' | 'command-timeout'
|
|
491
|
+
```
|
|
492
|
+
|
|
446
493
|
#### `DiscoveredSkill` _(deprecated)_
|
|
447
494
|
|
|
448
495
|
Re-export SkillEntry as DiscoveredSkill for backwards compatibility.
|
|
@@ -491,6 +538,18 @@ function createSandboxBackend(sandbox: SandboxLike, projectRoot?: string): Execu
|
|
|
491
538
|
|
|
492
539
|
**Returns:** A backend that proxies all filesystem calls into the sandbox.
|
|
493
540
|
|
|
541
|
+
#### `durationSeconds(word)`
|
|
542
|
+
|
|
543
|
+
The seconds a duration word stands for.
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
function durationSeconds(word: string): number | null
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
- `word` — A duration (`280`, `20s`, `1m`).
|
|
550
|
+
|
|
551
|
+
**Returns:** The seconds, or null when the word is not a duration.
|
|
552
|
+
|
|
494
553
|
#### `commandSegments(command)`
|
|
495
554
|
|
|
496
555
|
The segments of a command, each with its program and arguments — the answer every classifier
|
|
@@ -504,6 +563,25 @@ function commandSegments(command: string): CommandSegment[]
|
|
|
504
563
|
|
|
505
564
|
**Returns:** One entry per statement or pipeline stage that runs something, in order.
|
|
506
565
|
|
|
566
|
+
#### `commandTimeoutSeconds(command)`
|
|
567
|
+
|
|
568
|
+
The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
569
|
+
under (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
570
|
+
|
|
571
|
+
This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
572
|
+
finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
573
|
+
other command reader uses, rather than matched as text: `timeout` in an argument
|
|
574
|
+
(`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
575
|
+
the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
function commandTimeoutSeconds(command: string): number | null
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
- `command` — The shell command.
|
|
582
|
+
|
|
583
|
+
**Returns:** The seconds the command allows itself, or null.
|
|
584
|
+
|
|
507
585
|
#### `runsInCommandPosition(command, pattern)`
|
|
508
586
|
|
|
509
587
|
Whether any segment of the command runs a program whose command-position text matches
|
|
@@ -522,6 +600,102 @@ function runsInCommandPosition(command: string, pattern: RegExp): boolean
|
|
|
522
600
|
|
|
523
601
|
**Returns:** True when a segment in command position matches.
|
|
524
602
|
|
|
603
|
+
#### `commandRunState(result)`
|
|
604
|
+
|
|
605
|
+
The state a command's result records — THE reader every verdict about a run goes through.
|
|
606
|
+
|
|
607
|
+
A result that carries no state is a run that ended: the state is written where the run is
|
|
608
|
+
stopped ({@link commandCutRun}), so a result without it was never stopped by a budget this
|
|
609
|
+
platform set or can see. Results produced before this state existed read the same way, which is
|
|
610
|
+
what keeps a past turn's tally readable.
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
function commandRunState(result: unknown): CommandRunState
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
- `result` — The exec_command / wait_for_task result.
|
|
617
|
+
|
|
618
|
+
**Returns:** The state the result records.
|
|
619
|
+
|
|
620
|
+
#### `suiteTally(text)`
|
|
621
|
+
|
|
622
|
+
Whether text carries a runner's closing summary ({@link SUITE_TALLY}).
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
function suiteTally(text: string): boolean
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
- `text` — The run's stdout and stderr.
|
|
629
|
+
|
|
630
|
+
**Returns:** True when the runner's own end-of-run summary is there.
|
|
631
|
+
|
|
632
|
+
#### `commandCutRun(command, result, elapsedMs, budgetSeconds)`
|
|
633
|
+
|
|
634
|
+
The run a command's result records as stopped by a budget, or null when the result is a run that
|
|
635
|
+
ended (or says nothing about a run).
|
|
636
|
+
|
|
637
|
+
Evidence, in order — never a guess:
|
|
638
|
+
|
|
639
|
+
- `timeout`'s own 124 (the code the backend's wrapper returns whenever the TOOL's budget fired,
|
|
640
|
+
whatever the pipeline ended with) — and, when the clock says the command's own smaller bound
|
|
641
|
+
ran out first and less than the budget, that bound instead.
|
|
642
|
+
- the command carried its own `timeout <N>`, N is inside this tool's budget (a larger one never
|
|
643
|
+
fires — the tool's does), the run used N, and no runner printed a closing summary: the run was
|
|
644
|
+
killed at its own bound. Its exit code, when the pipeline handed one back, is the filter's.
|
|
645
|
+
- the run used this tool's whole budget behind a filter that withheld its output, and printed no
|
|
646
|
+
closing summary: the budget stopped it, and no code in the result is the run's.
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
function commandCutRun(
|
|
650
|
+
command: string,
|
|
651
|
+
result: unknown,
|
|
652
|
+
elapsedMs: number,
|
|
653
|
+
budgetSeconds?: number,
|
|
654
|
+
): CutRun | null
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
- `command` — The command the executor sent.
|
|
658
|
+
- `result` — Its result.
|
|
659
|
+
- `elapsedMs` — How long the command ran, in ms.
|
|
660
|
+
- `budgetSeconds` — This tool's foreground budget, in seconds (0 when it has none).
|
|
661
|
+
|
|
662
|
+
**Returns:** The stopped run, or null.
|
|
663
|
+
|
|
664
|
+
#### `cutRunFacts(result)`
|
|
665
|
+
|
|
666
|
+
The facts a result records about the run a budget stopped it in — what a reader needs to say
|
|
667
|
+
how far the run got WITHOUT re-reading its log or re-deriving the counts.
|
|
668
|
+
|
|
669
|
+
The numbers are read from the runner's own output where it printed them (Hurl's per-file lines,
|
|
670
|
+
Playwright's `[n/K]`), so a caller that can also see the suite itself — the file count of the
|
|
671
|
+
`*.hurl` directory the run walked — fills in the `total` this could not know, and both then say
|
|
672
|
+
the same sentence ({@link cutRunSentence}).
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
function cutRunFacts(result: unknown): CutRun | null
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
- `result` — The exec_command / wait_for_task result.
|
|
679
|
+
|
|
680
|
+
**Returns:** The facts the result carries, or null when it records no stopped run.
|
|
681
|
+
|
|
682
|
+
#### `cutRunSentence(run)`
|
|
683
|
+
|
|
684
|
+
The first line of a stopped run's result: how long it was allowed, what stopped it, how far it
|
|
685
|
+
got, and the last thing it printed.
|
|
686
|
+
|
|
687
|
+
Written to be quotable by the model in one sentence and to leave no room for "nearly passing":
|
|
688
|
+
the counts come from the runner's own output ({@link runProgress}), and a `total` the caller
|
|
689
|
+
read elsewhere (the suite's own file count) fills in the "M of K" it can then state.
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
function cutRunSentence(run: CutRun): string
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
- `run` — The stopped run.
|
|
696
|
+
|
|
697
|
+
**Returns:** The sentence.
|
|
698
|
+
|
|
525
699
|
#### `normalizeToolInput(schema, raw)`
|
|
526
700
|
|
|
527
701
|
Move known aliases onto the declared parameter names and report what is still
|
|
@@ -1199,6 +1373,105 @@ function findPatternArgs(pattern: string): string
|
|
|
1199
1373
|
|
|
1200
1374
|
**Returns:** A `find` predicate: `-name '…'` for a bare name, else one or more `-path '…'` alternatives, ORed inside parens when there is more than one.
|
|
1201
1375
|
|
|
1376
|
+
#### `findPathArgs(pattern)`
|
|
1377
|
+
|
|
1378
|
+
The `-path` alternatives of {@link findPatternArgs}, for a pattern whose
|
|
1379
|
+
directory the caller has already rooted the search at.
|
|
1380
|
+
|
|
1381
|
+
`find_files` narrows the search root itself when the pattern spells its own
|
|
1382
|
+
directory out (see {@link literalDirPrefix}) — `node_modules` plus
|
|
1383
|
+
`@molecule/api-resource-user/dist/*.d.ts` searched that one directory. The
|
|
1384
|
+
remainder (`*.d.ts`) must then keep the `-path` form rather than collapsing to
|
|
1385
|
+
`-name`: `*` crosses `/` in an fnmatch glob, so the `-path` form still finds a
|
|
1386
|
+
declaration file in a `dist/utilities/` subdirectory, which `-name '*.d.ts'`
|
|
1387
|
+
would drop (a silent partial answer, which is worse than the timeout it
|
|
1388
|
+
replaced).
|
|
1389
|
+
|
|
1390
|
+
```typescript
|
|
1391
|
+
function findPathArgs(pattern: string): string
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
- `pattern` — The pattern's remainder, with a `/` in it or not.
|
|
1395
|
+
|
|
1396
|
+
**Returns:** One or more `-path '…'` alternatives, ORed inside parens when there is more than one.
|
|
1397
|
+
|
|
1398
|
+
#### `literalDirPrefix(pattern)`
|
|
1399
|
+
|
|
1400
|
+
The directory a GLOB pattern spells out, when it spells one out: everything
|
|
1401
|
+
before the pattern's first glob character, trimmed back to the last `/`.
|
|
1402
|
+
|
|
1403
|
+
A pattern is matched against the whole path with a leading "any parent"
|
|
1404
|
+
anchor ({@link findPatternArgs}), so `find` visits every entry below the
|
|
1405
|
+
search root. Handed `/workspace/node_modules` and
|
|
1406
|
+
`@molecule/api-resource-user/dist/*.d.ts` — a directory named outright —
|
|
1407
|
+
`find` walked the sandbox's entire dependency tree and hit the tool's 10 s
|
|
1408
|
+
deadline without printing a result (xr-molecule-flash-m94: the one directory
|
|
1409
|
+
the model meant answered a `list_files` in 0 s). The literal prefix is that
|
|
1410
|
+
directory, and rooting the search at it is what keeps the call instant.
|
|
1411
|
+
|
|
1412
|
+
A BARE first segment (`helpers/*`, `bonds/*`, `e2e/*.spec.ts`) is NOT one:
|
|
1413
|
+
`helpers/*` means "a `helpers/` directory anywhere below the root", which the
|
|
1414
|
+
tool documents and a live run relied on (xr-molecule-flash-m77 — a path
|
|
1415
|
+
pattern is not anchored to one depth). Rooting those at
|
|
1416
|
+
`<root>/helpers` would answer with a SUBSET, silently: the nested
|
|
1417
|
+
`api/src/helpers/` matches would disappear, and a missing file reads as
|
|
1418
|
+
"no such file". So a prefix is returned only when it names a path of two or
|
|
1419
|
+
more segments (`@molecule/api-resource-user/dist`), which is the shape that
|
|
1420
|
+
is a directory rather than a directory name.
|
|
1421
|
+
|
|
1422
|
+
```typescript
|
|
1423
|
+
function literalDirPrefix(pattern: string): string | null
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
- `pattern` — A pattern already accepted by {@link isValidGlob}.
|
|
1427
|
+
|
|
1428
|
+
**Returns:** The literal directory prefix, without a trailing slash, or null when the pattern has no glob character, no `/`, or only a bare name before one.
|
|
1429
|
+
|
|
1430
|
+
#### `isCommandTimeout(error)`
|
|
1431
|
+
|
|
1432
|
+
Whether a backend error is the sandbox's own command deadline.
|
|
1433
|
+
|
|
1434
|
+
The E2B SDK answers a timed-out `exec` with its generic text —
|
|
1435
|
+
`[deadline_exceeded] the operation timed out: … It can be modified by passing
|
|
1436
|
+
'timeoutMs' when making the request. Use '0' to disable the timeout.` — which
|
|
1437
|
+
names an option the executor cannot set and says nothing about the search
|
|
1438
|
+
that died. Handing that sentence to the model is what xr-molecule-flash-m94
|
|
1439
|
+
read after `find_files` spent 10 s walking `node_modules`; the tool answers
|
|
1440
|
+
the deadline in its own words instead (see {@link searchTimeoutError}).
|
|
1441
|
+
|
|
1442
|
+
```typescript
|
|
1443
|
+
function isCommandTimeout(error: unknown): boolean
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
- `error` — The error the backend threw.
|
|
1447
|
+
|
|
1448
|
+
**Returns:** True when the error reads as a timeout or a deadline.
|
|
1449
|
+
|
|
1450
|
+
#### `searchTimeoutError(tool, searched, pattern, seconds, hint)`
|
|
1451
|
+
|
|
1452
|
+
The answer a search tool gives when the backend's deadline fires: what was
|
|
1453
|
+
searched, for what, how long the tool allowed, and the two ways to get an
|
|
1454
|
+
answer — a narrower `path`, or a listing. Never the vendor's `timeoutMs`
|
|
1455
|
+
sentence (see {@link isCommandTimeout}).
|
|
1456
|
+
|
|
1457
|
+
```typescript
|
|
1458
|
+
function searchTimeoutError(
|
|
1459
|
+
tool: string,
|
|
1460
|
+
searched: string,
|
|
1461
|
+
pattern: string,
|
|
1462
|
+
seconds: number,
|
|
1463
|
+
hint: string,
|
|
1464
|
+
): string
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
- `tool` — The tool that timed out (`find_files`, `search_files`).
|
|
1468
|
+
- `searched` — The directory the search actually covered.
|
|
1469
|
+
- `pattern` — The pattern it was searching for.
|
|
1470
|
+
- `seconds` — The tool's own limit, in seconds.
|
|
1471
|
+
- `hint` — What to do differently — the narrower target or the listing tool.
|
|
1472
|
+
|
|
1473
|
+
**Returns:** The message's text.
|
|
1474
|
+
|
|
1202
1475
|
#### `pathArgError(path, tool)`
|
|
1203
1476
|
|
|
1204
1477
|
Validate a file tool's `path` argument is a non-empty string. A weak model
|
|
@@ -1664,6 +1937,15 @@ function fusedLine(content: string, newString: string): { line: number; text: st
|
|
|
1664
1937
|
|
|
1665
1938
|
### Constants
|
|
1666
1939
|
|
|
1940
|
+
#### `CUT_AT_BUDGET`
|
|
1941
|
+
|
|
1942
|
+
The state a result carries when a budget stopped its command: a run that did not finish is not a
|
|
1943
|
+
run that passed, and no reader may treat it as one.
|
|
1944
|
+
|
|
1945
|
+
```typescript
|
|
1946
|
+
const CUT_AT_BUDGET: 'cut_at_budget'
|
|
1947
|
+
```
|
|
1948
|
+
|
|
1667
1949
|
#### `TOOL_SCHEMAS`
|
|
1668
1950
|
|
|
1669
1951
|
Canonical tool schemas shared by the agent runtime and documentation.
|
|
@@ -1672,6 +1954,34 @@ Canonical tool schemas shared by the agent runtime and documentation.
|
|
|
1672
1954
|
const TOOL_SCHEMAS: Record<string, ToolSchema>
|
|
1673
1955
|
```
|
|
1674
1956
|
|
|
1957
|
+
#### `SPLIT_WORK_ADVICE`
|
|
1958
|
+
|
|
1959
|
+
The closing advice on an over-budget message for a command that is not a test
|
|
1960
|
+
suite: the work it was chaining is what to split.
|
|
1961
|
+
|
|
1962
|
+
```typescript
|
|
1963
|
+
const SPLIT_WORK_ADVICE: string
|
|
1964
|
+
```
|
|
1965
|
+
|
|
1966
|
+
#### `SUITE_OVER_BUDGET_ADVICE`
|
|
1967
|
+
|
|
1968
|
+
The closing advice on an over-budget message for a command that IS a suite run
|
|
1969
|
+
(Playwright, Vitest, a `hurl` run — {@link isSuiteRunCommand}): its files are not
|
|
1970
|
+
a bigger unit to break up.
|
|
1971
|
+
|
|
1972
|
+
X1 flash-m36 asked for this half and the message kept saying "run a smaller unit"
|
|
1973
|
+
for two months. It is the wrong direction for a suite, and measurably: a run
|
|
1974
|
+
stopped here reports NO tally, so nothing in the result says which test or request
|
|
1975
|
+
hung — the run has to be sent again in the background (off this budget, its output
|
|
1976
|
+
written to a log that can be read mid-run) or, when a consumer knows the runner's
|
|
1977
|
+
per-request limit, with each request bounded. xr-molecule-flash-m93 (+06:22,
|
|
1978
|
+
2026-10-16) lost 4.8 minutes to a Hurl run whose ONE unanswered request had hurl's
|
|
1979
|
+
300 s default per request holding the file, ten seconds past this tool's budget.
|
|
1980
|
+
|
|
1981
|
+
```typescript
|
|
1982
|
+
const SUITE_OVER_BUDGET_ADVICE: string
|
|
1983
|
+
```
|
|
1984
|
+
|
|
1675
1985
|
#### `MAX_READ_SIZE`
|
|
1676
1986
|
|
|
1677
1987
|
Max file size for read_file (5MB).
|
|
@@ -37,7 +37,19 @@ export interface CommandSegment {
|
|
|
37
37
|
args: readonly string[];
|
|
38
38
|
/** The program and its arguments as one line — what a runner pattern is matched against. */
|
|
39
39
|
text: string;
|
|
40
|
+
/**
|
|
41
|
+
* The seconds the segment's own `timeout` wrapper allows the command it runs
|
|
42
|
+
* (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
43
|
+
*/
|
|
44
|
+
timeoutSeconds: number | null;
|
|
40
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The seconds a duration word stands for.
|
|
48
|
+
*
|
|
49
|
+
* @param word - A duration (`280`, `20s`, `1m`).
|
|
50
|
+
* @returns The seconds, or null when the word is not a duration.
|
|
51
|
+
*/
|
|
52
|
+
export declare function durationSeconds(word: string): number | null;
|
|
41
53
|
/**
|
|
42
54
|
* The segments of a command, each with its program and arguments — the answer every classifier
|
|
43
55
|
* that reads a command as "this runs the suite" is asking for.
|
|
@@ -46,6 +58,20 @@ export interface CommandSegment {
|
|
|
46
58
|
* @returns One entry per statement or pipeline stage that runs something, in order.
|
|
47
59
|
*/
|
|
48
60
|
export declare function commandSegments(command: string): CommandSegment[];
|
|
61
|
+
/**
|
|
62
|
+
* The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
63
|
+
* under (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
64
|
+
*
|
|
65
|
+
* This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
66
|
+
* finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
67
|
+
* other command reader uses, rather than matched as text: `timeout` in an argument
|
|
68
|
+
* (`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
69
|
+
* the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
70
|
+
*
|
|
71
|
+
* @param command - The shell command.
|
|
72
|
+
* @returns The seconds the command allows itself, or null.
|
|
73
|
+
*/
|
|
74
|
+
export declare function commandTimeoutSeconds(command: string): number | null;
|
|
49
75
|
/**
|
|
50
76
|
* Whether any segment of the command runs a program whose command-position text matches
|
|
51
77
|
* `pattern` — the one question a runner-kind table asks.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAmFD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAyPD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAEjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAoCD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|
package/dist/command-position.js
CHANGED
|
@@ -92,6 +92,22 @@ const WRAPPER_VALUE_FLAGS = {
|
|
|
92
92
|
const ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
93
93
|
/** A wrapper's own duration — `timeout 280 …`, `timeout 1m …` — which is not a flag. */
|
|
94
94
|
const DURATION = /^\d+(?:\.\d+)?[smhd]?$/;
|
|
95
|
+
/** The seconds a `timeout` duration word names (`280`, `1m`, `1.5h`), or null when it is none. */
|
|
96
|
+
const DURATION_UNITS = { s: 1, m: 60, h: 3600, d: 86_400 };
|
|
97
|
+
/**
|
|
98
|
+
* The seconds a duration word stands for.
|
|
99
|
+
*
|
|
100
|
+
* @param word - A duration (`280`, `20s`, `1m`).
|
|
101
|
+
* @returns The seconds, or null when the word is not a duration.
|
|
102
|
+
*/
|
|
103
|
+
export function durationSeconds(word) {
|
|
104
|
+
if (!DURATION.test(word))
|
|
105
|
+
return null;
|
|
106
|
+
const [, number = '', unit = 's'] = /^(\d+(?:\.\d+)?)([smhd]?)$/.exec(word) ?? [];
|
|
107
|
+
const scale = DURATION_UNITS[unit] ?? 1;
|
|
108
|
+
const seconds = Number(number) * scale;
|
|
109
|
+
return Number.isFinite(seconds) ? seconds : null;
|
|
110
|
+
}
|
|
95
111
|
/**
|
|
96
112
|
* A word without its shell quotes.
|
|
97
113
|
*
|
|
@@ -155,14 +171,16 @@ function nestedCommand(words, from) {
|
|
|
155
171
|
return null;
|
|
156
172
|
}
|
|
157
173
|
/**
|
|
158
|
-
* Where the program starts in one segment's words.
|
|
174
|
+
* Where the program starts in one segment's words, and the bound the segment puts on itself.
|
|
159
175
|
*
|
|
160
176
|
* @param words - The segment's words, quotes kept.
|
|
161
177
|
* @returns The index of the word in command position (one past the end when the segment runs
|
|
162
|
-
* nothing),
|
|
178
|
+
* nothing), the command string of a `sh -c '<command>'` this segment turns out to be, and the
|
|
179
|
+
* seconds its own `timeout` wrapper allows that command (`timeout 280 hurl …` → 280) or null.
|
|
163
180
|
*/
|
|
164
181
|
function commandStart(words) {
|
|
165
182
|
let at = 0;
|
|
183
|
+
let timeoutSeconds = null;
|
|
166
184
|
for (let guard = 0; guard < MAX_WRAPPERS && at < words.length; guard++) {
|
|
167
185
|
const word = words[at] ?? '';
|
|
168
186
|
if (ENV_ASSIGNMENT.test(word)) {
|
|
@@ -178,7 +196,7 @@ function commandStart(words) {
|
|
|
178
196
|
if (SHELLS.has(name)) {
|
|
179
197
|
const nested = nestedCommand(words, at + 1);
|
|
180
198
|
if (nested !== null)
|
|
181
|
-
return { at, nested };
|
|
199
|
+
return { at, nested, timeoutSeconds };
|
|
182
200
|
at += 1 + flagWords(words, at + 1, name);
|
|
183
201
|
continue;
|
|
184
202
|
}
|
|
@@ -192,12 +210,14 @@ function commandStart(words) {
|
|
|
192
210
|
continue;
|
|
193
211
|
}
|
|
194
212
|
if (!LAUNCHERS.has(name))
|
|
195
|
-
return { at, nested: null };
|
|
213
|
+
return { at, nested: null, timeoutSeconds };
|
|
196
214
|
at += 1 + flagWords(words, at + 1, name);
|
|
197
|
-
if (name === 'timeout' && DURATION.test(words[at] ?? ''))
|
|
215
|
+
if (name === 'timeout' && DURATION.test(words[at] ?? '')) {
|
|
216
|
+
timeoutSeconds = durationSeconds(words[at] ?? '');
|
|
198
217
|
at += 1;
|
|
218
|
+
}
|
|
199
219
|
}
|
|
200
|
-
return { at, nested: null };
|
|
220
|
+
return { at, nested: null, timeoutSeconds };
|
|
201
221
|
}
|
|
202
222
|
/**
|
|
203
223
|
* A command's statements and pipeline stages, split where the shell splits them — inside quotes
|
|
@@ -335,6 +355,28 @@ function segmentWords(text) {
|
|
|
335
355
|
export function commandSegments(command) {
|
|
336
356
|
return segmentsOf(command, 0);
|
|
337
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
|
|
360
|
+
* under (`timeout 280 hurl --test …` → 280), or null when it carries none.
|
|
361
|
+
*
|
|
362
|
+
* This is the bound `man timeout` enforces by KILLING the command — so a run that used it did not
|
|
363
|
+
* finish, whatever the pipeline's own exit status says. It is read here, from the same parse every
|
|
364
|
+
* other command reader uses, rather than matched as text: `timeout` in an argument
|
|
365
|
+
* (`grep -n 'timeout 280' notes.md`) bounds nothing, and `-k 5` is the grace before the kill, not
|
|
366
|
+
* the bound ({@link WRAPPER_VALUE_FLAGS}).
|
|
367
|
+
*
|
|
368
|
+
* @param command - The shell command.
|
|
369
|
+
* @returns The seconds the command allows itself, or null.
|
|
370
|
+
*/
|
|
371
|
+
export function commandTimeoutSeconds(command) {
|
|
372
|
+
let bound = null;
|
|
373
|
+
for (const segment of segmentsOf(command, 0)) {
|
|
374
|
+
if (segment.timeoutSeconds === null)
|
|
375
|
+
continue;
|
|
376
|
+
bound = bound === null ? segment.timeoutSeconds : Math.max(bound, segment.timeoutSeconds);
|
|
377
|
+
}
|
|
378
|
+
return bound;
|
|
379
|
+
}
|
|
338
380
|
/**
|
|
339
381
|
* {@link commandSegments}'s recursion, so a `sh -c '<command>'` is opened as far as
|
|
340
382
|
* {@link MAX_NESTING} allows.
|
|
@@ -362,7 +404,12 @@ function segmentsOf(command, depth) {
|
|
|
362
404
|
continue;
|
|
363
405
|
const program = programName(word);
|
|
364
406
|
const args = words.slice(start.at + 1);
|
|
365
|
-
out.push({
|
|
407
|
+
out.push({
|
|
408
|
+
program,
|
|
409
|
+
args,
|
|
410
|
+
text: [program, ...args].join(' '),
|
|
411
|
+
timeoutSeconds: start.timeoutSeconds,
|
|
412
|
+
});
|
|
366
413
|
}
|
|
367
414
|
return out;
|
|
368
415
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A command that a budget stopped before it finished, and the state its result carries so no
|
|
3
|
+
* reader can mistake it for a run that ended.
|
|
4
|
+
*
|
|
5
|
+
* X1 xr-molecule-flash-m93 (2026-10-16, +12:37) sent the supplied RealWorld suite as
|
|
6
|
+
* `timeout 280 hurl --test --continue-on-error --jobs 1 … hurl/*.hurl 2>&1 | tail -60` and got
|
|
7
|
+
* back `exitCode: 0`, four finished files' lines and nothing else. Two lies in one result: the
|
|
8
|
+
* `timeout 280` killed the run at 280 s, and the `| tail` made the pipeline's status the status —
|
|
9
|
+
* so the executor read a suite that stopped at its fifth file as a suite that ran and nearly
|
|
10
|
+
* passed, fixed one message, and immediately sent the same 280-second command again (the run's
|
|
11
|
+
* third, ~14 minutes of a 60-minute cap across three of them).
|
|
12
|
+
*
|
|
13
|
+
* So the tool never reports a stopped run as `exitCode: 0`. Such a result carries
|
|
14
|
+
* `status: 'cut_at_budget'` and an exit code that is `null` whenever the number the pipeline
|
|
15
|
+
* reported belongs to the filter rather than to the run, and every reader of a run's verdict reads
|
|
16
|
+
* the state through {@link commandRunState} — a cut run is NOT RUN, never a pass.
|
|
17
|
+
*
|
|
18
|
+
* Two things stop a run, and both are read from the command and the result, never inferred:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Its own bound** — a `timeout <N>` the command carries ({@link commandTimeoutSeconds}). The
|
|
21
|
+
* run used it, so the run was killed at it; with no closing tally in the output, the run did
|
|
22
|
+
* not finish. This is m93's shape.
|
|
23
|
+
* 2. **This tool's budget** — the backend runs the command under `timeout` (see
|
|
24
|
+
* `backends/sandbox.ts`), which reports 124 whatever the pipeline did, and returns everything
|
|
25
|
+
* printed until then. The budget is the tool's own, so its number belongs to the tool.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* The state a result carries when a budget stopped its command: a run that did not finish is not a
|
|
31
|
+
* run that passed, and no reader may treat it as one.
|
|
32
|
+
*/
|
|
33
|
+
export declare const CUT_AT_BUDGET = "cut_at_budget";
|
|
34
|
+
/** What a command's result records: a run that ended, or one a budget stopped. */
|
|
35
|
+
export type CommandRunState = 'ran' | typeof CUT_AT_BUDGET;
|
|
36
|
+
/**
|
|
37
|
+
* The state a command's result records — THE reader every verdict about a run goes through.
|
|
38
|
+
*
|
|
39
|
+
* A result that carries no state is a run that ended: the state is written where the run is
|
|
40
|
+
* stopped ({@link commandCutRun}), so a result without it was never stopped by a budget this
|
|
41
|
+
* platform set or can see. Results produced before this state existed read the same way, which is
|
|
42
|
+
* what keeps a past turn's tally readable.
|
|
43
|
+
*
|
|
44
|
+
* @param result - The exec_command / wait_for_task result.
|
|
45
|
+
* @returns The state the result records.
|
|
46
|
+
*/
|
|
47
|
+
export declare function commandRunState(result: unknown): CommandRunState;
|
|
48
|
+
/**
|
|
49
|
+
* Whether text carries a runner's closing summary ({@link SUITE_TALLY}).
|
|
50
|
+
*
|
|
51
|
+
* @param text - The run's stdout and stderr.
|
|
52
|
+
* @returns True when the runner's own end-of-run summary is there.
|
|
53
|
+
*/
|
|
54
|
+
export declare function suiteTally(text: string): boolean;
|
|
55
|
+
/** What stopped a run. */
|
|
56
|
+
export type CutBy = 'tool-budget' | 'command-timeout';
|
|
57
|
+
/** A run a budget stopped, as its result reports it. */
|
|
58
|
+
export interface CutRun {
|
|
59
|
+
/** The bound the run was allowed, in seconds. */
|
|
60
|
+
seconds: number;
|
|
61
|
+
/** Whether this tool's budget stopped it or the `timeout` the command carried. */
|
|
62
|
+
stoppedBy: CutBy;
|
|
63
|
+
/**
|
|
64
|
+
* The exit code the run's own process reported, or null when the code in the result belongs to
|
|
65
|
+
* the pipeline's filter (`… | tail`) rather than to the run — the case that read as success.
|
|
66
|
+
*/
|
|
67
|
+
exitCode: number | null;
|
|
68
|
+
/** How many files or tests the runner's own output shows finishing, or null when it shows none. */
|
|
69
|
+
ran: number | null;
|
|
70
|
+
/** How many the run was walking, when the runner printed it (Playwright's `[n/K]`), or null. */
|
|
71
|
+
total: number | null;
|
|
72
|
+
/** The noun the runner counts in. */
|
|
73
|
+
noun: 'files' | 'tests';
|
|
74
|
+
/** The last thing the run printed — the line a reader looks at first. */
|
|
75
|
+
doing: string | null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The run a command's result records as stopped by a budget, or null when the result is a run that
|
|
79
|
+
* ended (or says nothing about a run).
|
|
80
|
+
*
|
|
81
|
+
* Evidence, in order — never a guess:
|
|
82
|
+
*
|
|
83
|
+
* - `timeout`'s own 124 (the code the backend's wrapper returns whenever the TOOL's budget fired,
|
|
84
|
+
* whatever the pipeline ended with) — and, when the clock says the command's own smaller bound
|
|
85
|
+
* ran out first and less than the budget, that bound instead.
|
|
86
|
+
* - the command carried its own `timeout <N>`, N is inside this tool's budget (a larger one never
|
|
87
|
+
* fires — the tool's does), the run used N, and no runner printed a closing summary: the run was
|
|
88
|
+
* killed at its own bound. Its exit code, when the pipeline handed one back, is the filter's.
|
|
89
|
+
* - the run used this tool's whole budget behind a filter that withheld its output, and printed no
|
|
90
|
+
* closing summary: the budget stopped it, and no code in the result is the run's.
|
|
91
|
+
*
|
|
92
|
+
* @param command - The command the executor sent.
|
|
93
|
+
* @param result - Its result.
|
|
94
|
+
* @param elapsedMs - How long the command ran, in ms.
|
|
95
|
+
* @param budgetSeconds - This tool's foreground budget, in seconds (0 when it has none).
|
|
96
|
+
* @returns The stopped run, or null.
|
|
97
|
+
*/
|
|
98
|
+
export declare function commandCutRun(command: string, result: unknown, elapsedMs: number, budgetSeconds?: number): CutRun | null;
|
|
99
|
+
/**
|
|
100
|
+
* The facts a result records about the run a budget stopped it in — what a reader needs to say
|
|
101
|
+
* how far the run got WITHOUT re-reading its log or re-deriving the counts.
|
|
102
|
+
*
|
|
103
|
+
* The numbers are read from the runner's own output where it printed them (Hurl's per-file lines,
|
|
104
|
+
* Playwright's `[n/K]`), so a caller that can also see the suite itself — the file count of the
|
|
105
|
+
* `*.hurl` directory the run walked — fills in the `total` this could not know, and both then say
|
|
106
|
+
* the same sentence ({@link cutRunSentence}).
|
|
107
|
+
*
|
|
108
|
+
* @param result - The exec_command / wait_for_task result.
|
|
109
|
+
* @returns The facts the result carries, or null when it records no stopped run.
|
|
110
|
+
*/
|
|
111
|
+
export declare function cutRunFacts(result: unknown): CutRun | null;
|
|
112
|
+
/**
|
|
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.
|
|
115
|
+
*
|
|
116
|
+
* Written to be quotable by the model in one sentence and to leave no room for "nearly passing":
|
|
117
|
+
* 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.
|
|
119
|
+
*
|
|
120
|
+
* @param run - The stopped run.
|
|
121
|
+
* @returns The sentence.
|
|
122
|
+
*/
|
|
123
|
+
export declare function cutRunSentence(run: CutRun): string;
|
|
124
|
+
//# sourceMappingURL=cut-run.d.ts.map
|
|
@@ -0,0 +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"}
|