sneakoscope 8.3.0 → 8.3.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 (37) hide show
  1. package/README.md +5 -3
  2. package/config/skills-hash-ledger.v1.json +3 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/config/skills-manifest.json +7 -4
  6. package/dist/core/codex-app/menubar/config.js +4 -4
  7. package/dist/core/codex-app/menubar/constants.js +2 -1
  8. package/dist/core/codex-app/menubar/global-install.js +16 -0
  9. package/dist/core/codex-app/menubar/installer.js +15 -2
  10. package/dist/core/codex-app/menubar/launch-agent.js +23 -9
  11. package/dist/core/codex-app/menubar/rollback.js +12 -2
  12. package/dist/core/codex-lb/combined-catalog/normalize.js +36 -3
  13. package/dist/core/codex-lb/desktop-bridge/header-policy.js +5 -3
  14. package/dist/core/codex-lb/desktop-bridge/http-forward.js +48 -3
  15. package/dist/core/codex-lb/desktop-bridge/server.js +13 -7
  16. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +2 -1
  17. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +46 -8
  18. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +12 -2
  19. package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +34 -8
  20. package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +13 -4
  21. package/dist/core/codex-lb/desktop-controller-v3/shared.js +2 -1
  22. package/dist/core/codex-lb/desktop-controller-v3/verification.js +10 -9
  23. package/dist/core/codex-lb/desktop-service.js +57 -4
  24. package/dist/core/codex-lb/route-index.js +1 -0
  25. package/dist/core/managed-assets/managed-assets-manifest.js +1 -0
  26. package/dist/core/release/canonical-test-proof.js +8 -2
  27. package/dist/core/routes/evidence.js +1 -1
  28. package/dist/core/version.js +1 -1
  29. package/dist/native/sks-menubar/Sources/CodexLifecyclePolicy.swift +18 -0
  30. package/dist/native/sks-menubar/Sources/ProcessClient.swift +3 -1
  31. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +86 -2
  32. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +49 -15
  33. package/dist/native/sks-menubar/Sources/SKSKeychainStore.swift +21 -1
  34. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +18 -17
  35. package/dist/native/sks-menubar/Sources/StatusItemController.swift +14 -14
  36. package/dist/scripts/canonical-test-runner.js +83 -3
  37. package/package.json +1 -1
@@ -27,7 +27,8 @@ export async function ensureDesktopBridge(options, operation) {
27
27
  if (service.running)
28
28
  report = await verifyDesktopBridgeV3('shallow', options);
29
29
  const status = await desktopBridgeStatusV3(options);
30
- return commandResult(operation, true, status, { service, catalog_sync: sync, capabilities: report }, [], options);
30
+ const outcome = desktopBridgeServiceCommandOutcome(service);
31
+ return commandResult(operation, outcome.ok, status, { service, catalog_sync: sync, capabilities: report }, outcome.blockers, options);
31
32
  }
