@cavelang/cli 0.35.0 → 0.36.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 +224 -12
- package/dist/internal/act/action.js +2 -1
- package/dist/internal/act/action.js.map +1 -1
- package/dist/internal/act/declare.d.ts.map +1 -1
- package/dist/internal/act/declare.js +82 -53
- package/dist/internal/act/declare.js.map +1 -1
- package/dist/internal/act/engine.d.ts +4 -3
- package/dist/internal/act/engine.d.ts.map +1 -1
- package/dist/internal/act/engine.js +121 -32
- package/dist/internal/act/engine.js.map +1 -1
- package/dist/internal/automate/cancellation.d.ts +3 -0
- package/dist/internal/automate/cancellation.d.ts.map +1 -0
- package/dist/internal/automate/cancellation.js +38 -0
- package/dist/internal/automate/cancellation.js.map +1 -0
- package/dist/internal/automate/declare.d.ts.map +1 -1
- package/dist/internal/automate/declare.js +49 -24
- package/dist/internal/automate/declare.js.map +1 -1
- package/dist/internal/automate/engine.d.ts +7 -3
- package/dist/internal/automate/engine.d.ts.map +1 -1
- package/dist/internal/automate/engine.js +196 -93
- package/dist/internal/automate/engine.js.map +1 -1
- package/dist/internal/automate/error-message.d.ts +3 -0
- package/dist/internal/automate/error-message.d.ts.map +1 -0
- package/dist/internal/automate/error-message.js +10 -0
- package/dist/internal/automate/error-message.js.map +1 -0
- package/dist/internal/automate/main.d.ts.map +1 -1
- package/dist/internal/automate/main.js +238 -35
- package/dist/internal/automate/main.js.map +1 -1
- package/dist/internal/automate/options.d.ts +4 -0
- package/dist/internal/automate/options.d.ts.map +1 -0
- package/dist/internal/automate/options.js +19 -0
- package/dist/internal/automate/options.js.map +1 -0
- package/dist/internal/connect/declared.d.ts +6 -27
- package/dist/internal/connect/declared.d.ts.map +1 -1
- package/dist/internal/connect/declared.js +165 -76
- package/dist/internal/connect/declared.js.map +1 -1
- package/dist/internal/connect/error-message.d.ts +3 -0
- package/dist/internal/connect/error-message.d.ts.map +1 -0
- package/dist/internal/connect/error-message.js +10 -0
- package/dist/internal/connect/error-message.js.map +1 -0
- package/dist/internal/connect/federation.d.ts +9 -0
- package/dist/internal/connect/federation.d.ts.map +1 -0
- package/dist/internal/connect/federation.js +19 -0
- package/dist/internal/connect/federation.js.map +1 -0
- package/dist/internal/connect/fetch-failure.d.ts +3 -0
- package/dist/internal/connect/fetch-failure.d.ts.map +1 -0
- package/dist/internal/connect/fetch-failure.js +36 -0
- package/dist/internal/connect/fetch-failure.js.map +1 -0
- package/dist/internal/connect/main.d.ts +1 -0
- package/dist/internal/connect/main.d.ts.map +1 -1
- package/dist/internal/connect/main.js +288 -114
- package/dist/internal/connect/main.js.map +1 -1
- package/dist/internal/connect/options.d.ts +3 -0
- package/dist/internal/connect/options.d.ts.map +1 -0
- package/dist/internal/connect/options.js +9 -0
- package/dist/internal/connect/options.js.map +1 -0
- package/dist/internal/connect/records.d.ts +3 -0
- package/dist/internal/connect/records.d.ts.map +1 -0
- package/dist/internal/connect/records.js +21 -0
- package/dist/internal/connect/records.js.map +1 -0
- package/dist/internal/connect/run.d.ts +1 -18
- package/dist/internal/connect/run.d.ts.map +1 -1
- package/dist/internal/connect/run.js +115 -55
- package/dist/internal/connect/run.js.map +1 -1
- package/dist/internal/connect/source.d.ts +3 -1
- package/dist/internal/connect/source.d.ts.map +1 -1
- package/dist/internal/connect/source.js +226 -47
- package/dist/internal/connect/source.js.map +1 -1
- package/dist/internal/connect/template.d.ts +2 -2
- package/dist/internal/connect/template.d.ts.map +1 -1
- package/dist/internal/connect/template.js +25 -9
- package/dist/internal/connect/template.js.map +1 -1
- package/dist/internal/connect/utf8.d.ts +3 -0
- package/dist/internal/connect/utf8.d.ts.map +1 -0
- package/dist/internal/connect/utf8.js +12 -0
- package/dist/internal/connect/utf8.js.map +1 -0
- package/dist/internal/eval/cleanup.d.ts +5 -0
- package/dist/internal/eval/cleanup.d.ts.map +1 -0
- package/dist/internal/eval/cleanup.js +50 -0
- package/dist/internal/eval/cleanup.js.map +1 -0
- package/dist/internal/eval/error-message.d.ts +3 -0
- package/dist/internal/eval/error-message.d.ts.map +1 -0
- package/dist/internal/eval/error-message.js +10 -0
- package/dist/internal/eval/error-message.js.map +1 -0
- package/dist/internal/eval/judge.d.ts.map +1 -1
- package/dist/internal/eval/judge.js +3 -31
- package/dist/internal/eval/judge.js.map +1 -1
- package/dist/internal/eval/loop.d.ts.map +1 -1
- package/dist/internal/eval/loop.js +18 -4
- package/dist/internal/eval/loop.js.map +1 -1
- package/dist/internal/eval/main.d.ts.map +1 -1
- package/dist/internal/eval/main.js +22 -10
- package/dist/internal/eval/main.js.map +1 -1
- package/dist/internal/eval/options.d.ts +6 -0
- package/dist/internal/eval/options.d.ts.map +1 -0
- package/dist/internal/eval/options.js +31 -0
- package/dist/internal/eval/options.js.map +1 -0
- package/dist/internal/eval/queries.d.ts.map +1 -1
- package/dist/internal/eval/queries.js +9 -6
- package/dist/internal/eval/queries.js.map +1 -1
- package/dist/internal/eval/run.d.ts.map +1 -1
- package/dist/internal/eval/run.js +112 -24
- package/dist/internal/eval/run.js.map +1 -1
- package/dist/internal/eval/score.d.ts.map +1 -1
- package/dist/internal/eval/score.js +9 -2
- package/dist/internal/eval/score.js.map +1 -1
- package/dist/internal/eval/suite.d.ts.map +1 -1
- package/dist/internal/eval/suite.js +14 -2
- package/dist/internal/eval/suite.js.map +1 -1
- package/dist/internal/ingest/cleanup.d.ts +5 -0
- package/dist/internal/ingest/cleanup.d.ts.map +1 -0
- package/dist/internal/ingest/cleanup.js +50 -0
- package/dist/internal/ingest/cleanup.js.map +1 -0
- package/dist/internal/ingest/content.d.ts +6 -0
- package/dist/internal/ingest/content.d.ts.map +1 -0
- package/dist/internal/ingest/content.js +25 -0
- package/dist/internal/ingest/content.js.map +1 -0
- package/dist/internal/ingest/context.d.ts.map +1 -1
- package/dist/internal/ingest/context.js +25 -7
- package/dist/internal/ingest/context.js.map +1 -1
- package/dist/internal/ingest/error-message.d.ts +3 -0
- package/dist/internal/ingest/error-message.d.ts.map +1 -0
- package/dist/internal/ingest/error-message.js +10 -0
- package/dist/internal/ingest/error-message.js.map +1 -0
- package/dist/internal/ingest/files.d.ts +4 -12
- package/dist/internal/ingest/files.d.ts.map +1 -1
- package/dist/internal/ingest/files.js +22 -10
- package/dist/internal/ingest/files.js.map +1 -1
- package/dist/internal/ingest/main.d.ts.map +1 -1
- package/dist/internal/ingest/main.js +46 -16
- package/dist/internal/ingest/main.js.map +1 -1
- package/dist/internal/ingest/options.d.ts +6 -0
- package/dist/internal/ingest/options.d.ts.map +1 -0
- package/dist/internal/ingest/options.js +27 -0
- package/dist/internal/ingest/options.js.map +1 -0
- package/dist/internal/ingest/prompt.d.ts.map +1 -1
- package/dist/internal/ingest/prompt.js +14 -10
- package/dist/internal/ingest/prompt.js.map +1 -1
- package/dist/internal/ingest/run.d.ts +9 -2
- package/dist/internal/ingest/run.d.ts.map +1 -1
- package/dist/internal/ingest/run.js +206 -62
- package/dist/internal/ingest/run.js.map +1 -1
- package/dist/internal/ingest/timeout.d.ts +3 -0
- package/dist/internal/ingest/timeout.d.ts.map +1 -0
- package/dist/internal/ingest/timeout.js +14 -0
- package/dist/internal/ingest/timeout.js.map +1 -0
- package/dist/internal/ingest/web.d.ts +2 -11
- package/dist/internal/ingest/web.d.ts.map +1 -1
- package/dist/internal/ingest/web.js +131 -25
- package/dist/internal/ingest/web.js.map +1 -1
- package/dist/internal/loop/budgets.d.ts +3 -0
- package/dist/internal/loop/budgets.d.ts.map +1 -0
- package/dist/internal/loop/budgets.js +10 -0
- package/dist/internal/loop/budgets.js.map +1 -0
- package/dist/internal/loop/index.d.ts +2 -0
- package/dist/internal/loop/index.d.ts.map +1 -1
- package/dist/internal/loop/index.js +2 -0
- package/dist/internal/loop/index.js.map +1 -1
- package/dist/internal/loop/json-answer.d.ts +7 -0
- package/dist/internal/loop/json-answer.d.ts.map +1 -0
- package/dist/internal/loop/json-answer.js +79 -0
- package/dist/internal/loop/json-answer.js.map +1 -0
- package/dist/internal/loop/json-spans.d.ts +3 -0
- package/dist/internal/loop/json-spans.d.ts.map +1 -0
- package/dist/internal/loop/json-spans.js +88 -0
- package/dist/internal/loop/json-spans.js.map +1 -0
- package/dist/internal/loop/llm.d.ts +3 -2
- package/dist/internal/loop/llm.d.ts.map +1 -1
- package/dist/internal/loop/llm.js +115 -32
- package/dist/internal/loop/llm.js.map +1 -1
- package/dist/internal/loop/process.d.ts +3 -1
- package/dist/internal/loop/process.d.ts.map +1 -1
- package/dist/internal/loop/process.js +159 -59
- package/dist/internal/loop/process.js.map +1 -1
- package/dist/internal/loop/read-snapshot.d.ts +4 -0
- package/dist/internal/loop/read-snapshot.d.ts.map +1 -0
- package/dist/internal/loop/read-snapshot.js +29 -0
- package/dist/internal/loop/read-snapshot.js.map +1 -0
- package/dist/internal/loop/reconstruct.d.ts +6 -5
- package/dist/internal/loop/reconstruct.d.ts.map +1 -1
- package/dist/internal/loop/reconstruct.js +19 -8
- package/dist/internal/loop/reconstruct.js.map +1 -1
- package/dist/internal/loop/score-settings.d.ts +5 -0
- package/dist/internal/loop/score-settings.d.ts.map +1 -0
- package/dist/internal/loop/score-settings.js +14 -0
- package/dist/internal/loop/score-settings.js.map +1 -0
- package/dist/internal/loop/sqlite.d.ts.map +1 -1
- package/dist/internal/loop/sqlite.js +15 -7
- package/dist/internal/loop/sqlite.js.map +1 -1
- package/dist/internal/loop/store.d.ts.map +1 -1
- package/dist/internal/loop/store.js +11 -7
- package/dist/internal/loop/store.js.map +1 -1
- package/dist/internal/loop/timeout.d.ts +3 -0
- package/dist/internal/loop/timeout.d.ts.map +1 -0
- package/dist/internal/loop/timeout.js +13 -0
- package/dist/internal/loop/timeout.js.map +1 -0
- package/dist/internal/mcp/error-message.d.ts +3 -0
- package/dist/internal/mcp/error-message.d.ts.map +1 -0
- package/dist/internal/mcp/error-message.js +10 -0
- package/dist/internal/mcp/error-message.js.map +1 -0
- package/dist/internal/mcp/main.d.ts.map +1 -1
- package/dist/internal/mcp/main.js +42 -27
- package/dist/internal/mcp/main.js.map +1 -1
- package/dist/internal/mcp/read-snapshot.d.ts +4 -0
- package/dist/internal/mcp/read-snapshot.d.ts.map +1 -0
- package/dist/internal/mcp/read-snapshot.js +22 -0
- package/dist/internal/mcp/read-snapshot.js.map +1 -0
- package/dist/internal/mcp/server.d.ts.map +1 -1
- package/dist/internal/mcp/server.js +181 -21
- package/dist/internal/mcp/server.js.map +1 -1
- package/dist/internal/mcp/source.d.ts +3 -0
- package/dist/internal/mcp/source.d.ts.map +1 -0
- package/dist/internal/mcp/source.js +13 -0
- package/dist/internal/mcp/source.js.map +1 -0
- package/dist/internal/mcp/tools.d.ts.map +1 -1
- package/dist/internal/mcp/tools.js +135 -72
- package/dist/internal/mcp/tools.js.map +1 -1
- package/dist/internal/rules/declarations.d.ts +6 -0
- package/dist/internal/rules/declarations.d.ts.map +1 -0
- package/dist/internal/rules/declarations.js +14 -0
- package/dist/internal/rules/declarations.js.map +1 -0
- package/dist/internal/rules/declare.d.ts.map +1 -1
- package/dist/internal/rules/declare.js +30 -16
- package/dist/internal/rules/declare.js.map +1 -1
- package/dist/internal/rules/engine.d.ts +8 -7
- package/dist/internal/rules/engine.d.ts.map +1 -1
- package/dist/internal/rules/engine.js +150 -44
- package/dist/internal/rules/engine.js.map +1 -1
- package/dist/internal/shape/alias-root.d.ts +3 -0
- package/dist/internal/shape/alias-root.d.ts.map +1 -0
- package/dist/internal/shape/alias-root.js +13 -0
- package/dist/internal/shape/alias-root.js.map +1 -0
- package/dist/internal/shape/check.d.ts +15 -3
- package/dist/internal/shape/check.d.ts.map +1 -1
- package/dist/internal/shape/check.js +230 -100
- package/dist/internal/shape/check.js.map +1 -1
- package/dist/internal/shape/client.d.ts.map +1 -1
- package/dist/internal/shape/client.js +42 -20
- package/dist/internal/shape/client.js.map +1 -1
- package/dist/internal/shape/constraints.d.ts +8 -0
- package/dist/internal/shape/constraints.d.ts.map +1 -0
- package/dist/internal/shape/constraints.js +17 -0
- package/dist/internal/shape/constraints.js.map +1 -0
- package/dist/internal/shape/edit-distance.d.ts +3 -0
- package/dist/internal/shape/edit-distance.d.ts.map +1 -0
- package/dist/internal/shape/edit-distance.js +33 -0
- package/dist/internal/shape/edit-distance.js.map +1 -0
- package/dist/internal/shape/gate.d.ts.map +1 -1
- package/dist/internal/shape/gate.js +0 -0
- package/dist/internal/shape/gate.js.map +1 -1
- package/dist/internal/shape/snapshot.d.ts +4 -0
- package/dist/internal/shape/snapshot.d.ts.map +1 -0
- package/dist/internal/shape/snapshot.js +20 -0
- package/dist/internal/shape/snapshot.js.map +1 -0
- package/dist/internal/shape/suggest.d.ts +8 -6
- package/dist/internal/shape/suggest.d.ts.map +1 -1
- package/dist/internal/shape/suggest.js +208 -92
- package/dist/internal/shape/suggest.js.map +1 -1
- package/dist/internal/sync/error-message.d.ts +3 -0
- package/dist/internal/sync/error-message.d.ts.map +1 -0
- package/dist/internal/sync/error-message.js +10 -0
- package/dist/internal/sync/error-message.js.map +1 -0
- package/dist/internal/sync/index.d.ts +3 -2
- package/dist/internal/sync/index.d.ts.map +1 -1
- package/dist/internal/sync/index.js +3 -2
- package/dist/internal/sync/index.js.map +1 -1
- package/dist/internal/sync/sync.d.ts +8 -5
- package/dist/internal/sync/sync.d.ts.map +1 -1
- package/dist/internal/sync/sync.js +322 -65
- package/dist/internal/sync/sync.js.map +1 -1
- package/dist/internal/view/api.d.ts +3 -1
- package/dist/internal/view/api.d.ts.map +1 -1
- package/dist/internal/view/api.js +154 -90
- package/dist/internal/view/api.js.map +1 -1
- package/dist/internal/view/error-message.d.ts +3 -0
- package/dist/internal/view/error-message.d.ts.map +1 -0
- package/dist/internal/view/error-message.js +10 -0
- package/dist/internal/view/error-message.js.map +1 -0
- package/dist/internal/view/main.d.ts.map +1 -1
- package/dist/internal/view/main.js +61 -8
- package/dist/internal/view/main.js.map +1 -1
- package/dist/internal/view/page.d.ts.map +1 -1
- package/dist/internal/view/page.js +119 -20
- package/dist/internal/view/page.js.map +1 -1
- package/dist/internal/view/report.d.ts.map +1 -1
- package/dist/internal/view/report.js +180 -145
- package/dist/internal/view/report.js.map +1 -1
- package/dist/internal/view/scope.d.ts +6 -2
- package/dist/internal/view/scope.d.ts.map +1 -1
- package/dist/internal/view/scope.js +107 -24
- package/dist/internal/view/scope.js.map +1 -1
- package/dist/internal/view/sensitivity.d.ts +4 -0
- package/dist/internal/view/sensitivity.d.ts.map +1 -0
- package/dist/internal/view/sensitivity.js +11 -0
- package/dist/internal/view/sensitivity.js.map +1 -0
- package/dist/internal/view/server.d.ts.map +1 -1
- package/dist/internal/view/server.js +57 -20
- package/dist/internal/view/server.js.map +1 -1
- package/dist/src/atomic-output.d.ts +3 -0
- package/dist/src/atomic-output.d.ts.map +1 -0
- package/dist/src/atomic-output.js +67 -0
- package/dist/src/atomic-output.js.map +1 -0
- package/dist/src/cli.d.ts +3 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +188 -173
- package/dist/src/cli.js.map +1 -1
- package/dist/src/dispatch.d.ts.map +1 -1
- package/dist/src/dispatch.js +3 -2
- package/dist/src/dispatch.js.map +1 -1
- package/dist/src/doctor.d.ts.map +1 -1
- package/dist/src/doctor.js +178 -102
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/error-message.d.ts +3 -0
- package/dist/src/error-message.d.ts.map +1 -0
- package/dist/src/error-message.js +20 -0
- package/dist/src/error-message.js.map +1 -0
- package/dist/src/hooks-config.d.ts +3 -0
- package/dist/src/hooks-config.d.ts.map +1 -0
- package/dist/src/hooks-config.js +23 -0
- package/dist/src/hooks-config.js.map +1 -0
- package/package.json +23 -20
- package/src/atomic-output.ts +47 -0
- package/src/cli.ts +170 -147
- package/src/dispatch.ts +3 -2
- package/src/doctor.ts +192 -111
- package/src/error-message.ts +13 -0
- package/src/hooks-config.ts +22 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* model twice per step (select, then "are we done?"). Both decisions fit
|
|
12
12
|
* one prompt: the model replies with the next cue to expand *or* `STOP`,
|
|
13
13
|
* and the loop already treats an `undefined` selection as the stop
|
|
14
|
-
* signal. `done` then only
|
|
14
|
+
* signal. `done` then only checks the stopping budgets, for free.
|
|
15
15
|
* - **Scoring stays local.** Models are better spent on select/stop than
|
|
16
16
|
* on per-edge arithmetic; `score` is the same parent × confidence ×
|
|
17
17
|
* decay the heuristic uses, so the scores shown in the prompt mean the
|
|
@@ -34,11 +34,25 @@ import { tmpdir } from 'node:os';
|
|
|
34
34
|
import { join } from 'node:path';
|
|
35
35
|
import { emitClaim } from '@cavelang/canonical';
|
|
36
36
|
import { runProcess, shellCommand } from "./process.js";
|
|
37
|
+
import { validateBudgets } from "./budgets.js";
|
|
38
|
+
import { propagatedScore, validateScoreSetting } from "./score-settings.js";
|
|
39
|
+
import { completionTimeoutMs } from "./timeout.js";
|
|
37
40
|
/** The reply that ends the reconstruction. */
|
|
38
41
|
export const stopToken = 'STOP';
|
|
39
42
|
const defaultMaxCues = 16;
|
|
43
|
+
const cueLimit = (maxCues = defaultMaxCues) => {
|
|
44
|
+
if (!Number.isSafeInteger(maxCues) || maxCues < 1) {
|
|
45
|
+
throw new TypeError('maxCues must be a positive safe integer');
|
|
46
|
+
}
|
|
47
|
+
return maxCues;
|
|
48
|
+
};
|
|
40
49
|
/** Strongest first; stable sort keeps FIFO order on ties, like the heuristic. */
|
|
41
50
|
const strongestFirst = (frontier) => [...frontier].sort((a, b) => b.score - a.score);
|
|
51
|
+
/** Keep familiar hundredths when exact without erasing distinctions used by selection. */
|
|
52
|
+
const scoreText = (score) => {
|
|
53
|
+
const rounded = score.toFixed(2);
|
|
54
|
+
return Number(rounded) === score ? rounded : String(score);
|
|
55
|
+
};
|
|
42
56
|
/**
|
|
43
57
|
* The single per-step prompt: the query, the claims collected so far as
|
|
44
58
|
* canonical CAVE lines, the strongest frontier cues with their scores, and
|
|
@@ -46,17 +60,20 @@ const strongestFirst = (frontier) => [...frontier].sort((a, b) => b.score - a.sc
|
|
|
46
60
|
* the same rendering under a different transport.
|
|
47
61
|
*/
|
|
48
62
|
export const selectPrompt = (state, options = {}) => {
|
|
49
|
-
const
|
|
63
|
+
const maxCues = cueLimit(options.maxCues);
|
|
64
|
+
const query = options.query;
|
|
65
|
+
const instructions = options.instructions;
|
|
66
|
+
const cues = strongestFirst(state.frontier).slice(0, maxCues);
|
|
50
67
|
return [
|
|
51
68
|
'You are driving memory reconstruction over a CAVE claim graph, one step at a time.',
|
|
52
|
-
...
|
|
69
|
+
...query === undefined ? [] : ['', `Query: ${query}`],
|
|
53
70
|
'',
|
|
54
71
|
'Claims collected so far (canonical CAVE):',
|
|
55
72
|
...state.collected.length === 0 ? ['(none yet)'] : state.collected.map(claim => emitClaim(claim)),
|
|
56
73
|
'',
|
|
57
74
|
'Frontier cues (entity @ score); expanding a cue collects its claims and its neighbors:',
|
|
58
|
-
...cues.map(cue => `${cue.entity} @ ${cue.score
|
|
59
|
-
...
|
|
75
|
+
...cues.map(cue => `${cue.entity} @ ${scoreText(cue.score)}`),
|
|
76
|
+
...instructions === undefined ? [] : ['', instructions],
|
|
60
77
|
'',
|
|
61
78
|
'Reply with exactly one line: the name of the single frontier cue to expand next, ' +
|
|
62
79
|
`or ${stopToken} when the collected claims already answer the query ` +
|
|
@@ -64,22 +81,39 @@ export const selectPrompt = (state, options = {}) => {
|
|
|
64
81
|
].join('\n');
|
|
65
82
|
};
|
|
66
83
|
/** List markers, punctuation, quotes and backticks around a model's answer. */
|
|
67
|
-
const
|
|
68
|
-
|
|
84
|
+
const unlisted = (line) => line.trim().replace(/^(?:[-*]|\d+[.)])\s+/, '');
|
|
85
|
+
const stripped = (line) => unlisted(line)
|
|
69
86
|
.replace(/[.!,;:]+$/, '')
|
|
70
87
|
.replace(/^[`'"]+|[`'"]+$/g, '')
|
|
71
88
|
.trim();
|
|
72
|
-
const
|
|
89
|
+
const wordChars = String.raw `[\p{L}\p{M}\p{N}_/-]`;
|
|
90
|
+
const startsWithWord = new RegExp(`^${wordChars}`, 'u');
|
|
91
|
+
// Dots and colons connect name segments only when another word character
|
|
92
|
+
// follows on that side; sentence punctuation such as `api. ` stays a boundary.
|
|
93
|
+
const continuesName = (reply, boundary, direction) => {
|
|
94
|
+
let index = boundary;
|
|
95
|
+
while (/[.:]/.test(reply[direction === -1 ? index - 1 : index] ?? '')) {
|
|
96
|
+
index += direction;
|
|
97
|
+
}
|
|
98
|
+
// Two code units preserve an adjacent astral code point on either side.
|
|
99
|
+
const adjacent = direction === -1
|
|
100
|
+
? [...reply.slice(Math.max(0, index - 2), index)].at(-1) ?? ''
|
|
101
|
+
: reply.slice(index, index + 2);
|
|
102
|
+
return startsWithWord.test(adjacent);
|
|
103
|
+
};
|
|
73
104
|
/** First occurrence of `entity` in `reply` not embedded in a larger word. */
|
|
74
105
|
const mentionAt = (reply, entity) => {
|
|
106
|
+
// Empty names can match exact replies, but cannot be mentioned in prose.
|
|
107
|
+
// indexOf('', from) clamps to reply.length, so advancing from never ends.
|
|
108
|
+
if (entity === '')
|
|
109
|
+
return -1;
|
|
75
110
|
for (let from = 0;;) {
|
|
76
111
|
const index = reply.indexOf(entity, from);
|
|
77
112
|
if (index === -1) {
|
|
78
113
|
return -1;
|
|
79
114
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
if ((before === undefined || !wordChar.test(before)) && (after === undefined || !wordChar.test(after))) {
|
|
115
|
+
const end = index + entity.length;
|
|
116
|
+
if (!continuesName(reply, index, -1) && !continuesName(reply, end, 1)) {
|
|
83
117
|
return index;
|
|
84
118
|
}
|
|
85
119
|
from = index + 1;
|
|
@@ -90,6 +124,7 @@ const mentionAt = (reply, entity) => {
|
|
|
90
124
|
*
|
|
91
125
|
* 1. the trimmed reply, its last non-empty line, or either with list
|
|
92
126
|
* markers/quotes stripped, is exactly a frontier entity → that cue;
|
|
127
|
+
* trailing sentence punctuation prefers the longest matching literal name;
|
|
93
128
|
* 2. the reply's first word is the stop token (any case) → stop;
|
|
94
129
|
* 3. the earliest frontier entity mentioned anywhere in the reply → that
|
|
95
130
|
* cue (ties at one position go to the longer name; occurrences inside
|
|
@@ -104,13 +139,23 @@ export const parseSelection = (reply, frontier) => {
|
|
|
104
139
|
}
|
|
105
140
|
const byEntity = new Map(frontier.map(cue => [cue.entity, cue]));
|
|
106
141
|
const lastLine = reply.split('\n').map(line => line.trim()).filter(line => line !== '').at(-1) ?? '';
|
|
107
|
-
for (const candidate of [reply.trim(), lastLine, stripped(lastLine), stripped(reply)]) {
|
|
108
|
-
|
|
142
|
+
for (const candidate of [reply.trim(), lastLine, unlisted(lastLine), unlisted(reply), stripped(lastLine), stripped(reply)]) {
|
|
143
|
+
let exact = byEntity.get(candidate);
|
|
144
|
+
if (exact === undefined) {
|
|
145
|
+
// Prefer a complete literal name before treating its suffix as punctuation.
|
|
146
|
+
for (const cue of frontier) {
|
|
147
|
+
if (candidate.startsWith(cue.entity) && /^[.!,;:]+$/.test(candidate.slice(cue.entity.length)) &&
|
|
148
|
+
(exact === undefined || cue.entity.length > exact.entity.length))
|
|
149
|
+
exact = cue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
109
152
|
if (exact !== undefined) {
|
|
110
153
|
return exact;
|
|
111
154
|
}
|
|
112
155
|
}
|
|
113
|
-
|
|
156
|
+
// Normalize only ASCII STOP spellings so case folding cannot move offsets.
|
|
157
|
+
const stopAt = mentionAt(reply.trim().replace(/stop/gi, stopToken), stopToken);
|
|
158
|
+
if (stopAt === 0) {
|
|
114
159
|
return undefined;
|
|
115
160
|
}
|
|
116
161
|
let mentioned;
|
|
@@ -127,7 +172,7 @@ export const parseSelection = (reply, frontier) => {
|
|
|
127
172
|
if (mentioned !== undefined) {
|
|
128
173
|
return mentioned.cue;
|
|
129
174
|
}
|
|
130
|
-
if (
|
|
175
|
+
if (stopAt !== -1) {
|
|
131
176
|
return undefined;
|
|
132
177
|
}
|
|
133
178
|
return strongestFirst(frontier)[0];
|
|
@@ -143,15 +188,23 @@ export const llmPolicy = (complete, options = {}) => {
|
|
|
143
188
|
const decay = options.decay ?? 0.8;
|
|
144
189
|
const maxSteps = options.maxSteps ?? 16;
|
|
145
190
|
const maxClaims = options.maxClaims ?? Number.POSITIVE_INFINITY;
|
|
191
|
+
validateBudgets(maxSteps, maxClaims);
|
|
192
|
+
validateScoreSetting('decay', decay);
|
|
193
|
+
const promptOptions = {
|
|
194
|
+
query: options.query,
|
|
195
|
+
instructions: options.instructions,
|
|
196
|
+
maxCues: cueLimit(options.maxCues)
|
|
197
|
+
};
|
|
146
198
|
return {
|
|
147
199
|
async select(state) {
|
|
148
|
-
|
|
200
|
+
const frontier = state.frontier.map(({ entity, score, depth }) => ({ entity, score, depth }));
|
|
201
|
+
if (frontier.length === 0) {
|
|
149
202
|
return undefined;
|
|
150
203
|
}
|
|
151
|
-
return parseSelection(await complete(selectPrompt(state,
|
|
204
|
+
return parseSelection(await complete(selectPrompt({ ...state, frontier }, promptOptions)), frontier);
|
|
152
205
|
},
|
|
153
206
|
async score(edge, from) {
|
|
154
|
-
return from.score
|
|
207
|
+
return propagatedScore(from.score, edge.conf, decay);
|
|
155
208
|
},
|
|
156
209
|
async done(state) {
|
|
157
210
|
return state.steps >= maxSteps || state.collected.length >= maxClaims;
|
|
@@ -171,33 +224,63 @@ export const llmPolicy = (complete, options = {}) => {
|
|
|
171
224
|
* ```
|
|
172
225
|
*/
|
|
173
226
|
export const shellComplete = (template, options = {}) => {
|
|
174
|
-
const
|
|
227
|
+
const timeoutMs = completionTimeoutMs(options.timeoutSeconds ?? 120);
|
|
228
|
+
const cwd = options.cwd;
|
|
229
|
+
const signal = options.signal;
|
|
230
|
+
const maxStdoutBytes = options.maxStdoutBytes;
|
|
231
|
+
const maxStderrBytes = options.maxStderrBytes;
|
|
175
232
|
return async (prompt) => {
|
|
233
|
+
signal?.throwIfAborted();
|
|
234
|
+
if (/[\uD800-\uDFFF]/u.test(prompt)) {
|
|
235
|
+
throw new TypeError('agent prompt must contain well-formed Unicode');
|
|
236
|
+
}
|
|
176
237
|
let dir;
|
|
238
|
+
const failures = [];
|
|
177
239
|
const substitutions = {};
|
|
178
|
-
if (template.includes('{prompt-file}')) {
|
|
179
|
-
dir = mkdtempSync(join(tmpdir(), 'cave-loop-'));
|
|
180
|
-
const file = join(dir, 'prompt.md');
|
|
181
|
-
writeFileSync(file, prompt);
|
|
182
|
-
substitutions['prompt-file'] = file;
|
|
183
|
-
}
|
|
184
240
|
try {
|
|
241
|
+
if (template.includes('{prompt-file}')) {
|
|
242
|
+
dir = mkdtempSync(join(tmpdir(), 'cave-loop-'));
|
|
243
|
+
const file = join(dir, 'prompt.md');
|
|
244
|
+
writeFileSync(file, prompt);
|
|
245
|
+
substitutions['prompt-file'] = file;
|
|
246
|
+
}
|
|
185
247
|
const result = await runProcess(shellCommand(template, substitutions), {
|
|
248
|
+
strictStdoutUtf8: true,
|
|
186
249
|
input: prompt,
|
|
187
|
-
timeoutMs
|
|
188
|
-
...
|
|
189
|
-
...
|
|
190
|
-
...
|
|
191
|
-
...
|
|
250
|
+
timeoutMs,
|
|
251
|
+
...cwd === undefined ? {} : { cwd },
|
|
252
|
+
...signal === undefined ? {} : { signal },
|
|
253
|
+
...maxStdoutBytes === undefined ? {} : { maxStdoutBytes },
|
|
254
|
+
...maxStderrBytes === undefined ? {} : { maxStderrBytes }
|
|
192
255
|
});
|
|
193
256
|
if (result.code !== 0) {
|
|
194
257
|
throw new Error(`agent exited with ${result.code ?? `signal ${result.signal}`}`);
|
|
195
258
|
}
|
|
196
259
|
return result.stdout;
|
|
197
260
|
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
failures.push(error);
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
198
265
|
finally {
|
|
199
|
-
if (dir !== undefined)
|
|
200
|
-
|
|
266
|
+
if (dir !== undefined) {
|
|
267
|
+
try {
|
|
268
|
+
rmSync(dir, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
if (failures.length === 0)
|
|
272
|
+
throw error;
|
|
273
|
+
const describe = (value) => {
|
|
274
|
+
try {
|
|
275
|
+
return value instanceof Error ? String(value.message) : String(value);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return '[unprintable thrown value]';
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
throw new AggregateError([failures[0], error], `${describe(failures[0])}; prompt cleanup also failed: ${describe(error)}`, { cause: failures[0] });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
201
284
|
}
|
|
202
285
|
};
|
|
203
286
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/llm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ export type ProcessResult = {
|
|
|
10
10
|
readonly stdout: string;
|
|
11
11
|
readonly stderr: string;
|
|
12
12
|
};
|
|
13
|
-
export type ProcessFailureKind = 'spawn' | 'timeout' | 'aborted' | 'stdout-limit' | 'stderr-limit';
|
|
13
|
+
export type ProcessFailureKind = 'spawn' | 'timeout' | 'aborted' | 'stdout-limit' | 'stderr-limit' | 'stdout-encoding';
|
|
14
14
|
export type ProcessFailureRecord = {
|
|
15
15
|
readonly kind: ProcessFailureKind;
|
|
16
16
|
readonly message: string;
|
|
@@ -36,6 +36,8 @@ export type ProcessOptions = {
|
|
|
36
36
|
readonly signal?: AbortSignal;
|
|
37
37
|
readonly maxStdoutBytes?: number;
|
|
38
38
|
readonly maxStderrBytes?: number;
|
|
39
|
+
/** Reject malformed UTF-8 stdout before structured consumers interpret it. */
|
|
40
|
+
readonly strictStdoutUtf8?: boolean;
|
|
39
41
|
};
|
|
40
42
|
export type SyncProcessOptions = Omit<ProcessOptions, 'signal'>;
|
|
41
43
|
export declare const defaultMaxStdoutBytes: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/process.ts"],"names":[],"mappings":"AAAA,0EAA0E;
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/process.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAM1E,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,YAAY,CAAA;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,cAAc,GAAG,cAAc,GAAG,iBAAiB,CAAA;AAEtH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED,yEAAyE;AACzE,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAkB,IAAI,oBAAmB;IACzC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;gBAEf,MAAM,EAAE,oBAAoB;IAQxC,MAAM,IAAI,oBAAoB;CAS/B;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAA;AAE/D,eAAO,MAAM,qBAAqB,QAAkB,CAAA;AACpD,eAAO,MAAM,qBAAqB,QAAc,CAAA;AAEhD,eAAO,MAAM,aAAa,GAAI,YAAY,MAAM,EAAE,OAAM,SAAS,MAAM,EAAO,KAAG,cACzD,CAAA;AAExB,eAAO,MAAM,cAAc,GAAI,WAAU,MAAM,CAAC,QAA2B,KAAG,WAC/B,CAAA;AAE/C,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,SAAQ,WAA8B,KAAG,MACW,CAAA;AAEtG,+EAA+E;AAC/E,eAAO,MAAM,eAAe,GAC1B,UAAU,MAAM,EAChB,eAAe,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC/C,SAAQ,WAA8B,KACrC,MAC6F,CAAA;AAYhG;;;GAGG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,gBAAe,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EACpD,WAAU,MAAM,CAAC,QAA2B,KAC3C,cAaF,CAAA;AAkED,kFAAkF;AAClF,eAAO,MAAM,UAAU,GACrB,SAAS,cAAc,EACvB,UAAS,cAAmB,KAC3B,OAAO,CAAC,aAAa,CAyIvB,CAAA;AA4BD;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS,cAAc,EAAE,UAAS,kBAAuB,KAAG,aA2C1F,CAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Portable, bounded external-process execution for CAVE integrations. */
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { isUtf8 } from 'node:buffer';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
/** Typed, command-redacted failure for resource and lifecycle errors. */
|
|
5
6
|
export class ProcessFailure extends Error {
|
|
@@ -62,12 +63,29 @@ export const shellCommand = (template, substitutions = {}, platform = process.pl
|
|
|
62
63
|
};
|
|
63
64
|
const emptyResult = () => ({ code: null, signal: null, stdout: '', stderr: '' });
|
|
64
65
|
const positiveLimit = (value, fallback, name) => {
|
|
65
|
-
const result = value
|
|
66
|
+
const result = value === undefined ? fallback : value;
|
|
66
67
|
if (!Number.isSafeInteger(result) || result < 0) {
|
|
67
68
|
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
68
69
|
}
|
|
69
70
|
return result;
|
|
70
71
|
};
|
|
72
|
+
const stdoutUtf8Mode = (value) => {
|
|
73
|
+
if (value !== undefined && typeof value !== 'boolean')
|
|
74
|
+
throw new TypeError('strictStdoutUtf8 must be a boolean');
|
|
75
|
+
return value === true;
|
|
76
|
+
};
|
|
77
|
+
const processInput = (value) => {
|
|
78
|
+
if (value !== undefined && typeof value !== 'string')
|
|
79
|
+
throw new TypeError('input must be a string');
|
|
80
|
+
return value;
|
|
81
|
+
};
|
|
82
|
+
const timeoutLimit = (value) => {
|
|
83
|
+
const timeoutMs = positiveLimit(value, 0, 'timeoutMs');
|
|
84
|
+
if (timeoutMs > 2147483647) {
|
|
85
|
+
throw new TypeError('timeoutMs must be in 0..2147483647');
|
|
86
|
+
}
|
|
87
|
+
return timeoutMs;
|
|
88
|
+
};
|
|
71
89
|
const killTree = (child) => {
|
|
72
90
|
const pid = child.pid;
|
|
73
91
|
if (pid === undefined)
|
|
@@ -107,14 +125,18 @@ const failureMessage = (kind, timeoutMs, limit) => {
|
|
|
107
125
|
case 'aborted': return 'process was cancelled';
|
|
108
126
|
case 'stdout-limit': return `process stdout exceeded ${limit} bytes`;
|
|
109
127
|
case 'stderr-limit': return `process stderr exceeded ${limit} bytes`;
|
|
128
|
+
case 'stdout-encoding': return 'process stdout contains invalid UTF-8';
|
|
110
129
|
}
|
|
111
130
|
};
|
|
112
131
|
/** Run an executable plus argument array with bounded output and tree cleanup. */
|
|
113
132
|
export const runProcess = (command, options = {}) => {
|
|
114
133
|
const stdoutLimit = positiveLimit(options.maxStdoutBytes, defaultMaxStdoutBytes, 'maxStdoutBytes');
|
|
115
134
|
const stderrLimit = positiveLimit(options.maxStderrBytes, defaultMaxStderrBytes, 'maxStderrBytes');
|
|
116
|
-
const timeoutMs =
|
|
117
|
-
|
|
135
|
+
const timeoutMs = timeoutLimit(options.timeoutMs);
|
|
136
|
+
const strictStdoutUtf8 = stdoutUtf8Mode(options.strictStdoutUtf8);
|
|
137
|
+
const input = processInput(options.input);
|
|
138
|
+
const signal = options.signal;
|
|
139
|
+
if (signal?.aborted === true) {
|
|
118
140
|
return Promise.reject(new ProcessFailure({
|
|
119
141
|
kind: 'aborted', message: failureMessage('aborted', timeoutMs), result: emptyResult()
|
|
120
142
|
}));
|
|
@@ -130,33 +152,66 @@ export const runProcess = (command, options = {}) => {
|
|
|
130
152
|
let terminating = false;
|
|
131
153
|
let timer;
|
|
132
154
|
let spawnErrorCode;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
let child;
|
|
156
|
+
try {
|
|
157
|
+
const cwd = options.cwd;
|
|
158
|
+
const env = options.env;
|
|
159
|
+
child = spawn(command.executable, [...command.args], {
|
|
160
|
+
shell: false,
|
|
161
|
+
detached: process.platform !== 'win32',
|
|
162
|
+
windowsHide: true,
|
|
163
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
164
|
+
...cwd === undefined ? {} : { cwd },
|
|
165
|
+
...env === undefined ? {} : { env }
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
reject(new ProcessFailure({
|
|
170
|
+
kind: 'spawn',
|
|
171
|
+
message: failureMessage('spawn', timeoutMs),
|
|
172
|
+
result: emptyResult()
|
|
173
|
+
}));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
141
176
|
const result = () => ({
|
|
142
177
|
code,
|
|
143
178
|
signal: exitSignal,
|
|
144
179
|
stdout: Buffer.concat(stdout).toString('utf8'),
|
|
145
180
|
stderr: Buffer.concat(stderr).toString('utf8')
|
|
146
181
|
});
|
|
147
|
-
const
|
|
148
|
-
if (timer !== undefined)
|
|
149
|
-
clearTimeout(timer);
|
|
150
|
-
options.signal?.removeEventListener('abort', abort);
|
|
151
|
-
};
|
|
152
|
-
const finish = (failure) => {
|
|
182
|
+
const finish = (failure, cleanupErrors = []) => {
|
|
153
183
|
if (settled)
|
|
154
184
|
return;
|
|
155
185
|
settled = true;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
if (failure === undefined && strictStdoutUtf8 && !isUtf8(Buffer.concat(stdout))) {
|
|
187
|
+
failure = new ProcessFailure({ kind: 'stdout-encoding', message: failureMessage('stdout-encoding', timeoutMs), result: result() });
|
|
188
|
+
}
|
|
189
|
+
for (const cleanup of [
|
|
190
|
+
() => { if (timer !== undefined)
|
|
191
|
+
clearTimeout(timer); },
|
|
192
|
+
() => signal?.removeEventListener('abort', abort),
|
|
193
|
+
() => child.stdin?.destroy(),
|
|
194
|
+
() => child.stdout?.destroy(),
|
|
195
|
+
() => child.stderr?.destroy()
|
|
196
|
+
]) {
|
|
197
|
+
try {
|
|
198
|
+
cleanup();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
cleanupErrors.push(error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (cleanupErrors.length > 0) {
|
|
205
|
+
const cleanup = new AggregateError(cleanupErrors, 'process cleanup failed', { cause: cleanupErrors[0] });
|
|
206
|
+
if (failure === undefined) {
|
|
207
|
+
reject(cleanup);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// Preserve the typed process outcome and captured output for callers.
|
|
211
|
+
// Raw cleanup details stay in cause, outside command-redacted messages.
|
|
212
|
+
failure.cause = cleanup;
|
|
213
|
+
failure.message += '; process cleanup also failed';
|
|
214
|
+
}
|
|
160
215
|
if (failure === undefined)
|
|
161
216
|
resolve(result());
|
|
162
217
|
else
|
|
@@ -166,12 +221,13 @@ export const runProcess = (command, options = {}) => {
|
|
|
166
221
|
if (settled || terminating)
|
|
167
222
|
return;
|
|
168
223
|
terminating = true;
|
|
169
|
-
|
|
224
|
+
const complete = (errors = []) => finish(new ProcessFailure({
|
|
170
225
|
kind,
|
|
171
226
|
message: failureMessage(kind, timeoutMs, limit),
|
|
172
227
|
result: result(),
|
|
173
228
|
...limit === undefined ? {} : { limit }
|
|
174
|
-
}))
|
|
229
|
+
}), errors);
|
|
230
|
+
void killTree(child).then(() => complete(), error => complete([error]));
|
|
175
231
|
};
|
|
176
232
|
const abort = () => terminate('aborted');
|
|
177
233
|
const collect = (stream, chunk) => {
|
|
@@ -190,42 +246,74 @@ export const runProcess = (command, options = {}) => {
|
|
|
190
246
|
if (bytes.length > remaining)
|
|
191
247
|
terminate(stream === 'stdout' ? 'stdout-limit' : 'stderr-limit', limit);
|
|
192
248
|
};
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
249
|
+
try {
|
|
250
|
+
child.stdout?.on('data', chunk => collect('stdout', chunk));
|
|
251
|
+
child.stderr?.on('data', chunk => collect('stderr', chunk));
|
|
252
|
+
child.on('error', (error) => {
|
|
253
|
+
if (terminating)
|
|
254
|
+
return;
|
|
255
|
+
spawnErrorCode = error.code;
|
|
256
|
+
finish(new ProcessFailure({
|
|
257
|
+
kind: 'spawn',
|
|
258
|
+
message: failureMessage('spawn', timeoutMs),
|
|
259
|
+
result: result(),
|
|
260
|
+
...spawnErrorCode === undefined ? {} : { errorCode: spawnErrorCode }
|
|
261
|
+
}));
|
|
262
|
+
});
|
|
263
|
+
child.on('exit', (observedCode, observedSignal) => {
|
|
264
|
+
code = observedCode;
|
|
265
|
+
exitSignal = observedSignal;
|
|
266
|
+
});
|
|
267
|
+
child.on('close', (observedCode, observedSignal) => {
|
|
268
|
+
code = observedCode;
|
|
269
|
+
exitSignal = observedSignal;
|
|
270
|
+
if (!terminating)
|
|
271
|
+
finish();
|
|
272
|
+
});
|
|
273
|
+
child.stdin?.on('error', () => { });
|
|
274
|
+
if (input !== undefined)
|
|
275
|
+
child.stdin?.end(input);
|
|
276
|
+
else
|
|
277
|
+
child.stdin?.end();
|
|
278
|
+
if (timeoutMs > 0)
|
|
279
|
+
timer = setTimeout(() => terminate('timeout'), timeoutMs);
|
|
280
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
281
|
+
// Close the check/listener race if cancellation landed during spawn setup.
|
|
282
|
+
if (signal?.aborted === true)
|
|
283
|
+
abort();
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Startup owns the child as soon as spawn returns, including partial
|
|
287
|
+
// listener registration and stdin setup. Keep startup diagnostics redacted.
|
|
288
|
+
terminate('spawn');
|
|
289
|
+
}
|
|
227
290
|
});
|
|
228
291
|
};
|
|
292
|
+
/** Capture Node's enumerable environment entries without JSON object hooks. */
|
|
293
|
+
const workerEnvironment = (env) => {
|
|
294
|
+
if (env == null)
|
|
295
|
+
return undefined;
|
|
296
|
+
let keys = [];
|
|
297
|
+
for (const key in env)
|
|
298
|
+
keys.push(key);
|
|
299
|
+
if (process.platform === 'win32') {
|
|
300
|
+
const seen = new Set();
|
|
301
|
+
keys = keys.sort().filter(key => {
|
|
302
|
+
const upper = key.toUpperCase();
|
|
303
|
+
if (seen.has(upper))
|
|
304
|
+
return false;
|
|
305
|
+
seen.add(upper);
|
|
306
|
+
return true;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
const captured = Object.create(null);
|
|
310
|
+
for (const key of keys) {
|
|
311
|
+
const value = env[key];
|
|
312
|
+
if (value !== undefined)
|
|
313
|
+
captured[key] = `${value}`;
|
|
314
|
+
}
|
|
315
|
+
return captured;
|
|
316
|
+
};
|
|
229
317
|
/**
|
|
230
318
|
* Synchronous bridge for compatibility-sensitive APIs. A short-lived Node
|
|
231
319
|
* worker owns the asynchronous tree cleanup, then returns one bounded result.
|
|
@@ -233,14 +321,26 @@ export const runProcess = (command, options = {}) => {
|
|
|
233
321
|
export const runProcessSync = (command, options = {}) => {
|
|
234
322
|
const stdoutLimit = positiveLimit(options.maxStdoutBytes, defaultMaxStdoutBytes, 'maxStdoutBytes');
|
|
235
323
|
const stderrLimit = positiveLimit(options.maxStderrBytes, defaultMaxStderrBytes, 'maxStderrBytes');
|
|
236
|
-
const timeoutMs =
|
|
324
|
+
const timeoutMs = timeoutLimit(options.timeoutMs);
|
|
325
|
+
const strictStdoutUtf8 = stdoutUtf8Mode(options.strictStdoutUtf8);
|
|
326
|
+
const input = processInput(options.input);
|
|
327
|
+
let request;
|
|
328
|
+
try {
|
|
329
|
+
const cwd = options.cwd;
|
|
330
|
+
const env = workerEnvironment(options.env);
|
|
331
|
+
const capturedCommand = { executable: command.executable, args: [...command.args] };
|
|
332
|
+
request = JSON.stringify({ command: capturedCommand, options: { timeoutMs, input, cwd, env, maxStdoutBytes: stdoutLimit, maxStderrBytes: stderrLimit, strictStdoutUtf8 } });
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
throw new ProcessFailure({ kind: 'spawn', message: failureMessage('spawn', timeoutMs), result: emptyResult() });
|
|
336
|
+
}
|
|
237
337
|
const extension = import.meta.url.endsWith('.ts') ? 'ts' : 'js';
|
|
238
338
|
const worker = fileURLToPath(new URL(`./process-worker.${extension}`, import.meta.url));
|
|
239
339
|
const result = spawnSync(process.execPath, ['--disable-warning=ExperimentalWarning', worker], {
|
|
240
340
|
shell: false,
|
|
241
341
|
windowsHide: true,
|
|
242
342
|
encoding: 'utf8',
|
|
243
|
-
input:
|
|
343
|
+
input: request,
|
|
244
344
|
// JSON can expand a captured control byte to a six-byte `\\u00xx` escape.
|
|
245
345
|
maxBuffer: Math.max(1024 * 1024, (stdoutLimit + stderrLimit) * 7 + 1024 * 1024),
|
|
246
346
|
timeout: timeoutMs > 0 ? timeoutMs + 5_000 : undefined
|