@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.
Files changed (88) hide show
  1. package/README.md +39 -1
  2. package/cli/index.js +90 -24
  3. package/cluster-templates/conductor-bootstrap.json +1 -1
  4. package/lib/agent-cli-provider/adapters/claude.js +1 -1
  5. package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
  6. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  7. package/lib/agent-cli-provider/adapters/codex.js +26 -1
  8. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  9. package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
  10. package/lib/agent-cli-provider/adapters/omp.js +1 -1
  11. package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
  12. package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts +4 -0
  13. package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts.map +1 -0
  14. package/lib/agent-cli-provider/omp/sdk-environment-policy.js +37 -0
  15. package/lib/agent-cli-provider/omp/sdk-environment-policy.js.map +1 -0
  16. package/lib/agent-cli-provider/omp/sdk-process-private-runtime.d.ts.map +1 -1
  17. package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js +4 -15
  18. package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js.map +1 -1
  19. package/lib/agent-cli-provider/omp/sdk-process-result.d.ts +2 -0
  20. package/lib/agent-cli-provider/omp/sdk-process-result.d.ts.map +1 -1
  21. package/lib/agent-cli-provider/omp/sdk-process-result.js +2 -2
  22. package/lib/agent-cli-provider/omp/sdk-process-result.js.map +1 -1
  23. package/lib/agent-cli-provider/omp/sdk-process-runner.d.ts.map +1 -1
  24. package/lib/agent-cli-provider/omp/sdk-process-runner.js +6 -1
  25. package/lib/agent-cli-provider/omp/sdk-process-runner.js.map +1 -1
  26. package/lib/agent-cli-provider/provider-registry.d.ts +3 -1
  27. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
  28. package/lib/agent-cli-provider/provider-registry.js +9 -2
  29. package/lib/agent-cli-provider/provider-registry.js.map +1 -1
  30. package/lib/agent-cli-provider/single-agent-runtime.d.ts +4 -1
  31. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/single-agent-runtime.js +18 -15
  33. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  34. package/lib/agent-cli-provider/types.d.ts +3 -0
  35. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  36. package/lib/agent-cli-provider/types.js.map +1 -1
  37. package/lib/cluster-worker/engine-adapter-common.js +18 -0
  38. package/lib/cluster-worker/engine-adapter.js +2 -1
  39. package/lib/detached-startup.d.ts +1 -0
  40. package/lib/detached-startup.js +2 -1
  41. package/lib/git-remote-utils.js +22 -5
  42. package/lib/start-cluster-environment.d.ts +1 -0
  43. package/lib/start-cluster-run-options.d.ts +1 -0
  44. package/lib/start-cluster-run-options.js +1 -0
  45. package/npm-shrinkwrap.json +6 -8
  46. package/package.json +4 -3
  47. package/scripts/omp/runtime-identities.js +31 -18
  48. package/scripts/omp/runtime.js +2 -0
  49. package/scripts/rust-distribution.js +13 -31
  50. package/src/agent/agent-lifecycle.js +17 -1
  51. package/src/agent/output-extraction-failures.js +73 -0
  52. package/src/agent/output-extraction-failures.ts +100 -0
  53. package/src/agent/output-extraction-types.ts +5 -0
  54. package/src/agent/provider-terminal-failure.js +2 -2
  55. package/src/agent/provider-terminal-failure.ts +4 -2
  56. package/src/agent-cli-provider/adapters/claude.ts +1 -1
  57. package/src/agent-cli-provider/adapters/codex.ts +36 -1
  58. package/src/agent-cli-provider/adapters/omp.ts +2 -1
  59. package/src/agent-cli-provider/omp/sdk-environment-policy.ts +38 -0
  60. package/src/agent-cli-provider/omp/sdk-process-private-runtime.ts +4 -16
  61. package/src/agent-cli-provider/omp/sdk-process-result.ts +4 -2
  62. package/src/agent-cli-provider/omp/sdk-process-runner.ts +7 -1
  63. package/src/agent-cli-provider/provider-registry.ts +10 -2
  64. package/src/agent-cli-provider/single-agent-runtime.ts +24 -15
  65. package/src/agent-cli-provider/types.ts +3 -0
  66. package/src/agents/git-pusher-template.js +78 -105
  67. package/src/attach/socket-paths.js +1 -1
  68. package/src/attach/socket-paths.ts +1 -1
  69. package/src/copy-containment.js +191 -0
  70. package/src/copy-containment.ts +259 -0
  71. package/src/copy-worker.js +29 -30
  72. package/src/copy-worker.ts +43 -30
  73. package/src/foreground-benchmark-run.js +24 -12
  74. package/src/isolation-manager.js +59 -20
  75. package/src/legacy-lib/detached-startup.ts +3 -1
  76. package/src/legacy-lib/git-remote-utils.ts +23 -5
  77. package/src/legacy-lib/start-cluster-environment.ts +1 -0
  78. package/src/legacy-lib/start-cluster-run-options.ts +2 -0
  79. package/src/omp-session-verifier.js +8 -5
  80. package/src/orchestrator.js +55 -3
  81. package/src/pr-body-template.js +71 -0
  82. package/src/preflight.js +31 -6
  83. package/src/providers/index.js +3 -3
  84. package/src/task-execution-context.js +22 -0
  85. package/src/watcher-prompt-channel.js +1 -1
  86. package/src/worktree-claude-config.js +25 -0
  87. package/src/worktree-claude-config.ts +28 -0
  88. 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
- const directSpec = packageManifest.devDependencies?.['js-yaml'];
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 devDependency');
736
+ failIntegrity('rust-distribution.js requires a direct js-yaml dependency');
751
737
  }
752
- const lockSpec = packageLock.packages?.['']?.devDependencies?.['js-yaml'];
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
- fs.readFileSync(path.join(repositoryRoot, 'package.json'), 'utf8')
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) => setTimeout(resolve, jitterMs));
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 = classifyProviderFailure(providerName, cliError.error);
93
- const category = categoryForProviderFailure(cliError.error, classification);
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 = /^@?[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/u;
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 host spawn requires the packaged Linux subreaper/pidfd supervisor; container invocations require the container spawn owner.'
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
- !MINIMAL_ENV_NAMES.has(normalized) ||
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: 'host-process-tree',
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 {