sneakoscope 3.1.3 → 3.1.5

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 (51) hide show
  1. package/README.md +1 -1
  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/cli/install-helpers.js +56 -4
  8. package/dist/commands/codex-app.js +45 -1
  9. package/dist/commands/codex-lb.js +12 -9
  10. package/dist/commands/doctor.js +44 -1
  11. package/dist/core/codex-app/codex-agent-role-sync.js +119 -0
  12. package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
  13. package/dist/core/codex-app/codex-app-execution-profile.js +39 -0
  14. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
  15. package/dist/core/codex-app/codex-app-harness-matrix.js +127 -0
  16. package/dist/core/codex-app/codex-app-types.js +21 -0
  17. package/dist/core/codex-app/codex-hook-approval-probe.js +188 -0
  18. package/dist/core/codex-app/codex-hook-lifecycle.js +61 -0
  19. package/dist/core/codex-app/codex-init-deep.js +180 -0
  20. package/dist/core/codex-app/codex-skill-sync.js +164 -0
  21. package/dist/core/codex-app/lazycodex-analysis.js +72 -0
  22. package/dist/core/codex-app/lazycodex-interop-policy.js +60 -0
  23. package/dist/core/codex-app/lazycodex-live-analyzer.js +98 -0
  24. package/dist/core/commands/loop-command.js +11 -0
  25. package/dist/core/commands/mad-sks-command.js +113 -17
  26. package/dist/core/commands/qa-loop-command.js +3 -2
  27. package/dist/core/commands/research-command.js +2 -2
  28. package/dist/core/doctor/doctor-readiness-matrix.js +7 -0
  29. package/dist/core/doctor/doctor-zellij-repair.js +40 -0
  30. package/dist/core/feature-fixtures.js +1 -0
  31. package/dist/core/feature-registry.js +4 -1
  32. package/dist/core/fsx.js +1 -1
  33. package/dist/core/hooks-runtime.js +13 -0
  34. package/dist/core/init.js +4 -1
  35. package/dist/core/loops/loop-continuation-enforcer.js +40 -0
  36. package/dist/core/loops/loop-planner.js +29 -3
  37. package/dist/core/loops/loop-worker-runtime.js +27 -7
  38. package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
  39. package/dist/core/qa-loop.js +39 -4
  40. package/dist/core/research/research-cycle-runner.js +1 -0
  41. package/dist/core/research/research-stage-runner.js +9 -2
  42. package/dist/core/research.js +35 -1
  43. package/dist/core/version.js +1 -1
  44. package/dist/core/zellij/homebrew-policy.js +44 -0
  45. package/dist/core/zellij/zellij-capability.js +32 -3
  46. package/dist/core/zellij/zellij-self-heal-types.js +45 -0
  47. package/dist/core/zellij/zellij-self-heal.js +414 -0
  48. package/dist/core/zellij/zellij-update.js +39 -6
  49. package/dist/scripts/sks-3-1-4-directive-check-lib.js +241 -0
  50. package/dist/scripts/sks-3-1-5-directive-check-lib.js +347 -0
  51. package/package.json +52 -2
package/README.md CHANGED
@@ -35,7 +35,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.1.3** hardens Loop Mesh for production use: fixture workers/gates/final arbiters are test-only, `gpt:final-arbiter` is explicitly finalizer-owned, integration merge uses a strategy ladder, side-effect reports come from loop diffs and mutation ledgers, loop kill interrupts active worker sessions, and global loop concurrency budgets prevent worker/model-call oversubscription. It remains Codex 0.139-aware while bundling @openai/codex-sdk 0.138.0 at this release boundary; see [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md), [docs/codex-0.139-real-probes.md](docs/codex-0.139-real-probes.md), [docs/loop-fixture-policy.md](docs/loop-fixture-policy.md), and [docs/loop-merge-strategy.md](docs/loop-merge-strategy.md).
38
+ SKS **3.1.5** productionizes the Codex App harness with typed probe surfaces, dry-run Zellij self-heal planning, hook approval and `agent_type` evidence, LazyCodex live-source analysis, init-deep directory memory hints, and execution profile routing across Loop, QA, Research, and Naruto artifacts.
39
39
 
