sneakoscope 4.7.0 → 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.
- package/README.md +5 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +222 -66
- package/dist/commands/codex-lb.js +143 -7
- package/dist/commands/doctor.js +10 -1
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +15 -3
- package/dist/core/codex-app/codex-app-restart.js +65 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +14 -3
- package/dist/core/codex-app.js +237 -3
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -2
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/codex-lb-setup.js +1 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/imagegen/imagegen-capability.js +2 -2
- package/dist/core/init.js +5 -8
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -1
- package/dist/core/provider/provider-context.js +1 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-app-provider-model-ui-check.js +114 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +90 -0
- package/dist/scripts/codex-lb-setup-truthfulness-check.js +6 -1
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +10 -2
- package/dist/scripts/gpt-image-2-real-file-smoke.js +2 -2
- package/dist/scripts/provider-context-config-toml-check.js +2 -2
- package/package.json +3 -1
|
@@ -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(
|
|
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)
|
package/dist/commands/doctor.js
CHANGED
|
@@ -56,7 +56,7 @@ async function runDoctor(args = [], root, doctorFix) {
|
|
|
56
56
|
const actualCodexProbeRequested = flag(args, '--actual-codex') || flag(args, '--require-actual-codex') || Boolean(codexBin);
|
|
57
57
|
const actualCodexProbeEnabled = deepDiagnostics || actualCodexProbeRequested;
|
|
58
58
|
const requireActualCodexProbe = flag(args, '--require-actual-codex') || (deepDiagnostics && doctorFix);
|
|
59
|
-
const shouldEvaluateCodexAppUiRepair = deepDiagnostics || flag(args, '--repair-codex-app-ui');
|
|
59
|
+
const shouldEvaluateCodexAppUiRepair = doctorFix || deepDiagnostics || flag(args, '--repair-codex-app-ui');
|
|
60
60
|
const shouldRunZellijRepair = deepDiagnostics || flag(args, '--repair-zellij') || flag(args, '--install-homebrew') || process.env.SKS_REQUIRE_ZELLIJ === '1';
|
|
61
61
|
const nativeCapabilityDiagnosticsRequested = deepDiagnostics || flag(args, '--repair-native-capabilities');
|
|
62
62
|
const doctorPhaseIds = doctorPhaseIdsForProfile(doctorProfile);
|
|
@@ -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,8 +3,10 @@ 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
|
-
const FAST_UI_TOP_LEVEL_RE = /^\s*service_tier\s*=/;
|
|
8
|
+
const FAST_UI_TOP_LEVEL_RE = /^\s*(?:model|model_reasoning_effort|service_tier)\s*=/;
|
|
9
|
+
const CODEX_APP_MODE_LOCK_TOP_LEVEL_RE = /^\s*(?:model|model_reasoning_effort)\s*=/;
|
|
8
10
|
const FAST_UI_FEATURE_LINE_RE = /^\s*fast_mode\s*=/;
|
|
9
11
|
const FAST_UI_USER_TABLE_LINE_RE = /^\s*(enabled|visible|locked|hidden|disabled)\s*=/;
|
|
10
12
|
const SKS_CAUSED_RE = /(?:SKS|Sneakoscope|codex-lb|sks-mad|sks fast)/i;
|
|
@@ -60,6 +62,12 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
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
|
+
});
|
|
63
71
|
const changed = actions.some((action) => action.changed);
|
|
64
72
|
const requiresConfirmation = unsafeReasons.length > 0 && input.force !== true;
|
|
65
73
|
const safeAutoApply = changed && !requiresConfirmation;
|
|
@@ -80,7 +88,10 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
80
88
|
detected_project_local_forbidden_keys: [...new Set(detectedProjectLocalForbiddenKeys)],
|
|
81
89
|
unsafe_repair_reasons: [...new Set(unsafeReasons)],
|
|
82
90
|
fast_selector: changed ? (input.apply ? 'repaired' : 'manual_action_required') : before.indicators.fast_selector === 'maybe_hidden_or_locked' ? 'manual_action_required' : 'ok',
|
|
83
|
-
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 || [],
|
|
84
95
|
host_owned_config: input.apply && changed ? 'repaired_with_backup' : changed ? 'preserved_until_explicit_apply' : 'preserved',
|
|
85
96
|
actions,
|
|
86
97
|
before_fast_selector: before.indicators.fast_selector,
|
|
@@ -125,10 +136,11 @@ function stripProjectLocalForbiddenKeys(text) {
|
|
|
125
136
|
}
|
|
126
137
|
function stripSksCausedHostOwnedLines(text) {
|
|
127
138
|
return stripMatchingLines(text, (line, table, previous, next) => {
|
|
139
|
+
const isCodexAppModeLock = !table && CODEX_APP_MODE_LOCK_TOP_LEVEL_RE.test(line);
|
|
128
140
|
const isFastUiLine = FAST_UI_TOP_LEVEL_RE.test(line)
|
|
129
141
|
|| (table === 'features' && FAST_UI_FEATURE_LINE_RE.test(line))
|
|
130
142
|
|| (table === 'user.fast_mode' && FAST_UI_USER_TABLE_LINE_RE.test(line));
|
|
131
|
-
return isFastUiLine && (SKS_CAUSED_RE.test(line) || SKS_CAUSED_RE.test(previous) || SKS_CAUSED_RE.test(next));
|
|
143
|
+
return isCodexAppModeLock || (isFastUiLine && (SKS_CAUSED_RE.test(line) || SKS_CAUSED_RE.test(previous) || SKS_CAUSED_RE.test(next)));
|
|
132
144
|
});
|
|
133
145
|
}
|
|
134
146
|
function stripMatchingLines(text, shouldRemove) {
|
|
@@ -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
|
|
@@ -4,6 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { exists, nowIso, readText, sha256, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
export const CODEX_APP_UI_STATE_SNAPSHOT_SCHEMA = 'sks.codex-app-ui-state-snapshot.v1';
|
|
6
6
|
export const PROJECT_LOCAL_FORBIDDEN_CODEX_KEYS = [
|
|
7
|
+
'model',
|
|
8
|
+
'model_reasoning_effort',
|
|
7
9
|
'openai_base_url',
|
|
8
10
|
'chatgpt_base_url',
|
|
9
11
|
'apps_mcp_product_sku',
|
|
@@ -15,7 +17,7 @@ export const PROJECT_LOCAL_FORBIDDEN_CODEX_KEYS = [
|
|
|
15
17
|
'experimental_realtime_ws_base_url',
|
|
16
18
|
'otel'
|
|
17
19
|
];
|
|
18
|
-
const HOST_OWNED_KEY_RE = /^(?:openai_base_url|chatgpt_base_url|apps_mcp_product_sku|model_provider|model_providers(?:\.|$)|notify|profile|profiles(?:\.|$)|experimental_realtime_ws_base_url|otel(?:\.|$)|features\.fast_mode|service_tier|user\.fast_mode(?:\.|$))/;
|
|
20
|
+
const HOST_OWNED_KEY_RE = /^(?:model|model_reasoning_effort|openai_base_url|chatgpt_base_url|apps_mcp_product_sku|model_provider|model_providers(?:\.|$)|notify|profile|profiles(?:\.|$)|experimental_realtime_ws_base_url|otel(?:\.|$)|features\.fast_mode|service_tier|user\.fast_mode(?:\.|$))/;
|
|
19
21
|
const SECRET_KEY_RE = /(?:key|token|secret|password|credential|cookie|authorization|bearer|refresh|access)/i;
|
|
20
22
|
export function codexHome(input) {
|
|
21
23
|
return path.resolve(String(input?.codexHome || process.env.CODEX_HOME || path.join(os.homedir(), '.codex')));
|
|
@@ -35,11 +37,16 @@ export async function snapshotCodexAppUiState(root = process.cwd(), input = {})
|
|
|
35
37
|
const providerSignals = hostOwnedSignals
|
|
36
38
|
.filter((signal) => signal.provider_related)
|
|
37
39
|
.map((signal) => `${signal.key_path}=${signal.value_preview}`);
|
|
38
|
-
const
|
|
40
|
+
const baseConfigHostOwnedSignals = files
|
|
41
|
+
.filter((file) => !isProfileConfigSnapshotPath(file.path))
|
|
42
|
+
.flatMap((file) => file.signals.filter((signal) => signal.host_owned));
|
|
43
|
+
const fastSelectorLocked = baseConfigHostOwnedSignals.some((signal) => {
|
|
39
44
|
if (signal.key_path === 'features.fast_mode' && signal.value_preview === 'false')
|
|
40
45
|
return true;
|
|
41
46
|
if (signal.key_path.startsWith('user.fast_mode') && /hidden|fixed|disabled|false/i.test(signal.value_preview))
|
|
42
47
|
return true;
|
|
48
|
+
if (signal.key_path === 'model' || signal.key_path === 'model_reasoning_effort')
|
|
49
|
+
return true;
|
|
43
50
|
if (signal.key_path === 'service_tier' && signal.sks_related)
|
|
44
51
|
return true;
|
|
45
52
|
return false;
|
|
@@ -120,7 +127,7 @@ export function scanTomlSignals(text) {
|
|
|
120
127
|
value_hash: sha256(value),
|
|
121
128
|
line: index + 1,
|
|
122
129
|
host_owned: HOST_OWNED_KEY_RE.test(keyPath),
|
|
123
|
-
fast_ui_related: /(?:fast_mode|service_tier)/i.test(keyPath) || /(?:fast|priority|default)/i.test(value),
|
|
130
|
+
fast_ui_related: /(?:fast_mode|service_tier|model_reasoning_effort|^model$)/i.test(keyPath) || /(?:fast|priority|default)/i.test(value),
|
|
124
131
|
provider_related: /(?:provider|base_url|auth|profile|openai|chatgpt|codex-lb)/i.test(lowerPath),
|
|
125
132
|
sks_related: /(?:sks|sneakoscope|codex-lb)/i.test(lineText)
|
|
126
133
|
});
|
|
@@ -176,6 +183,10 @@ async function profileConfigFiles(home) {
|
|
|
176
183
|
return [];
|
|
177
184
|
}
|
|
178
185
|
}
|
|
186
|
+
function isProfileConfigSnapshotPath(file) {
|
|
187
|
+
const base = path.basename(file);
|
|
188
|
+
return /^profile-.+\.config\.toml$/.test(base) || /^[A-Za-z0-9_-]+\.config\.toml$/.test(base);
|
|
189
|
+
}
|
|
179
190
|
async function readAuthMetadata(file) {
|
|
180
191
|
const text = await readText(file, null);
|
|
181
192
|
if (text == null)
|