@the-open-engine/zeroshot 6.39.1 → 6.40.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 +45 -1
- package/cli/agent-provider-boundary.js +19 -0
- package/cli/agent-provider-boundary.ts +79 -0
- package/cli/export-stream.js +94 -0
- package/cli/export-stream.ts +118 -0
- package/cli/index.js +132 -61
- package/cli/json-export.js +2 -38
- package/cli/json-export.ts +2 -56
- package/cli/semantic-canonical.js +60 -0
- package/cli/semantic-canonical.ts +59 -0
- package/cli/semantic-contract.js +97 -0
- package/cli/semantic-contract.ts +153 -0
- package/cli/semantic-events.js +53 -0
- package/cli/semantic-events.ts +65 -0
- package/cli/semantic-evidence.js +63 -0
- package/cli/semantic-evidence.ts +65 -0
- package/cli/semantic-export.js +186 -0
- package/cli/semantic-export.ts +245 -0
- package/cli/semantic-json.js +69 -0
- package/cli/semantic-json.ts +69 -0
- package/cli/semantic-line-scanner.js +56 -0
- package/cli/semantic-line-scanner.ts +54 -0
- package/cli/semantic-parser.js +78 -0
- package/cli/semantic-parser.ts +95 -0
- package/cli/semantic-provider-line.js +91 -0
- package/cli/semantic-provider-line.ts +106 -0
- package/cli/trace-evidence.js +86 -0
- package/cli/trace-evidence.ts +115 -0
- package/cli/trace-export.js +120 -0
- package/cli/trace-export.ts +164 -0
- package/cli/trace-output-record.js +15 -0
- package/cli/trace-output-record.ts +18 -0
- package/cli/trace-output.js +98 -0
- package/cli/trace-output.ts +119 -0
- package/cluster-templates/conductor-bootstrap.json +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +1 -1
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +26 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +1 -1
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
- package/lib/agent-cli-provider/log-prefix.js +8 -6
- package/lib/agent-cli-provider/log-prefix.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts +4 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.js +37 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.js.map +1 -0
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js +4 -15
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-result.d.ts +2 -0
- package/lib/agent-cli-provider/omp/sdk-process-result.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-result.js +2 -2
- package/lib/agent-cli-provider/omp/sdk-process-result.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.js +6 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +3 -1
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +9 -2
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts +4 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +18 -15
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter-common.js +18 -0
- package/lib/cluster-worker/engine-adapter.js +2 -1
- package/lib/git-remote-utils.js +22 -5
- package/lib/stream-json-parser.js +6 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -4
- package/scripts/omp/runtime-identities.js +31 -18
- package/scripts/omp/runtime.js +2 -0
- package/src/agent/agent-lifecycle.js +17 -1
- package/src/agent/agent-task-executor.js +36 -40
- package/src/agent/output-extraction-failures.js +73 -0
- package/src/agent/output-extraction-failures.ts +100 -0
- package/src/agent/output-extraction-json.js +5 -6
- package/src/agent/output-extraction-json.ts +4 -7
- package/src/agent/output-extraction-types.ts +5 -0
- package/src/agent/provider-terminal-failure.js +2 -2
- package/src/agent/provider-terminal-failure.ts +4 -2
- package/src/agent-cli-provider/adapters/claude.ts +1 -1
- package/src/agent-cli-provider/adapters/codex.ts +36 -1
- package/src/agent-cli-provider/adapters/omp.ts +2 -1
- package/src/agent-cli-provider/log-prefix.ts +12 -7
- package/src/agent-cli-provider/omp/sdk-environment-policy.ts +38 -0
- package/src/agent-cli-provider/omp/sdk-process-private-runtime.ts +4 -16
- package/src/agent-cli-provider/omp/sdk-process-result.ts +4 -2
- package/src/agent-cli-provider/omp/sdk-process-runner.ts +7 -1
- package/src/agent-cli-provider/provider-registry.ts +10 -2
- package/src/agent-cli-provider/single-agent-runtime.ts +24 -15
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/agents/git-pusher-template.js +61 -71
- package/src/attach/socket-paths.js +1 -1
- package/src/attach/socket-paths.ts +1 -1
- package/src/claude-task-runner.js +9 -9
- package/src/foreground-benchmark-run.js +24 -12
- package/src/legacy-lib/git-remote-utils.ts +23 -5
- package/src/legacy-lib/stream-json-parser.ts +10 -5
- package/src/omp-session-verifier.js +8 -5
- package/src/orchestrator.js +42 -0
- package/src/preflight.js +31 -6
- package/src/providers/index.js +4 -22
- package/src/task-execution-context.js +22 -0
- package/src/task-log-line.d.ts +20 -0
- package/src/task-log-line.js +80 -0
- package/src/watcher-prompt-channel.js +1 -1
- package/src/worktree-claude-config.js +25 -0
- package/src/worktree-claude-config.ts +28 -0
- package/task-lib/commands/logs.js +5 -8
- package/task-lib/runner.js +57 -22
- package/task-lib/sdk-watcher-output.js +31 -0
- package/task-lib/sdk-watcher.js +4 -11
- package/task-lib/watcher-output-runtime.js +30 -16
package/README.md
CHANGED
|
@@ -74,6 +74,36 @@ Every step is written to a crash-safe SQLite ledger. Bring your own provider and
|
|
|
74
74
|
<em>One run: classify, execute, verify, and repeat when evidence fails.</em>
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
|
+
## Classification and routing
|
|
78
|
+
|
|
79
|
+
The conductor scores every task on complexity (TRIVIAL, SIMPLE, STANDARD, CRITICAL) and type (INQUIRY, TASK, DEBUG) before any code is written, and that score picks the workflow. A junior model runs the pass; when it can't call it, it answers UNCERTAIN and a senior model decides instead.
|
|
80
|
+
|
|
81
|
+
Rules are evaluated top down, first match wins:
|
|
82
|
+
|
|
83
|
+
| Classification | Workflow | Agents |
|
|
84
|
+
| -------------------------------------- | ------------------ | ------------------------------------------------------------- |
|
|
85
|
+
| DEBUG above TRIVIAL, any complexity | `debug-workflow` | investigator, fixer, tester, completion-detector |
|
|
86
|
+
| TRIVIAL TASK or DEBUG, `--pr`/`--ship` | `worker-validator` | worker, 1 validator |
|
|
87
|
+
| TRIVIAL | `single-worker` | worker only, **no validator** |
|
|
88
|
+
| SIMPLE | `worker-validator` | worker, 1 validator |
|
|
89
|
+
| STANDARD | `full-workflow` | planner, worker, 2 validators |
|
|
90
|
+
| CRITICAL | `full-workflow` | planner, worker, meta-coordinator, 4 validators in two stages |
|
|
91
|
+
|
|
92
|
+
TRIVIAL is the row worth knowing about: one worker, no verifier, so the executor–verifier split doesn't apply on that path. CRITICAL is meant to be rare in the other direction, and the conductor is instructed to pick STANDARD whenever it's torn, since CRITICAL spends a senior model and four validators.
|
|
93
|
+
|
|
94
|
+
## Custom workflows
|
|
95
|
+
|
|
96
|
+
Each workflow above is a JSON file in [`cluster-templates/base-templates/`](cluster-templates/base-templates/), and none of them is privileged. Underneath is a message bus: agents subscribe to topics, publish to topics, and the graph is that wiring.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
zeroshot config list # available workflows
|
|
100
|
+
zeroshot config show full-workflow # read one
|
|
101
|
+
zeroshot config validate ./mine.json # check yours
|
|
102
|
+
zeroshot run 123 --config ./mine.json # run it
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Agent ids, roles, and topic names are free strings, and a trigger can carry a JavaScript predicate deciding whether a message wakes its agent. Cycles are legal, reject-and-retry being one, though `zeroshot config validate` fails a ring of three or more unless something in it carries escape logic. Sub-clusters nest five deep.
|
|
106
|
+
|
|
77
107
|
## Providers and issue sources
|
|
78
108
|
|
|
79
109
|
Provider engines come from the registry: **Claude, Codex, bundled Gateway, Gemini, OpenCode, Pi, OMP, Kiro, and Copilot**. Model gateways stay behind the single Gateway provider.
|
|
@@ -108,6 +138,7 @@ zeroshot run 123 --pr # worktree + pull request
|
|
|
108
138
|
zeroshot run 123 --ship # worktree + PR + merge after approval
|
|
109
139
|
zeroshot run 123 --pr --pr-body $'## Summary\n\nCustom text\n\n{{issue_reference}}'
|
|
110
140
|
zeroshot run 123 -d # background run
|
|
141
|
+
zeroshot run 123 --config ./mine.json # custom workflow graph
|
|
111
142
|
|
|
112
143
|
zeroshot list # tasks and clusters (--json)
|
|
113
144
|
zeroshot status <id> # detailed status (--json)
|
|
@@ -115,10 +146,13 @@ zeroshot logs <id> -f # stream logs
|
|
|
115
146
|
zeroshot resume <id> [prompt] # resume a stopped or failed run
|
|
116
147
|
zeroshot stop <id> # graceful stop
|
|
117
148
|
zeroshot kill <id> # force stop
|
|
149
|
+
zeroshot export <id> --format trace --output run.trace.jsonl
|
|
150
|
+
zeroshot export <id> --format semantic --output run.semantic.jsonl
|
|
118
151
|
|
|
119
152
|
zeroshot providers # provider availability and defaults
|
|
120
153
|
zeroshot settings # effective settings
|
|
121
154
|
zeroshot agents list # available agents
|
|
155
|
+
zeroshot config list # workflow graphs (config show / config validate)
|
|
122
156
|
```
|
|
123
157
|
|
|
124
158
|
`--pr-body` supplies a deterministic pull-request body for `--pr` and `--ship` runs. The
|
|
@@ -126,6 +160,16 @@ template supports `{{issue_number}}`, `{{issue_title}}`, and `{{issue_reference}
|
|
|
126
160
|
expand to empty text for tasks without an issue, so manual runs never emit `Closes #unknown`.
|
|
127
161
|
The unrendered template is retained for detached and resumed runs.
|
|
128
162
|
|
|
163
|
+
The `trace` export is a deterministic, provider-neutral research bundle. It preserves the ordered
|
|
164
|
+
cluster ledger, exact selected prompts, and exact raw task-log bytes without interpreting a Claude,
|
|
165
|
+
Codex, Pi, or other provider protocol. Missing evidence is recorded explicitly in its footer. File
|
|
166
|
+
exports are create-only: choose a new output path rather than replacing an existing bundle. Live
|
|
167
|
+
tasks are exported only as explicitly incomplete snapshots.
|
|
168
|
+
The separate `semantic` export runs those task bytes through Zeroshot's existing stateful provider
|
|
169
|
+
adapters and emits bounded `text`, `thinking`, `tool_call`, `tool_result`, and `result` events.
|
|
170
|
+
Zeroshot-owned wrapper and stderr records remain native-only. Parser diagnostics affect only
|
|
171
|
+
semantic completeness; they do not alter the native trace or run.
|
|
172
|
+
|
|
129
173
|
</details>
|
|
130
174
|
|
|
131
175
|
<details>
|
|
@@ -181,7 +225,7 @@ Zeroshot is **Layer 01 · Verification** of [The Open Engine](https://theopeneng
|
|
|
181
225
|
| 02 | Constraints: **Opcore** | Sibling · alpha |
|
|
182
226
|
| 03-05 | Intent · Context · Runtime | In development |
|
|
183
227
|
|
|
184
|
-
Zeroshot runs the loop: an agent writes the change, and
|
|
228
|
+
Zeroshot runs the loop: an agent writes the change, and **independent** verifiers decide whether it holds, approving it or rejecting it with the specific objections that blocked it. **Opcore** is the sibling layer, a deterministic, local, read-only **constraints** gate for coding agents. Zeroshot packages Opcore `0.2.1` and uses introduced-change validation so existing repository debt never blocks an otherwise clean change. Verification asks _"does this meet the goal?"_; constraints ask _"is this within tolerance?"_
|
|
185
229
|
|
|
186
230
|
Each layer ships the same way: extracted from the platform we run, then opened. **Trust nothing. Verify everything.**
|
|
187
231
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripTimestampPrefix = exports.getProviderAdapter = void 0;
|
|
4
|
+
function isRuntime(value, method) {
|
|
5
|
+
return (value !== null && typeof value === 'object' && typeof Reflect.get(value, method) === 'function');
|
|
6
|
+
}
|
|
7
|
+
function isAdapterRuntime(value) {
|
|
8
|
+
return isRuntime(value, 'getProviderAdapter');
|
|
9
|
+
}
|
|
10
|
+
function isPrefixRuntime(value) {
|
|
11
|
+
return isRuntime(value, 'stripTimestampPrefix');
|
|
12
|
+
}
|
|
13
|
+
const adapters = require('../lib/agent-cli-provider/adapters');
|
|
14
|
+
const prefixes = require('../lib/agent-cli-provider/log-prefix');
|
|
15
|
+
if (!isAdapterRuntime(adapters) || !isPrefixRuntime(prefixes)) {
|
|
16
|
+
throw new Error('Built agent-provider runtime is unavailable');
|
|
17
|
+
}
|
|
18
|
+
exports.getProviderAdapter = adapters.getProviderAdapter;
|
|
19
|
+
exports.stripTimestampPrefix = prefixes.stripTimestampPrefix;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
interface TextEvent {
|
|
2
|
+
readonly type: 'text' | 'thinking';
|
|
3
|
+
readonly text: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface ToolCallEvent {
|
|
7
|
+
readonly type: 'tool_call';
|
|
8
|
+
readonly toolName: string | null | undefined;
|
|
9
|
+
readonly toolId: string | null | undefined;
|
|
10
|
+
readonly input: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ToolResultEvent {
|
|
14
|
+
readonly type: 'tool_result';
|
|
15
|
+
readonly toolId: string | null | undefined;
|
|
16
|
+
readonly content: unknown;
|
|
17
|
+
readonly isError: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ResultEvent {
|
|
21
|
+
readonly type: 'result';
|
|
22
|
+
readonly success: boolean;
|
|
23
|
+
readonly result?: unknown;
|
|
24
|
+
readonly error?: unknown;
|
|
25
|
+
readonly cost?: unknown;
|
|
26
|
+
readonly duration?: unknown;
|
|
27
|
+
readonly inputTokens?: number;
|
|
28
|
+
readonly outputTokens?: number;
|
|
29
|
+
readonly cacheReadInputTokens?: number;
|
|
30
|
+
readonly cacheCreationInputTokens?: number;
|
|
31
|
+
readonly modelUsage?: unknown;
|
|
32
|
+
readonly requests?: number;
|
|
33
|
+
readonly usageSource?: unknown;
|
|
34
|
+
readonly usageCompleteness?: unknown;
|
|
35
|
+
readonly invocation?: unknown;
|
|
36
|
+
readonly ompSdk?: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type OutputEvent = TextEvent | ToolCallEvent | ToolResultEvent | ResultEvent;
|
|
40
|
+
export type ProviderParseResult = OutputEvent | readonly OutputEvent[] | null;
|
|
41
|
+
|
|
42
|
+
export interface ProviderAdapter {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly adapterVersion: string;
|
|
45
|
+
createParserState(): object;
|
|
46
|
+
parseEvent(line: string, state: object): ProviderParseResult;
|
|
47
|
+
finishParsing?(state: object): ProviderParseResult;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface AdapterRuntime {
|
|
51
|
+
getProviderAdapter(provider: string): ProviderAdapter;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface PrefixRuntime {
|
|
55
|
+
stripTimestampPrefix(line: string): string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isRuntime(value: unknown, method: string): boolean {
|
|
59
|
+
return (
|
|
60
|
+
value !== null && typeof value === 'object' && typeof Reflect.get(value, method) === 'function'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isAdapterRuntime(value: unknown): value is AdapterRuntime {
|
|
65
|
+
return isRuntime(value, 'getProviderAdapter');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isPrefixRuntime(value: unknown): value is PrefixRuntime {
|
|
69
|
+
return isRuntime(value, 'stripTimestampPrefix');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const adapters: unknown = require('../lib/agent-cli-provider/adapters');
|
|
73
|
+
const prefixes: unknown = require('../lib/agent-cli-provider/log-prefix');
|
|
74
|
+
if (!isAdapterRuntime(adapters) || !isPrefixRuntime(prefixes)) {
|
|
75
|
+
throw new Error('Built agent-provider runtime is unavailable');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const getProviderAdapter = adapters.getProviderAdapter;
|
|
79
|
+
export const stripTimestampPrefix = prefixes.stripTimestampPrefix;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nullableString = nullableString;
|
|
4
|
+
exports.isRecord = isRecord;
|
|
5
|
+
exports.compareText = compareText;
|
|
6
|
+
exports.sameFileSnapshot = sameFileSnapshot;
|
|
7
|
+
exports.createExclusiveDestination = createExclusiveDestination;
|
|
8
|
+
exports.createReplacingDestination = createReplacingDestination;
|
|
9
|
+
exports.createRecordWriter = createRecordWriter;
|
|
10
|
+
const crypto = require("crypto");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
function nullableString(value) {
|
|
13
|
+
return typeof value === 'string' ? value : null;
|
|
14
|
+
}
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
function compareText(left, right) {
|
|
19
|
+
if (left < right)
|
|
20
|
+
return -1;
|
|
21
|
+
return left > right ? 1 : 0;
|
|
22
|
+
}
|
|
23
|
+
function sameFileSnapshot(before, after) {
|
|
24
|
+
return (before.dev === after.dev &&
|
|
25
|
+
before.ino === after.ino &&
|
|
26
|
+
before.size === after.size &&
|
|
27
|
+
before.mtimeNs === after.mtimeNs &&
|
|
28
|
+
before.ctimeNs === after.ctimeNs);
|
|
29
|
+
}
|
|
30
|
+
function writeAll(fd, value, label) {
|
|
31
|
+
const bytes = Buffer.from(value);
|
|
32
|
+
let offset = 0;
|
|
33
|
+
while (offset < bytes.length) {
|
|
34
|
+
const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
|
|
35
|
+
if (!Number.isInteger(written) || written <= 0) {
|
|
36
|
+
throw new Error(`${label} export destination stopped accepting bytes`);
|
|
37
|
+
}
|
|
38
|
+
offset += written;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function streamDestination(stdout, label) {
|
|
42
|
+
if (typeof stdout.fd === 'number' && Number.isInteger(stdout.fd)) {
|
|
43
|
+
const fd = stdout.fd;
|
|
44
|
+
return { close() { }, write: (value) => writeAll(fd, value, label) };
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
close() { },
|
|
48
|
+
write(value) {
|
|
49
|
+
stdout.write(value);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function createExclusiveDestination(outputPath, stdout, label) {
|
|
54
|
+
if (!outputPath)
|
|
55
|
+
return streamDestination(stdout, label);
|
|
56
|
+
const flags = fs.constants.O_WRONLY |
|
|
57
|
+
fs.constants.O_CREAT |
|
|
58
|
+
fs.constants.O_EXCL |
|
|
59
|
+
(fs.constants.O_NOFOLLOW || 0);
|
|
60
|
+
const fd = fs.openSync(outputPath, flags, 0o600);
|
|
61
|
+
try {
|
|
62
|
+
fs.fchmodSync(fd, 0o600);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
fs.closeSync(fd);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, label) };
|
|
69
|
+
}
|
|
70
|
+
function createReplacingDestination(outputPath, stdout) {
|
|
71
|
+
if (!outputPath)
|
|
72
|
+
return streamDestination(stdout, 'JSON');
|
|
73
|
+
const fd = fs.openSync(outputPath, 'w');
|
|
74
|
+
return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, 'JSON') };
|
|
75
|
+
}
|
|
76
|
+
function createRecordWriter(destination) {
|
|
77
|
+
const digest = crypto.createHash('sha256');
|
|
78
|
+
let records = 0;
|
|
79
|
+
const encode = (record) => `${JSON.stringify(record)}\n`;
|
|
80
|
+
return {
|
|
81
|
+
get records() {
|
|
82
|
+
return records;
|
|
83
|
+
},
|
|
84
|
+
write(record) {
|
|
85
|
+
const line = encode(record);
|
|
86
|
+
digest.update(line);
|
|
87
|
+
destination.write(line);
|
|
88
|
+
records += 1;
|
|
89
|
+
},
|
|
90
|
+
finish(record) {
|
|
91
|
+
destination.write(encode({ ...record, preceding_records: records, records_sha256: digest.digest('hex') }));
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import crypto = require('crypto');
|
|
2
|
+
import fs = require('fs');
|
|
3
|
+
|
|
4
|
+
export interface ExportStream {
|
|
5
|
+
readonly fd?: number;
|
|
6
|
+
write(value: string): unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Destination {
|
|
10
|
+
close(): void;
|
|
11
|
+
write(value: string): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RecordWriter {
|
|
15
|
+
readonly records: number;
|
|
16
|
+
finish(record: Record<string, unknown>): void;
|
|
17
|
+
write(record: Record<string, unknown>): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function nullableString(value: unknown): string | null {
|
|
21
|
+
return typeof value === 'string' ? value : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function compareText(left: string, right: string): number {
|
|
29
|
+
if (left < right) return -1;
|
|
30
|
+
return left > right ? 1 : 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function sameFileSnapshot(before: fs.BigIntStats, after: fs.BigIntStats): boolean {
|
|
34
|
+
return (
|
|
35
|
+
before.dev === after.dev &&
|
|
36
|
+
before.ino === after.ino &&
|
|
37
|
+
before.size === after.size &&
|
|
38
|
+
before.mtimeNs === after.mtimeNs &&
|
|
39
|
+
before.ctimeNs === after.ctimeNs
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeAll(fd: number, value: string, label: string): void {
|
|
44
|
+
const bytes = Buffer.from(value);
|
|
45
|
+
let offset = 0;
|
|
46
|
+
while (offset < bytes.length) {
|
|
47
|
+
const written = fs.writeSync(fd, bytes, offset, bytes.length - offset);
|
|
48
|
+
if (!Number.isInteger(written) || written <= 0) {
|
|
49
|
+
throw new Error(`${label} export destination stopped accepting bytes`);
|
|
50
|
+
}
|
|
51
|
+
offset += written;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function streamDestination(stdout: ExportStream, label: string): Destination {
|
|
56
|
+
if (typeof stdout.fd === 'number' && Number.isInteger(stdout.fd)) {
|
|
57
|
+
const fd = stdout.fd;
|
|
58
|
+
return { close(): void {}, write: (value) => writeAll(fd, value, label) };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
close(): void {},
|
|
62
|
+
write(value): void {
|
|
63
|
+
stdout.write(value);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createExclusiveDestination(
|
|
69
|
+
outputPath: string | null | undefined,
|
|
70
|
+
stdout: ExportStream,
|
|
71
|
+
label: string
|
|
72
|
+
): Destination {
|
|
73
|
+
if (!outputPath) return streamDestination(stdout, label);
|
|
74
|
+
const flags =
|
|
75
|
+
fs.constants.O_WRONLY |
|
|
76
|
+
fs.constants.O_CREAT |
|
|
77
|
+
fs.constants.O_EXCL |
|
|
78
|
+
(fs.constants.O_NOFOLLOW || 0);
|
|
79
|
+
const fd = fs.openSync(outputPath, flags, 0o600);
|
|
80
|
+
try {
|
|
81
|
+
fs.fchmodSync(fd, 0o600);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
fs.closeSync(fd);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, label) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createReplacingDestination(
|
|
90
|
+
outputPath: string | null | undefined,
|
|
91
|
+
stdout: ExportStream
|
|
92
|
+
): Destination {
|
|
93
|
+
if (!outputPath) return streamDestination(stdout, 'JSON');
|
|
94
|
+
const fd = fs.openSync(outputPath, 'w');
|
|
95
|
+
return { close: () => fs.closeSync(fd), write: (value) => writeAll(fd, value, 'JSON') };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createRecordWriter(destination: Destination): RecordWriter {
|
|
99
|
+
const digest = crypto.createHash('sha256');
|
|
100
|
+
let records = 0;
|
|
101
|
+
const encode = (record: Record<string, unknown>): string => `${JSON.stringify(record)}\n`;
|
|
102
|
+
return {
|
|
103
|
+
get records(): number {
|
|
104
|
+
return records;
|
|
105
|
+
},
|
|
106
|
+
write(record): void {
|
|
107
|
+
const line = encode(record);
|
|
108
|
+
digest.update(line);
|
|
109
|
+
destination.write(line);
|
|
110
|
+
records += 1;
|
|
111
|
+
},
|
|
112
|
+
finish(record): void {
|
|
113
|
+
destination.write(
|
|
114
|
+
encode({ ...record, preceding_records: records, records_sha256: digest.digest('hex') })
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
package/cli/index.js
CHANGED
|
@@ -59,6 +59,7 @@ const {
|
|
|
59
59
|
} = require('../lib/provider-names');
|
|
60
60
|
const { commandExists } = require('../lib/provider-detection');
|
|
61
61
|
const { getProvider, parseProviderChunk } = require('../src/providers');
|
|
62
|
+
const { decodeTaskLogLine } = require('../src/task-log-line');
|
|
62
63
|
const { readClustersFileSync } = require('../lib/clusters-registry');
|
|
63
64
|
const { MOUNT_PRESETS, resolveEnvs } = require('../lib/docker-config');
|
|
64
65
|
const {
|
|
@@ -78,7 +79,10 @@ const {
|
|
|
78
79
|
resolveEffectiveRunPlan,
|
|
79
80
|
} = require('../lib/start-cluster');
|
|
80
81
|
const { requirePreflight } = require('../src/preflight');
|
|
82
|
+
const { resolveTaskExecutionContext } = require('../src/task-execution-context');
|
|
81
83
|
const {
|
|
84
|
+
buildForegroundResult,
|
|
85
|
+
exitCodeForForegroundResult,
|
|
82
86
|
exitCodeForResult,
|
|
83
87
|
isForegroundStatusSettled,
|
|
84
88
|
writeForegroundResult,
|
|
@@ -752,16 +756,25 @@ async function finishForegroundRun({ cluster, orchestrator, clusterId, plan, res
|
|
|
752
756
|
plan,
|
|
753
757
|
Boolean(resultPath)
|
|
754
758
|
);
|
|
755
|
-
if (
|
|
756
|
-
|
|
759
|
+
if (resultPath) {
|
|
760
|
+
const receipt = writeForegroundResult({
|
|
761
|
+
orchestrator,
|
|
762
|
+
cluster,
|
|
763
|
+
clusterId,
|
|
764
|
+
resultPath,
|
|
765
|
+
cancelled: foreground.cancelled,
|
|
766
|
+
});
|
|
767
|
+
process.exitCode = exitCodeForResult(receipt);
|
|
768
|
+
console.log(chalk.dim(`Result ${receipt.outcome} committed to ${resultPath}`));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const result = buildForegroundResult({
|
|
757
772
|
orchestrator,
|
|
758
773
|
cluster,
|
|
759
774
|
clusterId,
|
|
760
|
-
resultPath,
|
|
761
775
|
cancelled: foreground.cancelled,
|
|
762
776
|
});
|
|
763
|
-
process.exitCode =
|
|
764
|
-
console.log(chalk.dim(`Result ${receipt.outcome} committed to ${resultPath}`));
|
|
777
|
+
process.exitCode = exitCodeForForegroundResult(result);
|
|
765
778
|
} finally {
|
|
766
779
|
orchestrator.close();
|
|
767
780
|
}
|
|
@@ -1418,19 +1431,10 @@ function startClusterDbPolling(cluster, clusterId, isActive, options) {
|
|
|
1418
1431
|
}
|
|
1419
1432
|
|
|
1420
1433
|
function parseIncrementalTaskLogLine(line) {
|
|
1421
|
-
const
|
|
1422
|
-
if (!
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
let timestamp = Date.now();
|
|
1427
|
-
let jsonContent = trimmed;
|
|
1428
|
-
|
|
1429
|
-
const timestampMatch = jsonContent.match(/^\[(\d{13})\](.*)$/);
|
|
1430
|
-
if (timestampMatch) {
|
|
1431
|
-
timestamp = parseInt(timestampMatch[1], 10);
|
|
1432
|
-
jsonContent = timestampMatch[2];
|
|
1433
|
-
}
|
|
1434
|
+
const decoded = decodeTaskLogLine(line.trim());
|
|
1435
|
+
if (!decoded.providerOutput) return null;
|
|
1436
|
+
const timestamp = decoded.timestamp ?? Date.now();
|
|
1437
|
+
const jsonContent = decoded.content.trim();
|
|
1434
1438
|
|
|
1435
1439
|
if (!jsonContent.startsWith('{')) {
|
|
1436
1440
|
return null;
|
|
@@ -2607,37 +2611,7 @@ function findAgentFromLifecycle(agents, taskId, lifecycleMessages) {
|
|
|
2607
2611
|
}
|
|
2608
2612
|
|
|
2609
2613
|
function parseTaskLogLine(line) {
|
|
2610
|
-
|
|
2611
|
-
if (!trimmed.startsWith('[')) {
|
|
2612
|
-
return null;
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2615
|
-
const parsed = parseTimestampedLine(trimmed);
|
|
2616
|
-
if (!parsed.jsonContent.startsWith('{')) {
|
|
2617
|
-
return null;
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
try {
|
|
2621
|
-
const json = JSON.parse(parsed.jsonContent);
|
|
2622
|
-
if (json.type === 'system' && json.subtype === 'init') {
|
|
2623
|
-
return null;
|
|
2624
|
-
}
|
|
2625
|
-
} catch {
|
|
2626
|
-
return null;
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
return parsed;
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
|
-
function parseTimestampedLine(line) {
|
|
2633
|
-
const timestampMatch = line.match(/^\[(\d{13})\](.*)$/);
|
|
2634
|
-
if (!timestampMatch) {
|
|
2635
|
-
return { timestamp: Date.now(), jsonContent: line };
|
|
2636
|
-
}
|
|
2637
|
-
return {
|
|
2638
|
-
timestamp: parseInt(timestampMatch[1], 10),
|
|
2639
|
-
jsonContent: timestampMatch[2],
|
|
2640
|
-
};
|
|
2614
|
+
return parseIncrementalTaskLogLine(line);
|
|
2641
2615
|
}
|
|
2642
2616
|
|
|
2643
2617
|
function buildTaskLogMessage({ taskId, timestamp, jsonContent, cluster, agent, iteration }) {
|
|
@@ -3053,6 +3027,7 @@ taskCmd
|
|
|
3053
3027
|
requireDocker: false, // Docker not needed for plain tasks
|
|
3054
3028
|
quiet: false,
|
|
3055
3029
|
provider: providerOverride,
|
|
3030
|
+
executionContext: resolveTaskExecutionContext(),
|
|
3056
3031
|
});
|
|
3057
3032
|
|
|
3058
3033
|
// Dynamically import task command (ESM module)
|
|
@@ -3392,9 +3367,9 @@ program
|
|
|
3392
3367
|
.command('export <cluster-id>')
|
|
3393
3368
|
.helpGroup('Maintenance:')
|
|
3394
3369
|
.description('Export cluster conversation')
|
|
3395
|
-
.option('-f, --format <format>', 'Export format: json, markdown, html', 'html')
|
|
3370
|
+
.option('-f, --format <format>', 'Export format: json, trace, semantic, markdown, html', 'html')
|
|
3396
3371
|
.option('-o, --output <file>', 'Output file (auto-generated for html)')
|
|
3397
|
-
.action((clusterId, options) => {
|
|
3372
|
+
.action(async (clusterId, options) => {
|
|
3398
3373
|
try {
|
|
3399
3374
|
// Get messages from DB
|
|
3400
3375
|
const Ledger = require('../src/ledger');
|
|
@@ -3405,14 +3380,7 @@ program
|
|
|
3405
3380
|
throw new Error(`Cluster ${clusterId} not found (no DB file)`);
|
|
3406
3381
|
}
|
|
3407
3382
|
|
|
3408
|
-
|
|
3409
|
-
if (options.format === 'json') {
|
|
3410
|
-
exportClusterJson(dbPath, clusterId, options.output || null);
|
|
3411
|
-
if (options.output) {
|
|
3412
|
-
console.log(`Exported to ${options.output}`);
|
|
3413
|
-
}
|
|
3414
|
-
return;
|
|
3415
|
-
}
|
|
3383
|
+
if (await exportClusterDataFormat(dbPath, clusterId, options)) return;
|
|
3416
3384
|
|
|
3417
3385
|
const ledger = new Ledger(dbPath);
|
|
3418
3386
|
const messages = ledger.getAll(clusterId);
|
|
@@ -3727,6 +3695,7 @@ program
|
|
|
3727
3695
|
requireDocker: false,
|
|
3728
3696
|
quiet: false,
|
|
3729
3697
|
provider: providerName,
|
|
3698
|
+
executionContext: resolveTaskExecutionContext(),
|
|
3730
3699
|
});
|
|
3731
3700
|
|
|
3732
3701
|
// Try resuming as task
|
|
@@ -5092,9 +5061,18 @@ function formatToolCall(toolName, input) {
|
|
|
5092
5061
|
function formatToolResult(content, isError, toolName, toolInput) {
|
|
5093
5062
|
if (!content) return isError ? 'error' : 'done';
|
|
5094
5063
|
|
|
5064
|
+
let normalizedContent = content;
|
|
5065
|
+
if (typeof content !== 'string') {
|
|
5066
|
+
try {
|
|
5067
|
+
normalizedContent = JSON.stringify(content) ?? String(content);
|
|
5068
|
+
} catch {
|
|
5069
|
+
normalizedContent = String(content);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5095
5073
|
// For errors, show full message
|
|
5096
5074
|
if (isError) {
|
|
5097
|
-
const firstLine =
|
|
5075
|
+
const firstLine = normalizedContent.split('\n')[0].substring(0, 80);
|
|
5098
5076
|
return chalk.red(firstLine);
|
|
5099
5077
|
}
|
|
5100
5078
|
|
|
@@ -5123,7 +5101,7 @@ function formatToolResult(content, isError, toolName, toolInput) {
|
|
|
5123
5101
|
}
|
|
5124
5102
|
|
|
5125
5103
|
// For success, show summary
|
|
5126
|
-
const lines =
|
|
5104
|
+
const lines = normalizedContent.split('\n').filter((l) => l.trim());
|
|
5127
5105
|
if (lines.length === 0) return 'done';
|
|
5128
5106
|
if (lines.length === 1) {
|
|
5129
5107
|
const line = lines[0].substring(0, 60);
|
|
@@ -6179,6 +6157,98 @@ function exportClusterJson(dbPath, clusterId, outputPath) {
|
|
|
6179
6157
|
);
|
|
6180
6158
|
}
|
|
6181
6159
|
|
|
6160
|
+
function tryExportClusterTraceSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
|
|
6161
|
+
const Ledger = require('../src/ledger');
|
|
6162
|
+
const { streamClusterTraceExport } = require('./trace-export');
|
|
6163
|
+
const ledger = new Ledger(dbPath, { readonly: true });
|
|
6164
|
+
try {
|
|
6165
|
+
return ledger.withReadSnapshot(() => {
|
|
6166
|
+
if (ledger.needsAgentOutputReconciliation(clusterId)) return false;
|
|
6167
|
+
streamClusterTraceExport({
|
|
6168
|
+
ledger,
|
|
6169
|
+
clusterId,
|
|
6170
|
+
readTask,
|
|
6171
|
+
allowedLogRoot,
|
|
6172
|
+
outputPath,
|
|
6173
|
+
});
|
|
6174
|
+
return true;
|
|
6175
|
+
});
|
|
6176
|
+
} finally {
|
|
6177
|
+
ledger.close();
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
|
|
6181
|
+
function exportClusterTrace(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
|
|
6182
|
+
const Ledger = require('../src/ledger');
|
|
6183
|
+
const maxReconciliationAttempts = 3;
|
|
6184
|
+
for (let attempt = 0; attempt <= maxReconciliationAttempts; attempt += 1) {
|
|
6185
|
+
if (tryExportClusterTraceSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot)) {
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
if (attempt === maxReconciliationAttempts) break;
|
|
6189
|
+
const reconciliationLedger = new Ledger(dbPath);
|
|
6190
|
+
reconciliationLedger.close();
|
|
6191
|
+
}
|
|
6192
|
+
throw new Error(
|
|
6193
|
+
`Cluster ${clusterId} output changed during ${maxReconciliationAttempts} reconciliation attempts`
|
|
6194
|
+
);
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6197
|
+
function tryExportClusterSemanticSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
|
|
6198
|
+
const Ledger = require('../src/ledger');
|
|
6199
|
+
const { streamClusterSemanticExport } = require('./semantic-export');
|
|
6200
|
+
const ledger = new Ledger(dbPath, { readonly: true });
|
|
6201
|
+
try {
|
|
6202
|
+
return ledger.withReadSnapshot(() => {
|
|
6203
|
+
if (ledger.needsAgentOutputReconciliation(clusterId)) return false;
|
|
6204
|
+
streamClusterSemanticExport({
|
|
6205
|
+
ledger,
|
|
6206
|
+
clusterId,
|
|
6207
|
+
readTask,
|
|
6208
|
+
allowedLogRoot,
|
|
6209
|
+
outputPath,
|
|
6210
|
+
});
|
|
6211
|
+
return true;
|
|
6212
|
+
});
|
|
6213
|
+
} finally {
|
|
6214
|
+
ledger.close();
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
|
|
6218
|
+
function exportClusterSemantic(dbPath, clusterId, outputPath, readTask, allowedLogRoot) {
|
|
6219
|
+
const Ledger = require('../src/ledger');
|
|
6220
|
+
const maxReconciliationAttempts = 3;
|
|
6221
|
+
for (let attempt = 0; attempt <= maxReconciliationAttempts; attempt += 1) {
|
|
6222
|
+
if (tryExportClusterSemanticSnapshot(dbPath, clusterId, outputPath, readTask, allowedLogRoot)) {
|
|
6223
|
+
return;
|
|
6224
|
+
}
|
|
6225
|
+
if (attempt === maxReconciliationAttempts) break;
|
|
6226
|
+
const reconciliationLedger = new Ledger(dbPath);
|
|
6227
|
+
reconciliationLedger.close();
|
|
6228
|
+
}
|
|
6229
|
+
throw new Error(
|
|
6230
|
+
`Cluster ${clusterId} output changed during ${maxReconciliationAttempts} reconciliation attempts`
|
|
6231
|
+
);
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
async function exportClusterDataFormat(dbPath, clusterId, options) {
|
|
6235
|
+
if (options.format === 'json') {
|
|
6236
|
+
exportClusterJson(dbPath, clusterId, options.output || null);
|
|
6237
|
+
if (options.output) console.log(`Exported to ${options.output}`);
|
|
6238
|
+
return true;
|
|
6239
|
+
}
|
|
6240
|
+
if (options.format !== 'trace' && options.format !== 'semantic') return false;
|
|
6241
|
+
|
|
6242
|
+
const [{ getTask }, { LOGS_DIR }] = await Promise.all([
|
|
6243
|
+
import('../task-lib/store.js'),
|
|
6244
|
+
import('../task-lib/config.js'),
|
|
6245
|
+
]);
|
|
6246
|
+
const exporter = options.format === 'trace' ? exportClusterTrace : exportClusterSemantic;
|
|
6247
|
+
exporter(dbPath, clusterId, options.output || null, getTask, LOGS_DIR);
|
|
6248
|
+
if (options.output) console.log(`Exported ${options.format} to ${options.output}`);
|
|
6249
|
+
return true;
|
|
6250
|
+
}
|
|
6251
|
+
|
|
6182
6252
|
// Main entry point
|
|
6183
6253
|
async function main() {
|
|
6184
6254
|
printLegacyDistroNotice();
|
|
@@ -6235,4 +6305,5 @@ module.exports = {
|
|
|
6235
6305
|
buildCompletionPrompt,
|
|
6236
6306
|
resolveRunMode,
|
|
6237
6307
|
killRunningClusters,
|
|
6308
|
+
parseTaskLogLine,
|
|
6238
6309
|
};
|