sneakoscope 2.0.17 → 2.0.18

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 (39) hide show
  1. package/README.md +19 -28
  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/core/agents/agent-effort-policy.js +7 -1
  9. package/dist/core/codex-app/codex-app-handoff.js +77 -0
  10. package/dist/core/codex-control/codex-0138-capability.js +64 -0
  11. package/dist/core/codex-control/codex-model-capabilities.js +41 -0
  12. package/dist/core/codex-control/codex-sdk-config-policy.js +1 -1
  13. package/dist/core/codex-control/codex-task-runner.js +1 -1
  14. package/dist/core/codex-plugins/codex-plugin-json.js +152 -0
  15. package/dist/core/commands/mad-sks-command.js +4 -0
  16. package/dist/core/commands/naruto-command.js +3 -1
  17. package/dist/core/commands/qa-loop-command.js +111 -4
  18. package/dist/core/doctor/codex-0138-doctor.js +104 -0
  19. package/dist/core/doctor/doctor-readiness-matrix.js +11 -0
  20. package/dist/core/effort-orchestrator.js +9 -0
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/hooks-runtime.js +6 -9
  23. package/dist/core/image/image-artifact-path-contract.js +99 -0
  24. package/dist/core/image-ux-review/imagegen-adapter.js +24 -3
  25. package/dist/core/mad-db/mad-db-result-lifecycle.js +71 -0
  26. package/dist/core/mcp/mcp-plugin-inventory.js +29 -0
  27. package/dist/core/mcp/mcp-server-policy.js +24 -0
  28. package/dist/core/qa-loop/qa-loop-budget-policy.js +37 -0
  29. package/dist/core/qa-loop.js +28 -2
  30. package/dist/core/usage/codex-account-usage.js +78 -0
  31. package/dist/core/version.js +1 -1
  32. package/dist/core/zellij/zellij-slot-column-anchor.js +16 -7
  33. package/dist/core/zellij/zellij-slot-pane-renderer.js +18 -0
  34. package/dist/scripts/release-gate-existence-audit.js +5 -1
  35. package/package.json +25 -2
  36. package/schemas/codex-app/codex-app-handoff.schema.json +20 -0
  37. package/schemas/codex-plugins/codex-plugin-inventory.schema.json +32 -0
  38. package/schemas/image/image-artifact-path-contract.schema.json +32 -0
  39. package/schemas/usage/codex-account-usage.schema.json +27 -0
package/README.md CHANGED
@@ -16,43 +16,34 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
16
16
 
17
17
  ## Current Release
18
18
 
19
- SKS **2.0.17** is the micro-hardening release for strict production PID proof, true scheduler active-time utilization, live Zellij slot telemetry freshness, Mad-DB result lifecycle audit, and unified runtime/release proof summaries.
19
+ SKS **2.0.18** is the Codex 0.138 integration release: capability artifacts, Desktop `/app` handoff, plugin JSON inventory, image saved-path contracts, model-advertised effort order, account usage budget policy, and startup doctor checks.
20
20
 
21
21
  What changed:
22
22
 