40
40
  SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
41
41
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.3"
79
+ version = "3.1.5"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "3.1.3"
3
+ version = "3.1.5"
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 3.1.3"),
7
+ Some("--version") => println!("sks-rs 3.1.5"),
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": "3.1.3",
5
- "source_digest": "4a78851e9e9d4b9101b268276b0ed8776e2d41fa0615a6aced9f7371216618ce",
6
- "source_file_count": 2448,
7
- "built_at_source_time": 1781415222161
4
+ "package_version": "3.1.5",
5
+ "source_digest": "89b81e91ec4d82c33320922ae0dc33bd6562ccf37c58171858e7f703d7767e4b",
6
+ "source_file_count": 2518,
7
+ "built_at_source_time": 1781439913355
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.1.3';
2
+ const FAST_PACKAGE_VERSION = '3.1.5';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -144,6 +144,8 @@ async function reportPostinstallCodexLbAuth() {
144
144
  console.log(`codex-lb auth: restored from existing Codex login cache into ${codexLbAuth.env_path}.`);
145
145
  else if (codexLbAuth.status === 'synced' || codexLbAuth.status === 'present' || codexLbAuth.status === 'repaired')
146
146
  console.log(`codex-lb auth: preserved from ${codexLbAuth.env_path}.`);
147
+ else if (codexLbAuth.status === 'present_unselected')
148
+ console.log('codex-lb auth: preserved but not selected; ChatGPT OAuth remains active.');
147
149
  else if (codexLbAuth.status === 'skipped')
148
150
  console.log(`codex-lb auth: skipped (${codexLbAuth.reason}).`);
149
151
  else if (codexLbAuth.status === 'missing_env_key')
@@ -284,6 +286,7 @@ async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME |
284
286
  env_path: envPath,
285
287
  auth_path: authPath,
286
288
  base_url: baseUrl ? normalizeCodexLbBaseUrl(baseUrl) : null,
289
+ selected: hasTopLevelCodexLbSelected(config),
287
290
  auth_existed: authExisted,
288
291
  auth_text: authText
289
292
  };
@@ -294,7 +297,7 @@ async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
294
297
  let configRestored = false;
295
298
  if (snapshot.base_url) {
296
299
  const current = await readText(snapshot.config_path, '');
297
- const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, snapshot.base_url));
300
+ const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, snapshot.base_url, snapshot.selected === true));
298
301
  const alreadyOk = next === ensureTrailingNewline(current) && codexLbProviderBaseUrl(current);
