sneakoscope 2.0.18 → 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 (39) hide show
  1. package/README.md +125 -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-model-capabilities.js +25 -4
  15. package/dist/core/codex-control/codex-model-metadata.js +91 -0
  16. package/dist/core/codex-plugins/codex-plugin-cache.js +38 -0
  17. package/dist/core/codex-plugins/codex-plugin-diff.js +73 -0
  18. package/dist/core/codex-plugins/codex-plugin-json.js +35 -11
  19. package/dist/core/commands/mad-sks-command.js +4 -0
  20. package/dist/core/commands/naruto-command.js +27 -0
  21. package/dist/core/commands/qa-loop-command.js +41 -6
  22. package/dist/core/fsx.js +1 -1
  23. package/dist/core/image/image-artifact-path-contract.js +2 -0
  24. package/dist/core/image/image-artifact-registry.js +33 -0
  25. package/dist/core/image-ux-review/imagegen-adapter.js +27 -16
  26. package/dist/core/qa-loop/qa-loop-app-handoff-confirmation.js +51 -0
  27. package/dist/core/qa-loop/qa-loop-budget-policy.js +1 -1
  28. package/dist/core/qa-loop.js +44 -3
  29. package/dist/core/release/release-gate-cache-v2.js +47 -5
  30. package/dist/core/usage/codex-account-usage.js +77 -16
  31. package/dist/core/version.js +1 -1
  32. package/dist/core/zellij/zellij-slot-pane-renderer.js +5 -2
  33. package/dist/core/zellij/zellij-slot-telemetry.js +65 -12
  34. package/dist/core/zellij/zellij-ui-mode.js +8 -1
  35. package/dist/core/zellij/zellij-update.js +307 -0
  36. package/dist/core/zellij/zellij-worker-pane-manager.js +211 -145
  37. package/package.json +22 -2
  38. package/dist/core/naruto/naruto-work-stealing.js +0 -11
  39. 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
@@ -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
@@ -19,14 +19,18 @@ export async function runCodexPluginDetailJson(pluginId) {
19
19
  return runCodexJson(bin, ['plugin', 'detail', pluginId, '--json']);
20
20
  }
