sneakoscope 4.7.1 → 4.7.3

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.
@@ -1,12 +1,13 @@
1
1
  import path from 'node:path';
2
2
  import readline from 'node:readline/promises';
3
3
  import { stdin as input, stdout as output } from 'node:process';
4
- import { projectRoot, readStdin } from '../core/fsx.js';
4
+ import { projectRoot, readStdin, readText } from '../core/fsx.js';
5
5
  import { flag, readOption } from '../cli/args.js';
6
6
  import { printJson } from '../cli/output.js';
7
7
  import { codexLbMetrics, readCodexLbCircuit, recordCodexLbHealthEvent, resetCodexLbCircuit, codexLbProofEvidence } from '../core/codex-lb-circuit.js';
8
8
  import { checkCodexLbResponseChain, codexLbStatus, configureCodexLb, formatCodexLbStatusText, releaseCodexLbAuthHold, repairCodexLbAuth, unselectCodexLbProvider } from '../cli/install-helpers.js';
9
9
  import { buildCodexLbSetupPlan, codexLbPersistenceSummary, renderCodexLbSetupPlan } from '../core/codex-lb/codex-lb-setup.js';
10
+ import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
10
11
  export async function run(command, args = []) {
11
12
  const root = await projectRoot();
12
13
  const action = args[0] || 'status';
@@ -41,7 +42,7 @@ export async function run(command, args = []) {
41
42
  if (action === 'health' || action === 'verify-chain' || action === 'chain') {
42
43
  const status = await codexLbStatus();
43
44
  const blocker = !status.env_key_configured ? 'missing_env_key' : !status.base_url ? 'missing_base_url' : 'not_configured';
44
- const result = status.ok ? await checkCodexLbResponseChain(status, { force: true, root }) : { ok: false, status: blocker, codex_lb: status };
45
+ const result = status.ok ? await checkCodexLbResponseChain(status, { force: true, root, fastMode: flag(args, '--fast') || flag(args, '--priority') }) : { ok: false, status: blocker, codex_lb: status };
45
46
  if (flag(args, '--json'))
46
47
  return printJson(result);
47
48
  console.log(`codex-lb response chain: ${result.ok ? 'ok' : `failed (${result.status})`}`);
@@ -49,6 +50,47 @@ export async function run(command, args = []) {
49
50
  process.exitCode = 1;
50
51
  return;
51
52
  }
53
+ if (action === 'fast-check' || action === 'fast' || action === 'verify-fast') {
54
+ const status = await codexLbStatus();
55
+ const blocker = !status.env_key_configured ? 'missing_env_key' : !status.base_url ? 'missing_base_url' : !status.provider_contract_ok ? 'provider_contract_drift' : 'not_configured';
56
+ const chain = status.env_key_configured && status.base_url
57
+ ? await checkCodexLbResponseChain(status, { force: true, cache: false, root, fastMode: true })
58
+ : { ok: false, status: blocker, codex_lb: status };
59
+ const evidence = await fastEvidenceFromChain(chain, readOption(args, '--request-log', readOption(args, '--request-log-json', null)));
60
+ const providerReady = status.provider_contract_ok === true;
61
+ const result = {
62
+ schema: 'sks.codex-lb-fast-check.v1',
63
+ ok: Boolean(providerReady && chain.ok && evidence.fast_requested && evidence.fast_actual),
64
+ status: !providerReady
65
+ ? 'provider_contract_drift'
66
+ : chain.ok
67
+ ? evidence.fast_actual
68
+ ? 'fast_verified'
69
+ : evidence.fast_requested
70
+ ? 'fast_requested_but_actual_unverified'
71
+ : 'fast_not_requested'
72
+ : chain.status,
73
+ codex_lb: status,
74
+ chain,
75
+ evidence,
76
+ blockers: [
77
+ ...(providerReady ? [] : ['codex_lb_provider_contract_drift']),
78
+ ...(chain.ok
79
+ ? evidence.fast_actual
80
+ ? []
81
+ : ['codex_lb_actual_fast_service_tier_unverified']
82
+ : [chain.status || blocker])
83
+ ]
84
+ };
85
+ if (flag(args, '--json'))
86
+ return printJson(result);
87
+ console.log(`codex-lb fast check: ${result.ok ? 'ok' : `blocked (${result.status})`}`);
88
+ if (!result.ok) {
89
+ console.log('Need codex-lb request evidence: requestedServiceTier=priority and actualServiceTier/serviceTier=priority.');
90
+ process.exitCode = 1;
91
+ }
92
+ return;
93
+ }
52
94
  if (action === 'repair' || action === 'resync' || action === 'login') {
53
95
  const result = await repairCodexLbAuth();
54
96
  if (flag(args, '--json'))
@@ -97,20 +139,27 @@ export async function run(command, args = []) {
97
139
  process.exitCode = 1;
98
140
  return;
99
141
  }
100
- const result = await configureCodexLb({ host, apiKey: newKey });
142
+ const result = await configureCodexLb({ host, apiKey: newKey, authMode: flag(args, '--preserve-auth') ? 'preserve' : 'codex-lb', forceCodexLbApiKeyAuth: !flag(args, '--preserve-auth') });
143
+ const restart = await maybeRestartCodexAppForAuthSwitch(args, Boolean(result.ok));
144
+ const output = { ...result, action: 'set-key', restart_app: restart };
101
145
  if (flag(args, '--json'))
102
- return printJson({ ...result, action: 'set-key' });
146
+ return printJson(output);
103
147
  console.log(result.ok ? `codex-lb API key updated (${result.base_url || host}).` : `codex-lb key update failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
148
+ if (restart?.status === 'restarted')
149
+ console.log('Codex App restarted for the new codex-lb auth mode.');
104
150
  if (!result.ok)
105
151
  process.exitCode = 1;
106
152
  return;
107
153
  }
108
154
  if (action === 'use-codex-lb' || action === 'use-lb') {
109
155
  // Switch auth mode -> codex-lb (API key). Re-selects the provider and re-syncs auth.
110
- const result = await repairCodexLbAuth();
156
+ const result = await repairCodexLbAuth({ forceCodexLbApiKeyAuth: true, authMode: 'codex-lb', forceFastMode: !flag(args, '--no-fast') });
157
+ const restart = await maybeRestartCodexAppForAuthSwitch(args, Boolean(result.ok));
111
158
  if (flag(args, '--json'))
112
- return printJson({ ...result, mode: 'codex-lb' });
159
+ return printJson({ ...result, mode: 'codex-lb', restart_app: restart });
113
160
  console.log(result.ok ? 'Auth mode: codex-lb selected (API key).' : `Switch to codex-lb failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
161
+ if (restart?.status === 'restarted')
162
+ console.log('Codex App restarted for codex-lb auth.');
114
163
  if (!result.ok)
115
164
  process.exitCode = 1;
116
165
  return;
@@ -118,8 +167,9 @@ export async function run(command, args = []) {
118
167
  if (action === 'use-oauth' || action === 'use-chatgpt') {
119
168
  // Switch auth mode -> ChatGPT OAuth. Restores the saved OAuth login if present.
120
169
  const result = await releaseCodexLbAuthHold({ force: flag(args, '--force') });
170
+ const restart = await maybeRestartCodexAppForAuthSwitch(args, !['no_backup', 'auth_in_use', 'failed'].includes(result.status));
121
171
  if (flag(args, '--json'))
122
- return printJson({ ...result, mode: 'oauth' });
172
+ return printJson({ ...result, mode: 'oauth', restart_app: restart });
123
173
  if (result.status === 'no_backup') {
124
174
  console.log('No saved ChatGPT OAuth credentials to restore. Switch to OAuth by logging in:');
125
175
  console.log(' codex login');
@@ -128,6 +178,8 @@ export async function run(command, args = []) {
128
178
  return;
129
179
  }
130
180
  console.log(`Auth mode: ${['released', 'oauth_restored'].includes(result.status) ? 'ChatGPT OAuth restored' : result.status}.`);
181
+ if (restart?.status === 'restarted')
182
+ console.log('Codex App restarted for ChatGPT OAuth.');
131
183
  if (['auth_in_use', 'failed'].includes(result.status))
132
184
  process.exitCode = 1;
133
185
  return;
@@ -224,6 +276,9 @@ export async function run(command, args = []) {
224
276
  const result = await configureCodexLb({
225
277
  host: options.host,
226
278
  apiKey: options.apiKey,
279
+ authMode: flag(args, '--preserve-auth') ? 'preserve' : 'codex-lb',
280
+ forceCodexLbApiKeyAuth: !flag(args, '--preserve-auth'),
281
+ forceFastMode: !flag(args, '--no-fast'),
227
282
  keychain: options.keychain,
228
283
  storeKeychain: options.keychain,
229
284
  useDefaultProvider: options.useDefaultProvider,
@@ -234,7 +289,9 @@ export async function run(command, args = []) {
234
289
  apiKeySource: options.apiKeySource,
235
290
  allowInsecureHttp: options.allowInsecureLocalhost
236
291
  });
292
+ const restart = await maybeRestartCodexAppForAuthSwitch(args, Boolean(result.ok) && !flag(args, '--preserve-auth'));
237
293
  const shaped = { schema: 'sks.codex-lb-setup.v1', ...result, api_key: { present: Boolean(options.apiKey), redacted: true }, env_file_chmod: '0600' };
294
+ shaped.restart_app = restart;
238
295
  if (options.health)
239
296
  shaped.applied_actions = [...(shaped.applied_actions || []), { type: 'run_health_check', target: 'codex-lb response chain', ok: true }];
240
297
  if (options.health)
@@ -284,6 +341,85 @@ export async function run(command, args = []) {
284
341
  console.error(' use-oauth switch auth mode to ChatGPT OAuth (restores saved login, else: codex login)');
285
342
  process.exitCode = 1;
286
343
  }
344
+ async function maybeRestartCodexAppForAuthSwitch(args = [], enabled) {
345
+ if (!enabled)
346
+ return { schema: 'sks.codex-app-restart.v1', ok: true, status: 'skipped', skipped: true, reason: 'previous_step_failed', app_name: 'Codex', blockers: [] };
347
+ const requested = flag(args, '--restart-app') || flag(args, '--restart');
348
+ const shouldRestart = requested || (!flag(args, '--json') && !flag(args, '--no-restart-app') && !flag(args, '--no-restart'));
349
+ return restartCodexApp({ enabled: shouldRestart });
350
+ }
351
+ async function fastEvidenceFromChain(chain = {}, requestLogPath = null) {
352
+ const chainEvidence = chain.service_tier_evidence || {};
353
+ const logRows = requestLogPath ? await readRequestLogRows(String(requestLogPath)) : [];
354
+ const logEvidence = serviceTierEvidenceFromRows(logRows);
355
+ const requested = logEvidence.requested_service_tier || chainEvidence.requested_service_tier || chain.requested_service_tier || null;
356
+ const actual = logEvidence.actual_service_tier || chainEvidence.actual_service_tier || null;
357
+ const effective = logEvidence.effective_service_tier || chainEvidence.effective_service_tier || null;
358
+ return {
359
+ requested_service_tier: requested,
360
+ actual_service_tier: actual,
361
+ effective_service_tier: effective,
362
+ fast_requested: requested === 'priority' || chain.requested_service_tier === 'priority' || chainEvidence.fast_requested === true,
363
+ fast_actual: actual === 'priority' || effective === 'priority' || logEvidence.fast_actual === true || chainEvidence.fast_actual === true,
364
+ chain_evidence: chainEvidence,
365
+ request_log_path: requestLogPath || null,
366
+ request_log_rows: logRows.length
367
+ };
368
+ }
369
+ async function readRequestLogRows(file) {
370
+ if (!file)
371
+ return [];
372
+ const text = await readText(path.isAbsolute(file) ? file : path.resolve(process.cwd(), file), '').catch(() => '');
373
+ const rows = [];
374
+ const trimmed = text.trim();
375
+ if (!trimmed)
376
+ return rows;
377
+ try {
378
+ const parsed = JSON.parse(trimmed);
379
+ if (Array.isArray(parsed))
380
+ return parsed;
381
+ if (Array.isArray(parsed?.rows))
382
+ return parsed.rows;
383
+ if (Array.isArray(parsed?.requests))
384
+ return parsed.requests;
385
+ return [parsed];
386
+ }
387
+ catch { }
388
+ for (const line of text.split(/\r?\n/)) {
389
+ const candidate = line.trim();
390
+ if (!candidate)
391
+ continue;
392
+ try {
393
+ rows.push(JSON.parse(candidate));
394
+ }
395
+ catch { }
396
+ }
397
+ return rows;
398
+ }
399
+ function serviceTierEvidenceFromRows(rows = []) {
400
+ let requested = null;
401
+ let actual = null;
402
+ let effective = null;
403
+ for (const row of rows) {
404
+ requested ||= normalizeTier(row?.requestedServiceTier || row?.requested_service_tier || row?.request?.service_tier || row?.body?.service_tier);
405
+ actual ||= normalizeTier(row?.actualServiceTier || row?.actual_service_tier || row?.response?.actualServiceTier);
406
+ effective ||= normalizeTier(row?.serviceTier || row?.service_tier || row?.response?.serviceTier);
407
+ }
408
+ return {
409
+ requested_service_tier: requested,
410
+ actual_service_tier: actual,
411
+ effective_service_tier: effective,
412
+ fast_actual: actual === 'priority' || effective === 'priority'
413
+ };
414
+ }
415
+ function normalizeTier(value) {
416
+ const text = String(value || '').trim().toLowerCase();
417
+ if (text === 'fast')
418
+ return 'priority';
419
+ if (text === 'priority' || text === 'default' || text === 'flex')
420
+ return text;
421
+ return null;
422
+ }
287
423
  async function resolveNewApiKey(args = []) {
288
424
  const flagKey = readOption(args, '--api-key', '');
289
425
  if (flagKey)
@@ -801,9 +801,18 @@ async function runDoctor(args = [], root, doctorFix) {
801
801
  console.log(` native agent_type: ${codexAppHarnessMatrix.app_features?.agent_type_supported ? 'ok' : 'fallback message-role'}`);
802
802
  console.log(` init-deep memory: ${codexAppHarnessMatrix.sks_integrations?.init_deep_available ? 'available' : 'missing'}`);
803
803
  console.log(` loop mesh app profile: ${codexAppHarnessMatrix.sks_integrations?.loop_mesh_app_profile_available ? 'available' : 'missing'}`);
804
+ const codexAppUiStatus = codexAppUi;
804
805
  console.log('Codex App UI:');
805
806
  console.log(` fast selector: ${codexAppUi.fast_selector || 'unknown'}`);
806
807
  console.log(` provider selector: ${codexAppUi.provider_selector || 'unknown'}`);
808
+ if (Array.isArray(codexAppUiStatus.provider_blockers) && codexAppUiStatus.provider_blockers.length) {
809
+ console.log(` provider blockers: ${codexAppUiStatus.provider_blockers.join(', ')}`);
810
+ }
811
+ if (Array.isArray(codexAppUiStatus.provider_actions) && codexAppUiStatus.provider_actions.length) {
812
+ console.log(' provider actions:');
813
+ for (const action of codexAppUiStatus.provider_actions)
814
+ console.log(` - ${action}`);
815
+ }
807
816
  console.log(` host-owned config: ${codexAppUi.host_owned_config || 'unknown'}`);
808
817
  if (Array.isArray(codexAppUi.actions) && codexAppUi.actions.some((action) => action.changed)) {
809
818
  console.log(' repaired files:');
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { ensureDir, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
4
  import { codexHome, scanProjectLocalForbiddenKeys, snapshotCodexAppUiState } from './codex-app-ui-state-snapshot.js';
5
5
  import { assertCodexAppUiMutationAllowed } from './codex-app-ui-clobber-guard.js';
6
+ import { codexProviderModelUiStatus } from '../codex-app.js';
6
7
  export const CODEX_APP_FAST_UI_REPAIR_SCHEMA = 'sks.codex-app-fast-ui-repair.v1';
7
8
  const FAST_UI_TOP_LEVEL_RE = /^\s*(?:model|model_reasoning_effort|service_tier)\s*=/;
8
9
  const CODEX_APP_MODE_LOCK_TOP_LEVEL_RE = /^\s*(?:model|model_reasoning_effort)\s*=/;
@@ -61,6 +62,12 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
61
62
  });
62
63
  }
63
64
  const after = await snapshotCodexAppUiState(resolvedRoot, { codexHome: home });
65
+ const providerModelUi = await codexProviderModelUiStatus({
66
+ cwd: resolvedRoot,
67
+ home: path.dirname(home),
68
+ configPath: path.join(home, 'config.toml'),
69
+ codexLbEnvPath: path.join(home, 'sks-codex-lb.env')
70
+ });
64
71
  const changed = actions.some((action) => action.changed);
65
72
  const requiresConfirmation = unsafeReasons.length > 0 && input.force !== true;
66
73
  const safeAutoApply = changed && !requiresConfirmation;
@@ -81,7 +88,10 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
81
88
  detected_project_local_forbidden_keys: [...new Set(detectedProjectLocalForbiddenKeys)],
82
89
  unsafe_repair_reasons: [...new Set(unsafeReasons)],
83
90
  fast_selector: changed ? (input.apply ? 'repaired' : 'manual_action_required') : before.indicators.fast_selector === 'maybe_hidden_or_locked' ? 'manual_action_required' : 'ok',
84
- provider_selector: 'ok',
91
+ provider_selector: providerModelUi.ok ? 'ok' : 'manual_action_required',
92
+ provider_model_ui: providerModelUi,
93
+ provider_actions: providerModelUi.ui_actions || [],
94
+ provider_blockers: providerModelUi.blockers || [],
85
95
  host_owned_config: input.apply && changed ? 'repaired_with_backup' : changed ? 'preserved_until_explicit_apply' : 'preserved',
86
96
  actions,
87
97
  before_fast_selector: before.indicators.fast_selector,
@@ -0,0 +1,65 @@
1
+ import { exists, runProcess, which } from '../fsx.js';
2
+ export async function restartCodexApp(opts = {}) {
3
+ const env = opts.env || process.env;
4
+ const appName = String(opts.appName || env.SKS_CODEX_APP_NAME || 'Codex');
5
+ if (opts.enabled === false || env.SKS_SKIP_CODEX_APP_RESTART === '1') {
6
+ return skipped(appName, 'disabled');
7
+ }
8
+ if (process.platform !== 'darwin')
9
+ return skipped(appName, 'not_macos');
10
+ const run = opts.runProcessImpl || runProcess;
11
+ const osascript = await which('osascript').catch(() => null) || await exists('/usr/bin/osascript').then((ok) => ok ? '/usr/bin/osascript' : null).catch(() => null);
12
+ const open = await which('open').catch(() => null) || await exists('/usr/bin/open').then((ok) => ok ? '/usr/bin/open' : null).catch(() => null);
13
+ if (!osascript || !open) {
14
+ return {
15
+ schema: 'sks.codex-app-restart.v1',
16
+ ok: false,
17
+ status: 'blocked',
18
+ app_name: appName,
19
+ blockers: [
20
+ ...(osascript ? [] : ['osascript_missing']),
21
+ ...(open ? [] : ['open_missing'])
22
+ ]
23
+ };
24
+ }
25
+ const quit = await run(osascript, ['-e', `tell application ${JSON.stringify(appName)} to quit`], { timeoutMs: 5000, maxOutputBytes: 8192 }).catch((err) => ({
26
+ code: 1,
27
+ stdout: '',
28
+ stderr: err?.message || String(err)
29
+ }));
30
+ await sleep(Number(opts.delayMs ?? env.SKS_CODEX_APP_RESTART_DELAY_MS ?? 1200));
31
+ const launched = await run(open, ['-a', appName], { timeoutMs: 10000, maxOutputBytes: 8192 }).catch((err) => ({
32
+ code: 1,
33
+ stdout: '',
34
+ stderr: err?.message || String(err)
35
+ }));
36
+ const quitOk = quit.code === 0 || /not running|Can't get application|application isn't running/i.test(String(quit.stderr || quit.stdout || ''));
37
+ const openOk = launched.code === 0;
38
+ return {
39
+ schema: 'sks.codex-app-restart.v1',
40
+ ok: quitOk && openOk,
41
+ status: quitOk && openOk ? 'restarted' : 'blocked',
42
+ app_name: appName,
43
+ quit: { ok: quitOk, code: quit.code, error: quitOk ? null : String(quit.stderr || quit.stdout || '').trim() },
44
+ open: { ok: openOk, code: launched.code, error: openOk ? null : String(launched.stderr || launched.stdout || '').trim() },
45
+ blockers: [
46
+ ...(quitOk ? [] : ['codex_app_quit_failed']),
47
+ ...(openOk ? [] : ['codex_app_open_failed'])
48
+ ]
49
+ };
50
+ }
51
+ function skipped(appName, reason) {
52
+ return {
53
+ schema: 'sks.codex-app-restart.v1',
54
+ ok: true,
55
+ status: 'skipped',
56
+ skipped: true,
57
+ reason,
58
+ app_name: appName,
59
+ blockers: []
60
+ };
61
+ }
62
+ function sleep(ms) {
63
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
64
+ }
65
+ //# sourceMappingURL=codex-app-restart.js.map