start-command 0.40.0 → 0.42.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/CHANGELOG.md +22 -0
- package/eslint.config.mjs +31 -0
- package/package.json +3 -3
- package/src/bin/cli.js +11 -1
- package/src/lib/args-parser.js +33 -0
- package/src/lib/formal-ai-cli.js +237 -0
- package/src/lib/formal-ai-client.js +390 -0
- package/src/lib/formal-ai-files.js +384 -0
- package/src/lib/formal-ai-policy.js +508 -0
- package/src/lib/formal-ai-runtime.js +304 -0
- package/src/lib/formal-ai-tool.js +182 -0
- package/src/lib/formal-ai.js +482 -0
- package/src/lib/usage.js +5 -1
- package/test/fixtures/formal-ai/fake-server.cjs +17 -0
- package/test/fixtures/formal-ai/helpers.cjs +189 -0
- package/test/fixtures/formal-ai/receipts-258.json +431 -0
- package/test/fixtures/formal-ai/receipts.json +776 -0
- package/test/fixtures/formal-ai/replay.cjs +194 -0
- package/test/regression-255-cli.js +296 -0
- package/test/regression-255-e2e.js +167 -0
- package/test/regression-255-loop.js +480 -0
- package/test/regression-255-policy.js +436 -0
- package/test/regression-257.js +265 -0
- package/test/regression-258-files.js +430 -0
- package/test/regression-258.js +443 -0
- package/test/shared-golden.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# start-command
|
|
2
2
|
|
|
3
|
+
## 0.42.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5ebb7ec: `--use-formal-ai`: keep the cause of Formal AI server errors, offer structured file tools, and report capability gaps as incomplete (#257, #258, #259).
|
|
8
|
+
|
|
9
|
+
- **Server errors (#257):** an HTTP error from the Formal AI runtime now keeps its sanitized, bounded cause: the error type (100 chars), the message (300), a body preview (500), the body length and the number of attempts. The cause appears in the final message (`Formal AI returned HTTP 500: formal_ai_error: Aborted(...) (after 1 retry)`) and under `outcome.diagnostics` in the evidence. Authorization values, credentials, control characters and the environment are never recorded. There is still no fallback, the exit status is still 70, and 4xx responses are not retried.
|
|
10
|
+
- **File tools (#258):** next to `bash`, Formal AI is offered a structured `read` tool, plus `edit` (one literal replacement) and `write` (replace a file it has read) in `START_FORMAL_AI_EFFECTS=workspace-write` mode. The tools follow the workspace path policy: no paths outside the working directory, and a change only to a file read in the same task that has not changed since. They keep comments, whitespace, line endings, a byte order mark, the file mode and all unrelated content. Each change is checked by reading the file back, and the evidence records its hashes and effects. Files over `START_FORMAL_AI_MAX_FILE_BYTES` (1 MiB by default, at most 16 MiB) are refused, and interpreters are still not offered.
|
|
11
|
+
- **Capability gaps (#258):** an answer that matches one of Formal AI's known capability-gap records (`workspace-edit-capability-gap/v1` or `workspace-change-verification-gap/v1`, with `complete` and `verified` both `false`) is now reported as `capability-gap` with exit 65. Its remaining obligations are kept. Plain answers to questions still exit 0.
|
|
12
|
+
|
|
13
|
+
## 0.41.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 65ce70c: Add the experimental `--use-formal-ai` mode: `$ --use-formal-ai -- <request>` sends a natural-language request to a local Formal AI runtime and runs the tool calls it chooses (#255).
|
|
18
|
+
|
|
19
|
+
- **Opt-in only:** without the flag nothing changes. The request after `--` is never read by a shell or the alias table. Without a qualified runtime (Formal AI 0.355.27 or newer, from `START_FORMAL_AI_ROOT` or an explicit `START_FORMAL_AI_URL`), start exits 69 and runs nothing. There is no fallback to aliases, a shell or an LLM.
|
|
20
|
+
- **Tools:** a small read-only set of programs, run without a shell. It includes `ls`, `cat`, `head`, `tail`, `wc`, `find`, `grep`, `sort`, `diff`, `stat`, `tree`, `du` and read-only `git` (see docs/formal-ai.md). Changes (`mkdir`, `touch`, `rmdir`, `rm`, `cp`, `mv`) need `START_FORMAL_AI_EFFECTS=workspace-write`; a change may only name targets observed before it and is verified by readback. Shell syntax, other programs and paths outside the working directory are refused (exit 77).
|
|
21
|
+
- **Bounds:** steps, retries, total time, per-tool time, output bytes and request length (`START_FORMAL_AI_*`). Exhausted budgets exit 124 and unsupported requests exit 65. A cancelled run ends with the signal status.
|
|
22
|
+
- **Evidence:** the log records the runtime identity, every response, tool decision, exit status and file-system effect. The log stays local: this mode never uploads logs or creates issues. Tool output appears in the evidence only as a short preview, and the environment is not recorded.
|
|
23
|
+
- **Dependencies:** `lino-objects-codec` 0.9.4 and `eslint` 10.14.0; CI runs on Node.js 26.
|
|
24
|
+
|
|
3
25
|
## 0.40.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/eslint.config.mjs
CHANGED
|
@@ -59,6 +59,36 @@ export default [
|
|
|
59
59
|
'spaced-comment': ['error', 'always', { markers: ['/'] }],
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
// CommonJS test fixtures and helpers (.cjs): the test runner collects
|
|
64
|
+
// every .js/.mjs file under test/, so non-test modules there use .cjs.
|
|
65
|
+
files: ['**/*.cjs'],
|
|
66
|
+
plugins: {
|
|
67
|
+
prettier: prettierPlugin,
|
|
68
|
+
},
|
|
69
|
+
languageOptions: {
|
|
70
|
+
ecmaVersion: 'latest',
|
|
71
|
+
sourceType: 'commonjs',
|
|
72
|
+
globals: {
|
|
73
|
+
require: 'readonly',
|
|
74
|
+
module: 'readonly',
|
|
75
|
+
exports: 'readonly',
|
|
76
|
+
__dirname: 'readonly',
|
|
77
|
+
__filename: 'readonly',
|
|
78
|
+
console: 'readonly',
|
|
79
|
+
process: 'readonly',
|
|
80
|
+
Buffer: 'readonly',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
rules: {
|
|
84
|
+
'prettier/prettier': 'error',
|
|
85
|
+
eqeqeq: ['error', 'always'],
|
|
86
|
+
curly: ['error', 'all'],
|
|
87
|
+
'no-var': 'error',
|
|
88
|
+
'prefer-const': 'error',
|
|
89
|
+
'prefer-template': 'error',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
62
92
|
{
|
|
63
93
|
// ES module files (.mjs)
|
|
64
94
|
files: ['**/*.mjs'],
|
|
@@ -112,6 +142,7 @@ export default [
|
|
|
112
142
|
files: [
|
|
113
143
|
'test/**/*.js',
|
|
114
144
|
'test/**/*.mjs',
|
|
145
|
+
'test/**/*.cjs',
|
|
115
146
|
'tests/**/*.js',
|
|
116
147
|
'experiments/**/*.js',
|
|
117
148
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "start-command",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
|
|
5
5
|
"main": "src/bin/cli.js",
|
|
6
6
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@changesets/cli": "^3.0.3",
|
|
51
51
|
"@eslint/js": "^10.0.1",
|
|
52
|
-
"eslint": "^10.
|
|
52
|
+
"eslint": "^10.14.0",
|
|
53
53
|
"eslint-config-prettier": "^10.1.8",
|
|
54
54
|
"eslint-plugin-prettier": "^5.5.7",
|
|
55
55
|
"husky": "^9.1.7",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@link-foundation/sensitive-data-sanitizer": "^0.12.2",
|
|
72
72
|
"command-stream": "^2.6.0",
|
|
73
|
-
"lino-objects-codec": "^0.9.
|
|
73
|
+
"lino-objects-codec": "^0.9.5"
|
|
74
74
|
},
|
|
75
75
|
"overrides": {
|
|
76
76
|
"brace-expansion": "^5.0.12",
|
package/src/bin/cli.js
CHANGED
|
@@ -223,7 +223,9 @@ if (!isQueryMode && (!parsedCommand || parsedCommand.trim() === '')) {
|
|
|
223
223
|
let command = parsedCommand;
|
|
224
224
|
let substitutionResult = null;
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
// A --use-formal-ai request is only ever read by Formal AI (issue #255).
|
|
227
|
+
const useFormalAi = wrapperOptions.useFormalAi;
|
|
228
|
+
if (!config.disableSubstitutions && !useFormalAi && parsedCommand?.trim()) {
|
|
227
229
|
substitutionResult = processCommand(parsedCommand, {
|
|
228
230
|
customLinoPath: config.substitutionsPath,
|
|
229
231
|
verbose: config.verbose,
|
|
@@ -270,6 +272,14 @@ let launch = null;
|
|
|
270
272
|
console.error(`Error: ${error.message}`);
|
|
271
273
|
process.exit(1);
|
|
272
274
|
}
|
|
275
|
+
if (useFormalAi) {
|
|
276
|
+
const { requestText, runFormalAiCli } = require('../lib/formal-ai-cli');
|
|
277
|
+
const request = requestText(parsedArgs.rawCommand);
|
|
278
|
+
const store = getExecutionStore();
|
|
279
|
+
process.exit(
|
|
280
|
+
await runFormalAiCli({ request, sessionId, launch, store, config })
|
|
281
|
+
);
|
|
282
|
+
}
|
|
273
283
|
|
|
274
284
|
if (wrapperOptions.isolated === 'docker') {
|
|
275
285
|
const {
|
package/src/lib/args-parser.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* --keep-container-on-fail Remove successful docker containers, keep failed or OOM-killed ones
|
|
28
28
|
* --shell <shell> Shell to use in isolation environments: auto, bash, zsh, sh (default: auto)
|
|
29
29
|
* --use-command-stream Use command-stream library for command execution (experimental)
|
|
30
|
+
* --use-formal-ai Perform a natural-language request through Formal AI (experimental)
|
|
30
31
|
* --verbose Enable verbose/debug output (sets START_VERBOSE=1)
|
|
31
32
|
* --status <uuid> Show status of a previous command execution by UUID
|
|
32
33
|
* --list [--running] List all (or only running) tracked command executions
|
|
@@ -55,6 +56,7 @@ const { parseSequence, isSequence } = require('./sequence-parser');
|
|
|
55
56
|
const { buildCommandString } = require('./shell-utils');
|
|
56
57
|
const {
|
|
57
58
|
VALID_OUTPUT_FORMATS,
|
|
59
|
+
activeQueryModes,
|
|
58
60
|
createQueryOptionDefaults,
|
|
59
61
|
parseQueryOption,
|
|
60
62
|
validateQueryOptions,
|
|
@@ -220,6 +222,7 @@ function parseArgs(args) {
|
|
|
220
222
|
keepContainerOnFail: false, // Keep docker container filesystem when command fails or OOM-kills
|
|
221
223
|
shell: 'auto', // Shell to use in isolation environments: auto, bash, zsh, sh
|
|
222
224
|
useCommandStream: false, // Use command-stream library for command execution
|
|
225
|
+
useFormalAi: false, // Natural-language request performed through Formal AI (issue #255)
|
|
223
226
|
...createQueryOptionDefaults(),
|
|
224
227
|
...snapshotResumeOptions.createSnapshotOptionDefaults(),
|
|
225
228
|
...launchIdentityOptions.createLaunchIdentityOptionDefaults(),
|
|
@@ -557,6 +560,11 @@ function parseOption(args, index, options) {
|
|
|
557
560
|
return 1;
|
|
558
561
|
}
|
|
559
562
|
|
|
563
|
+
if (arg === '--use-formal-ai') {
|
|
564
|
+
options.useFormalAi = true;
|
|
565
|
+
return 1;
|
|
566
|
+
}
|
|
567
|
+
|
|
560
568
|
// --verbose (enable verbose/debug output, sets START_VERBOSE env var)
|
|
561
569
|
if (arg === '--verbose') {
|
|
562
570
|
process.env.START_VERBOSE = '1';
|
|
@@ -849,6 +857,7 @@ function validateOptions(options) {
|
|
|
849
857
|
snapshotResumeOptions.validate(options);
|
|
850
858
|
validateQueryOptions(options);
|
|
851
859
|
launchIdentityOptions.validate(options);
|
|
860
|
+
validateFormalAi(options);
|
|
852
861
|
|
|
853
862
|
// Validate shell option
|
|
854
863
|
if (options.shell !== null && options.shell !== undefined) {
|
|
@@ -869,6 +878,30 @@ function validateOptions(options) {
|
|
|
869
878
|
}
|
|
870
879
|
}
|
|
871
880
|
|
|
881
|
+
/**
|
|
882
|
+
* `--use-formal-ai` runs its tool calls directly in the current directory;
|
|
883
|
+
* other execution modes would change where and how they run (issue #255).
|
|
884
|
+
* @param {object} options - Parsed options
|
|
885
|
+
* @throws {Error} When combined with an unsupported option
|
|
886
|
+
*/
|
|
887
|
+
function validateFormalAi(options) {
|
|
888
|
+
if (!options.useFormalAi) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const conflicts = [
|
|
892
|
+
...(options.isolated !== null ? ['--isolated'] : []),
|
|
893
|
+
...(options.user ? ['--isolated-user'] : []),
|
|
894
|
+
...(options.detached ? ['--detached'] : []),
|
|
895
|
+
...(options.useCommandStream ? ['--use-command-stream'] : []),
|
|
896
|
+
...activeQueryModes(options),
|
|
897
|
+
];
|
|
898
|
+
if (conflicts.length) {
|
|
899
|
+
throw new Error(
|
|
900
|
+
`--use-formal-ai cannot be combined with ${conflicts.join(', ')} (experimental mode, see docs/formal-ai.md)`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
872
905
|
/**
|
|
873
906
|
* Generate a unique session name
|
|
874
907
|
* @param {string} [prefix='start'] - Prefix for the session name
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI side of the experimental `--use-formal-ai` mode (issue #255): resolves
|
|
3
|
+
* the bounds and the runtime, tracks the execution like a direct run (start
|
|
4
|
+
* and finish blocks, log, execution record, START_* environment) and writes
|
|
5
|
+
* the evidence of the run into its log.
|
|
6
|
+
*
|
|
7
|
+
* The log stays local: this mode never creates issues or uploads logs.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const process = require('process');
|
|
11
|
+
const { EXIT, resolveBounds } = require('./formal-ai-policy');
|
|
12
|
+
const runtimeLib = require('./formal-ai-runtime');
|
|
13
|
+
const { createClient } = require('./formal-ai-client');
|
|
14
|
+
const { runFormalAiTask } = require('./formal-ai');
|
|
15
|
+
const { ExecutionRecord } = require('./execution-store');
|
|
16
|
+
const { createStartBlock, createFinishBlock } = require('./output-blocks');
|
|
17
|
+
const { appendLogFile } = require('./isolation-log-utils');
|
|
18
|
+
const launchIdentity = require('./launch-identity');
|
|
19
|
+
|
|
20
|
+
const EVIDENCE_SCHEMA = 'start-formal-ai-evidence/1';
|
|
21
|
+
const PREFIX = '[formal-ai]';
|
|
22
|
+
const { AbortController } = globalThis;
|
|
23
|
+
|
|
24
|
+
function timestamp() {
|
|
25
|
+
return new Date().toISOString().replace('T', ' ').substring(0, 23);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Text of the request: the argv after `--`, joined by single spaces. It is
|
|
30
|
+
* only ever sent to Formal AI, never to a shell.
|
|
31
|
+
* @param {string[]} rawCommand - Parsed command argv
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function requestText(rawCommand) {
|
|
35
|
+
return rawCommand.join(' ').trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Run `--use-formal-ai`.
|
|
40
|
+
* @param {object} options
|
|
41
|
+
* @param {string} options.request - Request text
|
|
42
|
+
* @param {string} options.sessionId - Execution UUID
|
|
43
|
+
* @param {object} options.launch - prepareLaunch() result
|
|
44
|
+
* @param {object|null} options.store - ExecutionStore, or null
|
|
45
|
+
* @param {object} [options.config] - CLI configuration
|
|
46
|
+
* @param {object} [options.env] - Environment (process.env)
|
|
47
|
+
* @param {string} [options.cwd] - Working directory (process.cwd())
|
|
48
|
+
* @param {object} [options.out] - stdout stream
|
|
49
|
+
* @param {object} [options.err] - stderr stream
|
|
50
|
+
* @returns {Promise<number>} Exit code
|
|
51
|
+
*/
|
|
52
|
+
async function runFormalAiCli(options) {
|
|
53
|
+
const {
|
|
54
|
+
request,
|
|
55
|
+
sessionId,
|
|
56
|
+
launch,
|
|
57
|
+
store,
|
|
58
|
+
config = {},
|
|
59
|
+
env = process.env,
|
|
60
|
+
cwd = process.cwd(),
|
|
61
|
+
out = process.stdout,
|
|
62
|
+
err = process.stderr,
|
|
63
|
+
} = options;
|
|
64
|
+
const note = (text) =>
|
|
65
|
+
err.write(`${text.replace(/^/gm, `${PREFIX} `).replace(/ +$/gm, '')}\n`);
|
|
66
|
+
let bounds;
|
|
67
|
+
try {
|
|
68
|
+
bounds = resolveBounds(env);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
note(`Error: ${error.message}`);
|
|
71
|
+
return error.exitCode || EXIT.CONFIG;
|
|
72
|
+
}
|
|
73
|
+
if (!request) {
|
|
74
|
+
note('Error: --use-formal-ai needs a request after --');
|
|
75
|
+
return EXIT.USAGE;
|
|
76
|
+
}
|
|
77
|
+
if (request.length > bounds.maxRequestChars) {
|
|
78
|
+
note(
|
|
79
|
+
`Error: the request has ${request.length} characters; START_FORMAL_AI_MAX_REQUEST_CHARS is ${bounds.maxRequestChars}`
|
|
80
|
+
);
|
|
81
|
+
return EXIT.USAGE;
|
|
82
|
+
}
|
|
83
|
+
const deadline = Date.now() + bounds.timeoutMs;
|
|
84
|
+
let runtime;
|
|
85
|
+
try {
|
|
86
|
+
runtime = await runtimeLib.startRuntime({ env, bounds });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
note(`Error: ${error.message}`);
|
|
89
|
+
note(
|
|
90
|
+
'No command was run: --use-formal-ai never falls back to aliases, a shell or an LLM.'
|
|
91
|
+
);
|
|
92
|
+
return error.exitCode || EXIT.UNAVAILABLE;
|
|
93
|
+
}
|
|
94
|
+
note(`runtime: ${runtimeLib.describeIdentity(runtime.identity)}`);
|
|
95
|
+
note(`effects: ${bounds.effects}`);
|
|
96
|
+
|
|
97
|
+
const logPath = launch.logPath;
|
|
98
|
+
const startTime = timestamp();
|
|
99
|
+
const startMs = Date.now();
|
|
100
|
+
const record = store
|
|
101
|
+
? new ExecutionRecord({
|
|
102
|
+
uuid: sessionId,
|
|
103
|
+
command: request,
|
|
104
|
+
logPath,
|
|
105
|
+
shell: 'formal-ai',
|
|
106
|
+
workingDirectory: cwd,
|
|
107
|
+
options: {
|
|
108
|
+
formalAi: {
|
|
109
|
+
request,
|
|
110
|
+
effects: bounds.effects,
|
|
111
|
+
runtime: runtime.identity,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
: null;
|
|
116
|
+
if (record) {
|
|
117
|
+
launchIdentity.applyLaunch(record, launch);
|
|
118
|
+
launchIdentity.claimDirectLaunch(store, record, launch, config);
|
|
119
|
+
}
|
|
120
|
+
const header = [
|
|
121
|
+
'=== Start Command Log ===',
|
|
122
|
+
`Timestamp: ${startTime}`,
|
|
123
|
+
`Session ID: ${sessionId}`,
|
|
124
|
+
'Mode: --use-formal-ai (experimental)',
|
|
125
|
+
`Request: ${request}`,
|
|
126
|
+
`Formal AI: ${runtimeLib.describeIdentity(runtime.identity)}`,
|
|
127
|
+
`Effects: ${bounds.effects}`,
|
|
128
|
+
`Platform: ${process.platform}`,
|
|
129
|
+
`Working Directory: ${cwd}`,
|
|
130
|
+
'='.repeat(50),
|
|
131
|
+
'',
|
|
132
|
+
'',
|
|
133
|
+
].join('\n');
|
|
134
|
+
launchIdentity.writeLaunchLog(logPath, header, launch, record);
|
|
135
|
+
const logLine = (text) => appendLogFile(logPath, text);
|
|
136
|
+
|
|
137
|
+
out.write(
|
|
138
|
+
`${createStartBlock({ sessionId, timestamp: startTime, command: `--use-formal-ai -- ${request}` })}\n\n`
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const evidence = {
|
|
142
|
+
schema: EVIDENCE_SCHEMA,
|
|
143
|
+
sessionId,
|
|
144
|
+
runtime: runtime.identity,
|
|
145
|
+
};
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
let finalized = false;
|
|
148
|
+
const finalize = (exitCode) => {
|
|
149
|
+
if (finalized) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
finalized = true;
|
|
153
|
+
runtime.stop();
|
|
154
|
+
if (!evidence.outcome) {
|
|
155
|
+
evidence.outcome = {
|
|
156
|
+
status: 'cancelled',
|
|
157
|
+
exitCode,
|
|
158
|
+
message: 'start exited before the task finished',
|
|
159
|
+
remainingObligations: [
|
|
160
|
+
'the task was interrupted; later steps did not run',
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
evidence.durationMs = Date.now() - startMs;
|
|
165
|
+
const endTime = timestamp();
|
|
166
|
+
logLine(
|
|
167
|
+
`\n=== Formal AI Evidence ===\n${JSON.stringify(evidence, null, 2)}\n` +
|
|
168
|
+
`\n${'='.repeat(50)}\nFinished: ${endTime}\nExit Code: ${exitCode}\n`
|
|
169
|
+
);
|
|
170
|
+
if (record) {
|
|
171
|
+
record.options.formalAi.outcome = evidence.outcome.status;
|
|
172
|
+
record.complete(exitCode);
|
|
173
|
+
try {
|
|
174
|
+
store.save(record);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (config.verbose) {
|
|
177
|
+
note(
|
|
178
|
+
`[Tracking] Warning: Could not save execution record: ${error.message}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return endTime;
|
|
184
|
+
};
|
|
185
|
+
// Signals end start through process.exit() in the CLI; finish the
|
|
186
|
+
// evidence, stop the tool's process group and the runtime synchronously.
|
|
187
|
+
const onExit = (code) => {
|
|
188
|
+
if (evidence.activeKill) {
|
|
189
|
+
evidence.activeKill('SIGKILL');
|
|
190
|
+
}
|
|
191
|
+
finalize(code);
|
|
192
|
+
};
|
|
193
|
+
process.on('exit', onExit);
|
|
194
|
+
try {
|
|
195
|
+
const client = createClient({ baseUrl: runtime.baseUrl, bounds, deadline });
|
|
196
|
+
await runFormalAiTask({
|
|
197
|
+
request,
|
|
198
|
+
cwd,
|
|
199
|
+
env: launchIdentity.directLaunchEnv(sessionId, launch),
|
|
200
|
+
bounds,
|
|
201
|
+
client,
|
|
202
|
+
deadline,
|
|
203
|
+
signal: controller.signal,
|
|
204
|
+
evidence,
|
|
205
|
+
io: {
|
|
206
|
+
stdout: out,
|
|
207
|
+
stderr: err,
|
|
208
|
+
note: (text) => {
|
|
209
|
+
note(text);
|
|
210
|
+
logLine(`${PREFIX} ${text}\n`);
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
evidence.formalAiRequests = client.requests;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
evidence.outcome = {
|
|
217
|
+
status: 'internal-error',
|
|
218
|
+
exitCode: 1,
|
|
219
|
+
message: error.message,
|
|
220
|
+
remainingObligations: ['start failed; the task state is unknown'],
|
|
221
|
+
};
|
|
222
|
+
} finally {
|
|
223
|
+
process.removeListener('exit', onExit);
|
|
224
|
+
}
|
|
225
|
+
const { outcome } = evidence;
|
|
226
|
+
note(`${outcome.status}: ${outcome.message}`);
|
|
227
|
+
for (const obligation of outcome.remainingObligations) {
|
|
228
|
+
note(`remaining: ${obligation}`);
|
|
229
|
+
}
|
|
230
|
+
const endTime = finalize(outcome.exitCode);
|
|
231
|
+
out.write(
|
|
232
|
+
`\n${createFinishBlock({ sessionId, timestamp: endTime, exitCode: outcome.exitCode, logPath, durationMs: evidence.durationMs })}\n`
|
|
233
|
+
);
|
|
234
|
+
return outcome.exitCode;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = { EVIDENCE_SCHEMA, requestText, runFormalAiCli };
|