21
21
  export async function buildCodexPluginInventory() {
22
+ const started = Date.now();
22
23
  const capability = await detectCodex0138Capability();
23
24
  const listJson = await runCodexPluginListJson();
24
25
  const summaries = normalizePluginList(listJson);
25
- const plugins = [];
26
- for (const summary of summaries) {
26
+ const concurrency = Math.max(1, Number(process.env.SKS_CODEX_PLUGIN_DETAIL_CONCURRENCY || 6) || 6);
27
+ let failed = 0;
28
+ const plugins = await mapWithConcurrency(summaries, concurrency, async (summary) => {
27
29
  const detail = await runCodexPluginDetailJson(summary.id || summary.name).catch((err) => ({ error: err?.message || String(err) }));
28
- plugins.push(normalizePlugin(summary, detail));
29
- }
30
+ if (detail?.error || normalizeList(detail?.blockers).length > 0)
31
+ failed += 1;
32
+ return normalizePlugin(summary, detail);
33
+ });
30
34
  const blockers = [
31
35
  ...(capability.supports_plugin_json ? [] : ['codex_0_138_plugin_json_unavailable']),
32
36
  ...normalizeList(listJson?.blockers)
@@ -35,11 +39,29 @@ export async function buildCodexPluginInventory() {
35
39
  schema: 'sks.codex-plugin-inventory.v1',
36
40
  generated_at: nowIso(),
37
41
  codex_0138_capability: capability,
42
+ fetch_concurrency: concurrency,
43
+ detail_fetch_count: summaries.length,
44
+ detail_fetch_failed_count: failed,
45
+ duration_ms: Date.now() - started,
38
46
  plugins,
39
47
  marketplace_available: plugins.some((plugin) => plugin.source === 'marketplace' || plugin.source === 'remote') || Boolean(listJson?.marketplace_available || listJson?.marketplaceAvailable),
40
48
  blockers
41
49
  };
42
50
  }
51
+ export async function mapWithConcurrency(items, concurrency, fn) {
52
+ const limit = Math.max(1, Math.floor(concurrency || 1));
53
+ const results = new Array(items.length);
54
+ let next = 0;
55
+ async function worker() {
56
+ while (next < items.length) {
57
+ const index = next;
58
+ next += 1;
59
+ results[index] = await fn(items[index]);
60
+ }
61
+ }
62
+ await Promise.all(Array.from({ length: Math.min(limit, items.length || 1) }, () => worker()));
63
+ return results;
64
+ }
43
65
  export async function writeCodexPluginInventoryArtifacts(root, inventory = null) {
44
66
  const report = inventory || await buildCodexPluginInventory();
45
67
  const artifact = path.join(root, '.sneakoscope', 'codex-plugin-inventory.json');
@@ -126,15 +148,17 @@ function boolish(value, fallback = false) {
126
148
  return fallback;
127
149
  }
128
150
  function fakePluginList() {
151
+ const count = Math.max(1, Number(process.env.SKS_CODEX_PLUGIN_JSON_FAKE_COUNT || 1) || 1);
129
152
  return {
130
153
  marketplace_available: true,
131
- plugins: [{
132
- id: 'fixture-plugin',
133
- name: 'Fixture Plugin',
134
- source: 'marketplace',
135
- installed: true,
136
- enabled: true
137
- }]
154
+ plugins: Array.from({ length: count }, (_, index) => ({
155
+ id: 'fixture-plugin',
156
+ name: index === 0 ? 'Fixture Plugin' : `Fixture Plugin ${index + 1}`,
157
+ ...(index === 0 ? {} : { id: `fixture-plugin-${index + 1}` }),
158
+ source: 'marketplace',
159
+ installed: true,
160
+ enabled: true
161
+ }))
138
162
  };
139
163
  }
140
164
  function fakePluginDetail(pluginId) {
@@ -37,6 +37,10 @@ export async function madHighCommand(args = [], deps = {}) {
37
37
  process.exitCode = 1;
38
38
  return;
39
39
  }
40
+ // Zellij is checked the same way Codex is, but it stays NON-blocking: a
41
+ // failed or skipped zellij upgrade never prevents the MAD launch.
42
+ const zellijUpdate = deps.maybePromptZellijUpdateForLaunch ? await deps.maybePromptZellijUpdateForLaunch(args, { label: 'MAD launch' }).catch(() => ({ status: 'error' })) : { status: 'skipped' };
43
+ void zellijUpdate;
40
44
  const depStatus = deps.ensureMadLaunchDependencies ? await deps.ensureMadLaunchDependencies(args) : { ready: true, actions: [] };
41
45
  if (!depStatus.ready) {
42
46
  console.error('SKS MAD launch blocked by missing dependencies.');
@@ -7,6 +7,7 @@ import { buildNarutoCloneRoster, systemSafeNarutoConcurrency } from '../agents/a
7
7
  import { DEFAULT_NARUTO_CLONES, MAX_NARUTO_AGENT_COUNT } from '../agents/agent-schema.js';
8
8
  import { resolveOllamaWorkerConfig } from '../agents/ollama-worker-config.js';
9
9
  import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/zellij-launcher.js';
10
+ import { maybePromptZellijUpdateForLaunch } from '../zellij/zellij-update.js';
10
11
  import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
11
12
  import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
12
13
  import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
@@ -15,6 +16,7 @@ import { collectActualNarutoWorker, spawnActualNarutoWorker } from '../naruto/na
15
16
  import { allocateNarutoTasksToWorkers } from '../naruto/naruto-allocation-policy.js';
16
17
  import { rebalanceNarutoReadyWork } from '../naruto/naruto-rebalance-policy.js';
17
18
  import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
19
+ import { evaluateNarutoFinalizer } from '../naruto/naruto-finalizer.js';
18
20
  import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
19
21
  import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
20
22
  import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
@@ -41,6 +43,12 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
41
43
  return narutoWorkers(parsed);
42
44
  if (parsed.action === 'proof')
43
45
  return narutoProof(parsed);
46
+ // Like the Codex CLI update prompt: check the installed zellij version and
47
+ // offer an upgrade to the latest stable release before the live session
48
+ // opens. Never blocks the run.
49
+ if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
50
+ await maybePromptZellijUpdateForLaunch(args, { label: '$Naruto launch' }).catch(() => undefined);
51
+ }
44
52
  return narutoRun(parsed);
45
53
  }
46
54
  async function narutoRun(parsed) {
@@ -299,6 +307,10 @@ async function narutoRun(parsed) {
299
307
  console.log(' parallelism mode: ' + parsed.parallelism);
300
308
  if (activeSlots < roster.agent_count)
301
309
  console.log(' cap reasons: ' + (governor.reasons.join(', ') || 'host safety cap'));
310
+ // Backpressure used to throttle silently (50% when throttled, 25% when
311
+ // saturated); always tell the operator when host pressure reduced workers.
312
+ if (governor.backpressure !== 'normal')
313
+ console.log(' backpressure: ' + governor.backpressure + ' — host resource pressure reduced active workers (memory/cpu/fd/disk thresholds)');
302
314
  if (parsed.parallelism !== 'safe' && activeSlots < 10)
303
315
  console.log(' warning: active workers below 10 in non-safe mode');
304
316
  }
@@ -418,6 +430,18 @@ async function narutoRun(parsed) {
418
430
  });
419
431
  const clones = result.roster?.agent_count ?? roster.agent_count;
420
432
  const localWorkerSummary = summarizeNarutoLocalWorkerResult(localWorker, result);
433
+ // Finalizer policy: when local LLM workers contributed patches, the GPT
434
+ // final arbiter must have accepted before patches are considered final.
435
+ const finalizer = evaluateNarutoFinalizer({
436
+ localParticipated: Number(localWorkerSummary?.selected_worker_count || 0) > 0,
437
+ gptFinalStatus: result.proof?.gpt_final_status || null,
438
+ applyPatches: writeCapable
439
+ });
440
+ await writeJsonAtomic(path.join(mission.dir, 'naruto-finalizer.json'), {
441
+ ...finalizer,
442
+ generated_at: nowIso(),
443
+ mission_id: mission.id
444
+ });
421
445
  const summary = {
422
446
  schema: NARUTO_RESULT_SCHEMA,
423
447
  ok: result.ok === true,
@@ -477,6 +501,7 @@ async function narutoRun(parsed) {
477
501
  passed: parallelRuntime.passed
478
502
  } : null,
479
503
  local_worker: localWorkerSummary,
504
+ finalizer,
480
505
  proof: result.proof?.status || 'missing',
481
506
  run: compactNarutoRunResult(result),
482
507
  zellij: null
@@ -489,6 +514,8 @@ async function narutoRun(parsed) {
489
514
  console.log('Backend: ' + result.backend);
490
515
  console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
491
516
  console.log('Proof: ' + summary.proof);
517
+ if (!finalizer.ok)
518
+ console.log('Finalizer: blocked — ' + finalizer.blockers.join(', '));
492
519
  if (summary.parallel_runtime) {
493
520
  console.log('$Naruto parallel proof:');
494
521
  console.log(' max active workers: ' + summary.parallel_runtime.max_observed_active_workers);