299
302
  if (!alreadyOk) {
300
303
  const safeWrite = await safeWriteCodexConfigToml(snapshot.config_path, current, next, 'codex-lb-restore');
@@ -488,6 +491,12 @@ export async function codexLbStatus(opts = {}) {
488
491
  const providerEnvKey = codexLbProviderEnvKey(config);
489
492
  const providerUsesCodexLbEnvAuth = providerConfigured && providerEnvKey === 'CODEX_LB_API_KEY' && providerOpenAiAuthDisabled;
490
493
  const codexAppUsableWithCodexLb = providerUsesCodexLbEnvAuth && envKeyConfigured && Boolean(baseUrl);
494
+ const launchEnvironment = await inspectCodexLbMacLaunchEnvironment(baseUrl, opts).catch((err) => ({
495
+ checked: true,
496
+ available: false,
497
+ status: 'inspect_failed',
498
+ error: err.message
499
+ }));
491
500
  return {
492
501
  ok: providerConfigured && envKeyConfigured && Boolean(baseUrl) && providerUsesCodexLbEnvAuth,
493
502
  config_path: configPath,
@@ -514,7 +523,8 @@ export async function codexLbStatus(opts = {}) {
514
523
  auth_path: authPath,
515
524
  auth_mode: authMode.mode,
516
525
  auth_usable_for_codex_app: authMode.codex_app_usable || codexAppUsableWithCodexLb,
517
- auth_summary: codexAppUsableWithCodexLb ? 'codex-lb provider uses CODEX_LB_API_KEY env_key; OpenAI OAuth not required' : authMode.summary
526
+ auth_summary: codexAppUsableWithCodexLb ? 'codex-lb provider uses CODEX_LB_API_KEY env_key; OpenAI OAuth not required' : authMode.summary,
527
+ launch_environment: launchEnvironment
518
528
  };
519
529
  }
520
530
  export function formatCodexLbStatusText(status = {}, opts = {}) {
@@ -541,12 +551,12 @@ export function formatCodexLbStatusText(status = {}, opts = {}) {
541
551
  lines.push('', 'Sign in to Codex App/CLI again, then run: sks codex-lb repair');
542
552
  else if (status.ok && !status.selected)
543
553
  lines.push('', 'Run: sks codex-lb repair to activate codex-lb for Codex App.');
554
+ else if (status.ok)
555
+ lines.push('', 'Status: codex-lb active; no repair needed.');
544
556
  else if (!status.ok && status.base_url && status.env_key_configured)
545
557
  lines.push('', 'Run: sks codex-lb repair to restore the upstream codex-lb provider block.');
546
558
  else if (!status.ok)
547
559
  lines.push('', 'Run: sks codex-lb setup --host <domain> --api-key <key>');
548
- else
549
- lines.push('', 'Repair provider auth: sks codex-lb repair');
550
560
  if (backupPresent)
551
561
  lines.push('Switch fully away from codex-lb: sks codex-lb release');
552
562
  return `${lines.join('\n')}\n`;
@@ -862,6 +872,17 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
862
872
  const status = await codexLbStatus(opts);
863
873
  if (!status.selected && !status.provider_configured && !status.env_file)
864
874
  return { status: 'not_configured', codex_lb: status };
875
+ if (status.ok && !status.selected && status.auth_mode === 'chatgpt_oauth') {
876
+ return {
877
+ ok: true,
878
+ status: 'present_unselected',
879
+ reason: 'chatgpt_oauth_active_codex_lb_unselected',
880
+ config_path: status.config_path,
881
+ env_path: status.env_path,
882
+ base_url: status.base_url,
883
+ codex_lb: status
884
+ };
885
+ }
865
886
  await migrateCodexAuthKeyFormat({ home: opts.home });
866
887
  if (status.ok && (!status.selected || !status.provider_uses_codex_lb_env_auth))
867
888
  return repairCodexLbAuth(opts);
@@ -1236,6 +1257,9 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
1236
1257
  if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1')
1237
1258
  return { status: 'skipped' };
1238
1259
  let status = await codexLbStatus(opts);
1260
+ if (status.env_key_configured && status.base_url && !status.selected && status.auth_mode === 'chatgpt_oauth') {
1261
+ return { status: 'continued_to_codex', ok: false, chain_health: null, codex_lb: status, reason: 'chatgpt_oauth_active_codex_lb_unselected' };
1262
+ }
1239
1263
  if (status.env_key_configured && status.base_url && (!status.provider_configured || !status.selected || !status.provider_uses_codex_lb_env_auth)) {
1240
1264
  let promptedRestore = false;
1241
1265
  if (!status.provider_configured && canAskYesNo()) {
@@ -1355,6 +1379,34 @@ async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
1355
1379
  return { ok: false, status: 'launch_env_failed', variables: results.map((result) => result.key), failed, error: failed.map((result) => `${result.key}: ${result.error}`).join('; ') };
1356
1380
  return { ok: true, status: 'synced', variables: results.map((result) => result.key) };
1357
1381
  }
1382
+ async function inspectCodexLbMacLaunchEnvironment(baseUrl = '', opts = {}) {
1383
+ if (process.platform !== 'darwin' && !opts.forceLaunchEnv)
1384
+ return { checked: false, status: 'not_macos', skipped: true };
1385
+ const launchctl = opts.launchctlBin || await which('launchctl').catch(() => null) || await exists('/bin/launchctl').then((ok) => ok ? '/bin/launchctl' : null).catch(() => null);
1386
+ if (!launchctl)
1387
+ return { checked: true, available: false, status: 'launchctl_missing' };
1388
+ const readVar = async (key) => {
1389
+ const result = await runProcess(launchctl, ['getenv', key], { timeoutMs: 3000, maxOutputBytes: 8192 });
1390
+ return result.code === 0 ? String(result.stdout || '').trim() : '';
1391
+ };
1392
+ const currentBaseUrl = await readVar('CODEX_LB_BASE_URL');
1393
+ const currentApiKey = await readVar('CODEX_LB_API_KEY');
1394
+ const baseMatches = !baseUrl || currentBaseUrl === String(baseUrl || '').trim();
1395
+ const basePresent = Boolean(currentBaseUrl);
1396
+ const keyPresent = Boolean(currentApiKey);
1397
+ return {
1398
+ checked: true,
1399
+ available: true,
1400
+ status: basePresent && keyPresent && baseMatches ? 'synced' : basePresent || keyPresent ? 'partial' : 'missing',
1401
+ variables: [
1402
+ ...(keyPresent ? ['CODEX_LB_API_KEY'] : []),
1403
+ ...(basePresent ? ['CODEX_LB_BASE_URL'] : [])
1404
+ ],
1405
+ base_url_present: basePresent,
1406
+ base_url_matches: baseMatches,
1407
+ api_key_present: keyPresent
1408
+ };
1409
+ }
1358
1410
  async function maybeSyncCodexLbSharedLogin(apiKey, opts = {}) {
1359
1411
  if (!apiKey)
1360
1412
  return { ok: false, status: 'missing_env_key' };
@@ -2,10 +2,35 @@ import { flag } from '../cli/args.js';
2
2
  import { printJson } from '../cli/output.js';
3
3
  import { codexAccessTokenStatus, codexAppIntegrationStatus, codexChromeExtensionStatus, codexProductDesignPluginStatus, formatCodexAppStatus, formatCodexProductDesignPluginStatus } from '../core/codex-app.js';
4
4
  import { codexAppRemoteControlCommand } from '../cli/codex-app-command.js';
5
+ import { sksRoot } from '../core/fsx.js';
6
+ import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
7
+ import { syncCodexSksSkills } from '../core/codex-app/codex-skill-sync.js';
8
+ import { syncCodexAgentRoles } from '../core/codex-app/codex-agent-role-sync.js';
9
+ import { runCodexInitDeep } from '../core/codex-app/codex-init-deep.js';
10
+ import { buildCodexHookLifecycle } from '../core/codex-app/codex-hook-lifecycle.js';
11
+ import { resolveCodexAppExecutionProfile } from '../core/codex-app/codex-app-execution-profile.js';
12
+ import { buildLazyCodexInteropPolicy } from '../core/codex-app/lazycodex-interop-policy.js';
5
13
  export async function run(_command, args = []) {
6
14
  const action = args[0] || 'check';
7
15
  if (action === 'remote-control' || action === 'remote')
8
16
  return codexAppRemoteControlCommand(args.slice(1));
17
+ if (action === 'harness-matrix')
18
+ return printCodexAppResult(args, await buildCodexAppHarnessMatrix({ root: await sksRoot(), applyRepairs: flag(args, '--fix') || flag(args, '--apply') }));
19
+ if (action === 'skill-sync')
20
+ return printCodexAppResult(args, await syncCodexSksSkills({ root: await sksRoot(), apply: flag(args, '--apply') || flag(args, '--fix') }));
21
+ if (action === 'agent-role-sync')
22
+ return printCodexAppResult(args, await syncCodexAgentRoles({ root: await sksRoot(), apply: flag(args, '--apply') || flag(args, '--fix') }));
23
+ if (action === 'init-deep')
24
+ return printCodexAppResult(args, await runCodexInitDeep({ root: await sksRoot(), apply: !flag(args, '--check-only') && !flag(args, '--dry-run') }));
25
+ if (action === 'hook-lifecycle')
26
+ return printCodexAppResult(args, await buildCodexHookLifecycle({ root: await sksRoot(), apply: flag(args, '--apply') || flag(args, '--fix') }));
27
+ if (action === 'execution-profile')
28
+ return printCodexAppResult(args, await resolveCodexAppExecutionProfile({ root: await sksRoot() }));
29
+ if (action === 'interop' && args[1] === 'lazycodex') {
30
+ const modeArg = readOption(args, '--mode', 'coexist');
31
+ const mode = modeArg === 'sks-primary' || modeArg === 'handoff-to-omo' ? modeArg : 'coexist';
32
+ return printCodexAppResult(args, await buildLazyCodexInteropPolicy({ root: await sksRoot(), mode }));
33
+ }
9
34
  if (action === 'product-design' || action === 'design-product' || action === 'ensure-product-design') {
10
35
  const checkOnly = flag(args, '--check-only') || flag(args, '--no-install');
11
36
  const status = await codexProductDesignPluginStatus({
@@ -66,7 +91,26 @@ export async function run(_command, args = []) {
66
91
  process.exitCode = 1;
67
92
  return;
68
93
  }
69
- console.error('Usage: sks codex-app check|status|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
94
+ console.error('Usage: sks codex-app check|status|harness-matrix|skill-sync|agent-role-sync|init-deep|hook-lifecycle|execution-profile|interop lazycodex [--mode coexist]|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
70
95
  process.exitCode = 1;
71
96
  }
97
+ function printCodexAppResult(args = [], result) {
98
+ if (flag(args, '--json')) {
99
+ printJson(result);
100
+ if (result?.ok === false)
101
+ process.exitCode = 1;
102
+ return;
103
+ }
104
+ console.log(`${result?.schema || 'sks.codex-app-result'}: ${result?.ok === false ? 'blocked' : 'ok'}`);
105
+ for (const blocker of result?.blockers || [])
106
+ console.log(`- blocker: ${blocker}`);
107
+ for (const warning of result?.warnings || [])
108
+ console.log(`- warning: ${warning}`);
109
+ if (result?.ok === false)
110
+ process.exitCode = 1;
111
+ }
112
+ function readOption(args = [], name, fallback) {
113
+ const index = args.indexOf(name);
114
+ return index >= 0 && args[index + 1] ? String(args[index + 1]) : fallback;
115
+ }
72
116
  //# sourceMappingURL=codex-app.js.map
@@ -302,16 +302,19 @@ async function resolveNewApiKey(args = []) {
302
302
  return '';
303
303
  }
304
304
  function shapeCodexLbStatus(status = {}) {
305
- const mode = status.env_loader?.api_key?.source === 'env-file'
306
- ? 'durable_env_file'
307
- : status.env_loader?.api_key?.source === 'keychain'
308
- ? 'durable_keychain'
309
- : status.env_loader?.api_key?.source === 'process.env'
310
- ? 'process_only_ephemeral'
311
- : 'none';
305
+ const modes = [];
306
+ if (status.env_file && status.env_key_configured)
307
+ modes.push('durable_env_file');
308
+ if (status.env_loader?.api_key?.source === 'keychain' || status.env_loader?.keychain?.status === 'present')
309
+ modes.push('durable_keychain');
310
+ if (status.launch_environment?.status === 'synced')
311
+ modes.push('durable_launchctl');
312
+ if (!modes.length && status.env_loader?.api_key?.source === 'process.env')
313
+ modes.push('process_only_ephemeral');
314
+ const mode = modes[0] || 'none';
312
315
  const persistence = codexLbPersistenceSummary({
313
- selectedModes: mode === 'none' ? [] : [mode],
314
- appliedModes: mode === 'none' ? ['none'] : [mode],
316
+ selectedModes: modes.length ? modes : [],
317
+ appliedModes: modes.length ? modes : mode === 'none' ? ['none'] : [mode],
315
318
  processOnly: mode === 'process_only_ephemeral'
316
319
  });
317
320
  return {
@@ -23,6 +23,8 @@ import { writeCodex0138CapabilityArtifacts } from '../core/codex-control/codex-0
23
23
  import { runCodex0138Doctor } from '../core/doctor/codex-0138-doctor.js';
24
24
  import { writeCodexPluginInventoryArtifacts, pluginAppTemplatePolicy } from '../core/codex-plugins/codex-plugin-json.js';
25
25
  import { writeMcpPluginInventoryArtifacts } from '../core/mcp/mcp-plugin-inventory.js';
26
+ import { runDoctorZellijRepair, doctorZellijRepairConsoleLine } from '../core/doctor/doctor-zellij-repair.js';
27
+ import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
26
28
  export async function run(_command, args = []) {
27
29
  const doctorFix = flag(args, '--fix');
28
30
  let setupRepair = null;
@@ -149,6 +151,23 @@ export async function run(_command, args = []) {
149
151
  blockers: [err?.message || String(err)]
150
152
  }))
151
153
  : codexAppUiPlan;
154
+ const zellijRepair = await runDoctorZellijRepair({ root, args, doctorFix }).catch((err) => ({
155
+ schema: 'sks.zellij-self-heal.v1',
156
+ ok: false,
157
+ requested_by: 'doctor --fix',
158
+ fix_requested: doctorFix,
159
+ auto_approved: flag(args, '--yes') || flag(args, '-y'),
160
+ install_homebrew_allowed: false,
161
+ before: { status: 'unknown', version: null, bin: null },
162
+ latest_version: null,
163
+ strategy: 'failed',
164
+ command: 'sks doctor --fix --yes',
165
+ after: { status: 'unknown', version: null, bin: null },
166
+ mutation_guard_artifact: null,
167
+ homebrew: { present: false, bin: null, install_attempted: false, install_allowed: false },
168
+ blockers: [err?.message || String(err)],
169
+ warnings: []
170
+ }));
152
171
  const zellij = await checkZellijCapability({ root, require: process.env.SKS_REQUIRE_ZELLIJ === '1' });
153
172
  const localModel = await readLocalModelConfig().catch(() => null);
154
173
  const permissionProfiles = await inventoryCodexPermissionProfiles(root, { writeReport: true });
@@ -178,6 +197,15 @@ export async function run(_command, args = []) {
178
197
  const mcpPluginInventory = pluginInventory?.report
179
198
  ? await writeMcpPluginInventoryArtifacts(root, { inventory: pluginInventory.report }).catch((err) => ({ error: err?.message || String(err), candidates: null }))
180
199
  : null;
200
+ const codexAppHarnessMatrix = await buildCodexAppHarnessMatrix({ root, applyRepairs: doctorFix }).catch((err) => ({
201
+ schema: 'sks.codex-app-harness-matrix.v1',
202
+ ok: false,
203
+ codex_cli: { available: false, version: null },
204
+ app_features: {},
205
+ sks_integrations: {},
206
+ blockers: [err?.message || String(err)],
207
+ warnings: []
208
+ }));
181
209
  const pkgBytes = await dirSize(root).catch(() => 0);
182
210
  const ready = await writeDoctorReadinessMatrix(root, {
183
211
  codex,
@@ -194,10 +222,12 @@ export async function run(_command, args = []) {
194
222
  codex_0138_doctor: codex0138Doctor,
195
223
  codex_plugin_inventory: pluginInventory?.report || null,
196
224
  codex_plugin_app_template_policy: pluginPolicy,
225
+ codex_app_harness_matrix: codexAppHarnessMatrix,
197
226
  require_codex_cli_config_load: flag(args, '--fix') || flag(args, '--require-actual-codex'),
198
227
  operator_actions: [
199
228
  ...(codexConfig.operator_actions || []),
200
229
  ...(configRepair?.operator_actions || []),
230
+ ...(zellijRepair && !zellijRepair.ok && zellijRepair.command ? [`Run: ${zellijRepair.command}`] : []),
201
231
  ...(pluginPolicy?.doctor_warnings || [])
202
232
  ]
203
233
  });
@@ -217,6 +247,7 @@ export async function run(_command, args = []) {
217
247
  codex_doctor: codexDoctor,
218
248
  codex_doctor_diff: codexDoctorDiff,
219
249
  zellij,
250
+ zellij_repair: zellijRepair,
220
251
  local_model: localModel,
221
252
  agent_role_config: agentRoleConfigRepair,
222
253
  zellij_readiness: zellijReadiness,
@@ -233,10 +264,11 @@ export async function run(_command, args = []) {
233
264
  plugin_app_template_policy: pluginPolicy,
234
265
  mcp_plugin_inventory: mcpPluginInventory?.candidates || null
235
266
  },
267
+ codex_app_harness_matrix: codexAppHarnessMatrix,
236
268
  ready,
237
269
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
238
270
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
239
- repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair }
271
+ repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup, agent_role_config: agentRoleConfigRepair, zellij: zellijRepair }
240
272
  };
241
273
  if (flag(args, '--json')) {
242
274
  printJson(result);
@@ -260,9 +292,20 @@ export async function run(_command, args = []) {
260
292
  console.log(` pane proof: ${zellijReadiness.pane_proof}`);
261
293
  console.log(` screen proof:${zellijReadiness.screen_proof}`);
262
294
  console.log(` tmux: ${zellijReadiness.tmux_removed_runtime ? 'removed_runtime' : 'present'}`);
295
+ const zellijRepairLine = doctorZellijRepairConsoleLine(zellijRepair);
296
+ if (zellijRepairLine)
297
+ console.log(zellijRepairLine);
263
298
  console.log(` codex doctor: ${codexDoctor.available ? (codexDoctor.exit_code === 0 ? 'ok' : 'warning') : 'unavailable'}`);
264
299
  console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
265
300
  console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
301
+ console.log('Codex App Harness:');
302
+ console.log(` plugins: ${codexAppHarnessMatrix.app_features?.plugin_json ? 'ok' : 'degraded'}`);
303
+ console.log(` hook approval: ${codexAppHarnessMatrix.app_features?.hook_approval_state_detectable ? 'ok' : 'unknown'}`);
304
+ console.log(` skills: ${codexAppHarnessMatrix.sks_integrations?.dollar_skills_synced ? 'ok' : 'degraded'}`);
305
+ console.log(` agent roles: ${codexAppHarnessMatrix.sks_integrations?.agent_roles_synced ? 'ok' : 'degraded'}`);
306
+ console.log(` native agent_type: ${codexAppHarnessMatrix.app_features?.agent_type_supported ? 'ok' : 'fallback message-role'}`);
307
+ console.log(` init-deep memory: ${codexAppHarnessMatrix.sks_integrations?.init_deep_available ? 'available' : 'missing'}`);
308
+ console.log(` loop mesh app profile: ${codexAppHarnessMatrix.sks_integrations?.loop_mesh_app_profile_available ? 'available' : 'missing'}`);
266
309
  console.log('Codex App UI:');
267
310
  console.log(` fast selector: ${codexAppUi.fast_selector || 'unknown'}`);
268
311
  console.log(` provider selector: ${codexAppUi.provider_selector || 'unknown'}`);
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { nowIso, writeJsonAtomic, writeTextAtomic, ensureDir } from '../fsx.js';
5
+ import { repairAgentRoleConfigs } from '../agents/agent-role-config.js';
6
+ import { agentRolePayloadFor, probeCodexAgentTypeSupport } from './codex-agent-type-probe.js';
7
+ const DIRECTIVE_ROLES = [
8
+ 'sks-explorer',
9
+ 'sks-planner',
10
+ 'sks-implementer',
11
+ 'sks-checker',
12
+ 'sks-release-verifier',
13
+ 'sks-zellij-ui-verifier',
14
+ 'sks-codex-probe-verifier'
15
+ ];
16
+ export async function syncCodexAgentRoles(input) {
17
+ const root = path.resolve(input.root);
18
+ const env = input.env || process.env;
19
+ const codexHome = input.codexHome || process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
20
+ const targetDir = path.join(codexHome, 'agents');
21
+ const baseRepair = await repairAgentRoleConfigs({
22
+ root,
23
+ apply: input.apply === true,
24
+ codexHome,
25
+ reportPath: path.join(root, '.sneakoscope', 'reports', 'agent-role-config-repair.json')
26
+ }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
27
+ const agentTypeProbe = input.agentTypeSupported === undefined
28
+ ? await probeCodexAgentTypeSupport(root, { env }).catch((err) => ({
29
+ schema: 'sks.codex-agent-type-probe.v1',
30
+ generated_at: nowIso(),
31
+ ok: false,
32
+ supported: false,
33
+ source: 'unknown',
34
+ spawn_tool_name: 'unknown',
35
+ schema_path: null,
36
+ evidence: [],
37
+ blockers: [messageOf(err)],
38
+ warnings: ['agent_type_probe_failed_message_role_fallback']
39
+ }))
40
+ : {
41
+ schema: 'sks.codex-agent-type-probe.v1',
42
+ generated_at: nowIso(),
43
+ ok: true,
44
+ supported: input.agentTypeSupported,
45
+ source: 'fixture',
46
+ spawn_tool_name: input.agentTypeSupported ? 'spawn_agent' : 'unknown',
47
+ schema_path: input.agentTypeSupported ? 'input.agentTypeSupported' : null,
48
+ evidence: [`input.agentTypeSupported=${input.agentTypeSupported}`],
49
+ blockers: [],
50
+ warnings: []
51
+ };
52
+ const rolePayloads = Object.fromEntries(DIRECTIVE_ROLES.map((role) => [role, agentRolePayloadFor(role, agentTypeProbe)]));
53
+ const created = [];
54
+ if (input.apply === true) {
55
+ await ensureDir(targetDir);
56
+ for (const role of DIRECTIVE_ROLES) {
57
+ const file = path.join(targetDir, `${role}.toml`);
58
+ const current = await fs.readFile(file, 'utf8').catch(() => '');
59
+ if (current && !current.includes('SKS managed 3.1.4 directive role') && !current.includes('SKS managed 3.1.5 directive role'))
60
+ continue;
61
+ await writeTextAtomic(file, roleToml(role, rolePayloads[role]));
62
+ created.push(file);
63
+ }
64
+ }
65
+ const report = {
66
+ schema: 'sks.codex-agent-role-sync.v1',
67
+ generated_at: nowIso(),
68
+ ok: recordOk(baseRepair) !== false && agentTypeProbe.ok !== false,
69
+ apply: input.apply === true,
70
+ agent_type_supported: agentTypeProbe.supported,
71
+ fallback: agentTypeProbe.supported ? 'agent_type' : 'message-role',
72
+ codex_home: codexHome,
73
+ directive_roles: DIRECTIVE_ROLES,
74
+ role_payloads: rolePayloads,
75
+ agent_type_probe: agentTypeProbe,
76
+ created,
77
+ base_repair: baseRepair,
78
+ blockers: blockersOf(baseRepair)
79
+ };
80
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-agent-role-sync.json'), report).catch(() => undefined);
81
+ return report;
82
+ }
83
+ function roleToml(role, payload) {
84
+ const strategyLine = payload?.strategy === 'agent_type'
85
+ ? `agent_type = "${payload.agent_type || role}"`
86
+ : `message_role_prefix = "${escapeToml(payload?.message_role_prefix || `Role: ${role}.`)}"`;
87
+ return [
88
+ `name = "${role}"`,
89
+ `description = "SKS managed 3.1.5 directive role: ${role}"`,
90
+ strategyLine,
91
+ 'model_reasoning_effort = "medium"',
92
+ role.includes('implementer') ? 'sandbox_mode = "workspace-write"' : 'sandbox_mode = "read-only"',
93
+ 'approval_policy = "never"',
94
+ 'developer_instructions = """',
95
+ `You are ${role}. SKS managed 3.1.5 directive role.`,
96
+ 'Use the assigned scope only, cite concrete repo evidence, keep mutation surfaces bounded, and never clobber user files.',
97
+ 'Report blockers as evidence-backed findings and write route artifacts before claiming completion.',
98
+ `Execution role strategy: ${payload?.strategy || 'message-role'}. Probe: ${payload?.probe_artifact_path || '.sneakoscope/reports/codex-agent-type-probe.json'}.`,
99
+ '"""',
100
+ ''
101
+ ].join('\n');
102
+ }
103
+ function blockersOf(value) {
104
+ return Boolean(value) && typeof value === 'object' && Array.isArray(value.blockers)
105
+ ? (value.blockers).map((item) => String(item)).filter(Boolean)
106
+ : [];
107
+ }
108
+ function recordOk(value) {
109
+ return Boolean(value) && typeof value === 'object' && typeof value.ok === 'boolean'
110
+ ? value.ok
111
+ : undefined;
112
+ }
113
+ function escapeToml(value) {
114
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
115
+ }
116
+ function messageOf(err) {
117
+ return err instanceof Error ? err.message : String(err);
118
+ }
119
+ //# sourceMappingURL=codex-agent-role-sync.js.map