@the-open-engine/zeroshot 6.39.1 → 6.39.2
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 +33 -1
- package/cli/index.js +30 -7
- 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/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/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- 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/output-extraction-failures.js +73 -0
- package/src/agent/output-extraction-failures.ts +100 -0
- 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/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/foreground-benchmark-run.js +24 -12
- package/src/legacy-lib/git-remote-utils.ts +23 -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 +3 -3
- package/src/task-execution-context.js +22 -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/runner.js +57 -22
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { MAX_SUMMARY_BYTES } = require('./contracts');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_COMPLETION_SUMMARY = 'Cluster completed';
|
|
3
6
|
const PUBLIC_ADAPTER_METHODS = Object.freeze([
|
|
4
7
|
'start',
|
|
5
8
|
'status',
|
|
@@ -8,6 +11,20 @@ const PUBLIC_ADAPTER_METHODS = Object.freeze([
|
|
|
8
11
|
'close',
|
|
9
12
|
]);
|
|
10
13
|
|
|
14
|
+
function completionSummary(value) {
|
|
15
|
+
const summary = typeof value === 'string' ? value : DEFAULT_COMPLETION_SUMMARY;
|
|
16
|
+
if (Buffer.byteLength(summary, 'utf8') <= MAX_SUMMARY_BYTES) return summary;
|
|
17
|
+
let bounded = '';
|
|
18
|
+
let bytes = 0;
|
|
19
|
+
for (const character of summary) {
|
|
20
|
+
const characterBytes = Buffer.byteLength(character, 'utf8');
|
|
21
|
+
if (bytes + characterBytes > MAX_SUMMARY_BYTES) break;
|
|
22
|
+
bounded += character;
|
|
23
|
+
bytes += characterBytes;
|
|
24
|
+
}
|
|
25
|
+
return bounded;
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
function createAdapterFacade(adapter) {
|
|
12
29
|
return Object.freeze(
|
|
13
30
|
Object.fromEntries(PUBLIC_ADAPTER_METHODS.map((name) => [name, adapter[name].bind(adapter)]))
|
|
@@ -30,6 +47,7 @@ function frozenResourceStatus(resource, state, details) {
|
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
module.exports = {
|
|
50
|
+
completionSummary,
|
|
33
51
|
createAdapterFacade,
|
|
34
52
|
declaredFailureEvent,
|
|
35
53
|
frozenResourceStatus,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { cloneJson } = require('./object-utils');
|
|
4
4
|
const { inputFromRequest } = require('./engine-input');
|
|
5
5
|
const {
|
|
6
|
+
completionSummary,
|
|
6
7
|
createAdapterFacade,
|
|
7
8
|
declaredFailureEvent,
|
|
8
9
|
frozenResourceStatus,
|
|
@@ -26,7 +27,7 @@ function terminalEventFromMessage(message) {
|
|
|
26
27
|
if (message.topic === 'CLUSTER_COMPLETE') {
|
|
27
28
|
return {
|
|
28
29
|
type: 'complete',
|
|
29
|
-
summary: data.reason || message.content?.text,
|
|
30
|
+
summary: completionSummary(data.reason || message.content?.text),
|
|
30
31
|
...completionPayload(data),
|
|
31
32
|
};
|
|
32
33
|
}
|
package/lib/git-remote-utils.js
CHANGED
|
@@ -124,6 +124,24 @@ function parseGitRemoteUrl(remoteUrl) {
|
|
|
124
124
|
}
|
|
125
125
|
return null;
|
|
126
126
|
}
|
|
127
|
+
function parseFetchRemoteLine(line) {
|
|
128
|
+
// Partial clones append their filter after the fetch marker, for example
|
|
129
|
+
// `origin https://github.com/org/repo.git (fetch) [blob:none]`.
|
|
130
|
+
const fetchMarker = ' (fetch)';
|
|
131
|
+
const fetchMarkerIndex = line.lastIndexOf(fetchMarker);
|
|
132
|
+
if (fetchMarkerIndex < 1)
|
|
133
|
+
return null;
|
|
134
|
+
const suffix = line.slice(fetchMarkerIndex + fetchMarker.length);
|
|
135
|
+
if (suffix.length > 0 && !(suffix.startsWith(' [') && suffix.endsWith(']')))
|
|
136
|
+
return null;
|
|
137
|
+
const remoteLine = line.slice(0, fetchMarkerIndex);
|
|
138
|
+
const separatorIndex = remoteLine.search(/\s/);
|
|
139
|
+
if (separatorIndex < 1) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const remoteUrl = remoteLine.slice(separatorIndex).trim();
|
|
143
|
+
return remoteUrl.length > 0 ? [remoteLine.slice(0, separatorIndex), remoteUrl] : null;
|
|
144
|
+
}
|
|
127
145
|
/**
|
|
128
146
|
* Detect git repository context from current working directory.
|
|
129
147
|
* Returns provider context extracted from git remote URL.
|
|
@@ -147,14 +165,13 @@ function detectGitContext(cwd = process.cwd()) {
|
|
|
147
165
|
});
|
|
148
166
|
const supportedRemotes = new Map();
|
|
149
167
|
for (const line of remoteOutput.split(/\r?\n/)) {
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
168
|
+
const remoteParts = parseFetchRemoteLine(line);
|
|
169
|
+
if (!remoteParts) {
|
|
152
170
|
continue;
|
|
153
171
|
}
|
|
154
|
-
const remoteCandidate =
|
|
155
|
-
const remoteUrl = match[2];
|
|
172
|
+
const [remoteCandidate, remoteUrl] = remoteParts;
|
|
156
173
|
const remote = normalizeGitRemoteName(remoteCandidate);
|
|
157
|
-
if (!remote
|
|
174
|
+
if (!remote) {
|
|
158
175
|
continue;
|
|
159
176
|
}
|
|
160
177
|
const context = parseGitRemoteUrl(remoteUrl);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.39.
|
|
3
|
+
"version": "6.39.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@the-open-engine/zeroshot",
|
|
9
|
-
"version": "6.39.
|
|
9
|
+
"version": "6.39.2",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.39.
|
|
3
|
+
"version": "6.39.2",
|
|
4
4
|
"description": "Independent executor–verifier orchestration for software changes.",
|
|
5
5
|
"main": "src/orchestrator.js",
|
|
6
6
|
"bin": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"typecheck:task-lib": "tsc --project tsconfig.task-lib.json",
|
|
48
48
|
"typecheck:hosted-target": "tsc --project tsconfig.hosted-target.json",
|
|
49
49
|
"typecheck:hosted-session": "tsc --project tsconfig.hosted-session.json",
|
|
50
|
+
"pretest:hosted": "npm run build:legacy-runtime",
|
|
50
51
|
"test:hosted": "npm run build:cluster && node --test tests/private-hosted-cli/*.test.js && npm run test:hosted-target",
|
|
51
52
|
"test:hosted-target": "npm run build:cluster && npm run build:target && node --test tests/hosted-target/*.test.ts",
|
|
52
53
|
"test:target": "npm run build:agent-cli-provider && npm run build:target && node --test tests/target/*.test.ts",
|
|
@@ -10,6 +10,7 @@ const BUN_PACKAGE_NAME = 'bun';
|
|
|
10
10
|
const BUN_RUNTIME_VERSION = '1.3.14';
|
|
11
11
|
const SIDECAR_RELATIVE_PATH = path.join('scripts', 'omp', 'sidecar.ts');
|
|
12
12
|
const HOST_SUPERVISOR_RELATIVE_PATH = path.join('scripts', 'omp', 'host-supervisor.ts');
|
|
13
|
+
const ACTIVE_CONTAINER_INVOCATION = Symbol('active-container-invocation');
|
|
13
14
|
const HOST_SDK_PLATFORM_KEYS = Object.freeze(['linux:arm64', 'linux:x64']);
|
|
14
15
|
|
|
15
16
|
const BUN_PLATFORM_PACKAGES = Object.freeze({
|
|
@@ -227,24 +228,26 @@ function resolveOmpSdkRuntime(options = {}) {
|
|
|
227
228
|
if (!fileExists(hostSupervisorPath)) {
|
|
228
229
|
throw new Error(`OMP SDK host supervisor is missing: ${hostSupervisorPath}`);
|
|
229
230
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
231
|
+
if (runtimeOptions.activeInvocation !== ACTIVE_CONTAINER_INVOCATION) {
|
|
232
|
+
let containmentProbe;
|
|
233
|
+
try {
|
|
234
|
+
containmentProbe = JSON.parse(
|
|
235
|
+
String(readContainmentProbe(bunExecutable, hostSupervisorPath)).trim()
|
|
236
|
+
);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
throw new Error(`Unable to attest Linux subreaper/pidfd containment: ${error.message}`);
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
containmentProbe?.protocolVersion !== 1 ||
|
|
242
|
+
containmentProbe?.type !== 'cleanup-attestation' ||
|
|
243
|
+
containmentProbe?.status !== 'clean' ||
|
|
244
|
+
containmentProbe?.mode !== 'linux-subreaper-pidfd' ||
|
|
245
|
+
containmentProbe?.subreaper !== true ||
|
|
246
|
+
containmentProbe?.pidfd !== true ||
|
|
247
|
+
containmentProbe?.ownedProcessCount !== 0
|
|
248
|
+
) {
|
|
249
|
+
throw new Error('Linux subreaper/pidfd containment probe returned invalid evidence');
|
|
250
|
+
}
|
|
248
251
|
}
|
|
249
252
|
return Object.freeze({
|
|
250
253
|
bunExecutable,
|
|
@@ -261,6 +264,15 @@ function resolveOmpSdkRuntime(options = {}) {
|
|
|
261
264
|
sidecarPath,
|
|
262
265
|
});
|
|
263
266
|
}
|
|
267
|
+
|
|
268
|
+
function resolveOmpSdkContainerRuntime(options = {}) {
|
|
269
|
+
// Image inspection may run as PID 1, where no parent process exists to supervise. The actual
|
|
270
|
+
// provider invocation still starts the supervisor and requires its final cleanup attestation.
|
|
271
|
+
return resolveOmpSdkRuntime({
|
|
272
|
+
...options,
|
|
273
|
+
activeInvocation: ACTIVE_CONTAINER_INVOCATION,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
264
276
|
module.exports = {
|
|
265
277
|
BUN_PACKAGE_INTEGRITY,
|
|
266
278
|
BUN_PACKAGE_NAME,
|
|
@@ -279,5 +291,6 @@ module.exports = {
|
|
|
279
291
|
REQUIRED_OMP_SDK_SOURCES,
|
|
280
292
|
SIDECAR_RELATIVE_PATH,
|
|
281
293
|
assertPackageIdentity,
|
|
294
|
+
resolveOmpSdkContainerRuntime,
|
|
282
295
|
resolveOmpSdkRuntime,
|
|
283
296
|
};
|
package/scripts/omp/runtime.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
REQUIRED_OMP_SDK_SOURCES,
|
|
13
13
|
SIDECAR_RELATIVE_PATH,
|
|
14
14
|
assertPackageIdentity,
|
|
15
|
+
resolveOmpSdkContainerRuntime,
|
|
15
16
|
resolveOmpSdkRuntime,
|
|
16
17
|
} = require('./runtime-identities');
|
|
17
18
|
const { validateOmpSdkReleaseAssets } = require('./runtime-release');
|
|
@@ -28,6 +29,7 @@ module.exports = {
|
|
|
28
29
|
REQUIRED_OMP_SDK_SOURCES,
|
|
29
30
|
SIDECAR_RELATIVE_PATH,
|
|
30
31
|
assertPackageIdentity,
|
|
32
|
+
resolveOmpSdkContainerRuntime,
|
|
31
33
|
resolveOmpSdkRuntime,
|
|
32
34
|
validateOmpSdkReleaseAssets,
|
|
33
35
|
};
|
|
@@ -221,6 +221,7 @@ function start(agent) {
|
|
|
221
221
|
*/
|
|
222
222
|
async function stop(agent) {
|
|
223
223
|
stopLivenessCheck(agent);
|
|
224
|
+
agent._cancelStartDelay?.();
|
|
224
225
|
|
|
225
226
|
const hasNestedExecutions = agent.nestedExecutions?.hasActive === true;
|
|
226
227
|
if (!agent.running && !agent.currentTask && !hasNestedExecutions && !agent._currentExecution) {
|
|
@@ -440,7 +441,21 @@ async function applyValidatorJitter(agent) {
|
|
|
440
441
|
`[Agent ${agent.id}] Adding ${Math.round(jitterMs / 1000)}s jitter to prevent lock contention`
|
|
441
442
|
);
|
|
442
443
|
}
|
|
443
|
-
await new Promise((resolve) =>
|
|
444
|
+
await new Promise((resolve) => {
|
|
445
|
+
let settled = false;
|
|
446
|
+
let timer;
|
|
447
|
+
let cancel;
|
|
448
|
+
const finish = () => {
|
|
449
|
+
if (settled) return;
|
|
450
|
+
settled = true;
|
|
451
|
+
clearTimeout(timer);
|
|
452
|
+
if (agent._cancelStartDelay === cancel) agent._cancelStartDelay = null;
|
|
453
|
+
resolve();
|
|
454
|
+
};
|
|
455
|
+
cancel = finish;
|
|
456
|
+
timer = setTimeout(finish, jitterMs);
|
|
457
|
+
agent._cancelStartDelay = cancel;
|
|
458
|
+
});
|
|
444
459
|
}
|
|
445
460
|
|
|
446
461
|
function publishTaskStarted(agent, triggeringMessage) {
|
|
@@ -641,6 +656,7 @@ async function runTaskAttempt(agent, triggeringMessage) {
|
|
|
641
656
|
// Spawn provider task
|
|
642
657
|
agent.state = 'executing_task';
|
|
643
658
|
await applyValidatorJitter(agent);
|
|
659
|
+
if (!agent.running) return;
|
|
644
660
|
publishTaskStarted(agent, triggeringMessage);
|
|
645
661
|
|
|
646
662
|
let result;
|
|
@@ -5,6 +5,20 @@ const piExtraction = require("./output-extraction-pi");
|
|
|
5
5
|
const { MAX_CLI_ERROR_BYTES, cliErrorDetail } = errorDetail;
|
|
6
6
|
const { isObjectRecord, parseJsonRecordLine } = jsonExtraction;
|
|
7
7
|
const { extractSettledPiFailure } = piExtraction;
|
|
8
|
+
const OMP_SDK_ERROR_CODES = new Map([
|
|
9
|
+
['invalid-request', 'request'],
|
|
10
|
+
['model-resolution', 'model'],
|
|
11
|
+
['model-fallback', 'model'],
|
|
12
|
+
['provider-auth', 'auth'],
|
|
13
|
+
['provider-rate-limit', 'rate-limit'],
|
|
14
|
+
['provider-timeout', 'timeout'],
|
|
15
|
+
['provider-error', 'provider'],
|
|
16
|
+
['schema-violation', 'schema'],
|
|
17
|
+
['cancelled', 'cancelled'],
|
|
18
|
+
['sdk-error', 'sdk'],
|
|
19
|
+
['cleanup-error', 'cleanup'],
|
|
20
|
+
['internal-error', 'internal'],
|
|
21
|
+
]);
|
|
8
22
|
function firstTruthyValue(...values) {
|
|
9
23
|
for (const value of values) {
|
|
10
24
|
if (value)
|
|
@@ -54,6 +68,63 @@ function opencodeFailureFromObject(value) {
|
|
|
54
68
|
const source = firstTruthyValue(recordField(recordField(error, 'data'), 'message'), recordField(error, 'message'), recordField(error, 'name'));
|
|
55
69
|
return { ...cliErrorDetail(source, 'Session error'), provider: 'opencode' };
|
|
56
70
|
}
|
|
71
|
+
function hasExactKeys(value, expected) {
|
|
72
|
+
return (Object.keys(value).length === expected.length &&
|
|
73
|
+
expected.every((key) => Object.prototype.hasOwnProperty.call(value, key)));
|
|
74
|
+
}
|
|
75
|
+
function isOmpSdkIdentity(value, nameField, expectedName, expectedVersion) {
|
|
76
|
+
return (isObjectRecord(value) &&
|
|
77
|
+
value[nameField] === expectedName &&
|
|
78
|
+
value.version === expectedVersion &&
|
|
79
|
+
hasExactKeys(value, [nameField, 'version']));
|
|
80
|
+
}
|
|
81
|
+
function ompSdkErrorMetadata(value) {
|
|
82
|
+
if (!isObjectRecord(value))
|
|
83
|
+
return null;
|
|
84
|
+
const { category, code, redacted, retryable } = value;
|
|
85
|
+
if (typeof code !== 'string' ||
|
|
86
|
+
typeof category !== 'string' ||
|
|
87
|
+
OMP_SDK_ERROR_CODES.get(code) !== category ||
|
|
88
|
+
typeof retryable !== 'boolean' ||
|
|
89
|
+
redacted !== true ||
|
|
90
|
+
!hasExactKeys(value, ['category', 'code', 'redacted', 'retryable'])) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return { category, code, retryable };
|
|
94
|
+
}
|
|
95
|
+
function ompProviderCategory(error) {
|
|
96
|
+
if (error.category === 'auth')
|
|
97
|
+
return 'authentication';
|
|
98
|
+
return error.retryable ? 'transient' : 'permanent';
|
|
99
|
+
}
|
|
100
|
+
function isOmpSdkRunId(value) {
|
|
101
|
+
return (typeof value === 'string' &&
|
|
102
|
+
value.length <= 128 &&
|
|
103
|
+
/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value));
|
|
104
|
+
}
|
|
105
|
+
function isOmpSdkErrorEnvelope(value) {
|
|
106
|
+
return (value.protocolVersion === 1 &&
|
|
107
|
+
value.type === 'error' &&
|
|
108
|
+
isOmpSdkRunId(value.runId) &&
|
|
109
|
+
hasExactKeys(value, ['backend', 'error', 'protocolVersion', 'runId', 'runtime', 'type']) &&
|
|
110
|
+
isOmpSdkIdentity(value.backend, 'id', 'omp-sdk', '17.2.1') &&
|
|
111
|
+
isOmpSdkIdentity(value.runtime, 'name', 'bun', '1.3.14'));
|
|
112
|
+
}
|
|
113
|
+
function ompSdkFailureFromObject(value) {
|
|
114
|
+
const error = ompSdkErrorMetadata(value.error);
|
|
115
|
+
if (error === null || !isOmpSdkErrorEnvelope(value))
|
|
116
|
+
return null;
|
|
117
|
+
const detail = `OMP SDK ${error.code} (${error.category})`;
|
|
118
|
+
return {
|
|
119
|
+
...cliErrorDetail(detail, 'OMP SDK turn failed'),
|
|
120
|
+
provider: 'omp',
|
|
121
|
+
providerCategory: ompProviderCategory(error),
|
|
122
|
+
providerClassification: {
|
|
123
|
+
retryable: error.retryable,
|
|
124
|
+
kind: error.retryable ? 'unknown-retryable' : 'permanent-pattern',
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
57
128
|
function failureFromProviderObject(value, providerName) {
|
|
58
129
|
if (!isObjectRecord(value))
|
|
59
130
|
return null;
|
|
@@ -65,6 +136,8 @@ function failureFromProviderObject(value, providerName) {
|
|
|
65
136
|
return geminiFailureFromObject(value);
|
|
66
137
|
if (providerName === 'opencode')
|
|
67
138
|
return opencodeFailureFromObject(value);
|
|
139
|
+
if (providerName === 'omp')
|
|
140
|
+
return ompSdkFailureFromObject(value);
|
|
68
141
|
return null;
|
|
69
142
|
}
|
|
70
143
|
function vertexModelFromResult(parsed, useVertex) {
|
|
@@ -12,6 +12,21 @@ const { MAX_CLI_ERROR_BYTES, cliErrorDetail } = errorDetail;
|
|
|
12
12
|
const { isObjectRecord, parseJsonRecordLine } = jsonExtraction;
|
|
13
13
|
const { extractSettledPiFailure } = piExtraction;
|
|
14
14
|
|
|
15
|
+
const OMP_SDK_ERROR_CODES = new Map([
|
|
16
|
+
['invalid-request', 'request'],
|
|
17
|
+
['model-resolution', 'model'],
|
|
18
|
+
['model-fallback', 'model'],
|
|
19
|
+
['provider-auth', 'auth'],
|
|
20
|
+
['provider-rate-limit', 'rate-limit'],
|
|
21
|
+
['provider-timeout', 'timeout'],
|
|
22
|
+
['provider-error', 'provider'],
|
|
23
|
+
['schema-violation', 'schema'],
|
|
24
|
+
['cancelled', 'cancelled'],
|
|
25
|
+
['sdk-error', 'sdk'],
|
|
26
|
+
['cleanup-error', 'cleanup'],
|
|
27
|
+
['internal-error', 'internal'],
|
|
28
|
+
]);
|
|
29
|
+
|
|
15
30
|
function firstTruthyValue(...values: readonly unknown[]): unknown {
|
|
16
31
|
for (const value of values) {
|
|
17
32
|
if (value) return value;
|
|
@@ -71,12 +86,97 @@ function opencodeFailureFromObject(value: JsonRecord): CliFailure | null {
|
|
|
71
86
|
return { ...cliErrorDetail(source, 'Session error'), provider: 'opencode' };
|
|
72
87
|
}
|
|
73
88
|
|
|
89
|
+
function hasExactKeys(value: JsonRecord, expected: readonly string[]): boolean {
|
|
90
|
+
return (
|
|
91
|
+
Object.keys(value).length === expected.length &&
|
|
92
|
+
expected.every((key) => Object.prototype.hasOwnProperty.call(value, key))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isOmpSdkIdentity(
|
|
97
|
+
value: unknown,
|
|
98
|
+
nameField: 'id' | 'name',
|
|
99
|
+
expectedName: string,
|
|
100
|
+
expectedVersion: string
|
|
101
|
+
): boolean {
|
|
102
|
+
return (
|
|
103
|
+
isObjectRecord(value) &&
|
|
104
|
+
value[nameField] === expectedName &&
|
|
105
|
+
value.version === expectedVersion &&
|
|
106
|
+
hasExactKeys(value, [nameField, 'version'])
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface OmpSdkErrorMetadata {
|
|
111
|
+
readonly category: string;
|
|
112
|
+
readonly code: string;
|
|
113
|
+
readonly retryable: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function ompSdkErrorMetadata(value: unknown): OmpSdkErrorMetadata | null {
|
|
117
|
+
if (!isObjectRecord(value)) return null;
|
|
118
|
+
const { category, code, redacted, retryable } = value;
|
|
119
|
+
if (
|
|
120
|
+
typeof code !== 'string' ||
|
|
121
|
+
typeof category !== 'string' ||
|
|
122
|
+
OMP_SDK_ERROR_CODES.get(code) !== category ||
|
|
123
|
+
typeof retryable !== 'boolean' ||
|
|
124
|
+
redacted !== true ||
|
|
125
|
+
!hasExactKeys(value, ['category', 'code', 'redacted', 'retryable'])
|
|
126
|
+
) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return { category, code, retryable };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function ompProviderCategory(
|
|
133
|
+
error: OmpSdkErrorMetadata
|
|
134
|
+
): NonNullable<CliFailure['providerCategory']> {
|
|
135
|
+
if (error.category === 'auth') return 'authentication';
|
|
136
|
+
return error.retryable ? 'transient' : 'permanent';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isOmpSdkRunId(value: unknown): value is string {
|
|
140
|
+
return (
|
|
141
|
+
typeof value === 'string' &&
|
|
142
|
+
value.length <= 128 &&
|
|
143
|
+
/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isOmpSdkErrorEnvelope(value: JsonRecord): boolean {
|
|
148
|
+
return (
|
|
149
|
+
value.protocolVersion === 1 &&
|
|
150
|
+
value.type === 'error' &&
|
|
151
|
+
isOmpSdkRunId(value.runId) &&
|
|
152
|
+
hasExactKeys(value, ['backend', 'error', 'protocolVersion', 'runId', 'runtime', 'type']) &&
|
|
153
|
+
isOmpSdkIdentity(value.backend, 'id', 'omp-sdk', '17.2.1') &&
|
|
154
|
+
isOmpSdkIdentity(value.runtime, 'name', 'bun', '1.3.14')
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function ompSdkFailureFromObject(value: JsonRecord): CliFailure | null {
|
|
159
|
+
const error = ompSdkErrorMetadata(value.error);
|
|
160
|
+
if (error === null || !isOmpSdkErrorEnvelope(value)) return null;
|
|
161
|
+
const detail = `OMP SDK ${error.code} (${error.category})`;
|
|
162
|
+
return {
|
|
163
|
+
...cliErrorDetail(detail, 'OMP SDK turn failed'),
|
|
164
|
+
provider: 'omp',
|
|
165
|
+
providerCategory: ompProviderCategory(error),
|
|
166
|
+
providerClassification: {
|
|
167
|
+
retryable: error.retryable,
|
|
168
|
+
kind: error.retryable ? 'unknown-retryable' : 'permanent-pattern',
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
74
173
|
function failureFromProviderObject(value: unknown, providerName: string): CliFailure | null {
|
|
75
174
|
if (!isObjectRecord(value)) return null;
|
|
76
175
|
if (providerName === 'claude') return claudeFailureFromObject(value);
|
|
77
176
|
if (providerName === 'codex') return codexFailureFromObject(value);
|
|
78
177
|
if (providerName === 'gemini') return geminiFailureFromObject(value);
|
|
79
178
|
if (providerName === 'opencode') return opencodeFailureFromObject(value);
|
|
179
|
+
if (providerName === 'omp') return ompSdkFailureFromObject(value);
|
|
80
180
|
return null;
|
|
81
181
|
}
|
|
82
182
|
|
|
@@ -16,6 +16,11 @@ export interface CliErrorDetail {
|
|
|
16
16
|
|
|
17
17
|
export interface CliFailure extends CliErrorDetail {
|
|
18
18
|
provider: string;
|
|
19
|
+
providerCategory?: 'authentication' | 'permanent' | 'transient';
|
|
20
|
+
providerClassification?: {
|
|
21
|
+
retryable: boolean;
|
|
22
|
+
kind: 'permanent-pattern' | 'unknown-retryable';
|
|
23
|
+
};
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export interface CliError {
|
|
@@ -73,8 +73,8 @@ function extractProviderFailure(output, providerName) {
|
|
|
73
73
|
const cliError = extractCliFailure(output, providerName);
|
|
74
74
|
if (!cliError)
|
|
75
75
|
return null;
|
|
76
|
-
const classification = classifyProviderFailure(providerName, cliError.error);
|
|
77
|
-
const category = categoryForProviderFailure(cliError.error, classification);
|
|
76
|
+
const classification = cliError.providerClassification ?? classifyProviderFailure(providerName, cliError.error);
|
|
77
|
+
const category = cliError.providerCategory ?? categoryForProviderFailure(cliError.error, classification);
|
|
78
78
|
return {
|
|
79
79
|
error: `Provider ${cliError.provider} failed (${category}; ${classification.kind})`,
|
|
80
80
|
provider: cliError.provider,
|
|
@@ -89,8 +89,10 @@ function extractProviderFailure(output: string, providerName: string): ProviderF
|
|
|
89
89
|
const cliError = extractCliFailure(output, providerName);
|
|
90
90
|
if (!cliError) return null;
|
|
91
91
|
|
|
92
|
-
const classification =
|
|
93
|
-
|
|
92
|
+
const classification =
|
|
93
|
+
cliError.providerClassification ?? classifyProviderFailure(providerName, cliError.error);
|
|
94
|
+
const category =
|
|
95
|
+
cliError.providerCategory ?? categoryForProviderFailure(cliError.error, classification);
|
|
94
96
|
return {
|
|
95
97
|
error: `Provider ${cliError.provider} failed (${category}; ${classification.kind})`,
|
|
96
98
|
provider: cliError.provider,
|
|
@@ -315,7 +315,7 @@ export const claudeAdapter: StructuredOutputRecoveryAdapter = {
|
|
|
315
315
|
displayName: 'Claude',
|
|
316
316
|
binary: 'claude',
|
|
317
317
|
adapterVersion: '1',
|
|
318
|
-
credentialEnvKeys: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
|
|
318
|
+
credentialEnvKeys: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'CLAUDE_API_KEY'],
|
|
319
319
|
modelCatalog: MODEL_CATALOG,
|
|
320
320
|
levelMapping: LEVEL_MAPPING,
|
|
321
321
|
defaultLevel: 'level2',
|
|
@@ -73,6 +73,7 @@ function detectCliFeatures(
|
|
|
73
73
|
supportsIgnoreUserConfig: !unknown && /--ignore-user-config\b/.test(help),
|
|
74
74
|
supportsIgnoreRules: !unknown && /--ignore-rules\b/.test(help),
|
|
75
75
|
supportsStrictConfig: !unknown && /--strict-config\b/.test(help),
|
|
76
|
+
supportsAddDir: !unknown && /--add-dir\b/.test(help),
|
|
76
77
|
unknown,
|
|
77
78
|
};
|
|
78
79
|
}
|
|
@@ -119,6 +120,14 @@ function addAutoApproveArgs(args: string[], options: BuildProviderCommandOptions
|
|
|
119
120
|
? 'danger-full-access'
|
|
120
121
|
: 'workspace-write';
|
|
121
122
|
args.push('--sandbox', sandboxMode, '--config', 'approval_policy="never"');
|
|
123
|
+
if (sandboxMode === 'workspace-write') {
|
|
124
|
+
args.push('--config', 'sandbox_workspace_write.network_access=true');
|
|
125
|
+
}
|
|
126
|
+
if (features.supportsAddDir) {
|
|
127
|
+
for (const directory of new Set(options.additionalWritableDirectories ?? [])) {
|
|
128
|
+
args.push('--add-dir', directory);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
@@ -129,6 +138,18 @@ function addSkipGitArgs(args: string[], options: BuildProviderCommandOptions): v
|
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
140
|
|
|
141
|
+
function codexExecAuthEnv(
|
|
142
|
+
authEnv: Readonly<Record<string, string>>
|
|
143
|
+
): Readonly<Record<string, string>> {
|
|
144
|
+
const openAiApiKey = authEnv.OPENAI_API_KEY;
|
|
145
|
+
if (openAiApiKey === undefined) return authEnv;
|
|
146
|
+
const { OPENAI_API_KEY: _openAiApiKey, ...rest } = authEnv;
|
|
147
|
+
return {
|
|
148
|
+
...rest,
|
|
149
|
+
CODEX_API_KEY: rest.CODEX_API_KEY ?? openAiApiKey,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
132
153
|
function applySchemaArgs(
|
|
133
154
|
args: string[],
|
|
134
155
|
cleanup: string[],
|
|
@@ -175,6 +196,19 @@ function collectWarnings(options: BuildProviderCommandOptions): WarningMetadata[
|
|
|
175
196
|
)
|
|
176
197
|
);
|
|
177
198
|
}
|
|
199
|
+
if (
|
|
200
|
+
options.autoApprove &&
|
|
201
|
+
(options.additionalWritableDirectories?.length ?? 0) > 0 &&
|
|
202
|
+
features.supportsAddDir === false
|
|
203
|
+
) {
|
|
204
|
+
warnings.push(
|
|
205
|
+
warning(
|
|
206
|
+
'codex',
|
|
207
|
+
'codex-add-dir',
|
|
208
|
+
'Codex CLI does not support --add-dir; Git worktree metadata outside cwd may be read-only.'
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
178
212
|
if (options.jsonSchema && features.supportsOutputSchema === false) {
|
|
179
213
|
warnings.push(
|
|
180
214
|
warning(
|
|
@@ -216,6 +250,7 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
|
|
|
216
250
|
}
|
|
217
251
|
const args: string[] = ['exec'];
|
|
218
252
|
const cleanup: string[] = [];
|
|
253
|
+
const authEnv = options.authEnv ?? {};
|
|
219
254
|
const resumeSessionId =
|
|
220
255
|
options.resumeSessionId && optionFeatures(options).supportsResume !== false
|
|
221
256
|
? options.resumeSessionId
|
|
@@ -244,7 +279,7 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
|
|
|
244
279
|
return commandSpec({
|
|
245
280
|
binary: 'codex',
|
|
246
281
|
args,
|
|
247
|
-
env:
|
|
282
|
+
env: codexExecAuthEnv(authEnv),
|
|
248
283
|
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
249
284
|
cleanup,
|
|
250
285
|
cleanupMetadata: cleanup.map((schemaFile) => ({
|
|
@@ -73,7 +73,8 @@ const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
|
|
|
73
73
|
const CREDENTIAL_ENV_KEYS = OMP_CREDENTIAL_ENV_KEYS;
|
|
74
74
|
|
|
75
75
|
const VERSION_TOKEN_PATTERN = /(?<![\w.])17\.2\.1(?![\w.])/;
|
|
76
|
-
const MODEL_ID_PATTERN =
|
|
76
|
+
const MODEL_ID_PATTERN =
|
|
77
|
+
/^(?=.{1,128}$)(?:@?[A-Za-z0-9][A-Za-z0-9._:/-]*|[a-z0-9][a-z0-9._-]*\/~[A-Za-z0-9][A-Za-z0-9._:/-]*)$/u;
|
|
77
78
|
|
|
78
79
|
function detectCliFeatures(helpText?: string | null, versionText?: string | null): OmpCliFeatures {
|
|
79
80
|
const help = helpText ?? '';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getProviderRegistryEntry } from '../provider-registry';
|
|
2
|
+
import type { PreparedEnvironmentPolicy } from '../types';
|
|
3
|
+
|
|
4
|
+
const MINIMAL_ENV_NAMES = [
|
|
5
|
+
'ALL_PROXY',
|
|
6
|
+
'HTTP_PROXY',
|
|
7
|
+
'HTTPS_PROXY',
|
|
8
|
+
'LANG',
|
|
9
|
+
'LC_ALL',
|
|
10
|
+
'NO_PROXY',
|
|
11
|
+
'PATH',
|
|
12
|
+
'SSL_CERT_DIR',
|
|
13
|
+
'SSL_CERT_FILE',
|
|
14
|
+
'TZ',
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
const ALLOWED_ENV_NAMES = new Set([
|
|
18
|
+
...MINIMAL_ENV_NAMES,
|
|
19
|
+
...(getProviderRegistryEntry('omp').configurationEnvKeys ?? []),
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function isOmpSdkEnvironmentName(name: string): boolean {
|
|
23
|
+
return ALLOWED_ENV_NAMES.has(name.toUpperCase());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ompSdkEnvironmentPolicy(
|
|
27
|
+
environment: NodeJS.ProcessEnv = process.env
|
|
28
|
+
): PreparedEnvironmentPolicy {
|
|
29
|
+
const values: Record<string, string> = {};
|
|
30
|
+
for (const name of ALLOWED_ENV_NAMES) {
|
|
31
|
+
const value = environment[name];
|
|
32
|
+
if (value !== undefined) values[name] = value;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
inherit: 'minimal',
|
|
36
|
+
values: Object.freeze(values),
|
|
37
|
+
};
|
|
38
|
+
}
|