sneakoscope 2.0.4 → 2.0.6

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 (120) hide show
  1. package/README.md +18 -11
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +78 -8
  8. package/dist/cli/install-helpers.js +23 -0
  9. package/dist/commands/codex-app.js +25 -3
  10. package/dist/commands/doctor.js +33 -4
  11. package/dist/commands/mad-sks.js +2 -2
  12. package/dist/core/agents/agent-orchestrator.js +22 -3
  13. package/dist/core/agents/agent-proof-evidence.js +59 -2
  14. package/dist/core/agents/agent-roster.js +35 -6
  15. package/dist/core/agents/agent-schema.js +1 -1
  16. package/dist/core/agents/agent-worker-pipeline.js +9 -1
  17. package/dist/core/agents/native-worker-backend-router.js +50 -10
  18. package/dist/core/agents/ollama-worker-config.js +164 -15
  19. package/dist/core/codex/codex-0-137-compat.js +119 -0
  20. package/dist/core/codex-app.js +124 -2
  21. package/dist/core/codex-control/codex-control-proof.js +4 -1
  22. package/dist/core/codex-control/codex-sdk-capability.js +1 -1
  23. package/dist/core/codex-control/codex-task-runner.js +329 -5
  24. package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
  25. package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
  26. package/dist/core/commands/local-model-command.js +65 -19
  27. package/dist/core/commands/naruto-command.js +124 -8
  28. package/dist/core/commands/run-command.js +1 -1
  29. package/dist/core/doctor/doctor-readiness-matrix.js +21 -2
  30. package/dist/core/fsx.js +1 -1
  31. package/dist/core/hooks-runtime.js +2 -233
  32. package/dist/core/init.js +8 -8
  33. package/dist/core/local-llm/local-llm-backpressure.js +20 -0
  34. package/dist/core/local-llm/local-llm-capability.js +29 -0
  35. package/dist/core/local-llm/local-llm-client.js +100 -0
  36. package/dist/core/local-llm/local-llm-config.js +6 -1
  37. package/dist/core/local-llm/local-llm-context-cache.js +21 -0
  38. package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
  39. package/dist/core/local-llm/local-llm-json-repair.js +52 -0
  40. package/dist/core/local-llm/local-llm-metrics.js +42 -0
  41. package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
  42. package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
  43. package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
  44. package/dist/core/local-llm/local-llm-scheduler.js +29 -0
  45. package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
  46. package/dist/core/local-llm/local-llm-smoke.js +83 -0
  47. package/dist/core/local-llm/local-llm-warmup.js +20 -0
  48. package/dist/core/local-llm/local-worker-eligibility.js +27 -0
  49. package/dist/core/naruto/hardware-capacity-probe.js +36 -0
  50. package/dist/core/naruto/naruto-active-pool.js +134 -0
  51. package/dist/core/naruto/naruto-backpressure.js +13 -0
  52. package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
  53. package/dist/core/naruto/naruto-finalizer.js +18 -0
  54. package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
  55. package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
  56. package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
  57. package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
  58. package/dist/core/naruto/naruto-role-policy.js +107 -0
  59. package/dist/core/naruto/naruto-verification-dag.js +42 -0
  60. package/dist/core/naruto/naruto-verification-pool.js +18 -0
  61. package/dist/core/naruto/naruto-work-graph.js +198 -0
  62. package/dist/core/naruto/naruto-work-item.js +40 -0
  63. package/dist/core/naruto/naruto-work-stealing.js +11 -0
  64. package/dist/core/naruto/resource-pressure-monitor.js +32 -0
  65. package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
  66. package/dist/core/pipeline/gpt-final-required.js +12 -0
  67. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  68. package/dist/core/ppt.js +31 -8
  69. package/dist/core/product-design-app-server.js +410 -0
  70. package/dist/core/product-design-plugin.js +139 -0
  71. package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
  72. package/dist/core/router/capability-card.js +13 -0
  73. package/dist/core/router/route-cache.js +3 -0
  74. package/dist/core/router/ultra-router.js +2 -1
  75. package/dist/core/routes.js +12 -12
  76. package/dist/core/version.js +1 -1
  77. package/dist/core/zellij/zellij-lane-runtime.js +2 -2
  78. package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
  79. package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
  80. package/dist/scripts/blackbox-command-import-smoke.js +10 -1
  81. package/dist/scripts/check-package-boundary.js +12 -3
  82. package/dist/scripts/codex-0-137-compat-check.js +27 -0
  83. package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
  84. package/dist/scripts/codex-plugin-list-json-check.js +8 -0
  85. package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
  86. package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
  87. package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
  88. package/dist/scripts/local-llm-cache-performance-check.js +10 -0
  89. package/dist/scripts/local-llm-capability-check.js +14 -0
  90. package/dist/scripts/local-llm-smoke-check.js +23 -0
  91. package/dist/scripts/local-llm-structured-output-check.js +11 -0
  92. package/dist/scripts/local-llm-throughput-check.js +10 -0
  93. package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
  94. package/dist/scripts/local-llm-warmup-check.js +11 -0
  95. package/dist/scripts/naruto-active-pool-check.js +39 -0
  96. package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
  97. package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
  98. package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
  99. package/dist/scripts/naruto-readonly-routing-check.js +116 -0
  100. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
  101. package/dist/scripts/naruto-role-distribution-check.js +23 -0
  102. package/dist/scripts/naruto-shadow-clone-swarm-check.js +13 -0
  103. package/dist/scripts/naruto-verification-pool-check.js +36 -0
  104. package/dist/scripts/naruto-work-graph-check.js +24 -0
  105. package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
  106. package/dist/scripts/product-design-auto-install-check.js +119 -0
  107. package/dist/scripts/product-design-plugin-routing-check.js +101 -0
  108. package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
  109. package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
  110. package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
  111. package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
  112. package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
  113. package/dist/scripts/release-parallel-check.js +16 -2
  114. package/dist/scripts/release-provenance-check.js +21 -0
  115. package/dist/scripts/release-real-check.js +5 -0
  116. package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
  117. package/package.json +36 -4
  118. package/schemas/local-llm/local-model-config.schema.json +74 -0
  119. package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
  120. package/schemas/naruto/naruto-work-graph.schema.json +22 -0
