sneakoscope 2.0.17 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +135 -90
  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/commands/doctor.js +39 -1
  8. package/dist/commands/mad-sks.js +2 -0
  9. package/dist/commands/zellij.js +58 -1
  10. package/dist/core/agents/agent-effort-policy.js +7 -1
  11. package/dist/core/agents/agent-scheduler.js +32 -24
  12. package/dist/core/agents/native-cli-session-swarm.js +22 -2
  13. package/dist/core/codex-app/codex-app-handoff.js +98 -0
  14. package/dist/core/codex-app/codex-app-launcher.js +103 -0
  15. package/dist/core/codex-control/codex-0138-capability.js +102 -0
  16. package/dist/core/codex-control/codex-model-capabilities.js +62 -0
  17. package/dist/core/codex-control/codex-model-metadata.js +91 -0
  18. package/dist/core/codex-control/codex-sdk-config-policy.js +1 -1
  19. package/dist/core/codex-control/codex-task-runner.js +1 -1
  20. package/dist/core/codex-plugins/codex-plugin-cache.js +38 -0
  21. package/dist/core/codex-plugins/codex-plugin-diff.js +73 -0
  22. package/dist/core/codex-plugins/codex-plugin-json.js +176 -0
  23. package/dist/core/commands/mad-sks-command.js +8 -0
  24. package/dist/core/commands/naruto-command.js +30 -1
  25. package/dist/core/commands/qa-loop-command.js +147 -5
  26. package/dist/core/doctor/codex-0138-doctor.js +104 -0
  27. package/dist/core/doctor/doctor-readiness-matrix.js +11 -0
  28. package/dist/core/effort-orchestrator.js +9 -0
  29. package/dist/core/fsx.js +1 -1
  30. package/dist/core/hooks-runtime.js +6 -9
  31. package/dist/core/image/image-artifact-path-contract.js +101 -0
  32. package/dist/core/image/image-artifact-registry.js +33 -0
  33. package/dist/core/image-ux-review/imagegen-adapter.js +49 -17
  34. package/dist/core/mad-db/mad-db-result-lifecycle.js +71 -0
  35. package/dist/core/mcp/mcp-plugin-inventory.js +29 -0
  36. package/dist/core/mcp/mcp-server-policy.js +24 -0
  37. package/dist/core/qa-loop/qa-loop-app-handoff-confirmation.js +51 -0
  38. package/dist/core/qa-loop/qa-loop-budget-policy.js +37 -0
  39. package/dist/core/qa-loop.js +70 -3
  40. package/dist/core/release/release-gate-cache-v2.js +47 -5
  41. package/dist/core/usage/codex-account-usage.js +139 -0
  42. package/dist/core/version.js +1 -1
  43. package/dist/core/zellij/zellij-slot-column-anchor.js +16 -7
  44. package/dist/core/zellij/zellij-slot-pane-renderer.js +23 -2
  45. package/dist/core/zellij/zellij-slot-telemetry.js +65 -12
  46. package/dist/core/zellij/zellij-ui-mode.js +8 -1
  47. package/dist/core/zellij/zellij-update.js +307 -0
  48. package/dist/core/zellij/zellij-worker-pane-manager.js +211 -145
  49. package/dist/scripts/release-gate-existence-audit.js +5 -1
  50. package/package.json +46 -3
  51. package/schemas/codex-app/codex-app-handoff.schema.json +20 -0
  52. package/schemas/codex-plugins/codex-plugin-inventory.schema.json +32 -0
  53. package/schemas/image/image-artifact-path-contract.schema.json +32 -0
  54. package/schemas/usage/codex-account-usage.schema.json +27 -0
  55. package/dist/core/naruto/naruto-work-stealing.js +0 -11
  56. package/dist/core/zellij/zellij-right-column-layout-proof.js +0 -42
@@ -1,3 +1,4 @@
1
+ import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
1
2
  const XHIGH_SIGNAL_RE = /(frontier|autoresearch|novelty|hypothesis|falsif|forensic|from-chat-img|image\s*work\s*order|새로운\s*연구|가설|포렌식)/i;
2
3
  const HIGH_SIGNAL_RE = /(database|supabase|sql|migration|security|permission|mad|release|publish|deploy|architecture|policy|schema|hook|rollback|db|보안|배포|마이그레이션|데이터베이스|권한|릴리즈)/i;
3
4
  const MEDIUM_SIGNAL_RE = /(tmux|terminal|cli|tool(?:\s|-)?call|router|routing|orchestrat|pipeline|multi[-\s]?session|multi[-\s]?agent|lease|ledger|proof|검증|파이프라인|오케스트레이션|병렬|에이전트)/i;
@@ -26,6 +27,7 @@ export function decideAgentEffort(input = {}) {
26
27
  effort = 'high';
27
28
  reason = 'implementation_lane_capped_at_high';
28
29
  }
