@the-open-engine/zeroshot 6.39.0 → 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 +39 -1
- package/cli/index.js +90 -24
- 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/detached-startup.d.ts +1 -0
- package/lib/detached-startup.js +2 -1
- package/lib/git-remote-utils.js +22 -5
- package/lib/start-cluster-environment.d.ts +1 -0
- package/lib/start-cluster-run-options.d.ts +1 -0
- package/lib/start-cluster-run-options.js +1 -0
- package/npm-shrinkwrap.json +6 -8
- package/package.json +4 -3
- package/scripts/omp/runtime-identities.js +31 -18
- package/scripts/omp/runtime.js +2 -0
- package/scripts/rust-distribution.js +13 -31
- 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 +78 -105
- package/src/attach/socket-paths.js +1 -1
- package/src/attach/socket-paths.ts +1 -1
- package/src/copy-containment.js +191 -0
- package/src/copy-containment.ts +259 -0
- package/src/copy-worker.js +29 -30
- package/src/copy-worker.ts +43 -30
- package/src/foreground-benchmark-run.js +24 -12
- package/src/isolation-manager.js +59 -20
- package/src/legacy-lib/detached-startup.ts +3 -1
- package/src/legacy-lib/git-remote-utils.ts +23 -5
- package/src/legacy-lib/start-cluster-environment.ts +1 -0
- package/src/legacy-lib/start-cluster-run-options.ts +2 -0
- package/src/omp-session-verifier.js +8 -5
- package/src/orchestrator.js +55 -3
- package/src/pr-body-template.js +71 -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
|
@@ -325,10 +325,7 @@ function stagedLockDependencies(cargoLock, workspaceCargoToml) {
|
|
|
325
325
|
|
|
326
326
|
function stageCargoLock(cargoLock, version, workspaceCargoToml) {
|
|
327
327
|
const targetPackage = workspaceLockPackage(cargoLock);
|
|
328
|
-
let stagedPackage = targetPackage.text.replace(
|
|
329
|
-
/^(version = ")[^"]+(")$/m,
|
|
330
|
-
`$1${version}$2`
|
|
331
|
-
);
|
|
328
|
+
let stagedPackage = targetPackage.text.replace(/^(version = ")[^"]+(")$/m, `$1${version}$2`);
|
|
332
329
|
for (const dependency of stagedLockDependencies(cargoLock, workspaceCargoToml)) {
|
|
333
330
|
const dependencyPattern = new RegExp(
|
|
334
331
|
`^(\\s*")${escapeRegExp(dependency.name)}(?: [^"]+)?(",\\r?)$`,
|
|
@@ -339,10 +336,7 @@ function stageCargoLock(cargoLock, version, workspaceCargoToml) {
|
|
|
339
336
|
`RUST_VERSION_STAGE_FAILED: Cargo.lock zeroshot-rust entry has no ${dependency.name} dependency`
|
|
340
337
|
);
|
|
341
338
|
}
|
|
342
|
-
stagedPackage = stagedPackage.replace(
|
|
343
|
-
dependencyPattern,
|
|
344
|
-
`$1${dependency.reference}$2`
|
|
345
|
-
);
|
|
339
|
+
stagedPackage = stagedPackage.replace(dependencyPattern, `$1${dependency.reference}$2`);
|
|
346
340
|
}
|
|
347
341
|
return (
|
|
348
342
|
cargoLock.slice(0, targetPackage.start) +
|
|
@@ -359,10 +353,7 @@ function verifyStagedCargoLock(cargoLock, version, workspaceCargoToml) {
|
|
|
359
353
|
);
|
|
360
354
|
}
|
|
361
355
|
for (const dependency of stagedLockDependencies(cargoLock, workspaceCargoToml)) {
|
|
362
|
-
const dependencyPattern = new RegExp(
|
|
363
|
-
`^\\s*"${escapeRegExp(dependency.reference)}",\\r?$`,
|
|
364
|
-
'm'
|
|
365
|
-
);
|
|
356
|
+
const dependencyPattern = new RegExp(`^\\s*"${escapeRegExp(dependency.reference)}",\\r?$`, 'm');
|
|
366
357
|
if (!dependencyPattern.test(targetPackage.text)) {
|
|
367
358
|
throw new Error(
|
|
368
359
|
`${VERSION_ERROR}: Cargo.lock zeroshot-rust dependency ${dependency.name} is not coupled to ${dependency.version}`
|
|
@@ -400,8 +391,7 @@ function stageVersion(
|
|
|
400
391
|
function checkVersionCoupling(tag, cargoToml, cargoLock, workspaceCargoToml) {
|
|
401
392
|
const useRepositoryFiles = cargoToml === undefined;
|
|
402
393
|
const manifest =
|
|
403
|
-
cargoToml ??
|
|
404
|
-
fs.readFileSync(path.join(repositoryRoot, 'zeroshot-rust', 'Cargo.toml'), 'utf8');
|
|
394
|
+
cargoToml ?? fs.readFileSync(path.join(repositoryRoot, 'zeroshot-rust', 'Cargo.toml'), 'utf8');
|
|
405
395
|
const releaseVersion = normalizeVersion(tag);
|
|
406
396
|
const manifestVersion = cargoVersion(manifest);
|
|
407
397
|
if (releaseVersion !== manifestVersion) {
|
|
@@ -416,8 +406,7 @@ function checkVersionCoupling(tag, cargoToml, cargoLock, workspaceCargoToml) {
|
|
|
416
406
|
: undefined);
|
|
417
407
|
if (lock !== undefined) {
|
|
418
408
|
const workspace =
|
|
419
|
-
workspaceCargoToml ??
|
|
420
|
-
fs.readFileSync(path.join(repositoryRoot, 'Cargo.toml'), 'utf8');
|
|
409
|
+
workspaceCargoToml ?? fs.readFileSync(path.join(repositoryRoot, 'Cargo.toml'), 'utf8');
|
|
421
410
|
verifyStagedCargoLock(lock, releaseVersion, workspace);
|
|
422
411
|
}
|
|
423
412
|
return releaseVersion;
|
|
@@ -483,9 +472,7 @@ const SCRIPT_INSTALL_CONTRACTS = Object.freeze([
|
|
|
483
472
|
]);
|
|
484
473
|
|
|
485
474
|
function invokesRustDistribution(step) {
|
|
486
|
-
return (
|
|
487
|
-
typeof step.run === 'string' && RUST_DISTRIBUTION_INVOCATION.test(step.run)
|
|
488
|
-
);
|
|
475
|
+
return typeof step.run === 'string' && RUST_DISTRIBUTION_INVOCATION.test(step.run);
|
|
489
476
|
}
|
|
490
477
|
|
|
491
478
|
function checkScriptInstall(job, { jobName, installName, command, checkoutRef }) {
|
|
@@ -507,9 +494,7 @@ function checkScriptInstall(job, { jobName, installName, command, checkoutRef })
|
|
|
507
494
|
checkout.with.repository !== '${{ github.repository }}') ||
|
|
508
495
|
checkout.with?.ref !== checkoutRef
|
|
509
496
|
) {
|
|
510
|
-
failIntegrity(
|
|
511
|
-
`${jobName} must checkout expected current repository source at workspace root`
|
|
512
|
-
);
|
|
497
|
+
failIntegrity(`${jobName} must checkout expected current repository source at workspace root`);
|
|
513
498
|
}
|
|
514
499
|
const installIndex = job.steps.indexOf(install);
|
|
515
500
|
const checkoutIndex = job.steps.indexOf(checkout);
|
|
@@ -745,11 +730,12 @@ function hasValidSri(integrity) {
|
|
|
745
730
|
}
|
|
746
731
|
|
|
747
732
|
function checkScriptDependencies(packageManifest, packageLock) {
|
|
748
|
-
|
|
733
|
+
// js-yaml is a runtime dependency: lib/compose-utils.js requires it from the published package.
|
|
734
|
+
const directSpec = packageManifest.dependencies?.['js-yaml'];
|
|
749
735
|
if (typeof directSpec !== 'string' || directSpec.length === 0) {
|
|
750
|
-
failIntegrity('rust-distribution.js requires a direct js-yaml
|
|
736
|
+
failIntegrity('rust-distribution.js requires a direct js-yaml dependency');
|
|
751
737
|
}
|
|
752
|
-
const lockSpec = packageLock.packages?.['']?.
|
|
738
|
+
const lockSpec = packageLock.packages?.['']?.dependencies?.['js-yaml'];
|
|
753
739
|
if (lockSpec !== directSpec) {
|
|
754
740
|
failIntegrity('package-lock root js-yaml spec must match package.json');
|
|
755
741
|
}
|
|
@@ -773,12 +759,8 @@ function checkRepository(
|
|
|
773
759
|
shimTargets = JSON.parse(
|
|
774
760
|
fs.readFileSync(path.join(repositoryRoot, 'npm', 'zeroshot-rust', 'targets.json'), 'utf8')
|
|
775
761
|
),
|
|
776
|
-
packageManifest = JSON.parse(
|
|
777
|
-
|
|
778
|
-
),
|
|
779
|
-
packageLock = JSON.parse(
|
|
780
|
-
fs.readFileSync(path.join(repositoryRoot, 'package-lock.json'), 'utf8')
|
|
781
|
-
)
|
|
762
|
+
packageManifest = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package.json'), 'utf8')),
|
|
763
|
+
packageLock = JSON.parse(fs.readFileSync(path.join(repositoryRoot, 'package-lock.json'), 'utf8'))
|
|
782
764
|
) {
|
|
783
765
|
let document;
|
|
784
766
|
try {
|
|
@@ -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
|
+
}
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
PreparedProviderInvoke,
|
|
20
20
|
} from '../types';
|
|
21
21
|
import type { ProcessResult, ProcessRunnerOptions } from '../process-runner';
|
|
22
|
+
import { isOmpSdkEnvironmentName } from './sdk-environment-policy';
|
|
22
23
|
import { OmpSdkProcessRunnerError } from './sdk-process-error';
|
|
23
24
|
|
|
24
25
|
export { credentialPayload, redactDiagnostic } from './sdk-process-credentials';
|
|
@@ -33,19 +34,6 @@ const PRIVATE_ENV_NAMES = new Set([
|
|
|
33
34
|
'XDG_STATE_HOME',
|
|
34
35
|
'PI_CODING_AGENT_DIR',
|
|
35
36
|
]);
|
|
36
|
-
const MINIMAL_ENV_NAMES = new Set([
|
|
37
|
-
'ALL_PROXY',
|
|
38
|
-
'HTTP_PROXY',
|
|
39
|
-
'HTTPS_PROXY',
|
|
40
|
-
'LANG',
|
|
41
|
-
'LC_ALL',
|
|
42
|
-
'NO_PROXY',
|
|
43
|
-
'PATH',
|
|
44
|
-
'SSL_CERT_DIR',
|
|
45
|
-
'SSL_CERT_FILE',
|
|
46
|
-
'TZ',
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
37
|
export interface OmpSdkPreparedInvocation extends PreparedSingleAgentProviderCommand {
|
|
50
38
|
readonly invoke: PreparedProviderInvoke & {
|
|
51
39
|
readonly lane: 'spawn';
|
|
@@ -119,13 +107,13 @@ export function assertPrepared(
|
|
|
119
107
|
}
|
|
120
108
|
export function assertHostContainment(requirement: OmpSdkContainmentRequirement): void {
|
|
121
109
|
if (
|
|
122
|
-
requirement.mode !== 'host-process-tree' ||
|
|
110
|
+
(requirement.mode !== 'host-process-tree' && requirement.mode !== 'container') ||
|
|
123
111
|
process.platform !== 'linux' ||
|
|
124
112
|
(process.arch !== 'x64' && process.arch !== 'arm64')
|
|
125
113
|
) {
|
|
126
114
|
throw new OmpSdkProcessRunnerError(
|
|
127
115
|
'containment-error',
|
|
128
|
-
'OMP SDK
|
|
116
|
+
'OMP SDK spawn requires the packaged Linux subreaper/pidfd supervisor.'
|
|
129
117
|
);
|
|
130
118
|
}
|
|
131
119
|
}
|
|
@@ -228,7 +216,7 @@ export function childEnvironment(
|
|
|
228
216
|
for (const [name, value] of Object.entries(policy.values)) {
|
|
229
217
|
const normalized = name.toUpperCase();
|
|
230
218
|
if (
|
|
231
|
-
!
|
|
219
|
+
!isOmpSdkEnvironmentName(normalized) ||
|
|
232
220
|
PRIVATE_ENV_NAMES.has(normalized) ||
|
|
233
221
|
/(?:^|_)(?:AUTH|CREDENTIAL|KEY|PASSWORD|SECRET|TOKEN)(?:_|$)/i.test(name)
|
|
234
222
|
) {
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
OmpSdkProtocolCollector,
|
|
5
5
|
OmpSdkSidecarRequest,
|
|
6
6
|
} from './sdk-protocol';
|
|
7
|
-
import type { OmpSdkCleanupAttestation } from '../types';
|
|
7
|
+
import type { OmpSdkCleanupAttestation, OmpSdkContainmentRequirement } from '../types';
|
|
8
8
|
import { cancelledTerminal, type ChildOutcome } from './sdk-process-io';
|
|
9
9
|
import {
|
|
10
10
|
OmpSdkProcessRunnerError,
|
|
@@ -35,6 +35,7 @@ interface OmpSdkResultContext {
|
|
|
35
35
|
readonly secretValues: readonly string[];
|
|
36
36
|
readonly startedAt: number;
|
|
37
37
|
readonly externalAbort: () => void;
|
|
38
|
+
readonly containmentMode: OmpSdkContainmentRequirement['mode'];
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export async function collectOmpSdkProcessResult(
|
|
@@ -52,6 +53,7 @@ export async function collectOmpSdkProcessResult(
|
|
|
52
53
|
secretValues,
|
|
53
54
|
startedAt,
|
|
54
55
|
externalAbort,
|
|
56
|
+
containmentMode,
|
|
55
57
|
} = context;
|
|
56
58
|
let rootRemoved = false;
|
|
57
59
|
try {
|
|
@@ -113,7 +115,7 @@ export async function collectOmpSdkProcessResult(
|
|
|
113
115
|
await removePrivateRoot(privateRuntime);
|
|
114
116
|
rootRemoved = true;
|
|
115
117
|
const cleanupAttestation: OmpSdkCleanupAttestation = {
|
|
116
|
-
mode:
|
|
118
|
+
mode: containmentMode,
|
|
117
119
|
terminalBuffered: true,
|
|
118
120
|
descendantsReaped: true,
|
|
119
121
|
clean: true,
|
|
@@ -60,6 +60,10 @@ export async function spawnOmpSdkProcess(
|
|
|
60
60
|
const environment = childEnvironment(prepared.environmentPolicy, privateRuntime);
|
|
61
61
|
const { payload, secretValues } = credentialPayload(prepared.credentialNames, process.env);
|
|
62
62
|
const requestProvider = request.modelSelector.slice(0, request.modelSelector.indexOf('/'));
|
|
63
|
+
const requestContainmentMode =
|
|
64
|
+
request.executionContext === 'docker' || request.executionContext === 'benchmark'
|
|
65
|
+
? 'container'
|
|
66
|
+
: 'host-process-tree';
|
|
63
67
|
const sidecarPath = prepared.commandSpec.args[0] ?? '';
|
|
64
68
|
if (
|
|
65
69
|
!isAbsolute(prepared.commandSpec.binary) ||
|
|
@@ -70,7 +74,8 @@ export async function spawnOmpSdkProcess(
|
|
|
70
74
|
Object.keys(prepared.commandSpec.env).length !== 0 ||
|
|
71
75
|
prepared.semanticIdentity.requestedModelSelector !== request.modelSelector ||
|
|
72
76
|
prepared.semanticIdentity.reasoningEffort !== request.reasoningEffort ||
|
|
73
|
-
prepared.semanticIdentity.provider !== requestProvider
|
|
77
|
+
prepared.semanticIdentity.provider !== requestProvider ||
|
|
78
|
+
prepared.containmentRequirement.mode !== requestContainmentMode
|
|
74
79
|
) {
|
|
75
80
|
payload.fill(0);
|
|
76
81
|
throw new OmpSdkProcessRunnerError(
|
|
@@ -258,6 +263,7 @@ export async function spawnOmpSdkProcess(
|
|
|
258
263
|
secretValues,
|
|
259
264
|
startedAt,
|
|
260
265
|
externalAbort,
|
|
266
|
+
containmentMode: prepared.containmentRequirement.mode,
|
|
261
267
|
});
|
|
262
268
|
|
|
263
269
|
return {
|