23
- - Zellij slot panes distinguish `slot_status_renderer` panes from Codex worker panes, and the first visible worker now stacks downward below the `SLOTS` anchor with real geometry proof available under `real-check`.
24
- - Naruto allocation owners now flow into work graph items, scheduler slices, queue ownership, and worker runtime proof; inactive owners are rebalanced and active write conflicts stay out of concurrent execution.
25
- - Naruto active-pool checks now exercise actual child-worker spawn/result collection paths while production source-of-truth stays with the agent orchestrator scheduler.
26
- - Worktree candidate output requires GPT Final approval before apply; GPT `modified` output replaces candidate patches and GPT `rejected` blocks apply.
27
- - Visible Zellij reservations are capped before pane launch so concurrent worker starts cannot over-open the right column.
28
- - Git worktree integration now proves the primary repo receives validated worktree diffs, with rollback hash evidence recorded around the apply step.
29
- - Agent role config repair detects stale generated role files and rewrites structured GPT-5.5-compatible configs atomically.
30
- - Release gates now include slot-only UI, compact slot renderer, headless overflow, role-config repair, worktree primary-runtime, real active-pool, extreme real parallelism, and real right-column geometry checks.
31
- - Release audit, dynamic selection, and stamp hashing use `release-gates.v2.json` as the manifest source of truth.
32
- - Git capability checks detect repo roots, Git dirs, worktree support, and safe cache roots while blocking in-repo worktree roots unless `SKS_ALLOW_IN_REPO_WORKTREES=1` is explicit.
33
- - Worker worktree allocation creates isolated branches/paths under `$SKS_WORKTREE_ROOT`, `$XDG_CACHE_HOME/sks/worktrees`, or `~/.cache/sks/worktrees`; main checkouts stay untouched until integration.
34
- - Product Design plugin readiness now checks both local and remote Codex App catalogs, auto-installs the remote plugin when needed, and records the installed/enabled skill surface.
35
- - UI/design/PPT runtime routes prefer Product Design for research, ideation, audit, design QA, prototype, URL-to-code, image-to-code, share, and user-context steps.
36
- - Naruto read-only runs force write mode off, propagate no-patch reasons through worker proof, and skip changed-file lease checks when no write-capable patch envelope exists.
37
- - `codex-sdk` is the default native agent backend for Team, QA, Research, Naruto, MAD-SKS, and direct agent runs, with every runtime task entering through `runCodexTask`.
38
- - Codex App UI snapshot, preservation, clobber guard, and doctor repair checks protect host-owned Fast UI/profile settings around `sks --mad`.
39
- - Provider context resolves `openai`, `codex-lb`, and `codex-app` with badge/fallback surfaces while avoiding private Codex App UI mutation.
40
- - UltraRouter writes `ultra-router-proof.json` decisions with tier, scores, hard filters, cache state, and cheapest-good-enough profile selection.
41
- - Reliability Shield writes `codex-reliability-shield.json` for empty-result retry, stream-idle blocking, tool-result repair, and no-CoT keepalive heartbeats.
42
- - Raw `codex exec` execution is removed from runtime fallback paths; explicit legacy requests are blocked with `legacy_codex_exec_runtime_removed`.
43
- - SDK runs write `codex-control-proof.json`, `codex-thread-registry.json`, `codex-sdk-events.jsonl`, and schema-validated worker results.
44
- - Zellij proof now links `pane_id`, `slot_id`, `generation_index`, `session_id`, `sdk_thread_id`, provider, and `service_tier`.
45
- - Production runtime scripts are TypeScript source under `src/scripts` and build to `dist/scripts`; Python remains optional diagnostics under `pytools`.
46
- - Release gates include `codex-control:*`, `ultra-router:*`, `codex-sdk:*`, Codex App Fast UI preservation, provider badges, Zellij spawn-on-demand, slot/pane binding, release truth, and real smoke checks.
47
- - Research synthesis is now evidence-bound in non-mock runs; deterministic report generation is mock/fallback only.
48
- - Research quality checks reject repeated paragraphs, template-like prose, low source density, low claim density, and thin implementation sections.
49
- - Research handoffs now include context, key claims, evidence summary, blueprint sections, parallel work items, acceptance tests, rollback, and source appendix for `$Team` or `$Naruto`.
23
+ - `sks doctor` now reports Codex 0.138 feature readiness, plugin JSON inventory, candidate-only remote MCP servers, unavailable app templates, and repairable plugin discovery cache state.
24
+ - QA-LOOP can write a Codex Desktop `/app` handoff artifact with `--app-handoff` or require it with `--app-handoff-required`; this never substitutes for Codex Chrome Extension web UI evidence.
25
+ - Zellij slot panes and the right-column anchor surface pending QA `/app` handoffs so desktop review is visible during long native-agent runs.
26
+ - Codex plugin detail JSON is normalized into `.sneakoscope/codex-plugin-inventory.json`, and plugin-provided remote MCP servers remain candidate-only until explicitly enabled under DB/Mad-DB safety policy.
27
+ - Imagegen and QA image flows write `image-artifact-path-contract.json` with exact saved file paths and follow-up edit hints.
28
+ - Effort routing now understands the fallback order `minimal < low < medium < high < xhigh`, records model capability, and escalates QA effort after repeated failures.
29
+ - Codex account token usage can be recorded from an app-server usage endpoint, and QA budget policy reduces remote concurrency near limits while preserving GPT final review.
30
+ - Naruto final pass status now depends on the parallel runtime proof, and Mad-DB post-tool lifecycle recording handles MCP `isError` failures.
50
31
 
