sneakoscope 2.0.18 → 3.0.1

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 (41) hide show
  1. package/README.md +127 -71
  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/mad-sks.js +2 -0
  8. package/dist/commands/zellij.js +58 -1
  9. package/dist/core/agents/agent-scheduler.js +32 -24
  10. package/dist/core/agents/native-cli-session-swarm.js +22 -2
  11. package/dist/core/codex-app/codex-app-handoff.js +30 -9
  12. package/dist/core/codex-app/codex-app-launcher.js +103 -0
  13. package/dist/core/codex-control/codex-0138-capability.js +42 -4
  14. package/dist/core/codex-control/codex-0139-capability.js +102 -0
  15. package/dist/core/codex-control/codex-model-capabilities.js +25 -4
  16. package/dist/core/codex-control/codex-model-metadata.js +91 -0
  17. package/dist/core/codex-plugins/codex-plugin-cache.js +38 -0
  18. package/dist/core/codex-plugins/codex-plugin-diff.js +73 -0
  19. package/dist/core/codex-plugins/codex-plugin-json.js +35 -11
  20. package/dist/core/commands/mad-sks-command.js +8 -0
  21. package/dist/core/commands/naruto-command.js +29 -0
  22. package/dist/core/commands/qa-loop-command.js +41 -6
  23. package/dist/core/fsx.js +1 -1
  24. package/dist/core/image/image-artifact-path-contract.js +2 -0
  25. package/dist/core/image/image-artifact-registry.js +33 -0
  26. package/dist/core/image-ux-review/imagegen-adapter.js +27 -16
  27. package/dist/core/pipeline-internals/runtime-core.js +4 -2
  28. package/dist/core/qa-loop/qa-loop-app-handoff-confirmation.js +51 -0
  29. package/dist/core/qa-loop/qa-loop-budget-policy.js +1 -1
  30. package/dist/core/qa-loop.js +44 -3
  31. package/dist/core/release/release-gate-cache-v2.js +47 -5
  32. package/dist/core/usage/codex-account-usage.js +77 -16
  33. package/dist/core/version.js +1 -1
  34. package/dist/core/zellij/zellij-slot-pane-renderer.js +5 -2
  35. package/dist/core/zellij/zellij-slot-telemetry.js +65 -12
  36. package/dist/core/zellij/zellij-ui-mode.js +8 -1
  37. package/dist/core/zellij/zellij-update.js +307 -0
  38. package/dist/core/zellij/zellij-worker-pane-manager.js +211 -145
  39. package/package.json +23 -2
  40. package/dist/core/naruto/naruto-work-stealing.js +0 -11
  41. package/dist/core/zellij/zellij-right-column-layout-proof.js +0 -42
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
3
3
  import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ import { attemptCodexAppLaunch } from './codex-app-launcher.js';