30
+ const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
29
31
  return {
30
32
  schema: 'sks.agent-effort-decision.v1',
31
33
  policy_version: 1,
@@ -33,6 +35,7 @@ export function decideAgentEffort(input = {}) {
33
35
  role,
34
36
  reasoning_effort: effort,
35
37
  model_reasoning_effort: effort,
38
+ model_effort_capability: modelCapability,
36
39
  reasoning_profile: reasoningProfileName(effort),
37
40
  service_tier: 'fast',
38
41
  reason,
@@ -73,6 +76,7 @@ export function decideNarutoCloneEffort(input = {}) {
73
76
  const writes = !readonly || /write|edit|route-local|workspace|patch|integrat/i.test(writePolicy) || hasActionTool;
74
77
  const toolUse = writes || NARUTO_ACTION_TOOL_RE.test(prompt);
75
78
  const effort = toolUse ? 'medium' : 'low';
79
+ const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
76
80
  return {
77
81
  schema: 'sks.agent-effort-decision.v1',
78
82
  policy_version: 1,
@@ -80,6 +84,7 @@ export function decideNarutoCloneEffort(input = {}) {
80
84
  role,
81
85
  reasoning_effort: effort,
82
86
  model_reasoning_effort: effort,
87
+ model_effort_capability: modelCapability,
83
88
  reasoning_profile: reasoningProfileName(effort),
84
89
  service_tier: 'fast',
85
90
  reason: toolUse ? 'naruto_tool_use_medium' : 'naruto_simple_no_tool_low',
@@ -107,7 +112,8 @@ export function buildAgentEffortPolicy(roster = {}) {
107
112
  policy_version: 1,
108
113
  dynamic: true,
109
114
  service_tier: 'fast',
110
- allowed_efforts: ['low', 'medium', 'high', 'xhigh'],
115
+ allowed_efforts: codexModelEffortCapability().advertised_efforts,
116
+ model_effort_capability: codexModelEffortCapability(),
111
117
  max_agents: roster.max_agents || 20,
112
118
  agent_count: roster.agent_count || decisions.length,
113
119
  concurrency: roster.concurrency || decisions.length,
@@ -144,30 +144,38 @@ export async function runAgentScheduler(input) {
144
144
  batch_id: batchId,
145
145
  meta: { launch_count: launches.length, active_count_before: active.size }
146
146
  }).catch(() => undefined);
147
+ // Telemetry appends run concurrently across launches (per-slot ordering
148
+ // preserved inside each async chain). Awaiting these file writes in
149
+ // series before each dispatch serialized worker launch by 2 disk writes
150
+ // per slot — with 20 slots that is 40 sequential appends before the last
151
+ // worker even started.
152
+ const dispatchTelemetryWrites = [];
147
153
  for (const launch of launches) {
148
154
  const { slot, openedSlot, generation, agent, workItem } = launch;
149
- await appendParallelRuntimeEvent(input.root, input.missionId, {
150
- event_type: 'slot_reserved',
151
- slot_id: slot.slot_id,
152
- generation_index: generation.generation_index,
153
- session_id: generation.session_id,
154
- pid: null,
155
- backend: 'scheduler',
156
- placement: 'unknown',
157
- batch_id: batchId,
158
- meta: { work_item_id: workItem.id }
159
- }).catch(() => undefined);
160
- await appendParallelRuntimeEvent(input.root, input.missionId, {
161
- event_type: 'worker_launch_invoked',
162
- slot_id: slot.slot_id,
163
- generation_index: generation.generation_index,
164
- session_id: generation.session_id,
165
- pid: null,
166
- backend: 'scheduler',
167
- placement: 'unknown',
168
- batch_id: batchId,
169
- meta: { work_item_id: workItem.id }
170
- }).catch(() => undefined);
155
+ dispatchTelemetryWrites.push((async () => {
156
+ await appendParallelRuntimeEvent(input.root, input.missionId, {
157
+ event_type: 'slot_reserved',
158
+ slot_id: slot.slot_id,
159
+ generation_index: generation.generation_index,
160
+ session_id: generation.session_id,
161
+ pid: null,
162
+ backend: 'scheduler',
163
+ placement: 'unknown',
164
+ batch_id: batchId,
165
+ meta: { work_item_id: workItem.id }
166
+ }).catch(() => undefined);
167
+ await appendParallelRuntimeEvent(input.root, input.missionId, {
168
+ event_type: 'worker_launch_invoked',
169
+ slot_id: slot.slot_id,
170
+ generation_index: generation.generation_index,
171
+ session_id: generation.session_id,
172
+ pid: null,
173
+ backend: 'scheduler',
174
+ placement: 'unknown',
175
+ batch_id: batchId,
176
+ meta: { work_item_id: workItem.id }
177
+ }).catch(() => undefined);
178
+ })());
171
179
  const promise = Promise.resolve()
172
180
  .then(() => input.launchSession({ agent, workItem, generation, slot: openedSlot, queue, state }))
173
181
  .then((result) => ({
@@ -212,14 +220,14 @@ export async function runAgentScheduler(input) {
212
220
  accumulateActiveSlotTime();
213
221
  active.set(generation.session_id, { slot_id: slot.slot_id, work_item_id: workItem.id, session_id: generation.session_id, promise });
214
222
  }
223
+ await Promise.all(dispatchTelemetryWrites);
215
224
  await appendAgentWorkQueueEvent(input.root, 'batch_work_items_dispatched', {
216
225
  batch_id: batchId,
217
226
  launch_count: launches.length,
218
227
  session_ids: launches.map((launch) => launch.generation.session_id),
219
228
  work_item_ids: launches.map((launch) => launch.workItem.id)
220
229
  });
221
- for (const launch of launches)
222
- await appendAgentWorkQueueEvent(input.root, 'work_item_dispatched', { work_item_id: launch.workItem.id, session_id: launch.generation.session_id, slot_id: launch.slot.slot_id });
230
+ await Promise.all(launches.map((launch) => appendAgentWorkQueueEvent(input.root, 'work_item_dispatched', { work_item_id: launch.workItem.id, session_id: launch.generation.session_id, slot_id: launch.slot.slot_id })));
223
231
  if (backfill) {
224
232
  const firstLaunch = launches[0];
225
233
  const refillLatencyMs = Math.max(0, Date.now() - backfill.closed_at_ms);
@@ -11,6 +11,7 @@ import { buildZellijSlotPaneCommand } from '../zellij/zellij-slot-pane-renderer.
11
11
  import { resolveZellijWorkerPaneUiMode } from '../zellij/zellij-ui-mode.js';
12
12
  import { appendZellijSlotTelemetry } from '../zellij/zellij-slot-telemetry.js';
13
13
  import { appendParallelRuntimeEvent } from './parallel-runtime-proof.js';
14
+ import { appendAgentMessage } from './agent-message-bus.js';
14
15
  export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
15
16
  export function createNativeCliSessionSwarmRecorder(root, input) {
16
17
  return new NativeCliSessionSwarmRecorder(root, input);
@@ -625,6 +626,15 @@ class NativeCliSessionSwarmRecorder {
625
626
  log_tail: input.logTail || '',
626
627
  blockers: input.blockers || []
627
628
  }).catch(() => undefined);
629
+ if (input.eventType === 'worker_completed' || input.eventType === 'worker_failed') {
630
+ await appendAgentMessage(this.root, {
631
+ from: String(ctx.agent?.slot_id || ctx.agent?.id || 'worker'),
632
+ session_id: ctx.agent?.session_id == null ? '' : String(ctx.agent.session_id),
633
+ to: 'orchestrator',
634
+ type: input.eventType,
635
+ body: input.logTail || input.eventType
636
+ }).catch(() => undefined);
637
+ }
628
638
  const parallelEvent = mapTelemetryToParallelEvent(input.eventType);
629
639
  if (parallelEvent) {
630
640
  await appendParallelRuntimeEvent(this.root, this.input.missionId, {
@@ -653,6 +663,12 @@ class NativeCliSessionSwarmRecorder {
653
663
  summary() {
654
664
  const closed = this.records.filter((row) => row.status === 'closed');
655
665
  const processIds = this.records.map((row) => row.pid).filter((pid) => Number.isFinite(Number(pid)));
666
+ // Both pane-backed primitives count as zellij pane worker sessions: the
667
+ // worker command can run inside the pane (full-debug) or headless behind a
668
+ // live slot renderer pane (compact-slots default). Counting only the
669
+ // former under-reported pane sessions as 0 in the default UI mode.
670
+ const paneBackedRecords = this.records.filter((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane'
671
+ || row.scaling_primitive === 'native_cli_process_with_zellij_slot_renderer');
656
672
  return {
657
673
  schema: NATIVE_CLI_SESSION_SWARM_SCHEMA,
658
674
  generated_at: nowIso(),
@@ -660,8 +676,12 @@ class NativeCliSessionSwarmRecorder {
660
676
  mission_id: this.input.missionId,
661
677
  route: this.input.route,
662
678
  backend: this.input.backend,
663
- scaling_primitive: this.records.some((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane') ? 'native_cli_process_in_zellij_worker_pane' : 'native_cli_process',
664
- zellij_pane_worker_sessions: this.records.filter((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane').length,
679
+ scaling_primitive: this.records.some((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane')
680
+ ? 'native_cli_process_in_zellij_worker_pane'
681
+ : paneBackedRecords.length
682
+ ? 'native_cli_process_with_zellij_slot_renderer'
683
+ : 'native_cli_process',
684
+ zellij_pane_worker_sessions: paneBackedRecords.length,
665
685
  requested_agents: this.input.requestedAgents,
666
686
  target_active_slots: this.input.targetActiveSlots,
667
687
  spawned_worker_process_count: this.records.length,
@@ -0,0 +1,98 @@
1
+ import path from 'node:path';
2
+ import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
3
+ import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ import { attemptCodexAppLaunch } from './codex-app-launcher.js';
5
+ export function buildCodexAppHandoffPrompt(request) {
6
+ return [
7
+ 'SKS Codex Desktop /app handoff request',
8
+ `Mission: ${request.mission_id}`,
9
+ `Route: ${request.route}`,
10
+ `Reason: ${request.reason}`,
11
+ `Workspace: ${request.workspace_path}`,
12
+ request.thread_ref ? `Thread: ${request.thread_ref}` : '',
13
+ '',
14
+ 'Artifacts:',
15
+ ...(request.artifacts || []).map((artifact) => `- ${artifact}`),
16
+ '',
17
+ 'Prompt:',
18
+ request.prompt,
19
+ '',
20
+ 'Operator instruction: open Codex Desktop with `codex /app` and continue this mission using the artifacts above. Do not treat this handoff artifact as web UI verification evidence.'
21
+ ].filter((line) => line !== '').join('\n');
22
+ }
23
+ export async function runCodexAppHandoff(root, request) {
24
+ const capability = await detectCodex0138Capability();
25
+ const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
26
+ const desktopSupported = capability.supports_app_handoff === true && platformSupported;
27
+ const launchMode = request.launch_mode || 'artifact-only';
28
+ const dir = path.join(root, '.sneakoscope', 'missions', request.mission_id, 'qa-loop');
29
+ const artifactPath = path.join(dir, 'app-handoff.json');
30
+ const promptArtifactPath = path.join(dir, 'app-handoff-prompt.md');
31
+ const blockers = [
32
+ ...(capability.supports_app_handoff ? [] : ['codex_0_138_app_handoff_unavailable']),
33
+ ...(platformSupported ? [] : ['codex_app_handoff_platform_unsupported'])
34
+ ];
35
+ const prompt = buildCodexAppHandoffPrompt(request);
36
+ await writeTextAtomic(promptArtifactPath, prompt);
37
+ const launchAttempt = desktopSupported
38
+ ? await attemptCodexAppLaunch({
39
+ cwd: root,
40
+ promptArtifactPath,
41
+ mode: launchMode,
42
+ timeoutMs: 3000
43
+ })
44
+ : await attemptCodexAppLaunch({
45
+ cwd: root,
46
+ promptArtifactPath,
47
+ mode: 'artifact-only',
48
+ timeoutMs: 3000
49
+ });
50
+ const status = request.require_desktop && !desktopSupported
51
+ ? 'blocked_for_desktop_review'
52
+ : request.require_desktop && launchMode === 'attempt-launch' && launchAttempt.attempted && !launchAttempt.launched
53
+ ? 'blocked_for_desktop_review'
54
+ : desktopSupported && launchAttempt.launched
55
+ ? 'launched_pending_confirmation'
56
+ : desktopSupported
57
+ ? 'pending'
58
+ : 'skipped';
59
+ const result = {
60
+ schema: 'sks.codex-app-handoff-result.v1',
61
+ ok: request.require_desktop ? status !== 'blocked_for_desktop_review' : true,
62
+ attempted: launchAttempt.attempted,
63
+ launched: launchAttempt.launched,
64
+ status,
65
+ codex_0138_capability: capability,
66
+ command_line: launchAttempt.command_line,
67
+ launch_attempt: launchAttempt,
68
+ confirmation_required: request.require_desktop,
69
+ desktop_handoff_supported: desktopSupported,
70
+ fallback_reason: desktopSupported
71
+ ? launchAttempt.fallback_reason || 'desktop_handoff_pending_operator_confirmation'
72
+ : blockers.join(';') || null,
73
+ artifact_path: artifactPath,
74
+ prompt_artifact_path: promptArtifactPath,
75
+ blockers: request.require_desktop ? [...blockers, ...(status === 'blocked_for_desktop_review' ? launchAttempt.blockers : [])] : []
76
+ };
77
+ await writeJsonAtomic(artifactPath, {
78
+ ...result,
79
+ request,
80
+ operator_instruction: {
81
+ open: 'codex /app',
82
+ prompt_artifact: path.relative(root, promptArtifactPath),
83
+ created_at: nowIso()
84
+ }
85
+ });
86
+ return result;
87
+ }
88
+ export function qaLoopShouldRequestAppHandoff(input = {}) {
89
+ const args = input.args || [];
90
+ return args.includes('--app-handoff')
91
+ || process.env.SKS_QA_LOOP_APP_HANDOFF === '1'
92
+ || input.visualArtifactsPresent === true
93
+ || input.zellijUiBlocked === true
94
+ || input.pluginAppTemplateUnavailable === true
95
+ || input.userRequestedDesktopReview === true
96
+ || input.uiRequired === true && process.env.SKS_QA_LOOP_APP_HANDOFF_FOR_VISUAL === '1';
97
+ }
98
+ //# sourceMappingURL=codex-app-handoff.js.map
@@ -0,0 +1,103 @@
1
+ import { findCodexBinary } from '../codex-adapter.js';
2
+ import { runProcess } from '../fsx.js';
3
+ export async function attemptCodexAppLaunch(input) {
4
+ const platform = process.platform;
5
+ const timeoutMs = Math.max(1, Math.min(Number(input.timeoutMs || 3000), 3000));
6
+ const codexBin = input.codexBin || await findCodexBinary();
7
+ const commandLine = [codexBin || 'codex', '/app'];
8
+ if (input.mode === 'artifact-only') {
9
+ return launchAttempt({
10
+ attempted: false,
11
+ launched: false,
12
+ platform,
13
+ mode: input.mode,
14
+ command_line: commandLine,
15
+ exit_code: null,
16
+ fallback_reason: 'artifact_only_mode',
17
+ blockers: []
18
+ });
19
+ }
20
+ const platformSupported = platform === 'darwin' || platform === 'win32';
21
+ if (!platformSupported) {
22
+ return launchAttempt({
23
+ attempted: false,
24
+ launched: false,
25
+ platform,
26
+ mode: input.mode,
27
+ command_line: commandLine,
28
+ exit_code: null,
29
+ fallback_reason: 'unsupported_platform_artifact_only_fallback',
30
+ blockers: ['codex_app_handoff_platform_unsupported']
31
+ });
32
+ }
33
+ if (!codexBin) {
34
+ return launchAttempt({
35
+ attempted: false,
36
+ launched: false,
37
+ platform,
38
+ mode: input.mode,
39
+ command_line: commandLine,
40
+ exit_code: null,
41
+ fallback_reason: 'codex_cli_missing_artifact_only_fallback',
42
+ blockers: ['codex_cli_missing']
43
+ });
44
+ }
45
+ if (process.env.SKS_CODEX_APP_LAUNCH_FAKE === '1') {
46
+ const launched = process.env.SKS_CODEX_APP_LAUNCH_FAKE_LAUNCHED !== '0';
47
+ return launchAttempt({
48
+ attempted: true,
49
+ launched,
50
+ platform,
51
+ mode: input.mode,
52
+ command_line: commandLine,
53
+ exit_code: launched ? 0 : 1,
54
+ stdout_tail: launched ? 'fake codex /app launched' : '',
55
+ stderr_tail: launched ? '' : 'fake launch failed',
56
+ fallback_reason: launched ? null : 'fake_launch_failed',
57
+ blockers: launched ? [] : ['codex_app_launch_failed']
58
+ });
59
+ }
60
+ const result = await runProcess(codexBin, ['/app'], {
61
+ cwd: input.cwd,
62
+ timeoutMs,
63
+ maxOutputBytes: 32 * 1024,
64
+ input: `Continue the SKS mission using this prompt artifact:\n${input.promptArtifactPath}\n`
65
+ }).catch((err) => ({
66
+ code: -1,
67
+ stdout: '',
68
+ stderr: err instanceof Error ? err.message : String(err),
69
+ timedOut: false
70
+ }));
71
+ const code = typeof result.code === 'number' ? result.code : null;
72
+ const stdout = String(result.stdout || '');
73
+ const stderr = String(result.stderr || '');
74
+ const markerLaunched = /(?:handoff|desktop|app).*?(?:launched|opened|ready)|(?:launched|opened).*?(?:handoff|desktop|app)/i.test(`${stdout}\n${stderr}`);
75
+ const launched = code === 0 || markerLaunched;
76
+ const timedOut = result.timedOut === true || code === 124;
77
+ const fallbackReason = launched
78
+ ? null
79
+ : timedOut
80
+ ? 'codex_app_handoff_interactive_or_timed_out_artifact_only_fallback'
81
+ : 'codex_app_launch_failed_artifact_only_fallback';
82
+ return launchAttempt({
83
+ attempted: true,
84
+ launched,
85
+ platform,
86
+ mode: input.mode,
87
+ command_line: commandLine,
88
+ exit_code: code,
89
+ stdout_tail: stdout.slice(-4000),
90
+ stderr_tail: stderr.slice(-4000),
91
+ fallback_reason: fallbackReason,
92
+ blockers: launched ? [] : [timedOut ? 'codex_app_launch_interactive_or_timeout' : 'codex_app_launch_failed']
93
+ });
94
+ }
95
+ function launchAttempt(input) {
96
+ return {
97
+ schema: 'sks.codex-app-launch-attempt.v1',
98
+ stdout_tail: input.stdout_tail || '',
99
+ stderr_tail: input.stderr_tail || '',
100
+ ...input
101
+ };
102
+ }
103
+ //# sourceMappingURL=codex-app-launcher.js.map
@@ -0,0 +1,102 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { compareSemverLike, parseCodexVersionText } from '../codex-compat/codex-version-policy.js';
4
+ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
5
+ export async function detectCodex0138Capability(input = {}) {
6
+ const fake = process.env.SKS_CODEX_0138_FAKE === '1';
7
+ const codexBin = fake
8
+ ? input.codexBin || process.env.CODEX_BIN || 'codex'
9
+ : input.codexBin || process.env.CODEX_BIN || await findCodexBinary();
10
+ const versionText = fake
11
+ ? String(process.env.SKS_CODEX_VERSION_FAKE || 'codex-cli 0.138.0')
12
+ : await readCodexVersionText(codexBin);
13
+ const parsed = parseCodexVersion(versionText);
14
+ const atLeast138 = Boolean(parsed && semverGte(parsed, '0.138.0'));
15
+ const probeMode = process.env.SKS_CODEX_0138_PROBE === '1' ? 'feature-probe' : 'version-only';
16
+ const featureProbeResults = probeMode === 'feature-probe'
17
+ ? await probeCodex0138Features(codexBin, { fake })
18
+ : {
19
+ plugin_json: 'skipped',
20
+ app_handoff_platform: 'skipped',
21
+ image_path_exposure_contract: 'sks-enforced'
22
+ };
23
+ const pluginJsonOk = atLeast138 && (probeMode === 'version-only' || featureProbeResults.plugin_json !== 'failed');
24
+ const appHandoffOk = atLeast138 && (probeMode === 'version-only' || featureProbeResults.app_handoff_platform === 'passed');
25
+ const blockers = [
26
+ ...(!codexBin ? ['codex_cli_missing'] : []),
27
+ ...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features']),
28
+ ...(probeMode === 'feature-probe' && featureProbeResults.plugin_json === 'failed' ? ['codex_plugin_json_probe_failed'] : [])
29
+ ];
30
+ return {
31
+ schema: 'sks.codex-0138-capability.v1',
32
+ ok: atLeast138 && blockers.length === 0,
33
+ probe_mode: probeMode,
34
+ codex_bin: codexBin || null,
35
+ version_text: versionText || null,
36
+ parsed_version: parsed,
37
+ supports_app_handoff: appHandoffOk,
38
+ supports_plugin_json: pluginJsonOk,
39
+ supports_image_path_exposure: atLeast138,
40
+ supports_model_defined_efforts: atLeast138,
41
+ supports_app_server_token_usage: atLeast138,
42
+ supports_v2_pat_auth: atLeast138,
43
+ supports_oauth_mcp_prerefresh: atLeast138,
44
+ feature_probe_results: featureProbeResults,
45
+ blockers
46
+ };
47
+ }
48
+ export async function writeCodex0138CapabilityArtifacts(root, input = {}) {
49
+ const capability = await detectCodex0138Capability({ codexBin: input.codexBin || null });
50
+ const report = { ...capability, generated_at: nowIso() };
51
+ const rootArtifact = path.join(root, '.sneakoscope', 'codex-0138-capability.json');
52
+ await writeJsonAtomic(rootArtifact, report);
53
+ let missionArtifact = null;
54
+ if (input.missionId) {
55
+ missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0138-capability.json');
56
+ await writeJsonAtomic(missionArtifact, report);
57
+ }
58
+ return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
59
+ }
60
+ export function parseCodexVersion(text) {
61
+ return parseCodexVersionText(text);
62
+ }
63
+ export function semverGte(actual, minimum) {
64
+ return compareSemverLike(actual, minimum) >= 0;
65
+ }
66
+ async function readCodexVersionText(codexBin) {
67
+ if (!codexBin)
68
+ return null;
69
+ const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
70
+ code: 1,
71
+ stdout: '',
72
+ stderr: err?.message || String(err)
73
+ }));
74
+ const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
75
+ return result.code === 0 ? text : text || null;
76
+ }
77
+ async function probeCodex0138Features(codexBin, opts = {}) {
78
+ if (opts.fake) {
79
+ return {
80
+ plugin_json: process.env.SKS_CODEX_0138_FAKE_PLUGIN_JSON_FAIL === '1' ? 'failed' : 'passed',
81
+ app_handoff_platform: process.platform === 'darwin' || process.platform === 'win32' ? 'passed' : 'failed',
82
+ image_path_exposure_contract: 'sks-enforced'
83
+ };
84
+ }
85
+ const timeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0138_PROBE_TIMEOUT_MS || 3000) || 3000);
86
+ const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
87
+ if (!codexBin) {
88
+ return {
89
+ plugin_json: 'failed',
90
+ app_handoff_platform: platformSupported ? 'skipped' : 'failed',
91
+ image_path_exposure_contract: 'sks-enforced'
92
+ };
93
+ }
94
+ const list = await runProcess(codexBin, ['plugin', 'list', '--json'], { timeoutMs, maxOutputBytes: 64 * 1024 }).catch(() => ({ code: 1 }));
95
+ const detailHelp = await runProcess(codexBin, ['plugin', 'detail', '--help'], { timeoutMs, maxOutputBytes: 64 * 1024 }).catch(() => ({ code: 1 }));
96
+ return {
97
+ plugin_json: list.code === 0 && detailHelp.code === 0 ? 'passed' : 'failed',
98
+ app_handoff_platform: platformSupported ? 'passed' : 'failed',
99
+ image_path_exposure_contract: 'sks-enforced'
100
+ };
101
+ }
102
+ //# sourceMappingURL=codex-0138-capability.js.map
@@ -0,0 +1,62 @@
1
+ import path from 'node:path';
2
+ import { writeJsonAtomic } from '../fsx.js';
3
+ import { collectCodexModelMetadata } from './codex-model-metadata.js';
4
+ export const SKS_FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
5
+ export function codexModelEffortCapability(input = {}) {
6
+ const metadataIsFallback = input.metadata?.source === 'fallback';
7
+ const advertised = metadataIsFallback ? [] : normalizeAdvertisedEfforts(input.metadata?.advertised_efforts || input.advertisedEfforts);
8
+ const order = advertised.length ? advertised : SKS_FALLBACK_EFFORT_ORDER;
9
+ const requestedDefault = input.metadata?.default_effort || input.defaultEffort;
10
+ const defaultEffort = order.includes(String(requestedDefault || '')) ? String(requestedDefault) : order.includes('medium') ? 'medium' : order[0] || 'medium';
11
+ return {
12
+ model: String(input.metadata?.model || input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
13
+ advertised_efforts: order,
14
+ default_effort: defaultEffort,
15
+ order_source: advertised.length ? 'model-advertised' : 'sks-fallback',
16
+ metadata_source: input.metadata?.source || null,
17
+ metadata_blockers: input.metadata?.blockers || []
18
+ };
19
+ }
20
+ export async function resolveCodexModelEffortCapability(input = {}) {
21
+ const metadata = await collectCodexModelMetadata({ model: input.model || null });
22
+ return codexModelEffortCapability({ metadata });
23
+ }
24
+ export async function writeCodexModelEffortCapabilityArtifact(root, input) {
25
+ const capability = await resolveCodexModelEffortCapability({ model: input.model || null });
26
+ const artifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-model-effort-capability.json');
27
+ await writeJsonAtomic(artifact, {
28
+ schema: 'sks.codex-model-effort-capability-artifact.v1',
29
+ generated_at: new Date().toISOString(),
30
+ ...capability
31
+ });
32
+ return { capability, artifact };
33
+ }
34
+ export function normalizeAdvertisedEfforts(value) {
35
+ const rows = Array.isArray(value) ? value : String(value || '').split(',');
36
+ const seen = new Set();
37
+ const out = [];
38
+ for (const row of rows) {
39
+ const effort = String(row || '').trim().toLowerCase();
40
+ if (!effort || seen.has(effort))
41
+ continue;
42
+ seen.add(effort);
43
+ out.push(effort);
44
+ }
45
+ return out;
46
+ }
47
+ export function nextAdvertisedEffort(current, capability = codexModelEffortCapability()) {
48
+ const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
49
+ const index = Math.max(0, order.indexOf(current));
50
+ return order[Math.min(order.length - 1, index + 1)] || current || capability.default_effort;
51
+ }
52
+ export function modelEffortAtLeast(target, capability = codexModelEffortCapability()) {
53
+ const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
54
+ if (order.includes(target))
55
+ return target;
56
+ if (target === 'recovery')
57
+ return order.includes('high') ? 'high' : order[order.length - 1];
58
+ if (target === 'forensic_vision')
59
+ return order.includes('xhigh') ? 'xhigh' : order[order.length - 1];
60
+ return capability.default_effort;
61
+ }
62
+ //# sourceMappingURL=codex-model-capabilities.js.map
@@ -0,0 +1,91 @@
1
+ import { findCodexBinary } from '../codex-adapter.js';
2
+ import { runProcess } from '../fsx.js';
3
+ const FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
4
+ export async function collectCodexModelMetadata(input = {}) {
5
+ if (process.env.SKS_CODEX_MODEL_METADATA_FAKE === '1') {
6
+ const advertised = normalizeAdvertisedEfforts(process.env.SKS_CODEX_MODEL_EFFORTS || 'low,medium,high,xhigh');
7
+ return metadata(String(input.model || process.env.SKS_CODEX_MODEL || 'gpt-5.5'), advertised, 'medium', 'app-server', []);
8
+ }
9
+ const model = String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5');
10
+ const appServer = await readAppServerMetadata(model);
11
+ if (appServer)
12
+ return appServer;
13
+ const cli = await readCodexCliMetadata(model);
14
+ if (cli)
15
+ return cli;
16
+ const envEfforts = normalizeAdvertisedEfforts(process.env.SKS_CODEX_MODEL_EFFORTS || '');
17
+ if (envEfforts.length)
18
+ return metadata(model, envEfforts, process.env.SKS_CODEX_MODEL_DEFAULT_EFFORT || 'medium', 'env', []);
19
+ return metadata(model, FALLBACK_EFFORT_ORDER, 'medium', 'fallback', ['codex_model_metadata_unavailable']);
20
+ }
21
+ async function readAppServerMetadata(model) {
22
+ const url = String(process.env.CODEX_APP_SERVER_METADATA_URL || process.env.SKS_CODEX_APP_SERVER_METADATA_URL || '').trim();
23
+ if (!url)
24
+ return null;
25
+ try {
26
+ const response = await fetch(url, { signal: AbortSignal.timeout(3000) });
27
+ if (!response.ok)
28
+ return null;
29
+ const payload = await response.json();
30
+ return normalizePayload(payload, model, 'app-server');
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function readCodexCliMetadata(model) {
37
+ const bin = await findCodexBinary();
38
+ if (!bin)
39
+ return null;
40
+ const commands = [
41
+ ['model', 'metadata', '--json'],
42
+ ['debug', 'model-metadata', '--json'],
43
+ ['capabilities', '--json']
44
+ ];
45
+ for (const args of commands) {
46
+ const result = await runProcess(bin, args, { timeoutMs: 3000, maxOutputBytes: 64 * 1024 }).catch(() => null);
47
+ if (!result || result.code !== 0)
48
+ continue;
49
+ try {
50
+ const payload = JSON.parse(`${result.stdout || ''}${result.stderr || ''}`.trim() || '{}');
51
+ const normalized = normalizePayload(payload, model, 'codex-cli');
52
+ if (normalized.advertised_efforts.length)
53
+ return normalized;
54
+ }
55
+ catch { }
56
+ }
57
+ return null;
58
+ }
59
+ function normalizePayload(payload, fallbackModel, source) {
60
+ const row = Array.isArray(payload?.models)
61
+ ? payload.models.find((candidate) => String(candidate?.id || candidate?.model || candidate?.name || '') === fallbackModel) || payload.models[0]
62
+ : payload?.model_metadata || payload?.metadata || payload;
63
+ const efforts = normalizeAdvertisedEfforts(row?.advertised_efforts || row?.advertisedEfforts || row?.reasoning_efforts || row?.reasoningEfforts || payload?.advertised_efforts);
64
+ return metadata(String(row?.model || row?.id || row?.name || fallbackModel), efforts, row?.default_effort || row?.defaultEffort || payload?.default_effort || 'medium', source, efforts.length ? [] : ['codex_model_metadata_efforts_missing']);
65
+ }
66
+ function metadata(model, efforts, defaultEffort, source, blockers) {
67
+ const advertised = normalizeAdvertisedEfforts(efforts);
68
+ const defaultValue = advertised.includes(defaultEffort) ? defaultEffort : advertised.includes('medium') ? 'medium' : advertised[0] || 'medium';
69
+ return {
70
+ schema: 'sks.codex-model-metadata.v1',
71
+ model,
72
+ advertised_efforts: advertised,
73
+ default_effort: defaultValue,
74
+ source,
75
+ blockers
76
+ };
77
+ }
78
+ function normalizeAdvertisedEfforts(value) {
79
+ const rows = Array.isArray(value) ? value : String(value || '').split(',');
80
+ const seen = new Set();
81
+ const out = [];
82
+ for (const row of rows) {
83
+ const effort = String(row || '').trim().toLowerCase();
84
+ if (!effort || seen.has(effort))
85
+ continue;
86
+ seen.add(effort);
87
+ out.push(effort);
88
+ }
89
+ return out;
90
+ }
91
+ //# sourceMappingURL=codex-model-metadata.js.map
@@ -2,7 +2,7 @@ export function buildCodexSdkConfig(input) {
2
2
  const config = {
3
3
  model: String(process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
4
4
  service_tier: 'fast',
5
- model_reasoning_effort: 'medium',
5
+ model_reasoning_effort: String(input.modelReasoningEffort || input.reasoningEffort || process.env.SKS_CODEX_REASONING || process.env.CODEX_MODEL_REASONING_EFFORT || 'medium'),
6
6
  mcp_servers: {},
7
7
  sks: {
8
8
  route: input.route,
@@ -445,7 +445,7 @@ async function ensurePythonCodexLbConfig(env, config) {
445
445
  `model = ${tomlQuote(model)}`,
446
446
  'model_provider = "codex-lb"',
447
447
  'service_tier = "fast"',
448
- 'model_reasoning_effort = "minimal"',
448
+ `model_reasoning_effort = ${tomlQuote(String(config.model_reasoning_effort || env.SKS_CODEX_REASONING || env.CODEX_MODEL_REASONING_EFFORT || 'minimal'))}`,
449
449
  'approval_policy = "never"',
450
450
  '',
451
451
  '[model_providers.codex-lb]',