51
32
  Quick checks:
52
33
 
53
34
  ```bash
54
35
  npm run typecheck
55
36
  npm run build
37
+ npm run codex:0138-capability
38
+ npm run codex-sdk:version-compat
39
+ npm run codex-app:handoff
40
+ npm run codex-plugin:inventory
41
+ npm run qa-loop:app-handoff
42
+ npm run image:artifact-path-contract
43
+ npm run codex:effort-order
44
+ npm run codex:account-usage
45
+ npm run codex:0138-doctor
46
+ npm run doctor:codex-0138-fix
56
47
  npm run codex-control:capability
57
48
  npm run codex-control:structured-output
58
49
  npm run codex-control:event-stream-ledger
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "2.0.17"
79
+ version = "2.0.18"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "2.0.17"
3
+ version = "2.0.18"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 2.0.17"),
7
+ Some("--version") => println!("sks-rs 2.0.18"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "2.0.17",
5
- "source_digest": "1c6ef84350a97acdfd592ae544ec9054e2ee0a997076e725bc99b771c4693cf2",
6
- "source_file_count": 2201,
7
- "built_at_source_time": 1780974441836
4
+ "package_version": "2.0.18",
5
+ "source_digest": "3c246288e22bf5f29b5ba20a2a05b3f15a6afb9e6d680c531f7e78ef996c8c33",
6
+ "source_file_count": 2238,
7
+ "built_at_source_time": 1780992531184
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '2.0.17';
2
+ const FAST_PACKAGE_VERSION = '2.0.18';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -19,6 +19,10 @@ import { repairCodexAppFastUi } from '../core/codex-app/codex-app-fast-ui-repair
19
19
  import { resolveProviderContext } from '../core/provider/provider-context.js';
20
20
  import { readLocalModelConfig } from '../core/agents/ollama-worker-config.js';
21
21
  import { repairAgentRoleConfigs } from '../core/agents/agent-role-config.js';
22
+ import { writeCodex0138CapabilityArtifacts } from '../core/codex-control/codex-0138-capability.js';
23
+ import { runCodex0138Doctor } from '../core/doctor/codex-0138-doctor.js';
24
+ import { writeCodexPluginInventoryArtifacts, pluginAppTemplatePolicy } from '../core/codex-plugins/codex-plugin-json.js';
25
+ import { writeMcpPluginInventoryArtifacts } from '../core/mcp/mcp-plugin-inventory.js';
22
26
  export async function run(_command, args = []) {
23
27
  const doctorFix = flag(args, '--fix');
24
28
  let setupRepair = null;
@@ -167,6 +171,13 @@ export async function run(_command, args = []) {
167
171
  : null;
168
172
  const { detectImagegenCapability } = await import('../core/imagegen/imagegen-capability.js');
169
173
  const imagegen = await detectImagegenCapability({ codexBin: codexBin || undefined }).catch((err) => ({ ok: false, error: err.message, auth_readiness: null }));
174
+ const codex0138Capability = await writeCodex0138CapabilityArtifacts(root, { codexBin: codexBin || null }).catch((err) => ({ error: err?.message || String(err), report: null }));
175
+ const codex0138Doctor = await runCodex0138Doctor(root, { fix: doctorFix }).catch((err) => ({ schema: 'sks.codex-0138-doctor.v1', ok: false, error: err?.message || String(err), blockers: ['codex_0138_doctor_exception'], warnings: [] }));
176
+ const pluginInventory = await writeCodexPluginInventoryArtifacts(root).catch((err) => ({ error: err?.message || String(err), report: null, artifact: null }));
177
+ const pluginPolicy = pluginInventory?.report ? pluginAppTemplatePolicy(pluginInventory.report) : null;
178
+ const mcpPluginInventory = pluginInventory?.report
179
+ ? await writeMcpPluginInventoryArtifacts(root, { inventory: pluginInventory.report }).catch((err) => ({ error: err?.message || String(err), candidates: null }))
180
+ : null;
170
181
  const pkgBytes = await dirSize(root).catch(() => 0);
171
182
  const ready = await writeDoctorReadinessMatrix(root, {
172
183
  codex,
@@ -180,10 +191,14 @@ export async function run(_command, args = []) {
180
191
  agent_role_config: agentRoleConfigRepair,
181
192
  repair: configRepair,
182
193
  codex_app_ui: codexAppUi,
194
+ codex_0138_doctor: codex0138Doctor,
195
+ codex_plugin_inventory: pluginInventory?.report || null,
196
+ codex_plugin_app_template_policy: pluginPolicy,
183
197
  require_codex_cli_config_load: flag(args, '--fix') || flag(args, '--require-actual-codex'),
184
198
  operator_actions: [
185
199
  ...(codexConfig.operator_actions || []),
186
- ...(configRepair?.operator_actions || [])
200
+ ...(configRepair?.operator_actions || []),
201
+ ...(pluginPolicy?.doctor_warnings || [])
187
202
  ]
188
203
  });
189
204
  const zellijReadiness = buildZellijReadiness(root, zellij, ready);
@@ -211,6 +226,13 @@ export async function run(_command, args = []) {
211
226
  auth_readiness: imagegen.auth_readiness || null,
212
227
  codex_app_builtin_available: imagegen.codex_app?.available === true
213
228
  },
229
+ codex_0138: {
230
+ capability: codex0138Capability.report || null,
231
+ doctor: codex0138Doctor,
232
+ plugins: pluginInventory?.report || null,
233
+ plugin_app_template_policy: pluginPolicy,
234
+ mcp_plugin_inventory: mcpPluginInventory?.candidates || null
235
+ },
214
236
  ready,
215
237
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
216
238
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
@@ -262,6 +284,22 @@ export async function run(_command, args = []) {
262
284
  console.log(` - ${action}`);
263
285
  }
264
286
  }
287
+ const codex0138 = codex0138Capability.report || {};
288
+ console.log('Codex 0.138 features:');
289
+ console.log(` /app handoff: ${codex0138.supports_app_handoff ? 'ok' : 'unavailable'}`);
290
+ console.log(` plugin JSON: ${codex0138.supports_plugin_json ? 'ok' : 'unavailable'}`);
291
+ console.log(` image path exposure: ${codex0138.supports_image_path_exposure ? 'ok' : 'unavailable'}`);
292
+ console.log(` OAuth MCP pre-refresh: ${codex0138.supports_oauth_mcp_prerefresh ? 'ok' : 'unavailable'}`);
293
+ const plugins = pluginInventory?.report?.plugins || [];
294
+ const remoteMcpCount = plugins.flatMap((plugin) => plugin.remote_mcp_servers || []).length;
295
+ const unavailableTemplates = pluginPolicy?.unavailable_app_templates?.length || 0;
296
+ console.log(`Codex plugins: ${pluginInventory?.report ? 'ok' : 'warning'}`);
297
+ console.log(` Remote MCP servers: ${remoteMcpCount} candidates`);
298
+ console.log(` Unavailable app templates: ${unavailableTemplates}`);
299
+ for (const warning of pluginPolicy?.doctor_warnings || [])
300
+ console.log(` warning: ${warning}`);
301
+ if (codex0138Doctor?.fixed?.length)
302
+ console.log(` doctor --fix repaired: ${codex0138Doctor.fixed.join(', ')}`);
265
303
  console.log(`codex-lb: ${codexLb.ok ? 'ok' : `warning ${codexLb.circuit?.state || 'unknown'}`}`);
266
304
  if (localModel) {
267
305
  console.log('Local LLM:');
@@ -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,
@@ -0,0 +1,77 @@
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
+ export function buildCodexAppHandoffPrompt(request) {
5
+ return [
6
+ 'SKS Codex Desktop /app handoff request',
7
+ `Mission: ${request.mission_id}`,
8
+ `Route: ${request.route}`,
9
+ `Reason: ${request.reason}`,
10
+ `Workspace: ${request.workspace_path}`,
11
+ request.thread_ref ? `Thread: ${request.thread_ref}` : '',
12
+ '',
13
+ 'Artifacts:',
14
+ ...(request.artifacts || []).map((artifact) => `- ${artifact}`),
15
+ '',
16
+ 'Prompt:',
17
+ request.prompt,
18
+ '',
19
+ '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.'
20
+ ].filter((line) => line !== '').join('\n');
21
+ }
22
+ export async function runCodexAppHandoff(root, request) {
23
+ const capability = await detectCodex0138Capability();
24
+ const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
25
+ const desktopSupported = capability.supports_app_handoff === true && platformSupported;
26
+ const dir = path.join(root, '.sneakoscope', 'missions', request.mission_id, 'qa-loop');
27
+ const artifactPath = path.join(dir, 'app-handoff.json');
28
+ const promptArtifactPath = path.join(dir, 'app-handoff-prompt.md');
29
+ const blockers = [
30
+ ...(capability.supports_app_handoff ? [] : ['codex_0_138_app_handoff_unavailable']),
31
+ ...(platformSupported ? [] : ['codex_app_handoff_platform_unsupported'])
32
+ ];
33
+ const prompt = buildCodexAppHandoffPrompt(request);
34
+ await writeTextAtomic(promptArtifactPath, prompt);
35
+ const status = request.require_desktop && !desktopSupported
36
+ ? 'blocked_for_desktop_review'
37
+ : desktopSupported
38
+ ? 'pending'
39
+ : 'skipped';
40
+ const result = {
41
+ schema: 'sks.codex-app-handoff-result.v1',
42
+ ok: request.require_desktop ? desktopSupported : true,
43
+ attempted: false,
44
+ launched: false,
45
+ status,
46
+ codex_0138_capability: capability,
47
+ command_line: ['codex', '/app'],
48
+ desktop_handoff_supported: desktopSupported,
49
+ fallback_reason: desktopSupported
50
+ ? 'interactive_tui_handoff_pending_operator'
51
+ : blockers.join(';') || null,
52
+ artifact_path: artifactPath,
53
+ prompt_artifact_path: promptArtifactPath,
54
+ blockers: request.require_desktop ? blockers : []
55
+ };
56
+ await writeJsonAtomic(artifactPath, {
57
+ ...result,
58
+ request,
59
+ operator_instruction: {
60
+ open: 'codex /app',
61
+ prompt_artifact: path.relative(root, promptArtifactPath),
62
+ created_at: nowIso()
63
+ }
64
+ });
65
+ return result;
66
+ }
67
+ export function qaLoopShouldRequestAppHandoff(input = {}) {
68
+ const args = input.args || [];
69
+ return args.includes('--app-handoff')
70
+ || process.env.SKS_QA_LOOP_APP_HANDOFF === '1'
71
+ || input.visualArtifactsPresent === true
72
+ || input.zellijUiBlocked === true
73
+ || input.pluginAppTemplateUnavailable === true
74
+ || input.userRequestedDesktopReview === true
75
+ || input.uiRequired === true && process.env.SKS_QA_LOOP_APP_HANDOFF_FOR_VISUAL === '1';
76
+ }
77
+ //# sourceMappingURL=codex-app-handoff.js.map
@@ -0,0 +1,64 @@
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 blockers = [
16
+ ...(!codexBin ? ['codex_cli_missing'] : []),
17
+ ...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features'])
18
+ ];
19
+ return {
20
+ schema: 'sks.codex-0138-capability.v1',
21
+ ok: atLeast138,
22
+ codex_bin: codexBin || null,
23
+ version_text: versionText || null,
24
+ parsed_version: parsed,
25
+ supports_app_handoff: atLeast138,
26
+ supports_plugin_json: atLeast138,
27
+ supports_image_path_exposure: atLeast138,
28
+ supports_model_defined_efforts: atLeast138,
29
+ supports_app_server_token_usage: atLeast138,
30
+ supports_v2_pat_auth: atLeast138,
31
+ supports_oauth_mcp_prerefresh: atLeast138,
32
+ blockers
33
+ };
34
+ }
35
+ export async function writeCodex0138CapabilityArtifacts(root, input = {}) {
36
+ const capability = await detectCodex0138Capability({ codexBin: input.codexBin || null });
37
+ const report = { ...capability, generated_at: nowIso() };
38
+ const rootArtifact = path.join(root, '.sneakoscope', 'codex-0138-capability.json');
39
+ await writeJsonAtomic(rootArtifact, report);
40
+ let missionArtifact = null;
41
+ if (input.missionId) {
42
+ missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0138-capability.json');
43
+ await writeJsonAtomic(missionArtifact, report);
44
+ }
45
+ return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
46
+ }
47
+ export function parseCodexVersion(text) {
48
+ return parseCodexVersionText(text);
49
+ }
50
+ export function semverGte(actual, minimum) {
51
+ return compareSemverLike(actual, minimum) >= 0;
52
+ }
53
+ async function readCodexVersionText(codexBin) {
54
+ if (!codexBin)
55
+ return null;
56
+ const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
57
+ code: 1,
58
+ stdout: '',
59
+ stderr: err?.message || String(err)
60
+ }));
61
+ const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
62
+ return result.code === 0 ? text : text || null;
63
+ }
64
+ //# sourceMappingURL=codex-0138-capability.js.map
@@ -0,0 +1,41 @@
1
+ export const SKS_FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
2
+ export function codexModelEffortCapability(input = {}) {
3
+ const advertised = normalizeAdvertisedEfforts(input.advertisedEfforts);
4
+ const order = advertised.length ? advertised : SKS_FALLBACK_EFFORT_ORDER;
5
+ const defaultEffort = order.includes(String(input.defaultEffort || '')) ? String(input.defaultEffort) : order.includes('medium') ? 'medium' : order[0] || 'medium';
6
+ return {
7
+ model: String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
8
+ advertised_efforts: order,
9
+ default_effort: defaultEffort,
10
+ order_source: advertised.length ? 'model-advertised' : 'sks-fallback'
11
+ };
12
+ }
13
+ export function normalizeAdvertisedEfforts(value) {
14
+ const rows = Array.isArray(value) ? value : String(value || '').split(',');
15
+ const seen = new Set();
16
+ const out = [];
17
+ for (const row of rows) {
18
+ const effort = String(row || '').trim().toLowerCase();
19
+ if (!effort || seen.has(effort))
20
+ continue;
21
+ seen.add(effort);
22
+ out.push(effort);
23
+ }
24
+ return out;
25
+ }
26
+ export function nextAdvertisedEffort(current, capability = codexModelEffortCapability()) {
27
+ const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
28
+ const index = Math.max(0, order.indexOf(current));
29
+ return order[Math.min(order.length - 1, index + 1)] || current || capability.default_effort;
30
+ }
31
+ export function modelEffortAtLeast(target, capability = codexModelEffortCapability()) {
32
+ const order = capability.advertised_efforts.length ? capability.advertised_efforts : SKS_FALLBACK_EFFORT_ORDER;
33
+ if (order.includes(target))
34
+ return target;
35
+ if (target === 'recovery')
36
+ return order.includes('high') ? 'high' : order[order.length - 1];
37
+ if (target === 'forensic_vision')
38
+ return order.includes('xhigh') ? 'xhigh' : order[order.length - 1];
39
+ return capability.default_effort;
40
+ }
41
+ //# sourceMappingURL=codex-model-capabilities.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]',
@@ -0,0 +1,152 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
4
+ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
5
+ export async function runCodexPluginListJson() {
6
+ if (process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1')
7
+ return fakePluginList();
8
+ const bin = await findCodexBinary();
9
+ if (!bin)
10
+ return { plugins: [], blockers: ['codex_cli_missing'] };
11
+ return runCodexJson(bin, ['plugin', 'list', '--json']);
12
+ }
13
+ export async function runCodexPluginDetailJson(pluginId) {
14
+ if (process.env.SKS_CODEX_PLUGIN_JSON_FAKE === '1')
15
+ return fakePluginDetail(pluginId);
16
+ const bin = await findCodexBinary();
17
+ if (!bin)
18
+ return { blockers: ['codex_cli_missing'] };
19
+ return runCodexJson(bin, ['plugin', 'detail', pluginId, '--json']);
20
+ }
21
+ export async function buildCodexPluginInventory() {
22
+ const capability = await detectCodex0138Capability();
23
+ const listJson = await runCodexPluginListJson();
24
+ const summaries = normalizePluginList(listJson);
25
+ const plugins = [];
26
+ for (const summary of summaries) {
27
+ const detail = await runCodexPluginDetailJson(summary.id || summary.name).catch((err) => ({ error: err?.message || String(err) }));
28
+ plugins.push(normalizePlugin(summary, detail));
29
+ }
30
+ const blockers = [
31
+ ...(capability.supports_plugin_json ? [] : ['codex_0_138_plugin_json_unavailable']),
32
+ ...normalizeList(listJson?.blockers)
33
+ ];
34
+ return {
35
+ schema: 'sks.codex-plugin-inventory.v1',
36
+ generated_at: nowIso(),
37
+ codex_0138_capability: capability,
38
+ plugins,
39
+ marketplace_available: plugins.some((plugin) => plugin.source === 'marketplace' || plugin.source === 'remote') || Boolean(listJson?.marketplace_available || listJson?.marketplaceAvailable),
40
+ blockers
41
+ };
42
+ }
43
+ export async function writeCodexPluginInventoryArtifacts(root, inventory = null) {
44
+ const report = inventory || await buildCodexPluginInventory();
45
+ const artifact = path.join(root, '.sneakoscope', 'codex-plugin-inventory.json');
46
+ await writeJsonAtomic(artifact, report);
47
+ return { report, artifact };
48
+ }
49
+ export function pluginAppTemplatePolicy(inventory) {
50
+ const unavailable = inventory.plugins.flatMap((plugin) => plugin.unavailable_app_templates.map((template) => ({
51
+ plugin: plugin.id,
52
+ template
53
+ })));
54
+ return {
55
+ schema: 'sks.codex-plugin-app-template-policy.v1',
56
+ ok: true,
57
+ unavailable_app_templates: unavailable,
58
+ qa_loop_app_handoff_recommended: unavailable.length > 0,
59
+ doctor_warnings: unavailable.map((row) => `plugin_app_template_unavailable:${row.plugin}`)
60
+ };
61
+ }
62
+ async function runCodexJson(bin, args) {
63
+ const result = await runProcess(bin, args, { timeoutMs: 20_000, maxOutputBytes: 256 * 1024 }).catch((err) => ({
64
+ code: 1,
65
+ stdout: '',
66
+ stderr: err?.message || String(err)
67
+ }));
68
+ const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
69
+ try {
70
+ return text ? JSON.parse(text) : {};
71
+ }
72
+ catch {
73
+ return { raw_text: text, blockers: [`codex_plugin_json_parse_failed:${args.join(' ')}`] };
74
+ }
75
+ }
76
+ function normalizePluginList(value) {
77
+ if (Array.isArray(value))
78
+ return value;
79
+ for (const key of ['plugins', 'installed_plugins', 'installedPlugins', 'items']) {
80
+ if (Array.isArray(value?.[key]))
81
+ return value[key];
82
+ }
83
+ return [];
84
+ }
85
+ function normalizePlugin(summary, detail) {
86
+ const raw = { summary, detail };
87
+ const id = String(detail?.id || summary?.id || summary?.plugin_id || summary?.name || 'unknown');
88
+ const name = String(detail?.name || summary?.name || id);
89
+ const sourceText = String(detail?.source || detail?.marketplaceSource || summary?.source || summary?.marketplaceSource || '').toLowerCase();
90
+ const source = sourceText.includes('marketplace') ? 'marketplace'
91
+ : sourceText.includes('remote') ? 'remote'
92
+ : sourceText.includes('local') ? 'local'
93
+ : 'unknown';
94
+ return {
95
+ id,
96
+ name,
97
+ source,
98
+ installed: boolish(detail?.installed ?? summary?.installed, true),
99
+ enabled: boolish(detail?.enabled ?? summary?.enabled, true),
100
+ default_prompts: normalizeList(detail?.default_prompts || detail?.defaultPrompts || detail?.prompts),
101
+ remote_mcp_servers: normalizeMcpServers(detail?.remote_mcp_servers || detail?.remoteMcpServers || detail?.mcp_servers || detail?.mcpServers),
102
+ unavailable_app_templates: normalizeList(detail?.unavailable_app_templates || detail?.unavailableAppTemplates || detail?.app_templates_unavailable),
103
+ raw
104
+ };
105
+ }
106
+ function normalizeMcpServers(value) {
107
+ const rows = Array.isArray(value) ? value : value && typeof value === 'object' ? Object.entries(value).map(([name, row]) => ({ name, ...(row || {}) })) : [];
108
+ return rows.map((row, index) => ({
109
+ name: String(row?.name || row?.id || `remote-mcp-${index + 1}`),
110
+ url: stringOrNull(row?.url || row?.endpoint),
111
+ auth_type: stringOrNull(row?.auth_type || row?.authType || row?.auth)
112
+ }));
113
+ }
114
+ function normalizeList(value) {
115
+ return Array.isArray(value) ? value.filter(Boolean).map(String) : value ? [String(value)] : [];
116
+ }
117
+ function stringOrNull(value) {
118
+ const text = String(value || '').trim();
119
+ return text ? text : null;
120
+ }
121
+ function boolish(value, fallback = false) {
122
+ if (value === true || value === 'true')
123
+ return true;
124
+ if (value === false || value === 'false')
125
+ return false;
126
+ return fallback;
127
+ }
128
+ function fakePluginList() {
129
+ return {
130
+ marketplace_available: true,
131
+ plugins: [{
132
+ id: 'fixture-plugin',
133
+ name: 'Fixture Plugin',
134
+ source: 'marketplace',
135
+ installed: true,
136
+ enabled: true
137
+ }]
138
+ };
139
+ }
140
+ function fakePluginDetail(pluginId) {
141
+ return {
142
+ id: pluginId,
143
+ name: pluginId,
144
+ source: 'marketplace',
145
+ installed: true,
146
+ enabled: true,
147
+ default_prompts: ['Use the fixture plugin safely.'],
148
+ remote_mcp_servers: [{ name: 'fixture-db-docs', url: 'https://mcp.example.test', auth_type: 'oauth' }],
149
+ unavailable_app_templates: ['fixture-desktop-template']
150
+ };
151
+ }
152
+ //# sourceMappingURL=codex-plugin-json.js.map
@@ -20,6 +20,7 @@ import { runCodexLaunchPreflight } from '../preflight/parallel-preflight-engine.
20
20
  import { diffCodexAppUiSnapshots, writeCodexAppUiSnapshot } from '../codex-app/codex-app-ui-state-snapshot.js';
21
21
  import { checkSksUpdateNotice } from '../update/update-notice.js';
22
22
  import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
23
+ import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
23
24
  export async function madHighCommand(args = [], deps = {}) {
24
25
  const subcommand = firstSubcommand(args);
25
26
  if (subcommand)
@@ -379,6 +380,7 @@ async function activateMadZellijPermissionState(cwd = process.cwd(), args = [])
379
380
  const has = (scope) => allowedScopes.has(scope);
380
381
  const dbWriteAllowed = has('db_write');
381
382
  const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: 'sks --mad Zellij scoped high-power maintenance session' });
383
+ await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
382
384
  const protectedCore = resolveProtectedCore({ packageRoot: packageRoot(), targetRoot: cwd });
383
385
  // The interactive launch 'before' snapshot is only persisted (env + policy json)
384
386
  // and is never compared against an 'after' snapshot during the session, so the
@@ -564,6 +566,7 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
564
566
  }
565
567
  export async function madSksFixture(root) {
566
568
  const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: '$MAD-SKS fixture permission gate' });
569
+ await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
567
570
  const gate = { schema_version: 1, passed: true, mad_sks_permission_active: true, permissions_deactivated: true, catastrophic_safety_guard_active: true, permission_profile: permissionGateSummary(), fixture: true };
568
571
  await writeJsonAtomic(path.join(dir, 'mad-sks-gate.json'), gate);
569
572
  return { mission_id: id, dir, gate };
@@ -738,6 +741,7 @@ async function materializeMadSksRun(root, targetRoot, permission, userIntent, js
738
741
  if (!(await exists(path.join(root, '.sneakoscope'))))
739
742
  await initProject(root, {});
740
743
  const { id, dir } = await createMission(root, { mode: 'mad-sks', prompt: userIntent });
744
+ await writeCodex0138CapabilityArtifacts(root, { missionId: id }).catch(() => null);
741
745
  const before = await snapshotProtectedCore(packageRoot(), 'before');
742
746
  const authorization = opts.authorizationManifest || createMadSksAuthorizationManifest({ permission, userIntent });
743
747
  const authorizationPath = opts.authorizationManifestPath || path.join(dir, 'mad-sks-authorization.json');