4
5
  export function buildCodexAppHandoffPrompt(request) {
5
6
  return [
6
7
  'SKS Codex Desktop /app handoff request',
@@ -23,6 +24,7 @@ export async function runCodexAppHandoff(root, request) {
23
24
  const capability = await detectCodex0138Capability();
24
25
  const platformSupported = process.platform === 'darwin' || process.platform === 'win32';
25
26
  const desktopSupported = capability.supports_app_handoff === true && platformSupported;
27
+ const launchMode = request.launch_mode || 'artifact-only';
26
28
  const dir = path.join(root, '.sneakoscope', 'missions', request.mission_id, 'qa-loop');
27
29
  const artifactPath = path.join(dir, 'app-handoff.json');
28
30
  const promptArtifactPath = path.join(dir, 'app-handoff-prompt.md');
@@ -32,26 +34,45 @@ export async function runCodexAppHandoff(root, request) {
32
34
  ];
33
35
  const prompt = buildCodexAppHandoffPrompt(request);
34
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
+ });
35
50
  const status = request.require_desktop && !desktopSupported
36
51
  ? 'blocked_for_desktop_review'
37
- : desktopSupported
38
- ? 'pending'
39
- : 'skipped';
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';
40
59
  const result = {
41
60
  schema: 'sks.codex-app-handoff-result.v1',
42
- ok: request.require_desktop ? desktopSupported : true,
43
- attempted: false,
44
- launched: false,
61
+ ok: request.require_desktop ? status !== 'blocked_for_desktop_review' : true,
62
+ attempted: launchAttempt.attempted,
63
+ launched: launchAttempt.launched,
45
64
  status,
46
65
  codex_0138_capability: capability,
47
- command_line: ['codex', '/app'],
66
+ command_line: launchAttempt.command_line,
67
+ launch_attempt: launchAttempt,
68
+ confirmation_required: request.require_desktop,
48
69
  desktop_handoff_supported: desktopSupported,
49
70
  fallback_reason: desktopSupported
50
- ? 'interactive_tui_handoff_pending_operator'
71
+ ? launchAttempt.fallback_reason || 'desktop_handoff_pending_operator_confirmation'
51
72
  : blockers.join(';') || null,
52
73
  artifact_path: artifactPath,
53
74
  prompt_artifact_path: promptArtifactPath,
54
- blockers: request.require_desktop ? blockers : []
75
+ blockers: request.require_desktop ? [...blockers, ...(status === 'blocked_for_desktop_review' ? launchAttempt.blockers : [])] : []
55
76
  };
56
77
  await writeJsonAtomic(artifactPath, {
57
78
  ...result,
@@ -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
@@ -12,23 +12,36 @@ export async function detectCodex0138Capability(input = {}) {
12
12
  : await readCodexVersionText(codexBin);
13
13
  const parsed = parseCodexVersion(versionText);
14
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');
15
25
  const blockers = [
16
26
  ...(!codexBin ? ['codex_cli_missing'] : []),
17
- ...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features'])
27
+ ...(atLeast138 ? [] : ['codex_0_138_required_for_app_plugin_features']),
28
+ ...(probeMode === 'feature-probe' && featureProbeResults.plugin_json === 'failed' ? ['codex_plugin_json_probe_failed'] : [])
18
29
  ];
19
30
  return {
20
31
  schema: 'sks.codex-0138-capability.v1',
21
- ok: atLeast138,
32
+ ok: atLeast138 && blockers.length === 0,
33
+ probe_mode: probeMode,
22
34
  codex_bin: codexBin || null,
23
35
  version_text: versionText || null,
24
36
  parsed_version: parsed,
25
- supports_app_handoff: atLeast138,
26
- supports_plugin_json: atLeast138,
37
+ supports_app_handoff: appHandoffOk,
38
+ supports_plugin_json: pluginJsonOk,
27
39
  supports_image_path_exposure: atLeast138,
28
40
  supports_model_defined_efforts: atLeast138,
29
41
  supports_app_server_token_usage: atLeast138,
30
42
  supports_v2_pat_auth: atLeast138,
31
43
  supports_oauth_mcp_prerefresh: atLeast138,
44
+ feature_probe_results: featureProbeResults,
32
45
  blockers
33
46
  };
34
47
  }
@@ -61,4 +74,29 @@ async function readCodexVersionText(codexBin) {
61
74
  const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
62
75
  return result.code === 0 ? text : text || null;
63
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
+ }
64
102
  //# sourceMappingURL=codex-0138-capability.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 detectCodex0139Capability(input = {}) {
6
+ const fake = process.env.SKS_CODEX_0139_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.139.0')
12
+ : await readCodexVersionText(codexBin);
13
+ const parsed = parseCodexVersionText(versionText);
14
+ const atLeast139 = Boolean(parsed && compareSemverLike(parsed, '0.139.0') >= 0);
15
+ const probeMode = process.env.SKS_CODEX_0139_PROBE === '1' ? 'feature-probe' : 'version-only';
16
+ const featureProbeResults = probeMode === 'feature-probe'
17
+ ? await probeCodex0139Features(codexBin, { fake })
18
+ : {
19
+ marketplace_list_json: 'skipped',
20
+ sandbox_profile_alias: 'skipped'
21
+ };
22
+ const marketplaceOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.marketplace_list_json !== 'failed');
23
+ const profileAliasOk = atLeast139 && (probeMode === 'version-only' || featureProbeResults.sandbox_profile_alias !== 'failed');
24
+ const blockers = [
25
+ ...(!codexBin ? ['codex_cli_missing'] : []),
26
+ ...(atLeast139 ? [] : ['codex_0_139_required_for_search_schema_marketplace_features']),
27
+ ...(probeMode === 'feature-probe' && featureProbeResults.marketplace_list_json === 'failed' ? ['codex_marketplace_list_json_probe_failed'] : [])
28
+ ];
29
+ return {
30
+ schema: 'sks.codex-0139-capability.v1',
31
+ ok: atLeast139 && blockers.length === 0,
32
+ probe_mode: probeMode,
33
+ codex_bin: codexBin || null,
34
+ version_text: versionText || null,
35
+ parsed_version: parsed,
36
+ supports_code_mode_web_search: atLeast139,
37
+ supports_rich_tool_schemas: atLeast139,
38
+ supports_doctor_env_details: atLeast139,
39
+ supports_marketplace_source_field: marketplaceOk,
40
+ supports_plugin_catalog_cache: atLeast139,
41
+ supports_sandbox_profile_alias: profileAliasOk,
42
+ supports_interrupt_agent_rename: atLeast139,
43
+ feature_probe_results: featureProbeResults,
44
+ blockers
45
+ };
46
+ }
47
+ export async function writeCodex0139CapabilityArtifacts(root, input = {}) {
48
+ const capability = await detectCodex0139Capability({ codexBin: input.codexBin || null });
49
+ const report = { ...capability, generated_at: nowIso() };
50
+ const rootArtifact = path.join(root, '.sneakoscope', 'codex-0139-capability.json');
51
+ await writeJsonAtomic(rootArtifact, report);
52
+ let missionArtifact = null;
53
+ if (input.missionId) {
54
+ missionArtifact = path.join(root, '.sneakoscope', 'missions', input.missionId, 'codex-0139-capability.json');
55
+ await writeJsonAtomic(missionArtifact, report);
56
+ }
57
+ return { report, root_artifact: rootArtifact, mission_artifact: missionArtifact };
58
+ }
59
+ async function readCodexVersionText(codexBin) {
60
+ if (!codexBin)
61
+ return null;
62
+ const result = await runProcess(codexBin, ['--version'], { timeoutMs: 10_000, maxOutputBytes: 16 * 1024 }).catch((err) => ({
63
+ code: 1,
64
+ stdout: '',
65
+ stderr: err?.message || String(err)
66
+ }));
67
+ const text = `${result.stdout || ''}${result.stderr || ''}`.trim();
68
+ return result.code === 0 ? text : text || null;
69
+ }
70
+ async function probeCodex0139Features(codexBin, opts = {}) {
71
+ if (opts.fake) {
72
+ return {
73
+ marketplace_list_json: process.env.SKS_CODEX_0139_FAKE_MARKETPLACE_FAIL === '1' ? 'failed' : 'passed',
74
+ sandbox_profile_alias: 'passed'
75
+ };
76
+ }
77
+ const timeoutMs = Math.max(1, Number(process.env.SKS_CODEX_0139_PROBE_TIMEOUT_MS || 3000) || 3000);
78
+ if (!codexBin) {
79
+ return { marketplace_list_json: 'failed', sandbox_profile_alias: 'failed' };
80
+ }
81
+ const marketplace = await runProcess(codexBin, ['plugin', 'marketplace', 'list', '--json'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
82
+ const marketplaceListJson = marketplace.code === 0 && marketplaceSourcesPresent(marketplace.stdout) ? 'passed' : 'failed';
83
+ const help = await runProcess(codexBin, ['--help'], { timeoutMs, maxOutputBytes: 256 * 1024 }).catch(() => ({ code: 1, stdout: '' }));
84
+ const aliasOk = help.code === 0 && /(^|\s)-P[,\s]/m.test(String(help.stdout || ''));
85
+ return {
86
+ marketplace_list_json: marketplaceListJson,
87
+ sandbox_profile_alias: aliasOk ? 'passed' : 'failed'
88
+ };
89
+ }
90
+ export function marketplaceSourcesPresent(stdout) {
91
+ try {
92
+ const parsed = JSON.parse(String(stdout || ''));
93
+ const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.marketplaces) ? parsed.marketplaces : Array.isArray(parsed?.items) ? parsed.items : [];
94
+ if (!rows.length)
95
+ return true;
96
+ return rows.some((row) => typeof row?.source === 'string' && row.source.length > 0);
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ //# sourceMappingURL=codex-0139-capability.js.map
@@ -1,15 +1,36 @@
1
+ import path from 'node:path';
2
+ import { writeJsonAtomic } from '../fsx.js';
3
+ import { collectCodexModelMetadata } from './codex-model-metadata.js';
1
4
  export const SKS_FALLBACK_EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh'];
2
5
  export function codexModelEffortCapability(input = {}) {
3
- const advertised = normalizeAdvertisedEfforts(input.advertisedEfforts);
6
+ const metadataIsFallback = input.metadata?.source === 'fallback';
7
+ const advertised = metadataIsFallback ? [] : normalizeAdvertisedEfforts(input.metadata?.advertised_efforts || input.advertisedEfforts);
4
8
  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';
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';
6
11
  return {
7
- model: String(input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
12
+ model: String(input.metadata?.model || input.model || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || 'gpt-5.5'),
8
13
  advertised_efforts: order,
9
14
  default_effort: defaultEffort,
10
- order_source: advertised.length ? 'model-advertised' : 'sks-fallback'
15
+ order_source: advertised.length ? 'model-advertised' : 'sks-fallback',
16
+ metadata_source: input.metadata?.source || null,
17
+ metadata_blockers: input.metadata?.blockers || []
11
18
  };
12
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
+ }
13
34
  export function normalizeAdvertisedEfforts(value) {
14
35
  const rows = Array.isArray(value) ? value : String(value || '').split(',');
15
36
  const seen = new Set();
@@ -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
@@ -0,0 +1,38 @@
1
+ import path from 'node:path';
2
+ import { readJson, writeJsonAtomic } from '../fsx.js';
3
+ import { buildCodexPluginInventory } from './codex-plugin-json.js';
4
+ export function codexPluginInventoryCachePath(root) {
5
+ return path.join(root, '.sneakoscope', 'cache', 'codex-plugin-inventory.json');
6
+ }
7
+ export async function readCodexPluginInventoryCache(root) {
8
+ const cache = await readJson(codexPluginInventoryCachePath(root), null);
9
+ return cache?.schema === 'sks.codex-plugin-inventory-cache.v1' ? cache : null;
10
+ }
11
+ export async function writeCodexPluginInventoryCache(root, inventory, ttlMs = defaultTtlMs()) {
12
+ const generatedAt = new Date();
13
+ const cache = {
14
+ schema: 'sks.codex-plugin-inventory-cache.v1',
15
+ generated_at: generatedAt.toISOString(),
16
+ expires_at: new Date(generatedAt.getTime() + ttlMs).toISOString(),
17
+ ttl_ms: ttlMs,
18
+ inventory
19
+ };
20
+ await writeJsonAtomic(codexPluginInventoryCachePath(root), cache);
21
+ return cache;
22
+ }
23
+ export async function getCodexPluginInventoryCached(root, opts = {}) {
24
+ const ttlMs = Math.max(1, Number(opts.ttlMs || defaultTtlMs()) || defaultTtlMs());
25
+ const cachePath = codexPluginInventoryCachePath(root);
26
+ const existing = opts.forceRefresh ? null : await readCodexPluginInventoryCache(root);
27
+ if (existing && Date.parse(existing.expires_at) > Date.now()) {
28
+ return { inventory: existing.inventory, cache_hit: true, cache_path: cachePath, cache: existing };
29
+ }
30
+ const inventory = await (opts.inventoryFactory || buildCodexPluginInventory)();
31
+ const cache = await writeCodexPluginInventoryCache(root, inventory, ttlMs);
32
+ return { inventory, cache_hit: false, cache_path: cachePath, cache };
33
+ }
34
+ function defaultTtlMs() {
35
+ const value = Number(process.env.SKS_CODEX_PLUGIN_CACHE_TTL_MS || 10 * 60 * 1000);
36
+ return Number.isFinite(value) && value > 0 ? value : 10 * 60 * 1000;
37
+ }
38
+ //# sourceMappingURL=codex-plugin-cache.js.map
@@ -0,0 +1,73 @@
1
+ import path from 'node:path';
2
+ import { writeJsonAtomic } from '../fsx.js';
3
+ export function diffCodexPluginInventories(previous, current) {
4
+ const prev = pluginMap(previous);
5
+ const next = pluginMap(current);
6
+ const added = [...next.keys()].filter((id) => !prev.has(id)).sort();
7
+ const removed = [...prev.keys()].filter((id) => !next.has(id)).sort();
8
+ const shared = [...next.keys()].filter((id) => prev.has(id));
9
+ const changedRemote = [];
10
+ const changedTemplates = [];
11
+ const changedPrompts = [];
12
+ const changedMetadata = [];
13
+ for (const id of shared) {
14
+ const before = prev.get(id);
15
+ const after = next.get(id);
16
+ if (!sameJson(normalizePluginMetadata(before), normalizePluginMetadata(after)))
17
+ changedMetadata.push(id);
18
+ if (!sameJson(normalizeRemoteServers(before?.remote_mcp_servers), normalizeRemoteServers(after?.remote_mcp_servers)))
19
+ changedRemote.push(id);
20
+ if (!sameJson(sorted(before?.unavailable_app_templates), sorted(after?.unavailable_app_templates)))
21
+ changedTemplates.push(id);
22
+ if (!sameJson(sorted(before?.default_prompts), sorted(after?.default_prompts)))
23
+ changedPrompts.push(id);
24
+ }
25
+ const changedCount = added.length + removed.length + changedMetadata.length + changedRemote.length + changedTemplates.length + changedPrompts.length;
26
+ return {
27
+ schema: 'sks.codex-plugin-inventory-diff.v1',
28
+ generated_at: new Date().toISOString(),
29
+ ok: true,
30
+ added_plugins: added,
31
+ removed_plugins: removed,
32
+ changed_remote_mcp_servers: changedRemote.sort(),
33
+ changed_unavailable_app_templates: changedTemplates.sort(),
34
+ changed_default_prompts: changedPrompts.sort(),
35
+ changed_plugin_metadata: changedMetadata.sort(),
36
+ changed_count: changedCount
37
+ };
38
+ }
39
+ export async function writeCodexPluginInventoryDiff(root, previous, current) {
40
+ const diff = diffCodexPluginInventories(previous, current);
41
+ const artifact = path.join(root, '.sneakoscope', 'codex-plugin-inventory.diff.json');
42
+ await writeJsonAtomic(artifact, diff);
43
+ return { diff, artifact };
44
+ }
45
+ function pluginMap(inventory) {
46
+ const map = new Map();
47
+ for (const plugin of inventory?.plugins || [])
48
+ map.set(String(plugin.id || plugin.name), plugin);
49
+ return map;
50
+ }
51
+ function normalizeRemoteServers(rows) {
52
+ return (rows || []).map((row) => ({
53
+ name: row.name,
54
+ url: row.url,
55
+ auth_type: row.auth_type
56
+ })).sort((a, b) => `${a.name}:${a.url}:${a.auth_type}`.localeCompare(`${b.name}:${b.url}:${b.auth_type}`));
57
+ }
58
+ function normalizePluginMetadata(row) {
59
+ return {
60
+ id: row?.id || null,
61
+ name: row?.name || null,
62
+ source: row?.source || null,
63
+ installed: row?.installed === true,
64
+ enabled: row?.enabled === true
65
+ };
66
+ }
67
+ function sorted(rows) {
68
+ return [...(rows || [])].map(String).sort();
69
+ }
70
+ function sameJson(a, b) {
71
+ return JSON.stringify(a) === JSON.stringify(b);
72
+ }
73
+ //# sourceMappingURL=codex-plugin-diff.js.map