32
33
  export async function repairDesktopBridge(options) {
33
34
  let core = await loadCore(options);
@@ -43,7 +44,16 @@ export async function repairDesktopBridge(options) {
43
44
  core = await loadCore(options);
44
45
  const report = service.running ? await verifyDesktopBridgeV3('shallow', options) : null;
45
46
  const status = await desktopBridgeStatusV3(options);
46
- return commandResult('repair', true, status, { service, capabilities: report }, [], options);
47
+ const outcome = desktopBridgeServiceCommandOutcome(service);
48
+ return commandResult('repair', outcome.ok, status, { service, capabilities: report }, outcome.blockers, options);
49
+ }
50
+ export function desktopBridgeServiceCommandOutcome(service) {
51
+ const blockers = [...new Set(stringArray(service.blockers))];
52
+ const ok = service.ok && service.running && blockers.length === 0;
53
+ return {
54
+ ok,
55
+ blockers: ok ? [] : blockers.length > 0 ? blockers : ['desktop_bridge_service_not_running']
56
+ };
47
57
  }
48
58
  export async function setDefaultProvider(providerId, options) {
49
59
  const core = await loadCore(options);
@@ -6,6 +6,39 @@ import { runAuxiliarySurfacesProbeV3 } from '../probes/auxiliary-surfaces-probe.
6
6
  import { capabilityProbeResultV3 } from '../probes/probe-evidence.js';
7
7
  import { validateCapabilityDeepEvidenceV2 } from '../trusted-deep-evidence.js';
8
8
  import { bridgeClientUrl, providerCode, safeCode, timeoutMs, unique } from './shared.js';
9
+ export function textResponsePayloadValid(contentType, text) {
10
+ if (/text\/event-stream/i.test(contentType || '')) {
11
+ let completed = false;
12
+ for (const line of text.split('\n')) {
13
+ if (!line.startsWith('data:'))
14
+ continue;
15
+ const data = line.slice(5).trim();
16
+ if (!data || data === '[DONE]')
17
+ continue;
18
+ let event;
19
+ try {
20
+ event = JSON.parse(data);
21
+ }
22
+ catch {
23
+ continue;
24
+ }
25
+ const type = event && typeof event === 'object' ? event.type : null;
26
+ if (type === 'response.failed' || type === 'error')
27
+ return false;
28
+ if (type === 'response.completed')
29
+ completed = true;
30
+ }
31
+ return completed;
32
+ }
33
+ let payload = null;
34
+ try {
35
+ payload = text ? JSON.parse(text) : null;
36
+ }
37
+ catch {
38
+ payload = null;
39
+ }
40
+ return payload !== null && typeof payload === 'object';
41
+ }
9
42
  export async function probeProviderText(core, providerId, loopbackOrigin, context, options) {
10
43
  const route = core.policy
11
44
  ? Object.entries(core.policy.model_routes).find(([, target]) => target.provider_id === providerId)
@@ -69,14 +102,7 @@ export async function probeProviderText(core, providerId, loopbackOrigin, contex
69
102
  const text = await response.text();
70
103
  if (Buffer.byteLength(text) > 4 * 1024 * 1024)
71
104
  throw new Error('provider_text_response_too_large');
72
- let payload = null;
73
- try {
74
- payload = text ? JSON.parse(text) : null;
75
- }
76
- catch {
77
- payload = null;
78
- }
79
- const valid = response.ok && payload !== null && typeof payload === 'object';
105
+ const valid = response.ok && textResponsePayloadValid(response.headers.get('content-type'), text);
80
106
  const root = valid ? null : `${providerCode(providerId)}_text_response_failed`;
81
107
  return capabilityProbeResultV3({
82
108
  ...context,
@@ -44,9 +44,11 @@ export function combinedRoutePolicyProbe(core, context) {
44
44
  }
45
45
  });
46
46
  }
47
- export function combinedModelRouteProbe(core, status, context) {
47
+ export function combinedModelRouteProbe(core, status, context, liveTextResults = []) {
48
48
  const active = activeProviderIds(core);
49
49
  const verified = active.length > 0 && active.every((providerId) => Boolean(core.policy && Object.values(core.policy.model_routes).some((route) => route.provider_id === providerId)));
50
+ const liveRouteProven = verified && active.every((providerId) => liveTextResults.some((result) => result.scope === `provider:${providerId}`
51
+ && result.capability === 'text_responses' && result.state === 'verified'));
50
52
  return capabilityProbeResultV3({
51
53
  ...context,
52
54
  capability: 'model_route',
@@ -58,10 +60,11 @@ export function combinedModelRouteProbe(core, status, context) {
58
60
  blockers: verified ? [] : ['catalog_model_route_missing'],
59
61
  retryable: !verified,
60
62
  recoveryAction: verified ? null : 'refresh_catalog_or_select_supported_model',
61
- source: 'config',
63
+ source: liveRouteProven ? 'transport' : 'config',
62
64
  evidence: {
63
65
  active_provider_ids: active,
64
66
  route_count: status.catalog_sync.route_count,
67
+ live_route_proven: liveRouteProven,
65
68
  fallback: 'none'
66
69
  }
67
70
  });
@@ -119,11 +122,16 @@ export function providerAuthProbe(core, providerId, context) {
119
122
  }
120
123
  });
121
124
  }
122
- export function providerModelRouteProbe(core, providerId, context) {
125
+ export function providerModelRouteProbe(core, providerId, context, liveText = null) {
123
126
  const route = core.policy
124
127
  ? Object.entries(core.policy.model_routes).find(([, target]) => target.provider_id === providerId)
125
128
  : null;
126
129
  const verified = Boolean(route && core.catalogSync.providers[providerId].state === 'verified');
130
+ const liveRouteProven = Boolean(verified && route && liveText
131
+ && liveText.capability === 'text_responses'
132
+ && liveText.scope === `provider:${providerId}`
133
+ && liveText.state === 'verified'
134
+ && liveText.evidence.public_model === route[0]);
127
135
  return capabilityProbeResultV3({
128
136
  ...context,
129
137
  capability: 'model_route',
@@ -132,12 +140,13 @@ export function providerModelRouteProbe(core, providerId, context) {
132
140
  state: verified ? 'verified' : 'not_attempted',
133
141
  retryable: !verified,
134
142
  recoveryAction: verified ? null : 'refresh_catalog_or_select_supported_model',
135
- source: 'config',
143
+ source: liveRouteProven ? 'transport' : 'config',
136
144
  evidence: {
137
145
  provider_id: providerId,
138
146
  public_model: route?.[0] || null,
139
147
  upstream_model: route?.[1].upstream_model || null,
140
148
  catalog_generation: core.policy?.catalog_generation || null,
149
+ live_route_proven: liveRouteProven,
141
150
  fallback: 'none'
142
151
  }
143
152
  });
@@ -118,7 +118,8 @@ export async function persistRuntimeSettings(core, options, behavior = {}) {
118
118
  }
119
119
  if (!restarted.ok || !restarted.running) {
120
120
  await stopAfterFailedRestart(restartOptions, options);
121
- throw new Error(restarted.blockers[0] || 'desktop_bridge_restart_failed');
121
+ const rootCause = restarted.blockers.find((blocker) => blocker === 'desktop_bridge_entry_macos_protected_folder');
122
+ throw new Error(rootCause || restarted.blockers[0] || 'desktop_bridge_restart_failed');
122
123
  }
123
124
  }
124
125
  export async function quiesceRunningBridge(core, options) {
@@ -30,6 +30,12 @@ export async function verifyDesktopBridgeV3(requestedLevel, options = {}) {
30
30
  sessionId,
31
31
  attemptId: 1
32
32
  };
33
+ const activeProviders = activeProviderIds(core);
34
+ const enabledProviders = ['codex-lb', 'openrouter']
35
+ .filter((providerId) => core.registry.profiles[providerId].enabled);
36
+ const textResults = requestedLevel !== 'shallow'
37
+ ? await Promise.all(activeProviders.map((providerId) => probeProviderText(core, providerId, status.service.loopback_origin, probeContext, options)))
38
+ : [];
33
39
  const results = [
34
40
  ...runBridgeProbeV3({
35
41
  ...probeContext,
@@ -40,18 +46,13 @@ export async function verifyDesktopBridgeV3(requestedLevel, options = {}) {
40
46
  }),
41
47
  nativeIdentityProbe(core, probeContext),
42
48
  combinedRoutePolicyProbe(core, probeContext),
43
- combinedModelRouteProbe(core, status, probeContext)
49
+ combinedModelRouteProbe(core, status, probeContext, textResults)
44
50
  ];
45
- const activeProviders = activeProviderIds(core);
46
- const enabledProviders = ['codex-lb', 'openrouter']
47
- .filter((providerId) => core.registry.profiles[providerId].enabled);
48
51
  for (const providerId of ['codex-lb', 'openrouter']) {
49
- results.push(providerCredentialProbe(core, providerId, probeContext), providerAuthProbe(core, providerId, probeContext), providerModelRouteProbe(core, providerId, probeContext));
50
- }
51
- if (requestedLevel !== 'shallow') {
52
- const textResults = await Promise.all(activeProviders.map((providerId) => probeProviderText(core, providerId, status.service.loopback_origin, probeContext, options)));
53
- results.push(...textResults);
52
+ const liveText = textResults.find((result) => result.scope === `provider:${providerId}`) || null;
53
+ results.push(providerCredentialProbe(core, providerId, probeContext), providerAuthProbe(core, providerId, probeContext), providerModelRouteProbe(core, providerId, probeContext, liveText));
54
54
  }
55
+ results.push(...textResults);
55
56
  if (requestedLevel === 'deep') {
56
57
  const deepResults = await Promise.all(activeProviders.map((providerId) => probeProviderDeep(core, providerId, probeContext, options)));
57
58
  results.push(...deepResults.flat());
@@ -354,13 +354,16 @@ export async function installAndStartDesktopBridgeService(options = {}) {
354
354
  const domain = launchDomain(options.uid);
355
355
  await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
356
356
  await removeStaleState(paths.state_path, options.processExists);
357
- const bootstrap = await run(ctl, ['bootstrap', domain, paths.launch_agent_path], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch((error) => failedProcess(error));
357
+ const bootstrap = await bootstrapLaunchdWithRetry(options, domain, service, paths.launch_agent_path);
358
358
  if (bootstrap.code !== 0 && !bootstrap.timedOut)
359
359
  return failedStatus(paths, settings, service, 'missing', 'desktop_bridge_launchd_bootstrap_failed');
360
360
  await run(ctl, ['kickstart', '-k', service], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch(() => undefined);
361
361
  const status = await waitForBridge({ ...options, home });
362
362
  if (!status.ok) {
363
363
  await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
364
+ if (await launchTargetsProtectedFolder([command.executable, ...command.arguments], home)) {
365
+ return withProtectedFolderBlocker(status);
366
+ }
364
367
  return status;
365
368
  }
366
369
  try {
@@ -382,10 +385,14 @@ export async function bootstrapExistingDesktopBridgeService(options = {}) {
382
385
  const ctl = options.launchctl || '/bin/launchctl';
383
386
  const service = launchService(options.uid);
384
387
  await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
385
- const result = await run(ctl, ['bootstrap', launchDomain(options.uid), paths.launch_agent_path], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch((error) => failedProcess(error));
388
+ const result = await bootstrapLaunchdWithRetry(options, launchDomain(options.uid), service, paths.launch_agent_path);
386
389
  if (result.code === 0 || result.timedOut)
387
390
  await run(ctl, ['kickstart', '-k', service], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch(() => undefined);
388
- return waitForBridge({ ...options, home });
391
+ const status = await waitForBridge({ ...options, home });
392
+ if (!status.ok && await launchAgentTargetsProtectedFolder(paths.launch_agent_path, home)) {
393
+ return withProtectedFolderBlocker(status);
394
+ }
395
+ return status;
389
396
  }
390
397
  export async function stopDesktopBridgeService(options = {}) {
391
398
  const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
@@ -569,10 +576,56 @@ function launchService(uid) { return `${launchDomain(uid)}/${DESKTOP_BRIDGE_LAUN
569
576
  async function resolveLaunchCommand(options) { if (options.executablePath)
570
577
  return await exists(path.resolve(options.executablePath)) ? { executable: path.resolve(options.executablePath), arguments: [...(options.executableArguments || [])] } : null; const entry = String(process.argv[1] || ''); if (entry && ['sks', 'sneakoscope'].includes(path.basename(entry).replace(/\.js$/i, '')) && await exists(entry))
571
578
  return { executable: path.resolve(process.execPath), arguments: [path.resolve(entry)] }; const sks = await which('sks').catch(() => null); return sks ? { executable: path.resolve(sks), arguments: [] } : null; }
579
+ function macosProtectedUserPath(target, home) {
580
+ if (!target)
581
+ return false;
582
+ return ['Desktop', 'Documents', 'Downloads']
583
+ .some((dir) => target.startsWith(`${path.resolve(home, dir)}${path.sep}`));
584
+ }
585
+ async function launchTargetsProtectedFolder(targets, home) {
586
+ const resolved = await Promise.all(targets.map(async (target) => {
587
+ try {
588
+ return await fsp.realpath(target);
589
+ }
590
+ catch {
591
+ return target;
592
+ }
593
+ }));
594
+ return resolved.some((target) => macosProtectedUserPath(target, home));
595
+ }
596
+ async function launchAgentTargetsProtectedFolder(plistPath, home) {
597
+ let text = '';
598
+ try {
599
+ text = await fsp.readFile(plistPath, 'utf8');
600
+ }
601
+ catch {
602
+ return false;
603
+ }
604
+ const targets = [...text.matchAll(/<string>([^<]+)<\/string>/g)].map((match) => match[1]);
605
+ return launchTargetsProtectedFolder(targets, home);
606
+ }
607
+ function withProtectedFolderBlocker(status) {
608
+ return { ...status, blockers: [...new Set([...status.blockers, 'desktop_bridge_entry_macos_protected_folder'])] };
609
+ }
610
+ export async function bootstrapLaunchdWithRetry(options, domain, service, plistPath) {
611
+ const run = options.run || runProcess;
612
+ const ctl = options.launchctl || '/bin/launchctl';
613
+ let result = { code: 1, timedOut: false };
614
+ for (let attempt = 0; attempt < 3; attempt += 1) {
615
+ if (attempt > 0)
616
+ await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
617
+ for (let poll = 0; poll < 20 && (await inspectLaunchd(options, service)).loaded; poll += 1)
618
+ await delay(100);
619
+ result = await run(ctl, ['bootstrap', domain, plistPath], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch((error) => failedProcess(error));
620
+ if (result.code === 0 || result.timedOut)
621
+ return result;
622
+ }
623
+ return result;
624
+ }
572
625
  async function inspectLaunchd(options, service) { if ((options.platform || process.platform) !== 'darwin')
573
626
  return { loaded: false, running: false }; const result = await (options.run || runProcess)(options.launchctl || '/bin/launchctl', ['print', service], { timeoutMs: 3_000, maxOutputBytes: 32 * 1024 }).catch(() => null); if (!result || result.code !== 0)
574
627
  return { loaded: false, running: false }; const text = `${result.stdout}\n${result.stderr}`; return { loaded: true, running: /state = running/.test(text) && /pid = \d+/.test(text) }; }
575
- async function waitForBridge(options) { let status = await desktopBridgeServiceStatus(options); for (let i = 0; i < 40 && !status.ok; i += 1) {
628
+ async function waitForBridge(options) { let status = await desktopBridgeServiceStatus(options); for (let i = 0; i < 150 && !status.ok; i += 1) {
576
629
  await delay(100);
577
630
  status = await desktopBridgeServiceStatus(options);
578
631
  } return status; }
@@ -35,6 +35,7 @@ export function buildBridgeRouteIndex(input) {
35
35
  const model = {
36
36
  ...source,
37
37
  public_id: publicId,
38
+ slug: source.slug || publicId,
38
39
  upstream_model: upstream,
39
40
  capabilities: unique(source.capabilities).sort(),
40
41
  route_key: providerRouteKey(source.provider_id, publicId)
@@ -192,6 +192,7 @@ Verify with the narrowest compile, deterministic template, or live native check
192
192
  instructions: `You are the scoped Computer Use operator.
193
193
 
194
194
  Use Codex Computer Use only for the explicit native macOS, desktop-app, OS-settings, or non-web visual slice assigned by the parent.
195
+ Do not target the hosting Codex Desktop app (com.openai.codex). For Codex-linked checks, observe Codex through structured host/process evidence and operate only the external native target.
195
196
  Do not replace judgment, debugging, planning, or security review; return captured evidence to the appropriate Sol Max specialist.
196
197
  Honor the parent permission scope, avoid destructive or irreversible UI actions, do not edit source files, and report exactly what was observed or changed.`
197
198
  }),
@@ -25,7 +25,7 @@ export function canonicalTestProofPath(root) {
25
25
  }
26
26
  export function canonicalTestFiles(root) {
27
27
  const compiled = discover(path.join(root, 'dist'), (file) => {
28
- if (!file.endsWith('.test.js') || !file.includes(`${path.sep}__tests__${path.sep}`))
28
+ if (!isCurrentCompiledTest(root, file))
29
29
  return false;
30
30
  const relative = repoRelative(root, file);
31
31
  return relative.startsWith('dist/core/release/__tests__/') || CURRENT_COMPILED_TESTS.has(relative);
@@ -43,7 +43,7 @@ export function allCanonicalTestFiles(root) {
43
43
  unit.push(...discover(path.join(root, 'test', 'regression'), (file) => file.endsWith('.test.mjs')));
44
44
  unit.sort();
45
45
  return {
46
- compiled: discover(path.join(root, 'dist'), (file) => file.endsWith('.test.js') && file.includes(`${path.sep}__tests__${path.sep}`)),
46
+ compiled: discover(path.join(root, 'dist'), (file) => isCurrentCompiledTest(root, file)),
47
47
  unit
48
48
  };
49
49
  }
@@ -150,6 +150,12 @@ function validCorpus(value) {
150
150
  function readPackage(root) {
151
151
  return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
152
152
  }
153
+ function isCurrentCompiledTest(root, file) {
154
+ if (!file.endsWith('.test.js') || !file.includes(`${path.sep}__tests__${path.sep}`))
155
+ return false;
156
+ const relative = path.relative(path.join(root, 'dist'), file);
157
+ return fs.existsSync(path.join(root, 'src', `${relative.slice(0, -'.js'.length)}.ts`));
158
+ }
153
159
  function discover(dir, accept) {
154
160
  const out = [];
155
161
  if (!fs.existsSync(dir))
@@ -22,7 +22,7 @@ export const QA_INTERACTION_SURFACES = Object.freeze([
22
22
  ]);
23
23
  export const CODEX_QA_SURFACE_ROUTING_POLICY = `Codex QA surface routing follows the official Codex App split: use @Browser / in-app Browser (${CODEX_IN_APP_BROWSER_DOC_URL}) first for localhost, local development servers, file-backed previews, and public pages that do not require sign-in; use @Chrome / Codex Chrome Extension (${CODEX_CHROME_EXTENSION_DOC_URL}) for signed-in websites, cookies, browser profiles, extensions, existing tabs, or internal tools; use @Computer or @AppName (${CODEX_COMPUTER_USE_DOC_URL}) for native macOS/Windows apps, OS settings, cross-app workflows, and GUI-only bugs. Prefer structured Plugins/MCPs for repeatable data operations, then verify rendered user-visible results with Browser, Chrome, or Computer Use. Playwright, Selenium, Puppeteer, Chrome MCP, static screenshots, plugin cache, and final-agent prose are not Codex App live action proof. App Server evidence (${CODEX_APP_SERVER_DOC_URL}) must correlate thread, turn, item/tool events, approvals, diffs, actions, observations, findings, fixes, and same-flow replay before a real QA pass is claimed.`;
24
24
  export const CODEX_WEB_VERIFICATION_POLICY = CODEX_QA_SURFACE_ROUTING_POLICY;
25
- export const CODEX_COMPUTER_USE_ONLY_POLICY = `Codex Computer Use is a live GUI surface for supported macOS and Windows environments, invoked with @Computer or @AppName for native apps, OS settings, browser contexts that truly require GUI-level operation, and cross-app workflows. Do not replace @Browser localhost/public-page checks or @Chrome signed-in checks with Computer Use unless the surface router records a specific GUI-only/cross-app reason. If live Computer Use tools, permissions, or app access are unavailable, mark the affected native/GUI evidence blocked or unverified instead of fabricating screenshots or actions. Codex App readiness/config checks are capability evidence only, not target interaction proof.`;
25
+ export const CODEX_COMPUTER_USE_ONLY_POLICY = `Codex Computer Use is a live GUI surface for supported macOS and Windows environments, invoked with @Computer or @AppName for native apps, OS settings, browser contexts that truly require GUI-level operation, and cross-app workflows. Computer Use must never target the hosting Codex Desktop app itself (com.openai.codex), because host self-control is outside the allowed safety boundary. For Codex-linked native QA, use structured App Server, process, or NSWorkspace evidence for the Codex side and direct Computer Use only at the external native target. Do not replace @Browser localhost/public-page checks or @Chrome signed-in checks with Computer Use unless the surface router records a specific GUI-only/cross-app reason. If live Computer Use tools, permissions, or app access are unavailable, mark the affected native/GUI evidence blocked or unverified instead of fabricating screenshots or actions. Codex App readiness/config checks are capability evidence only, not target interaction proof.`;
26
26
  export const IMAGEGEN_SOCIAL_SOURCE_POLICY = 'Use public X/social/community reports only as prompt-quality and workflow-sentiment hints after official OpenAI/Codex docs. Social posts are not capability specs, evidence of tool availability, or proof that a generated asset was created.';
27
27
  export const CODEX_IMAGEGEN_REQUIRED_POLICY = 'Pipeline image generation, raster asset creation/editing, and generated image-review evidence must use gpt-image-2 through the user-selected Codex provider when that evidence is required for full verification: Codex App imagegen/$imagegen, or the selected and ready codex-lb Responses provider. Only completed response output or a final response.output_item.done image result qualifies; streamed partial preview frames never count as generated-image evidence. For newest-model image requests, prompt explicitly for "ChatGPT Images 2.0 / GPT Image 2.0 with gpt-image-2" instead of relying on generic image-generation wording. Do not substitute placeholder SVG/HTML/CSS, prose-only critique, stock-like stand-ins, manually fabricated files, or missing-output ledgers for requested/generated raster assets or required generated review images. If imagegen/gpt-image-2 is unavailable or generated annotated images cannot be created/linked, record the blocker and cap any closeout at verified_partial/reference-only instead of claiming generated-image evidence or full route verification; that partial closeout requires source screenshots plus hashes, docs evidence, source Image Voxel anchors, and Honest Mode evidence. In Codex App prompts, invoke $imagegen when live image generation is needed; SKS hooks and skills can require the policy but cannot attach missing host image-generation tools to an already-started turn. Official OpenAI/Codex docs are authoritative for capabilities, surfaces, limits, and evidence rules; X/social/community reports may inform prompt style only.';
28
28
  export const DEFAULT_CODEX_APP_PLUGINS = Object.freeze([
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '8.3.0';
1
+ export const PACKAGE_VERSION = '8.3.1';
@@ -0,0 +1,18 @@
1
+ import Foundation
2
+
3
+ enum CodexLifecyclePolicy {
4
+ static func followsCodex(from config: [String: Any]?) -> Bool {
5
+ if let current = config?["follow_codex_lifecycle"] as? Bool { return current }
6
+ return config?["quit_with_codex"] as? Bool == true
7
+ }
8
+
9
+ static func initialVisibility(followCodex: Bool, codexRunning: Bool) -> Bool {
10
+ !followCodex || codexRunning
11
+ }
12
+
13
+ static func visibilityAfterCodexLaunch() -> Bool { true }
14
+
15
+ static func visibilityAfterCodexTermination(followCodex: Bool) -> Bool {
16
+ !followCodex
17
+ }
18
+ }
@@ -16,7 +16,9 @@ final class ProcessClient {
16
16
  private let actionScript: String
17
17
  private let logPath: String
18
18
  private let projectRoot: String
19
- private let outputLimit = 64 * 1024
19
+ // Bridge status/repair JSON scales with the combined catalog (395+ models
20
+ // ≈ 120KB today); 64KB truncated every routing-aware command result.
21
+ private let outputLimit = 1024 * 1024
20
22
  private let processIdentityGuard: ProcessIdentityGuard
21
23
  private let ownedProcessRegistry: OwnedProcessRegistry
22
24
 
@@ -247,7 +247,8 @@ struct DesktopBridgeStatusV3Truth {
247
247
  private static let keys: Set<String> = [
248
248
  "schema", "checked_at", "correlation_id", "management", "service",
249
249
  "http_probe", "websocket_probe", "native_identity", "providers", "routing",
250
- "catalog_sync", "capabilities", "readiness", "recovery_actions"
250
+ "catalog_sync", "capabilities", "readiness", "recovery_actions",
251
+ "ok", "execution_ok", "command_summary"
251
252
  ]
252
253
  let raw: [String: Any]
253
254
  let checkedAt: String
@@ -265,7 +266,9 @@ struct DesktopBridgeStatusV3Truth {
265
266
  let routing = json["routing"] as? [String: Any],
266
267
  let catalog = json["catalog_sync"] as? [String: Any],
267
268
  let readiness = json["readiness"] as? [String: Any],
268
- json["recovery_actions"] is [String] else {
269
+ json["recovery_actions"] is [String],
270
+ json["ok"] is Bool, json["execution_ok"] is Bool,
271
+ nonempty(json["command_summary"]) != nil else {
269
272
  throw ProviderFacadeError.schemaInvalid("desktop_bridge_status_schema_invalid")
270
273
  }
271
274
  let managed = management["managed"] as? Bool == true
@@ -356,6 +359,70 @@ struct DesktopBridgeStatusV3Truth {
356
359
  }
357
360
  }
358
361
 
362
+ struct DesktopBridgeCommandResultTruth: Equatable {
363
+ private static let keys: Set<String> = [
364
+ "schema", "operation", "operation_id", "correlation_id", "checked_at", "ok",
365
+ "execution", "readiness", "status", "result", "recovery_action",
366
+ "execution_ok", "command_summary"
367
+ ]
368
+ let completed: Bool
369
+ let blockers: [String]
370
+ let recoveryAction: String?
371
+
372
+ static func decode(from json: [String: Any], expectedOperation: String) throws -> DesktopBridgeCommandResultTruth {
373
+ guard Set(json.keys) == keys,
374
+ json["schema"] as? String == "sks.desktop-bridge-command-result.v1",
375
+ json["operation"] as? String == expectedOperation,
376
+ nonempty(json["operation_id"]) != nil,
377
+ nonempty(json["correlation_id"]) != nil,
378
+ nonempty(json["checked_at"]) != nil,
379
+ let topLevelOK = json["ok"] as? Bool,
380
+ let execution = json["execution"] as? [String: Any],
381
+ Set(execution.keys) == ["ok", "status", "blockers"],
382
+ let executionOK = execution["ok"] as? Bool,
383
+ let executionStatus = execution["status"] as? String,
384
+ ["completed", "partial", "failed"].contains(executionStatus),
385
+ let blockers = execution["blockers"] as? [String],
386
+ let readiness = json["readiness"] as? [String: Any],
387
+ Set(readiness.keys) == ["ready", "blockers", "warnings"],
388
+ readiness["ready"] is Bool,
389
+ readiness["blockers"] is [String],
390
+ readiness["warnings"] is [String],
391
+ (json["status"] is NSNull || json["status"] is [String: Any]),
392
+ let result = json["result"] as? [String: Any],
393
+ (json["recovery_action"] is NSNull || json["recovery_action"] is String),
394
+ json["execution_ok"] as? Bool == executionOK,
395
+ nonempty(json["command_summary"]) != nil,
396
+ topLevelOK == executionOK else {
397
+ throw ProviderFacadeError.schemaInvalid("desktop_bridge_command_result_schema_invalid")
398
+ }
399
+ let completed = topLevelOK && executionStatus == "completed" && blockers.isEmpty
400
+ let partial = topLevelOK && executionStatus == "partial" && !blockers.isEmpty
401
+ let failed = !topLevelOK && executionStatus == "failed"
402
+ guard completed || partial || failed else {
403
+ throw ProviderFacadeError.schemaInvalid("desktop_bridge_command_result_execution_invalid")
404
+ }
405
+ if completed && expectedOperation == "repair" {
406
+ guard let service = result["service"] as? [String: Any],
407
+ service["ok"] as? Bool == true,
408
+ service["running"] as? Bool == true else {
409
+ throw ProviderFacadeError.schemaInvalid("desktop_bridge_command_result_service_invalid")
410
+ }
411
+ }
412
+ return DesktopBridgeCommandResultTruth(
413
+ completed: completed,
414
+ blockers: blockers,
415
+ recoveryAction: json["recovery_action"] as? String
416
+ )
417
+ }
418
+
419
+ private static func nonempty(_ value: Any?) -> String? {
420
+ guard let string = value as? String else { return nil }
421
+ let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
422
+ return trimmed.isEmpty ? nil : trimmed
423
+ }
424
+ }
425
+
359
426
  struct CapabilityDisplayRow: Equatable {
360
427
  let scope: CapabilityScope
361
428
  let capability: String
@@ -385,3 +452,20 @@ struct CapabilityDisplayRow: Equatable {
385
452
  return nil
386
453
  }
387
454
  }
455
+
456
+ enum CapabilityDisplayFilter {
457
+ static func rows(_ rows: [CapabilityDisplayRow], showAll: Bool) -> [CapabilityDisplayRow] {
458
+ showAll ? rows : rows.filter { isIssue($0.state) }
459
+ }
460
+
461
+ static func issueCount(_ rows: [CapabilityDisplayRow]) -> Int {
462
+ rows.filter { isIssue($0.state) }.count
463
+ }
464
+
465
+ private static func isIssue(_ state: CapabilityProbeState) -> Bool {
466
+ switch state {
467
+ case .degraded, .blocked, .failed, .stale: return true
468
+ case .notAttempted, .running, .verified, .unsupported: return false
469
+ }
470
+ }
471
+ }
@@ -29,6 +29,7 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
29
29
  let capabilityStatus = NativeView.detail("Capabilities have not been verified.")
30
30
  let capabilityLastCheckedStatus = NativeView.detail("Last feature check: never")
31
31
  let capabilityStack = NSStackView()
32
+ var capabilityFilterButton: NSButton!
32
33
  let routeModelField: NSTextField = {
33
34
  let field = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
34
35
  field.placeholderString = "Select or enter an exact catalog model"
@@ -55,6 +56,7 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
55
56
  var lastStatusCheckedAt: Date?
56
57
  var lastStatusCorrelationId: String?
57
58
  var providerEnabled = ["codex-lb": false, "openrouter": false]
59
+ var showAllCapabilities = false
58
60
 
59
61
  init(processClient: ProcessClient, operations: OperationCoordinator) {
60
62
  self.processClient = processClient; self.operations = operations
@@ -103,7 +105,9 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
103
105
  }
104
106
 
105
107
  private func makeCapabilityMatrixCard() -> NSBox {
106
- let card = NativeView.card(title: "Capability Matrix", subtitle: "Each row keeps its own scope, route, OAuth requirement, stage, root cause, and allowlisted recovery. Deep-only work not run is not a failure.", views: [capabilityStatus, capabilityLastCheckedStatus, capabilityStack])
108
+ capabilityFilterButton = NativeView.button("Show All Capabilities", target: self, action: #selector(toggleCapabilityFilter))
109
+ capabilityFilterButton.setAccessibilityIdentifier("sks-provider-capability-filter")
110
+ let card = NativeView.card(title: "Capability Matrix", subtitle: "Issues are shown first. Expand the verified and not-attempted rows only when you need the full diagnostic trace.", views: [capabilityStatus, capabilityLastCheckedStatus, ControlKit.actionRow([capabilityFilterButton]), capabilityStack])
107
111
  card.setAccessibilityIdentifier("sks-provider-card-capability-matrix")
108
112
  return card
109
113
  }
@@ -120,17 +124,6 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
120
124
  if providerActionInFlight.isEmpty { globalSpinner.stopAnimation(nil) } else { globalSpinner.startAnimation(nil) }
121
125
  }
122
126
 
123
- private func run(_ args: [String], title: String, kind: String, group: String?, timeout: TimeInterval = NativeView.mutationTimeout, completion: (() -> Void)? = nil) {
124
- guard let snapshot = operations.begin(kind: kind, mutationGroup: group, summary: title) else { providerStatus.stringValue = "Another guarded mutation is running."; return }
125
- setBusy(true)
126
- _ = operations.update(snapshot, state: .running, stage: "running", progress: nil, summary: title)
127
- processClient.run(args, timeout: timeout) { [weak self] result in
128
- guard let self = self else { return }; self.setBusy(false)
129
- _ = self.operations.update(snapshot, state: result.code == 0 ? .succeeded : .failed, stage: "complete", progress: 1, summary: result.code == 0 ? "\(title) completed" : "\(title) failed")
130
- completion?()
131
- }
132
- }
133
-
134
127
  func refresh() {
135
128
  refreshCredentialHealth()
136
129
  processClient.run(["bridge", "status", "--json"], timeout: NativeView.statusTimeout) { [weak self] result in
@@ -234,7 +227,9 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
234
227
  private func renderCapabilityReport(_ report: DesktopCapabilityReportV3) {
235
228
  lastCapabilityReport = report
236
229
  capabilityStack.arrangedSubviews.forEach { capabilityStack.removeArrangedSubview($0); $0.removeFromSuperview() }
237
- for row in CapabilityDisplayRow.rows(from: report) {
230
+ let allRows = CapabilityDisplayRow.rows(from: report)
231
+ let visibleRows = CapabilityDisplayFilter.rows(allRows, showAll: showAllCapabilities)
232
+ for row in visibleRows {
238
233
  let cause = row.rootCause.map { " · root cause \(ProviderSecretRedactor.redact($0))" } ?? ""
239
234
  let recoveryAction = row.recoveryAction.flatMap(ProviderRecoveryAction.init(rawValue:))
240
235
  let recovery = recoveryAction.map { " · recovery \($0.buttonTitle)" } ?? ""
@@ -250,8 +245,14 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
250
245
  capabilityStack.addArrangedSubview(value)
251
246
  }
252
247
  }
248
+ if visibleRows.isEmpty {
249
+ capabilityStack.addArrangedSubview(NativeView.detail("No capability issues in this report. Show all capabilities to inspect the complete trace."))
250
+ }
251
+ capabilityFilterButton.title = showAllCapabilities ? "Show Issues Only" : "Show All Capabilities"
252
+ capabilityFilterButton.setAccessibilityLabel(capabilityFilterButton.title)
253
+ let issueCount = CapabilityDisplayFilter.issueCount(allRows)
253
254
  let satisfied = report.summary.levelSatisfied
254
- capabilityStatus.stringValue = "\(report.requestedLevel.capitalized) diagnostic completed · readiness \(satisfied ? "satisfied" : "needs action") · full deep \(report.summary.fullFeatureVerified ? "verified" : "not verified")"
255
+ capabilityStatus.stringValue = "\(report.requestedLevel.capitalized) diagnostic completed · \(issueCount) issue\(issueCount == 1 ? "" : "s") · readiness \(satisfied ? "satisfied" : "needs action") · full deep \(report.summary.fullFeatureVerified ? "verified" : "not verified")"
255
256
  capabilityStatus.textColor = satisfied ? .systemGreen : .systemOrange
256
257
  capabilityLastCheckedStatus.stringValue = "Last feature check: \(report.checkedAt) · report \(report.reportId)"
257
258
  renderCatalogStatus(report.catalogSync)
@@ -261,7 +262,40 @@ final class ProvidersViewController: NSViewController, ControlCenterPage, NSText
261
262
  ProviderStatusColor.forState(state.rawValue)
262
263
  }
263
264
 
264
- @objc private func repairDesktopBridge() { run(["bridge", "repair", "--json"], title: "Repair Desktop Bridge", kind: "bridge-repair", group: "codex-config") { [weak self] in self?.refresh() } }
265
+ @objc private func toggleCapabilityFilter() {
266
+ showAllCapabilities.toggle()
267
+ if let report = lastCapabilityReport { renderCapabilityReport(report) }
268
+ }
269
+
270
+ @objc private func repairDesktopBridge() {
271
+ guard let snapshot = operations.begin(kind: "bridge-repair", mutationGroup: "codex-config", summary: "Repair Desktop Bridge") else {
272
+ providerStatus.stringValue = "Another guarded mutation is running."
273
+ return
274
+ }
275
+ setBusy(true)
276
+ providerStatus.stringValue = "Desktop Bridge repair running…"
277
+ _ = operations.update(snapshot, state: .running, stage: "repairing", progress: nil, summary: "Repair Desktop Bridge")
278
+ processClient.run(["bridge", "repair", "--json"], timeout: NativeView.mutationTimeout) { [weak self] result in
279
+ guard let self = self else { return }
280
+ self.setBusy(false)
281
+ let parsed = self.json(result.output)
282
+ let truth = parsed.flatMap {
283
+ try? DesktopBridgeCommandResultTruth.decode(from: $0, expectedOperation: "repair")
284
+ }
285
+ let nativeError = parsed.flatMap { row -> String? in
286
+ row["schema"] as? String == "sks.native-process-error.v1" ? row["error"] as? String : nil
287
+ }
288
+ let completed = result.code == 0 && truth?.completed == true
289
+ let blocker = (truth?.blockers.first ?? nativeError).map(ProviderSecretRedactor.redact)
290
+ let summary = completed
291
+ ? "Repair Desktop Bridge completed"
292
+ : blocker.map { "Repair Desktop Bridge needs action · \($0)" } ?? "Repair Desktop Bridge result schema invalid"
293
+ _ = self.operations.update(snapshot, state: completed ? .succeeded : .failed, stage: "complete", progress: 1, summary: summary)
294
+ self.providerStatus.stringValue = summary
295
+ self.providerStatus.textColor = completed ? .systemGreen : .systemRed
296
+ self.refresh()
297
+ }
298
+ }
265
299
 
266
300
  @objc private func performRecoveryAction(_ sender: NSButton) {
267
301
  let prefix = "sks-provider-recovery-"