@@ -1,5 +1,6 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
- import { appendJsonl, ensureDir, nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { appendJsonl, ensureDir, nowIso, packageRoot, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
4
  import { validateJsonSchemaRecursive } from '../json-schema-validator.js';
4
5
  import { resolveCodexOutputSchema } from './codex-output-schemas.js';
5
6
  import { detectCodexSdkCapability } from './codex-sdk-capability.js';
@@ -12,6 +13,9 @@ import { recordCodexThread } from './codex-thread-registry.js';
12
13
  import { runWithCodexReliabilityShield } from './codex-reliability-shield.js';
13
14
  import { routeCodexTask } from '../router/ultra-router.js';
14
15
  import { writeUltraRouterProof } from '../router/router-proof.js';
16
+ import { readLocalModelConfig } from '../agents/ollama-worker-config.js';
17
+ import { runLocalLlmTask } from '../local-llm/local-llm-control-adapter.js';
18
+ import { detectPythonCodexSdkCapability, runPythonCodexSdkTask } from './python-codex-sdk-adapter.js';
15
19
  export async function runCodexTask(input) {
16
20
  const root = path.resolve(input.mutationLedgerRoot);
17
21
  await ensureDir(root);
@@ -19,13 +23,22 @@ export async function runCodexTask(input) {
19
23
  const routerDecision = routeCodexTask(input);
20
24
  const task = { ...input, tier: input.tier || routerDecision.tier, outputSchema: schema };
21
25
  await writeUltraRouterProof(root, { task, decision: routerDecision });
26
+ const selectedBackend = selectCodexControlBackend(task, routerDecision);
27
+ if (selectedBackend === 'local-llm')
28
+ return runLocalControlTask(root, task, schema, routerDecision);
29
+ if (selectedBackend === 'python-codex-sdk')
30
+ return runPythonControlTask(root, task, schema, routerDecision);
22
31
  const capability = await detectCodexSdkCapability();
23
32
  const sandbox = mapCodexSdkSandboxPolicy(task);
24
33
  const runtime = codexSdkRuntimePolicies(task);
34
+ const bundledCodex = resolveBundledCodexBinary();
35
+ if (bundledCodex && !runtime.env.env.SKS_PYTHON_CODEX_SDK_CODEX_BIN)
36
+ runtime.env.env.SKS_PYTHON_CODEX_SDK_CODEX_BIN = bundledCodex;
25
37
  if (runtime.env.env.HOME)
26
38
  await ensureDir(runtime.env.env.HOME);
27
39
  if (runtime.env.env.CODEX_HOME)
28
40
  await ensureDir(runtime.env.env.CODEX_HOME);
41
+ await ensurePythonCodexLbConfig(runtime.env.env, runtime.config);
29
42
  const fakeAllowed = fakeCodexSdkAllowed();
30
43
  const blockers = [
31
44
  ...(capability.ok || fakeAllowed ? [] : capability.blockers),
@@ -84,6 +97,7 @@ export async function runCodexTask(input) {
84
97
  const result = {
85
98
  ok: finalBlockers.length === 0,
86
99
  backend: 'codex-sdk',
100
+ backend_family: fakeAllowed ? 'fake' : 'remote-gpt',
87
101
  sdkThreadId: String(adapterResult?.sdkThreadId || ''),
88
102
  sdkRunId: adapterResult?.sdkRunId ? String(adapterResult.sdkRunId) : null,
89
103
  streamEventCount: events.length,
@@ -99,6 +113,8 @@ export async function runCodexTask(input) {
99
113
  translatedEventCount: translatedEvents.length
100
114
  };
101
115
  await recordCodexThread(root, {
116
+ backend: result.backend,
117
+ backend_family: result.backend_family,
102
118
  route: task.route,
103
119
  mission_id: task.missionId,
104
120
  work_item_id: task.workItemId || null,
@@ -126,7 +142,213 @@ export async function runCodexTask(input) {
126
142
  });
127
143
  return result;
128
144
  }
129
- function normalizeWorkerResult(value, input, blockers, structuredOutputValid) {
145
+ async function runPythonControlTask(root, task, schema, routerDecision) {
146
+ const capability = await detectPythonCodexSdkCapability();
147
+ const runtime = codexSdkRuntimePolicies(task);
148
+ if (runtime.env.env.HOME)
149
+ await ensureDir(runtime.env.env.HOME);
150
+ if (runtime.env.env.CODEX_HOME)
151
+ await ensureDir(runtime.env.env.CODEX_HOME);
152
+ const fakeAllowed = process.env.SKS_PYTHON_CODEX_SDK_FAKE === '1';
153
+ const adapterResult = capability.ok || fakeAllowed
154
+ ? await runPythonCodexSdkTask(task, { env: runtime.env.env, config: runtime.config })
155
+ : { ok: false, events: [], translatedEvents: [], finalResponse: '', threadId: '', turnId: '', blockers: capability.blockers, capability };
156
+ const events = Array.isArray(adapterResult.events) ? adapterResult.events : [];
157
+ const translatedEvents = Array.isArray(adapterResult.translatedEvents) ? adapterResult.translatedEvents : [];
158
+ for (const event of translatedEvents)
159
+ await appendJsonl(path.join(root, 'python-codex-sdk-events.jsonl'), event);
160
+ const structuredOutput = parseStructuredOutput(adapterResult.finalResponse || '');
161
+ const validation = structuredOutput ? validateJsonSchemaRecursive(structuredOutput, schema) : { ok: false, issues: ['structured_output_missing'] };
162
+ const finalBlockers = [
163
+ ...(adapterResult.blockers || []),
164
+ ...(events.length > 0 ? [] : ['python_codex_sdk_event_stream_missing']),
165
+ ...(validation.ok ? [] : ['python_codex_sdk_structured_output_invalid', ...validation.issues.map((issue) => `schema:${issue}`)])
166
+ ];
167
+ const workerResult = normalizeWorkerResult(structuredOutput, task, finalBlockers, validation.ok, 'python-codex-sdk');
168
+ const workerResultPath = path.join(root, 'python-codex-sdk-worker-result.json');
169
+ await writeJsonAtomic(workerResultPath, workerResult);
170
+ const patchEnvelopePath = Array.isArray(workerResult.patch_envelopes) && workerResult.patch_envelopes.length
171
+ ? path.join(root, 'python-codex-sdk-patch-envelope.json')
172
+ : null;
173
+ if (patchEnvelopePath) {
174
+ await writeJsonAtomic(patchEnvelopePath, {
175
+ schema: 'sks.python-codex-sdk-patch-envelope.v1',
176
+ generated_at: nowIso(),
177
+ ok: true,
178
+ envelope_count: workerResult.patch_envelopes.length,
179
+ envelopes: workerResult.patch_envelopes
180
+ });
181
+ }
182
+ const pythonSdkProofPath = path.join(root, 'python-codex-sdk-proof.json');
183
+ await writeJsonAtomic(pythonSdkProofPath, {
184
+ schema: 'sks.python-codex-sdk-proof.v1',
185
+ generated_at: nowIso(),
186
+ ok: finalBlockers.length === 0,
187
+ backend: 'python-codex-sdk',
188
+ backend_family: fakeAllowed ? 'fake' : 'python-sdk',
189
+ package_name: capability.package_name,
190
+ import_name: capability.import_name,
191
+ python_bin: capability.python_bin,
192
+ python_version: capability.python_version,
193
+ sandbox: task.sandboxPolicy,
194
+ thread_id: adapterResult.threadId || '',
195
+ turn_id: adapterResult.turnId || '',
196
+ stream_event_count: events.length,
197
+ structured_output_valid: validation.ok,
198
+ worker_result_path: workerResultPath,
199
+ blockers: finalBlockers
200
+ });
201
+ const result = {
202
+ ok: finalBlockers.length === 0,
203
+ backend: 'python-codex-sdk',
204
+ backend_family: fakeAllowed ? 'fake' : 'python-sdk',
205
+ sdkThreadId: String(adapterResult.threadId || ''),
206
+ sdkRunId: adapterResult.turnId ? String(adapterResult.turnId) : null,
207
+ streamEventCount: events.length,
208
+ structuredOutputValid: validation.ok,
209
+ workerResultPath,
210
+ patchEnvelopePath,
211
+ pythonSdkProofPath,
212
+ blockers: finalBlockers,
213
+ reliabilityShield: {},
214
+ ultraRouterDecision: routerDecision,
215
+ outputSchemaId: task.outputSchemaId,
216
+ finalResponse: adapterResult.finalResponse || '',
217
+ eventTypes: events.map((event) => String(event?.event || event?.type || 'unknown')),
218
+ translatedEventCount: translatedEvents.length
219
+ };
220
+ await recordCodexThread(root, {
221
+ backend: result.backend,
222
+ backend_family: result.backend_family,
223
+ route: task.route,
224
+ mission_id: task.missionId,
225
+ work_item_id: task.workItemId || null,
226
+ slot_id: task.slotId || null,
227
+ generation_index: task.generationIndex ?? null,
228
+ session_id: task.sessionId || null,
229
+ zellij_pane_id: task.zellijPaneId || null,
230
+ sdk_thread_id: result.sdkThreadId,
231
+ sdk_run_id: result.sdkRunId,
232
+ stream_event_count: result.streamEventCount,
233
+ output_schema_id: task.outputSchemaId,
234
+ structured_output_valid: result.structuredOutputValid,
235
+ worker_result_path: result.workerResultPath
236
+ });
237
+ await writeCodexControlProof(root, {
238
+ task,
239
+ result,
240
+ capability: capability,
241
+ sandbox: { ok: true, sandbox_policy: task.sandboxPolicy, python_sandbox: mapPythonSandbox(task.sandboxPolicy), blockers: [] },
242
+ envProof: { ...runtime.env.proof, python_bin: capability.python_bin, python_version: capability.python_version },
243
+ config: { ...runtime.config, backend: 'python-codex-sdk', package_name: capability.package_name, import_name: capability.import_name },
244
+ reliabilityShield: null,
245
+ routerDecision: routerDecision,
246
+ translatedEvents
247
+ });
248
+ return result;
249
+ }
250
+ async function runLocalControlTask(root, task, schema, routerDecision) {
251
+ const config = await readLocalModelConfig();
252
+ const adapterResult = await runLocalLlmTask(task, { config, outputSchema: schema });
253
+ for (const event of adapterResult.events || [])
254
+ await appendJsonl(path.join(root, 'local-llm-events.jsonl'), event);
255
+ const structuredOutput = adapterResult.structuredOutput;
256
+ const validation = structuredOutput ? validateJsonSchemaRecursive(structuredOutput, schema) : { ok: false, issues: ['structured_output_missing'] };
257
+ const finalBlockers = [
258
+ ...(adapterResult.blockers || []),
259
+ ...(Array.isArray(adapterResult.events) && adapterResult.events.length > 0 ? [] : ['local_llm_event_stream_missing']),
260
+ ...(validation.ok ? [] : ['local_llm_structured_output_invalid', ...validation.issues.map((issue) => `schema:${issue}`)])
261
+ ];
262
+ const workerResult = normalizeWorkerResult(structuredOutput, task, finalBlockers, validation.ok, 'local-llm');
263
+ const workerResultPath = path.join(root, 'local-llm-worker-result.json');
264
+ await writeJsonAtomic(workerResultPath, workerResult);
265
+ const patchEnvelopePath = Array.isArray(workerResult.patch_envelopes) && workerResult.patch_envelopes.length
266
+ ? path.join(root, 'local-llm-patch-envelope.json')
267
+ : null;
268
+ if (patchEnvelopePath) {
269
+ await writeJsonAtomic(patchEnvelopePath, {
270
+ schema: 'sks.local-llm-patch-envelope.v1',
271
+ generated_at: nowIso(),
272
+ ok: true,
273
+ envelope_count: workerResult.patch_envelopes.length,
274
+ envelopes: workerResult.patch_envelopes,
275
+ requires_gpt_final: true
276
+ });
277
+ }
278
+ const localLlmProofPath = path.join(root, 'local-llm-proof.json');
279
+ await writeJsonAtomic(localLlmProofPath, adapterResult.proof);
280
+ const result = {
281
+ ok: finalBlockers.length === 0,
282
+ backend: 'local-llm',
283
+ backend_family: 'local-llm',
284
+ sdkThreadId: '',
285
+ sdkRunId: null,
286
+ streamEventCount: adapterResult.events?.length || 0,
287
+ structuredOutputValid: validation.ok,
288
+ workerResultPath,
289
+ patchEnvelopePath,
290
+ localLlmProofPath,
291
+ blockers: finalBlockers,
292
+ reliabilityShield: {},
293
+ ultraRouterDecision: routerDecision,
294
+ outputSchemaId: task.outputSchemaId,
295
+ finalResponse: adapterResult.finalResponse || '',
296
+ eventTypes: (adapterResult.events || []).map((event) => String(event?.type || 'unknown')),
297
+ translatedEventCount: adapterResult.events?.length || 0,
298
+ localLlmRequestId: adapterResult.requestId
299
+ };
300
+ await recordCodexThread(root, {
301
+ backend: result.backend,
302
+ backend_family: result.backend_family,
303
+ route: task.route,
304
+ mission_id: task.missionId,
305
+ work_item_id: task.workItemId || null,
306
+ slot_id: task.slotId || null,
307
+ generation_index: task.generationIndex ?? null,
308
+ session_id: task.sessionId || null,
309
+ zellij_pane_id: task.zellijPaneId || null,
310
+ sdk_thread_id: null,
311
+ sdk_run_id: null,
312
+ local_llm_request_id: adapterResult.requestId,
313
+ stream_event_count: result.streamEventCount,
314
+ output_schema_id: task.outputSchemaId,
315
+ structured_output_valid: result.structuredOutputValid,
316
+ worker_result_path: result.workerResultPath
317
+ });
318
+ await writeCodexControlProof(root, {
319
+ task,
320
+ result,
321
+ capability: config.capability,
322
+ sandbox: { ok: task.sandboxPolicy !== 'full-access', sandbox_policy: task.sandboxPolicy, blockers: task.sandboxPolicy === 'full-access' ? ['local_llm_full_access_blocked'] : [] },
323
+ envProof: { provider: config.provider, endpoint: config.base_url },
324
+ config: { provider: config.provider, model: config.model, endpoint: config.base_url, status: config.status },
325
+ reliabilityShield: null,
326
+ routerDecision: routerDecision,
327
+ translatedEvents: adapterResult.events || []
328
+ });
329
+ return result;
330
+ }
331
+ function selectCodexControlBackend(input, routerDecision) {
332
+ const prefs = Array.isArray(input.backendPreference) ? input.backendPreference : [];
333
+ for (const pref of prefs) {
334
+ if (pref === 'python-codex-sdk')
335
+ return 'python-codex-sdk';
336
+ if (pref === 'codex-sdk')
337
+ return 'codex-sdk';
338
+ if (pref === 'local-llm' && input.tier === 'worker')
339
+ return 'local-llm';
340
+ }
341
+ if (input.localLlmPolicy?.mode === 'disabled')
342
+ return 'codex-sdk';
343
+ if (input.allowLocalLlm === true && input.tier === 'worker' && routerDecision?.selected_profile === 'local-llm-worker')
344
+ return 'local-llm';
345
+ if (input.allowLocalLlm === true && input.tier === 'worker' && input.localLlmPolicy?.mode === 'local_only')
346
+ return 'local-llm';
347
+ if (input.allowLocalLlm === true && input.tier === 'worker' && input.localLlmPolicy?.mode === 'local_preferred')
348
+ return 'local-llm';
349
+ return 'codex-sdk';
350
+ }
351
+ function normalizeWorkerResult(value, input, blockers, structuredOutputValid, backend = 'codex-sdk') {
130
352
  const status = blockers.length ? 'blocked' : normalizeStatus(value?.status);
131
353
  return {
132
354
  ...value,
@@ -135,9 +357,9 @@ function normalizeWorkerResult(value, input, blockers, structuredOutputValid) {
135
357
  session_id: String(value?.session_id || input.sessionId || input.workItemId || 'codex-sdk-session'),
136
358
  persona_id: String(value?.persona_id || value?.agent_id || input.slotId || 'codex-sdk-worker'),
137
359
  task_slice_id: String(value?.task_slice_id || input.workItemId || ''),
138
- backend: 'codex-sdk',
360
+ backend,
139
361
  status,
140
- summary: String(value?.summary || (blockers.length ? 'Codex SDK task blocked.' : 'Codex SDK task completed.')),
362
+ summary: String(value?.summary || (blockers.length ? `${backend} task blocked.` : `${backend} task completed.`)),
141
363
  findings: Array.isArray(value?.findings) ? value.findings : [],
142
364
  proposed_changes: Array.isArray(value?.proposed_changes) ? value.proposed_changes : [],
143
365
  changed_files: Array.isArray(value?.changed_files) ? value.changed_files : [],
@@ -145,7 +367,7 @@ function normalizeWorkerResult(value, input, blockers, structuredOutputValid) {
145
367
  artifacts: Array.isArray(value?.artifacts) ? value.artifacts : [],
146
368
  blockers,
147
369
  confidence: String(value?.confidence || (structuredOutputValid ? 'verified_partial' : 'blocked')),
148
- handoff_notes: String(value?.handoff_notes || 'Codex SDK Control Plane produced this worker result.'),
370
+ handoff_notes: String(value?.handoff_notes || `${backend} Control Plane produced this worker result.`),
149
371
  unverified: Array.isArray(value?.unverified) ? value.unverified : [],
150
372
  writes: Array.isArray(value?.writes) ? value.writes : [],
151
373
  patch_envelopes: Array.isArray(value?.patch_envelopes) ? value.patch_envelopes : [],
@@ -157,4 +379,106 @@ function normalizeWorkerResult(value, input, blockers, structuredOutputValid) {
157
379
  function normalizeStatus(value) {
158
380
  return value === 'failed' || value === 'blocked' || value === 'done' ? value : 'done';
159
381
  }
382
+ function mapPythonSandbox(value) {
383
+ if (value === 'workspace-write')
384
+ return 'workspace_write';
385
+ if (value === 'full-access')
386
+ return 'full_access';
387
+ return 'read_only';
388
+ }
389
+ function parseStructuredOutput(text) {
390
+ const trimmed = String(text || '').trim();
391
+ if (!trimmed)
392
+ return null;
393
+ try {
394
+ return JSON.parse(trimmed);
395
+ }
396
+ catch {
397
+ const start = trimmed.indexOf('{');
398
+ const end = trimmed.lastIndexOf('}');
399
+ if (start >= 0 && end > start) {
400
+ try {
401
+ return JSON.parse(trimmed.slice(start, end + 1));
402
+ }
403
+ catch { }
404
+ }
405
+ return null;
406
+ }
407
+ }
408
+ async function ensurePythonCodexLbConfig(env, config) {
409
+ const codexHome = env.CODEX_HOME;
410
+ const lbBaseUrl = normalizeCodexLbBaseUrl(env.CODEX_LB_BASE_URL);
411
+ if (!codexHome || !lbBaseUrl || !env.CODEX_LB_API_KEY)
412
+ return;
413
+ const model = String(config.model || env.SKS_CODEX_MODEL || env.CODEX_MODEL || 'gpt-5.5');
414
+ const text = [
415
+ `model = ${tomlQuote(model)}`,
416
+ 'model_provider = "codex-lb"',
417
+ 'service_tier = "fast"',
418
+ 'model_reasoning_effort = "minimal"',
419
+ 'approval_policy = "never"',
420
+ '',
421
+ '[model_providers.codex-lb]',
422
+ 'name = "OpenAI"',
423
+ `base_url = ${tomlQuote(lbBaseUrl)}`,
424
+ 'wire_api = "responses"',
425
+ 'env_key = "CODEX_LB_API_KEY"',
426
+ 'supports_websockets = true',
427
+ 'requires_openai_auth = false',
428
+ ''
429
+ ].join('\n');
430
+ await writeTextAtomic(path.join(codexHome, 'config.toml'), text);
431
+ }
432
+ function normalizeCodexLbBaseUrl(value) {
433
+ let host = String(value || '').trim();
434
+ if (!host)
435
+ return '';
436
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(host))
437
+ host = `https://${host}`;
438
+ host = host.replace(/\/+$/, '');
439
+ return /\/backend-api\/codex$/i.test(host) ? host : `${host}/backend-api/codex`;
440
+ }
441
+ function tomlQuote(value) {
442
+ return JSON.stringify(value);
443
+ }
444
+ function resolveBundledCodexBinary() {
445
+ const platform = process.platform;
446
+ const arch = process.arch;
447
+ const pkg = platform === 'darwin' && arch === 'arm64'
448
+ ? '@openai/codex-darwin-arm64'
449
+ : platform === 'darwin' && arch === 'x64'
450
+ ? '@openai/codex-darwin-x64'
451
+ : platform === 'linux' && arch === 'arm64'
452
+ ? '@openai/codex-linux-arm64'
453
+ : platform === 'linux' && arch === 'x64'
454
+ ? '@openai/codex-linux-x64'
455
+ : platform === 'win32' && arch === 'x64'
456
+ ? '@openai/codex-win32-x64'
457
+ : platform === 'win32' && arch === 'arm64'
458
+ ? '@openai/codex-win32-arm64'
459
+ : '';
460
+ if (!pkg)
461
+ return '';
462
+ const binary = platform === 'win32' ? 'codex.exe' : 'codex';
463
+ const candidates = [
464
+ path.join(packageRoot(), 'node_modules', pkg, 'vendor', nativeTargetTriple(platform, arch), 'bin', binary),
465
+ path.join(packageRoot(), 'node_modules', pkg, 'vendor', nativeTargetTriple(platform, arch), 'codex', binary)
466
+ ];
467
+ return candidates.find((candidate) => fs.existsSync(candidate)) || '';
468
+ }
469
+ function nativeTargetTriple(platform, arch) {
470
+ if (platform === 'darwin' && arch === 'arm64')
471
+ return 'aarch64-apple-darwin';
472
+ if (platform === 'darwin' && arch === 'x64')
473
+ return 'x86_64-apple-darwin';
474
+ if (platform === 'linux' && arch === 'arm64')
475
+ return 'aarch64-unknown-linux-musl';
476
+ if (platform === 'linux' && arch === 'x64')
477
+ return 'x86_64-unknown-linux-musl';
478
+ if (platform === 'win32' && arch === 'arm64')
479
+ return 'aarch64-pc-windows-msvc';
480
+ if (platform === 'win32' && arch === 'x64')
481
+ return 'x86_64-pc-windows-msvc';
482
+ return '';
483
+ }
160
484
  //# sourceMappingURL=codex-task-runner.js.map
@@ -0,0 +1,197 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { packageRoot, runProcess, which } from '../fsx.js';
4
+ import { translatePythonCodexSdkEvents } from './python-codex-sdk-event-translator.js';
5
+ export async function detectPythonCodexSdkCapability() {
6
+ const python = await resolvePythonCodexSdkPython();
7
+ if (!python.path)
8
+ return capability(false, null, '', ['python_missing'], null, 'Install Python >= 3.10 and install the Codex Python SDK.');
9
+ const pyOk = parsePythonVersion(python.versionText) >= 3.10;
10
+ const probes = [];
11
+ let detected = null;
12
+ if (pyOk) {
13
+ for (const candidate of PYTHON_CODEX_SDK_CANDIDATES) {
14
+ const importProbe = await runProcess(python.path, ['-c', `import ${candidate.importName}; print("ok")`], { timeoutMs: 5000, maxOutputBytes: 4096 })
15
+ .catch((err) => ({ code: 1, stdout: '', stderr: err.message || String(err) }));
16
+ probes.push({
17
+ package_name: candidate.packageName,
18
+ import_name: candidate.importName,
19
+ ok: importProbe.code === 0,
20
+ stderr: String(importProbe.stderr || '').slice(-500)
21
+ });
22
+ if (importProbe.code === 0) {
23
+ detected = candidate;
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ const blockers = [
29
+ ...(pyOk ? [] : ['python_version_below_3_10']),
30
+ ...(detected ? [] : ['python_codex_sdk_unavailable'])
31
+ ];
32
+ return capability(blockers.length === 0, python.path, python.versionText, blockers, detected, blockers.length ? setupAction(python.path) : null, probes);
33
+ }
34
+ export async function runPythonCodexSdkTask(input, opts = {}) {
35
+ const cap = await detectPythonCodexSdkCapability();
36
+ if (!cap.ok && process.env.SKS_PYTHON_CODEX_SDK_FAKE !== '1') {
37
+ return { ok: false, events: [], translatedEvents: [], finalResponse: '', blockers: cap.blockers, capability: cap };
38
+ }
39
+ const python = opts.pythonBin || cap.python_bin || await which('python3') || 'python3';
40
+ const request = {
41
+ schema: 'sks.python-codex-sdk-request.v1',
42
+ route: input.route,
43
+ thread_policy: input.requestedScopeContract?.resume_thread_id ? 'resume' : 'new',
44
+ sandbox: mapSandbox(input.sandboxPolicy),
45
+ cwd: input.cwd,
46
+ model: typeof opts.config?.model === 'string' ? opts.config.model : '',
47
+ model_reasoning_effort: typeof opts.config?.model_reasoning_effort === 'string' ? opts.config.model_reasoning_effort : 'minimal',
48
+ prompt: input.prompt,
49
+ output_schema: input.outputSchema || {}
50
+ };
51
+ const events = await runPythonRunner(python, request, opts.env);
52
+ const translatedEvents = translatePythonCodexSdkEvents(events);
53
+ const last = [...events].reverse().find((event) => event?.event === 'turn_completed');
54
+ const errors = events.filter((event) => event?.event === 'error').map((event) => String(event.message || 'python_codex_sdk_error'));
55
+ return {
56
+ ok: errors.length === 0 && Boolean(last),
57
+ events,
58
+ translatedEvents,
59
+ finalResponse: String(last?.final_response || ''),
60
+ threadId: String(events.find((event) => event?.thread_id)?.thread_id || ''),
61
+ turnId: String(last?.turn_id || ''),
62
+ blockers: errors,
63
+ capability: cap
64
+ };
65
+ }
66
+ function runPythonRunner(python, request, envOverride) {
67
+ const runner = path.join(packageRoot(), 'pytools', 'codex_sdk_runner.py');
68
+ return new Promise((resolve, reject) => {
69
+ const child = spawn(python, [runner], {
70
+ stdio: ['pipe', 'pipe', 'pipe'],
71
+ env: envOverride ? { ...process.env, ...envOverride } : process.env,
72
+ detached: process.platform !== 'win32'
73
+ });
74
+ const events = [];
75
+ let stderr = '';
76
+ let timedOut = false;
77
+ const timeoutMs = Number(process.env.SKS_PYTHON_CODEX_SDK_TIMEOUT_MS || 120000);
78
+ const timer = setTimeout(() => {
79
+ timedOut = true;
80
+ events.push({ event: 'error', retryable: true, message: `python_codex_sdk_timeout:${timeoutMs}` });
81
+ terminatePythonRunner(child);
82
+ }, timeoutMs);
83
+ timer.unref?.();
84
+ child.stdout.setEncoding('utf8');
85
+ child.stderr.setEncoding('utf8');
86
+ child.stdout.on('data', (chunk) => {
87
+ for (const line of String(chunk).split(/\n/).filter(Boolean)) {
88
+ try {
89
+ events.push(JSON.parse(line));
90
+ }
91
+ catch {
92
+ events.push({ event: 'error', retryable: false, message: `invalid_python_jsonl:${line.slice(0, 200)}` });
93
+ }
94
+ }
95
+ });
96
+ child.stderr.on('data', (chunk) => { stderr += String(chunk); });
97
+ child.on('error', reject);
98
+ child.on('close', () => {
99
+ clearTimeout(timer);
100
+ if (stderr.trim())
101
+ events.push({ event: 'stderr', message: stderr.slice(-1000) });
102
+ if (timedOut && !events.some((event) => String(event?.message || '').startsWith('python_codex_sdk_timeout:'))) {
103
+ events.push({ event: 'error', retryable: true, message: `python_codex_sdk_timeout:${timeoutMs}` });
104
+ }
105
+ resolve(events);
106
+ });
107
+ child.stdin.end(JSON.stringify(request));
108
+ });
109
+ }
110
+ function terminatePythonRunner(child) {
111
+ if (!child.pid)
112
+ return;
113
+ try {
114
+ if (process.platform !== 'win32')
115
+ process.kill(-child.pid, 'SIGTERM');
116
+ else
117
+ child.kill('SIGTERM');
118
+ }
119
+ catch {
120
+ try {
121
+ child.kill('SIGTERM');
122
+ }
123
+ catch { }
124
+ }
125
+ setTimeout(() => {
126
+ try {
127
+ if (process.platform !== 'win32')
128
+ process.kill(-child.pid, 'SIGKILL');
129
+ else
130
+ child.kill('SIGKILL');
131
+ }
132
+ catch { }
133
+ }, 5000).unref?.();
134
+ }
135
+ function mapSandbox(value) {
136
+ if (value === 'workspace-write')
137
+ return 'workspace_write';
138
+ if (value === 'full-access')
139
+ return 'full_access';
140
+ return 'read_only';
141
+ }
142
+ function parsePythonVersion(text) {
143
+ const match = text.match(/Python\s+(\d+)\.(\d+)/i);
144
+ return match ? Number(`${match[1]}.${match[2]}`) : 0;
145
+ }
146
+ async function resolvePythonCodexSdkPython() {
147
+ const requested = [
148
+ process.env.SKS_PYTHON_CODEX_SDK_PYTHON,
149
+ process.env.PYTHON
150
+ ].filter((value) => Boolean(value && value.trim()));
151
+ const candidates = [...requested, 'python3.12', 'python3.11', 'python3.10', 'python3', 'python'];
152
+ const seen = new Set();
153
+ const probes = [];
154
+ for (const candidate of candidates) {
155
+ const resolved = candidate.includes('/') ? candidate : await which(candidate);
156
+ if (!resolved || seen.has(resolved))
157
+ continue;
158
+ seen.add(resolved);
159
+ const version = await runProcess(resolved, ['--version'], { timeoutMs: 5000, maxOutputBytes: 4096 })
160
+ .catch((err) => ({ code: 1, stdout: '', stderr: err.message || String(err) }));
161
+ const versionText = `${version.stdout || ''}${version.stderr || ''}`.trim();
162
+ probes.push({ path: resolved, versionText, score: parsePythonVersion(versionText) });
163
+ }
164
+ const supported = probes.find((probe) => probe.score >= 3.10);
165
+ return supported || probes[0] || { path: null, versionText: '' };
166
+ }
167
+ const PYTHON_CODEX_SDK_CANDIDATES = [
168
+ { packageName: 'codex-app-server', importName: 'codex_app_server', source: 'developers.openai.com/codex/sdk' },
169
+ { packageName: 'openai-codex', importName: 'openai_codex', source: 'sks-2.0.5-directive' },
170
+ { packageName: 'openai-codex-sdk', importName: 'openai_codex_sdk', source: 'pypi-wheel' }
171
+ ];
172
+ function setupAction(pythonBin) {
173
+ return [
174
+ `Install the current Codex Python SDK in \`${pythonBin}\`.`,
175
+ 'Preferred official source install: from the Codex repository root run `cd sdk/python && python -m pip install -e .`.',
176
+ `If using the published wrapper package, run \`${pythonBin} -m pip install openai-codex-sdk\`.`,
177
+ `If your environment provides the directive package, run \`${pythonBin} -m pip install openai-codex\`.`
178
+ ].join(' ');
179
+ }
180
+ function capability(ok, pythonBin, versionText, blockers, detected, setupActionValue, probes = []) {
181
+ const selected = detected || PYTHON_CODEX_SDK_CANDIDATES[0] || { packageName: 'codex-app-server', importName: 'codex_app_server', source: 'developers.openai.com/codex/sdk' };
182
+ return {
183
+ schema: 'sks.python-codex-sdk-capability.v1',
184
+ ok,
185
+ python_bin: pythonBin,
186
+ python_version: versionText,
187
+ package_name: selected.packageName,
188
+ import_name: selected.importName,
189
+ source: selected.source,
190
+ supported_packages: PYTHON_CODEX_SDK_CANDIDATES.map((candidate) => candidate.packageName),
191
+ supported_imports: PYTHON_CODEX_SDK_CANDIDATES.map((candidate) => candidate.importName),
192
+ import_probes: probes,
193
+ setup_action: setupActionValue,
194
+ blockers
195
+ };
196
+ }
197
+ //# sourceMappingURL=python-codex-sdk-adapter.js.map
@@ -0,0 +1,14 @@
1
+ export function translatePythonCodexSdkEvents(events = []) {
2
+ return events.map((event, index) => ({
3
+ schema: 'sks.python-codex-sdk-event.v1',
4
+ index,
5
+ event_type: String(event?.event || 'unknown'),
6
+ thread_id: event?.thread_id ? String(event.thread_id) : null,
7
+ turn_id: event?.turn_id ? String(event.turn_id) : null,
8
+ status: event?.status ? String(event.status) : null,
9
+ retryable: event?.retryable === true,
10
+ message: event?.message ? String(event.message) : null,
11
+ final_response: event?.final_response ? String(event.final_response) : null
12
+ }));
13
+ }
14
+ //# sourceMappingURL=python-codex-sdk-event-translator.js.map