sneakoscope 0.8.2 → 0.8.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.
- package/README.md +8 -4
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +267 -66
- package/src/cli/main.mjs +22 -10
- package/src/cli/maintenance-commands.mjs +16 -5
- package/src/core/codex-app.mjs +1 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +14 -3
- package/src/core/research.mjs +101 -15
- package/src/core/routes.mjs +2 -2
package/README.md
CHANGED
|
@@ -174,9 +174,9 @@ sks codex-lb repair
|
|
|
174
174
|
sks
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, syncs `codex login
|
|
177
|
+
Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, loads the provider env key for tmux launches, and syncs the macOS user launch environment so the Codex App can see `CODEX_LB_API_KEY` after restart. npm postinstall upgrades resync that stored env file when postinstall is not stopped by a hard harness conflict, and `sks doctor --fix` does the same during repair. If an older SKS release left the codex-lb dashboard key only in the shared Codex `auth.json` login cache, SKS migrates that key back into `~/.codex/sks-codex-lb.env` when a codex-lb provider or env base URL is already recoverable. It does not rewrite the shared Codex `auth.json` login cache by default; set `SKS_CODEX_LB_SYNC_CODEX_LOGIN=1` only if you intentionally want the old API-key login-cache behavior. When codex-lb is active, SKS opens a fresh `sks-codex-lb-*` tmux session and sweeps older detached codex-lb sessions for the same repo before launch so stale Responses API chains are not reused. Configured launch paths, including non-interactive runs, verify that codex-lb can continue a Responses API chain with `previous_response_id`; if that check fails, SKS bypasses codex-lb for that launch with `model_provider="openai"` instead of letting the Codex session fail mid-work.
|
|
178
178
|
|
|
179
|
-
If
|
|
179
|
+
If codex-lb provider auth drifts after launch/reinstall, run `sks doctor --fix` or `sks codex-lb repair`; to replace it, run `sks codex-lb reconfigure --host <domain> --api-key <key>`.
|
|
180
180
|
|
|
181
181
|
### MAD tmux Launch
|
|
182
182
|
|
|
@@ -185,7 +185,7 @@ sks --mad
|
|
|
185
185
|
sks --mad --yes
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
This syncs existing codex-lb
|
|
188
|
+
This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` full-access profile, opens the MAD-SKS permission gate for that tmux run, and launches a single Codex CLI pane. The session recreates the named session so stale split-pane MAD sessions collapse back to one pane. Catastrophic database wipe/all-row/project-management safeguards remain active, and the pipeline contract still forbids unrequested fallback implementation code.
|
|
189
189
|
|
|
190
190
|
Before launching, SKS checks npm for a newer `sneakoscope`; answer `y` to update or `n` to continue. Use `--yes` to approve missing dependency installs automatically.
|
|
191
191
|
|
|
@@ -275,7 +275,7 @@ For headless remotely controllable Codex App/server sessions on Codex CLI 0.130.
|
|
|
275
275
|
sks codex-app remote-control -- --help
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
-
`sks codex-app check` reports whether the installed Codex CLI is new enough, whether the required app flags are visible, whether Fast/speed-selector config is unlocked, and whether installed OpenAI default plugins such as Browser, Chrome, Computer Use, Documents, Presentations, Spreadsheets, and LaTeX are enabled. Codex CLI 0.130.0+ app-server/remote-control threads can pick up config changes live; older CLI/TUI sessions should still be restarted after `.codex/config.toml` or MCP/plugin changes.
|
|
278
|
+
`sks codex-app check` reports whether the installed Codex CLI is new enough, whether the required app flags are visible, whether Fast/speed-selector config is unlocked, and whether installed OpenAI default plugins such as Browser, Chrome, Computer Use, Documents, Presentations, Spreadsheets, and LaTeX are enabled. codex-lb can remain configured as a custom provider, but SKS keeps it off the top-level Codex App provider setting so native model, speed, and built-in feature UI stay visible. Codex CLI 0.130.0+ app-server/remote-control threads can pick up config changes live; older CLI/TUI sessions should still be restarted after `.codex/config.toml` or MCP/plugin changes.
|
|
279
279
|
|
|
280
280
|
Then open Codex App and use prompt commands directly in the chat. Examples:
|
|
281
281
|
|
|
@@ -407,6 +407,10 @@ codex mcp list
|
|
|
407
407
|
|
|
408
408
|
Codex App workflows need the app installed. UI/browser evidence requires first-party Codex Computer Use, and generated raster/image-review evidence requires real `$imagegen`/`gpt-image-2` output. After setup/upgrade, start a fresh thread so Codex reloads plugin tools.
|
|
409
409
|
|
|
410
|
+
### Codex App UI looks stale after codex-lb changes
|
|
411
|
+
|
|
412
|
+
If Codex App UI panels or auth-dependent controls still look wrong after codex-lb setup, repair, or upgrade, restart the app first. If the UI still does not recover, sign out of Codex App, sign back in, then run `sks codex-app check` or `sks codex-lb repair` as needed.
|
|
413
|
+
|
|
410
414
|
### Setup is blocked by another harness
|
|
411
415
|
|
|
412
416
|
```sh
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.8.
|
|
4
|
+
"version": "0.8.5",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -81,7 +81,8 @@ export async function postinstall({ bootstrap }) {
|
|
|
81
81
|
|
|
82
82
|
async function reportPostinstallCodexLbAuth() {
|
|
83
83
|
const codexLbAuth = await ensureCodexLbAuthDuringInstall();
|
|
84
|
-
if (codexLbAuth.
|
|
84
|
+
if (codexLbAuth.legacy_auth_migrated) console.log(`codex-lb auth: restored from existing Codex login cache into ${codexLbAuth.env_path}.`);
|
|
85
|
+
else if (codexLbAuth.status === 'synced' || codexLbAuth.status === 'present' || codexLbAuth.status === 'repaired') console.log(`codex-lb auth: preserved from ${codexLbAuth.env_path}.`);
|
|
85
86
|
else if (codexLbAuth.status === 'skipped') console.log(`codex-lb auth: skipped (${codexLbAuth.reason}).`);
|
|
86
87
|
else if (codexLbAuth.status === 'missing_env_key') console.log('codex-lb auth: stored key missing. Run `sks codex-lb setup --host <domain> --api-key <key>` to repair.');
|
|
87
88
|
else if (codexLbAuth.status === 'missing_base_url') console.log('codex-lb auth: stored key has no recoverable base URL. Run `sks codex-lb reconfigure --host <domain> --api-key <key>` once.');
|
|
@@ -155,6 +156,10 @@ export function codexLbEnvPath(home = process.env.HOME || os.homedir()) {
|
|
|
155
156
|
return path.join(home, '.codex', 'sks-codex-lb.env');
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
function codexAuthPath(home = process.env.HOME || os.homedir()) {
|
|
160
|
+
return path.join(home, '.codex', 'auth.json');
|
|
161
|
+
}
|
|
162
|
+
|
|
158
163
|
async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME || os.homedir()) {
|
|
159
164
|
const configPath = codexLbConfigPath(home);
|
|
160
165
|
const envPath = codexLbEnvPath(home);
|
|
@@ -169,10 +174,10 @@ async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME |
|
|
|
169
174
|
async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
|
|
170
175
|
if (!snapshot?.base_url) return { status: 'skipped', reason: 'no_snapshot' };
|
|
171
176
|
const current = await readText(snapshot.config_path, '');
|
|
172
|
-
|
|
177
|
+
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, snapshot.base_url));
|
|
178
|
+
if (next === ensureTrailingNewline(current) && codexLbProviderBaseUrl(current)) {
|
|
173
179
|
return { status: 'present', config_path: snapshot.config_path };
|
|
174
180
|
}
|
|
175
|
-
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, snapshot.base_url));
|
|
176
181
|
await writeTextAtomic(snapshot.config_path, next);
|
|
177
182
|
return { status: 'restored', config_path: snapshot.config_path };
|
|
178
183
|
}
|
|
@@ -198,9 +203,20 @@ export async function configureCodexLb(opts = {}) {
|
|
|
198
203
|
await writeTextAtomic(configPath, next);
|
|
199
204
|
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(baseUrl)}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
200
205
|
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
201
|
-
|
|
202
|
-
const codexLogin = await
|
|
203
|
-
|
|
206
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment({ env_path: envPath, base_url: baseUrl }, { ...opts, home });
|
|
207
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home, force: true });
|
|
208
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
209
|
+
return {
|
|
210
|
+
ok,
|
|
211
|
+
status: ok ? 'configured' : (codexEnvironment.status || codexLogin.status),
|
|
212
|
+
config_path: configPath,
|
|
213
|
+
env_path: envPath,
|
|
214
|
+
base_url: baseUrl,
|
|
215
|
+
env_key: 'CODEX_LB_API_KEY',
|
|
216
|
+
codex_environment: codexEnvironment,
|
|
217
|
+
codex_login: codexLogin,
|
|
218
|
+
error: codexEnvironment.error || codexLogin.error || null
|
|
219
|
+
};
|
|
204
220
|
}
|
|
205
221
|
|
|
206
222
|
export async function codexLbStatus(opts = {}) {
|
|
@@ -212,10 +228,10 @@ export async function codexLbStatus(opts = {}) {
|
|
|
212
228
|
const envText = envExists ? await readText(envPath, '') : '';
|
|
213
229
|
const envKeyConfigured = Boolean(parseCodexLbEnvKey(envText));
|
|
214
230
|
const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
|
|
215
|
-
const selected =
|
|
231
|
+
const selected = hasTopLevelCodexLbSelected(config);
|
|
216
232
|
const baseUrl = codexLbProviderBaseUrl(config) || parseCodexLbEnvBaseUrl(envText) || null;
|
|
217
233
|
return {
|
|
218
|
-
ok:
|
|
234
|
+
ok: providerConfigured && envKeyConfigured && Boolean(baseUrl),
|
|
219
235
|
config_path: configPath,
|
|
220
236
|
env_path: envPath,
|
|
221
237
|
provider_configured: providerConfigured,
|
|
@@ -360,10 +376,20 @@ function codexLbProviderBaseUrl(text = '') {
|
|
|
360
376
|
export async function repairCodexLbAuth(opts = {}) {
|
|
361
377
|
let status = await codexLbStatus(opts);
|
|
362
378
|
let configRepaired = false;
|
|
363
|
-
|
|
379
|
+
let legacyAuthMigrated = false;
|
|
380
|
+
let legacyAuthPath = null;
|
|
381
|
+
const currentConfig = await readText(status.config_path, '');
|
|
382
|
+
if (!status.env_key_configured && status.base_url && (status.provider_configured || status.selected || status.env_base_url_configured)) {
|
|
383
|
+
const legacyAuth = await restoreCodexLbEnvFromSharedLogin(status, opts);
|
|
384
|
+
if (legacyAuth.ok) {
|
|
385
|
+
legacyAuthMigrated = true;
|
|
386
|
+
legacyAuthPath = legacyAuth.auth_path;
|
|
387
|
+
status = await codexLbStatus(opts);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (status.env_key_configured && status.base_url && (!status.ok || status.selected || legacyAuthMigrated || hasTopLevelCodexModeLock(currentConfig))) {
|
|
364
391
|
await ensureDir(path.dirname(status.config_path));
|
|
365
|
-
const
|
|
366
|
-
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, status.base_url));
|
|
392
|
+
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(currentConfig, status.base_url));
|
|
367
393
|
await writeTextAtomic(status.config_path, next);
|
|
368
394
|
configRepaired = true;
|
|
369
395
|
status = await codexLbStatus(opts);
|
|
@@ -377,15 +403,21 @@ export async function repairCodexLbAuth(opts = {}) {
|
|
|
377
403
|
codex_lb: status
|
|
378
404
|
};
|
|
379
405
|
}
|
|
380
|
-
const
|
|
406
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
407
|
+
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
408
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
409
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
381
410
|
return {
|
|
382
|
-
ok
|
|
383
|
-
status:
|
|
411
|
+
ok,
|
|
412
|
+
status: ok ? 'repaired' : (codexEnvironment.status || codexLogin.status),
|
|
384
413
|
config_path: status.config_path,
|
|
385
414
|
env_path: status.env_path,
|
|
386
415
|
base_url: status.base_url,
|
|
387
416
|
config_repaired: configRepaired,
|
|
417
|
+
legacy_auth_migrated: legacyAuthMigrated,
|
|
418
|
+
legacy_auth_path: legacyAuthPath,
|
|
388
419
|
codex_lb: status,
|
|
420
|
+
codex_environment: codexEnvironment,
|
|
389
421
|
codex_login: codexLogin
|
|
390
422
|
};
|
|
391
423
|
}
|
|
@@ -395,34 +427,56 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
|
|
|
395
427
|
const status = await codexLbStatus(opts);
|
|
396
428
|
if (!status.selected && !status.provider_configured && !status.env_file) return { status: 'not_configured', codex_lb: status };
|
|
397
429
|
if (!status.ok) {
|
|
398
|
-
if (status.
|
|
430
|
+
if (status.base_url && (status.env_key_configured || status.provider_configured || status.selected || status.env_base_url_configured)) return repairCodexLbAuth(opts);
|
|
399
431
|
return { status: status.env_key_configured ? 'missing_base_url' : 'missing_env_key', codex_lb: status, config_path: status.config_path, env_path: status.env_path };
|
|
400
432
|
}
|
|
401
|
-
const
|
|
433
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
434
|
+
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
435
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir(), force: true });
|
|
436
|
+
const ok = Boolean(codexEnvironment.ok && codexLogin.ok);
|
|
402
437
|
return {
|
|
403
|
-
ok
|
|
404
|
-
status: codexLogin.status,
|
|
438
|
+
ok,
|
|
439
|
+
status: ok ? 'present' : (codexEnvironment.status || codexLogin.status),
|
|
405
440
|
config_path: status.config_path,
|
|
406
441
|
env_path: status.env_path,
|
|
407
442
|
base_url: status.base_url,
|
|
408
443
|
codex_lb: status,
|
|
444
|
+
codex_environment: codexEnvironment,
|
|
409
445
|
codex_login: codexLogin,
|
|
410
|
-
error: codexLogin.error || null
|
|
446
|
+
error: codexEnvironment.error || codexLogin.error || null
|
|
411
447
|
};
|
|
412
448
|
}
|
|
413
449
|
|
|
450
|
+
async function restoreCodexLbEnvFromSharedLogin(status = {}, opts = {}) {
|
|
451
|
+
const home = opts.home || process.env.HOME || os.homedir();
|
|
452
|
+
const authPath = opts.authPath || codexAuthPath(home);
|
|
453
|
+
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
454
|
+
const authText = await readText(authPath, '');
|
|
455
|
+
const apiKey = parseCodexSharedLoginApiKey(authText);
|
|
456
|
+
if (!apiKey) return { ok: false, status: 'missing_legacy_login_key', auth_path: authPath, env_path: envPath };
|
|
457
|
+
const baseUrl = status.base_url || parseCodexLbEnvBaseUrl(await readText(envPath, ''));
|
|
458
|
+
if (!baseUrl) return { ok: false, status: 'missing_base_url', auth_path: authPath, env_path: envPath };
|
|
459
|
+
await ensureDir(path.dirname(envPath));
|
|
460
|
+
await writeTextAtomic(envPath, `export CODEX_LB_BASE_URL=${shellSingleQuote(normalizeCodexLbBaseUrl(baseUrl))}\nexport CODEX_LB_API_KEY=${shellSingleQuote(apiKey)}\n`);
|
|
461
|
+
await fsp.chmod(envPath, 0o600).catch(() => {});
|
|
462
|
+
return { ok: true, status: 'migrated_login_cache', auth_path: authPath, env_path: envPath, base_url: normalizeCodexLbBaseUrl(baseUrl) };
|
|
463
|
+
}
|
|
464
|
+
|
|
414
465
|
export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
415
466
|
if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1') return { status: 'skipped' };
|
|
416
467
|
const status = await codexLbStatus(opts);
|
|
417
468
|
if (status.ok) {
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
469
|
+
const codexEnvironment = await syncCodexLbProviderEnvironment(status, opts);
|
|
470
|
+
if (codexEnvironment.status === 'synced') console.log('codex-lb provider auth synced for this user session.');
|
|
471
|
+
const apiKey = parseCodexLbEnvKey(await readText(status.env_path, ''));
|
|
472
|
+
const codexLogin = await maybeSyncCodexLbSharedLogin(apiKey, { ...opts, home: opts.home || process.env.HOME || os.homedir() });
|
|
473
|
+
if (codexLogin.status === 'synced') console.log('codex-lb auth synced with Codex CLI login cache.');
|
|
420
474
|
const chainHealth = await checkCodexLbResponseChain(status, opts);
|
|
421
475
|
if (!chainHealth.ok && chainHealth.chain_unhealthy) {
|
|
422
476
|
console.log(`codex-lb response chain check failed (${chainHealth.status}); bypassing codex-lb for this launch.`);
|
|
423
|
-
return { status: 'chain_unhealthy', ...status, ok: false, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
|
|
477
|
+
return { status: 'chain_unhealthy', ...status, ok: false, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
|
|
424
478
|
}
|
|
425
|
-
return { status: 'present', ...status, codex_login: codexLogin, chain_health: chainHealth };
|
|
479
|
+
return { status: 'present', ...status, codex_environment: codexEnvironment, codex_login: codexLogin, chain_health: chainHealth };
|
|
426
480
|
}
|
|
427
481
|
if (!canAskYesNo()) return { status: 'non_interactive', codex_lb: status };
|
|
428
482
|
const useCodexLb = (await askPostinstallQuestion('\nAuthenticate and route Codex through codex-lb? [y/N] ')).trim();
|
|
@@ -435,12 +489,57 @@ export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
|
|
|
435
489
|
return configured;
|
|
436
490
|
}
|
|
437
491
|
|
|
438
|
-
async function
|
|
492
|
+
async function syncCodexLbProviderEnvironment(status = {}, opts = {}) {
|
|
439
493
|
const home = opts.home || process.env.HOME || os.homedir();
|
|
440
494
|
const envPath = opts.envPath || status.env_path || codexLbEnvPath(home);
|
|
441
|
-
const
|
|
495
|
+
const envText = await readText(envPath, '');
|
|
496
|
+
const apiKey = parseCodexLbEnvKey(envText);
|
|
442
497
|
if (!apiKey) return { ok: false, status: 'missing_env_key' };
|
|
443
|
-
|
|
498
|
+
const baseUrl = status.base_url || parseCodexLbEnvBaseUrl(envText);
|
|
499
|
+
process.env.CODEX_LB_API_KEY = apiKey;
|
|
500
|
+
if (baseUrl) process.env.CODEX_LB_BASE_URL = baseUrl;
|
|
501
|
+
const launchEnv = await syncCodexLbMacLaunchEnvironment({ CODEX_LB_API_KEY: apiKey, ...(baseUrl ? { CODEX_LB_BASE_URL: baseUrl } : {}) }, opts);
|
|
502
|
+
const ok = launchEnv.ok || launchEnv.skipped || launchEnv.status === 'not_macos';
|
|
503
|
+
return {
|
|
504
|
+
ok,
|
|
505
|
+
status: launchEnv.status === 'synced' ? 'synced' : ok ? 'process_env' : launchEnv.status,
|
|
506
|
+
env_path: envPath,
|
|
507
|
+
base_url: baseUrl || null,
|
|
508
|
+
launch_environment: launchEnv,
|
|
509
|
+
error: launchEnv.error || null
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function syncCodexLbMacLaunchEnvironment(values = {}, opts = {}) {
|
|
514
|
+
if (opts.syncLaunchEnv === false || process.env.SKS_SKIP_CODEX_LB_LAUNCH_ENV === '1') return { ok: true, status: 'skipped', skipped: true, reason: 'SKS_SKIP_CODEX_LB_LAUNCH_ENV=1' };
|
|
515
|
+
if (process.platform !== 'darwin' && !opts.forceLaunchEnv) return { ok: true, status: 'not_macos', skipped: true };
|
|
516
|
+
const launchctl = opts.launchctlBin || await which('launchctl').catch(() => null) || await exists('/bin/launchctl').then((ok) => ok ? '/bin/launchctl' : null).catch(() => null);
|
|
517
|
+
if (!launchctl) return { ok: false, status: 'launchctl_missing', error: 'launchctl not found on PATH' };
|
|
518
|
+
const variables = Object.entries(values).filter(([, value]) => value);
|
|
519
|
+
const results = [];
|
|
520
|
+
for (const [key, value] of variables) {
|
|
521
|
+
const result = await runProcess(launchctl, ['setenv', key, value], { timeoutMs: 5000, maxOutputBytes: 8192 });
|
|
522
|
+
results.push({
|
|
523
|
+
key,
|
|
524
|
+
ok: result.code === 0,
|
|
525
|
+
error: result.code === 0 ? null : redactSecretText(result.stderr || result.stdout || 'launchctl setenv failed', [value]).trim()
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
const failed = results.filter((result) => !result.ok);
|
|
529
|
+
if (failed.length) return { ok: false, status: 'launch_env_failed', variables: results.map((result) => result.key), failed, error: failed.map((result) => `${result.key}: ${result.error}`).join('; ') };
|
|
530
|
+
return { ok: true, status: 'synced', variables: results.map((result) => result.key) };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function maybeSyncCodexLbSharedLogin(apiKey, opts = {}) {
|
|
534
|
+
if (!apiKey) return { ok: false, status: 'missing_env_key' };
|
|
535
|
+
if (!shouldSyncCodexLbSharedLogin(opts)) {
|
|
536
|
+
return { ok: true, status: 'skipped', reason: 'codex-lb uses provider env_key auth; set SKS_CODEX_LB_SYNC_CODEX_LOGIN=1 to also rewrite Codex shared login cache.' };
|
|
537
|
+
}
|
|
538
|
+
return syncCodexApiKeyLogin(apiKey, opts);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function shouldSyncCodexLbSharedLogin(opts = {}) {
|
|
542
|
+
return opts.syncCodexLogin === true || process.env.SKS_CODEX_LB_SYNC_CODEX_LOGIN === '1';
|
|
444
543
|
}
|
|
445
544
|
|
|
446
545
|
async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
@@ -460,7 +559,7 @@ async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
|
460
559
|
}
|
|
461
560
|
|
|
462
561
|
function upsertCodexLbConfig(text = '', baseUrl) {
|
|
463
|
-
let next =
|
|
562
|
+
let next = removeTopLevelTomlKeyIfValue(text, 'model_provider', 'codex-lb');
|
|
464
563
|
const block = [
|
|
465
564
|
'[model_providers.codex-lb]',
|
|
466
565
|
'name = "OpenAI"',
|
|
@@ -547,6 +646,14 @@ function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
|
547
646
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
548
647
|
}
|
|
549
648
|
|
|
649
|
+
function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
|
|
650
|
+
const lines = String(text || '').split('\n');
|
|
651
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
652
|
+
const end = firstTable === -1 ? lines.length : firstTable;
|
|
653
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?$`);
|
|
654
|
+
return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
655
|
+
}
|
|
656
|
+
|
|
550
657
|
function removeTomlTableKey(text, table, key) {
|
|
551
658
|
const lines = String(text || '').trimEnd().split('\n');
|
|
552
659
|
if (lines.length === 1 && lines[0] === '') return '';
|
|
@@ -655,6 +762,19 @@ function parseCodexLbEnvBaseUrl(text = '') {
|
|
|
655
762
|
return value ? normalizeCodexLbBaseUrl(value) : '';
|
|
656
763
|
}
|
|
657
764
|
|
|
765
|
+
function parseCodexSharedLoginApiKey(text = '') {
|
|
766
|
+
try {
|
|
767
|
+
const parsed = JSON.parse(String(text || ''));
|
|
768
|
+
const authMode = String(parsed?.auth_mode || parsed?.authMode || parsed?.mode || '').toLowerCase();
|
|
769
|
+
const key = parsed?.key || parsed?.api_key || parsed?.apiKey || parsed?.openai_api_key || parsed?.OPENAI_API_KEY;
|
|
770
|
+
if (!key || typeof key !== 'string') return '';
|
|
771
|
+
if (authMode && !/api[-_]?key|apikey/.test(authMode)) return '';
|
|
772
|
+
return key.trim();
|
|
773
|
+
} catch {
|
|
774
|
+
return '';
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
658
778
|
function parseShellEnvValue(text = '', key = '') {
|
|
659
779
|
const re = new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=\\s*(.+?)\\s*$`, 'm');
|
|
660
780
|
const envMatch = String(text || '').match(re);
|
|
@@ -1074,6 +1194,24 @@ async function safeReadText(file, fallback = '') {
|
|
|
1074
1194
|
}
|
|
1075
1195
|
}
|
|
1076
1196
|
|
|
1197
|
+
async function codexLbLoginCallCount(home) {
|
|
1198
|
+
return (await safeReadText(path.join(home, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function codexLbPostinstallEnv(baseEnv, overrides = {}) {
|
|
1202
|
+
return {
|
|
1203
|
+
...baseEnv,
|
|
1204
|
+
SKS_POSTINSTALL_NO_BOOTSTRAP: '1',
|
|
1205
|
+
SKS_SKIP_POSTINSTALL_SHIM: '1',
|
|
1206
|
+
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1207
|
+
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1208
|
+
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1209
|
+
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1210
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1',
|
|
1211
|
+
...overrides
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1077
1215
|
export async function selftestCodexLb(tmp) {
|
|
1078
1216
|
const codexLbHome = path.join(tmp, 'codex-lb-home');
|
|
1079
1217
|
await ensureDir(path.join(codexLbHome, '.codex'));
|
|
@@ -1083,7 +1221,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1083
1221
|
await writeTextAtomic(codexLbFakeCodex, "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo \"codex-cli 99.0.0\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"status\" ]; then echo \"logged in with browser auth\"; exit 0; fi\nif [ \"$1\" = \"login\" ] && [ \"$2\" = \"--with-api-key\" ]; then read key; mkdir -p \"$HOME/.codex\"; printf '{\\\"auth_mode\\\":\\\"apikey\\\",\\\"key\\\":\\\"%s\\\"}\\n' \"$key\" > \"$HOME/.codex/auth.json\"; printf '%s\\n' \"$key\" >> \"$HOME/.codex/login-calls.log\"; exit 0; fi\necho \"fake codex unsupported\" >&2\nexit 1\n");
|
|
1084
1222
|
await fsp.chmod(codexLbFakeCodex, 0o755);
|
|
1085
1223
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "low"\nservice_tier = "fast"\n\n[profiles.custom]\nmodel_reasoning_effort = "low"\n\n[notice]\nfast_default_opt_out = true\n\n[features]\ncodex_hooks = true\n');
|
|
1086
|
-
const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}
|
|
1224
|
+
const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}`, SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' };
|
|
1087
1225
|
const codexLbSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
|
|
1088
1226
|
cwd: tmp,
|
|
1089
1227
|
env: codexLbEnvForSelftest,
|
|
@@ -1095,48 +1233,50 @@ export async function selftestCodexLb(tmp) {
|
|
|
1095
1233
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1096
1234
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1097
1235
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1098
|
-
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' ||
|
|
1236
|
+
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' || hasTopLevelCodexLbSelected(codexLbConfig) || !codexLbConfig.includes('[model_providers.codex-lb]') || !codexLbEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbEnv.includes("CODEX_LB_API_KEY='sk-test'") || codexLbSetupJson.codex_environment?.ok !== true || codexLbSetupJson.codex_login?.status !== 'skipped' || codexLbAuth.trim()) throw new Error('selftest: codex-lb setup');
|
|
1237
|
+
const codexLbFailLaunchctl = path.join(codexLbFakeBin, 'launchctl-fail');
|
|
1238
|
+
await writeTextAtomic(codexLbFailLaunchctl, '#!/bin/sh\necho "launchctl denied" >&2\nexit 7\n');
|
|
1239
|
+
await fsp.chmod(codexLbFailLaunchctl, 0o755);
|
|
1240
|
+
const codexLbFailedLaunchEnv = await configureCodexLb({ home: path.join(tmp, 'codex-lb-launch-fail-home'), host: 'lb.example.test', apiKey: 'sk-fail', forceLaunchEnv: true, launchctlBin: codexLbFailLaunchctl });
|
|
1241
|
+
if (codexLbFailedLaunchEnv.ok || codexLbFailedLaunchEnv.status !== 'launch_env_failed' || !/launchctl denied/.test(codexLbFailedLaunchEnv.error || '')) throw new Error('selftest: codex-lb setup must expose launch-env failure');
|
|
1099
1242
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
1100
1243
|
await initProject(codexLbHome, { installScope: 'global', force: true, repair: true });
|
|
1101
1244
|
const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1102
|
-
if (
|
|
1245
|
+
if (hasTopLevelCodexLbSelected(codexLbRepairSetupConfig) || !codexLbRepairSetupConfig.includes('[model_providers.codex-lb]') || !codexLbRepairSetupConfig.includes('https://lb.example.test/backend-api/codex') || codexLbRepairSetupConfig.includes('sk-test')) throw new Error('selftest: init codex-lb');
|
|
1103
1246
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbRepairSetupConfig)) throw new Error('selftest: init codex-lb did not suppress Codex unstable feature warning');
|
|
1104
1247
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), `${codexLbConfig}\n[mcp_servers.supabase]\nurl = "https://mcp.supabase.com/mcp?project_ref=ref&read_only=true&features=database,docs"\n`);
|
|
1105
1248
|
const ptmp = path.join(tmp, 'codex-lb-project-config'), prevHome = process.env.HOME;
|
|
1106
1249
|
try { process.env.HOME = codexLbHome; await initProject(ptmp, { installScope: 'global' }); }
|
|
1107
1250
|
finally { if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; }
|
|
1108
1251
|
const pcfg = await safeReadText(path.join(ptmp, '.codex', 'config.toml'));
|
|
1109
|
-
if (
|
|
1252
|
+
if (hasTopLevelCodexLbSelected(pcfg) || !pcfg.includes('[model_providers.codex-lb]') || !pcfg.includes('[mcp_servers.supabase]') || !pcfg.includes('read_only=true')) throw new Error('selftest: project codex-lb');
|
|
1110
1253
|
if (!hasCodexUnstableFeatureWarningSuppression(pcfg)) throw new Error('selftest: project codex-lb config did not suppress Codex unstable feature warning');
|
|
1111
1254
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
1112
1255
|
const codexLbRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1113
1256
|
if (codexLbRepair.code !== 0) throw new Error(`selftest: codex-lb repair exited ${codexLbRepair.code}: ${codexLbRepair.stderr}`);
|
|
1114
1257
|
const codexLbRepairJson = JSON.parse(codexLbRepair.stdout);
|
|
1115
1258
|
const codexLbRepairedAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1116
|
-
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || !codexLbRepairedAuth.includes('"auth_mode":"
|
|
1117
|
-
const
|
|
1259
|
+
if (!codexLbRepairJson.ok || codexLbRepairJson.status !== 'repaired' || codexLbRepairJson.codex_environment?.ok !== true || codexLbRepairJson.codex_login?.status !== 'skipped' || !codexLbRepairedAuth.includes('"auth_mode":"browser"') || codexLbRepairedAuth.includes('sk-test')) throw new Error('selftest: codex-lb repair');
|
|
1260
|
+
const codexLbLegacyRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: { ...codexLbEnvForSelftest, SKS_CODEX_LB_SYNC_CODEX_LOGIN: '1' }, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1261
|
+
if (codexLbLegacyRepair.code !== 0) throw new Error(`selftest: codex-lb legacy login repair exited ${codexLbLegacyRepair.code}: ${codexLbLegacyRepair.stderr}`);
|
|
1262
|
+
const codexLbLegacyRepairJson = JSON.parse(codexLbLegacyRepair.stdout);
|
|
1263
|
+
const codexLbLegacyAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1264
|
+
if (!codexLbLegacyRepairJson.ok || codexLbLegacyRepairJson.codex_login?.status !== 'synced' || !codexLbLegacyAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyAuth.includes('sk-test')) throw new Error('selftest: codex-lb legacy login repair');
|
|
1265
|
+
const codexLbLoginCallsBeforePostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1118
1266
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
1119
1267
|
const codexLbPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1120
1268
|
cwd: tmp,
|
|
1121
|
-
env:
|
|
1122
|
-
...codexLbEnvForSelftest,
|
|
1123
|
-
SKS_POSTINSTALL_NO_BOOTSTRAP: '1',
|
|
1124
|
-
SKS_SKIP_POSTINSTALL_SHIM: '1',
|
|
1125
|
-
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1126
|
-
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1127
|
-
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1128
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1129
|
-
},
|
|
1269
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1130
1270
|
timeoutMs: 15000,
|
|
1131
1271
|
maxOutputBytes: 128 * 1024
|
|
1132
1272
|
});
|
|
1133
1273
|
if (codexLbPostinstall.code !== 0) throw new Error(`selftest: codex-lb postinstall auth preservation exited ${codexLbPostinstall.code}: ${codexLbPostinstall.stderr}`);
|
|
1134
1274
|
const codexLbPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1135
|
-
const codexLbLoginCallsAfterPostinstall =
|
|
1136
|
-
if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"
|
|
1137
|
-
const postinstallEnvKeys = ['HOME', 'PATH', 'INIT_CWD', 'SKS_GLOBAL_ROOT', 'SKS_POSTINSTALL_BOOTSTRAP', 'SKS_POSTINSTALL_NO_BOOTSTRAP', 'SKS_SKIP_POSTINSTALL_SHIM', 'SKS_SKIP_POSTINSTALL_CONTEXT7', 'SKS_SKIP_POSTINSTALL_GETDESIGN', 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS', 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH'];
|
|
1275
|
+
const codexLbLoginCallsAfterPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1276
|
+
if (!String(codexLbPostinstall.stdout || '').includes('codex-lb auth: preserved') || !codexLbPostinstallAuth.includes('"auth_mode":"browser"') || codexLbPostinstallAuth.includes('sk-test') || codexLbLoginCallsAfterPostinstall !== codexLbLoginCallsBeforePostinstall) throw new Error('selftest: postinstall auth');
|
|
1277
|
+
const postinstallEnvKeys = ['HOME', 'PATH', 'INIT_CWD', 'SKS_GLOBAL_ROOT', 'SKS_POSTINSTALL_BOOTSTRAP', 'SKS_POSTINSTALL_NO_BOOTSTRAP', 'SKS_SKIP_POSTINSTALL_SHIM', 'SKS_SKIP_POSTINSTALL_CONTEXT7', 'SKS_SKIP_POSTINSTALL_GETDESIGN', 'SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS', 'SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH', 'SKS_SKIP_CODEX_LB_LAUNCH_ENV', 'SKS_CODEX_LB_SYNC_CODEX_LOGIN'];
|
|
1138
1278
|
const postinstallEnvBefore = Object.fromEntries(postinstallEnvKeys.map((key) => [key, process.env[key]]));
|
|
1139
|
-
const codexLbLoginCallsBeforeBootstrap =
|
|
1279
|
+
const codexLbLoginCallsBeforeBootstrap = await codexLbLoginCallCount(codexLbHome);
|
|
1140
1280
|
try {
|
|
1141
1281
|
for (const key of postinstallEnvKeys) delete process.env[key];
|
|
1142
1282
|
Object.assign(process.env, {
|
|
@@ -1149,7 +1289,8 @@ export async function selftestCodexLb(tmp) {
|
|
|
1149
1289
|
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1150
1290
|
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1151
1291
|
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1152
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1292
|
+
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1293
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1'
|
|
1153
1294
|
});
|
|
1154
1295
|
await postinstall({
|
|
1155
1296
|
bootstrap: async () => {
|
|
@@ -1165,9 +1306,9 @@ export async function selftestCodexLb(tmp) {
|
|
|
1165
1306
|
}
|
|
1166
1307
|
const codexLbPostBootstrapAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1167
1308
|
const codexLbPostBootstrapConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1168
|
-
const codexLbLoginCallsAfterBootstrap =
|
|
1169
|
-
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"
|
|
1170
|
-
if (
|
|
1309
|
+
const codexLbLoginCallsAfterBootstrap = await codexLbLoginCallCount(codexLbHome);
|
|
1310
|
+
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"browser"') || codexLbPostBootstrapAuth.includes('sk-test') || codexLbLoginCallsAfterBootstrap !== codexLbLoginCallsBeforeBootstrap) throw new Error('selftest: postinstall drift auth');
|
|
1311
|
+
if (hasTopLevelCodexLbSelected(codexLbPostBootstrapConfig) || !codexLbPostBootstrapConfig.includes('[model_providers.codex-lb]') || !codexLbPostBootstrapConfig.includes('https://lb.example.test/backend-api/codex') || codexLbPostBootstrapConfig.includes('sk-test')) throw new Error('selftest: postinstall drift config');
|
|
1171
1312
|
const doctorProject = tmpdir();
|
|
1172
1313
|
await ensureDir(path.join(doctorProject, '.git'));
|
|
1173
1314
|
await writeTextAtomic(path.join(doctorProject, 'package.json'), '{"name":"codex-lb-doctor-project","version":"0.0.0"}\n');
|
|
@@ -1184,7 +1325,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1184
1325
|
const codexLbDoctorJson = JSON.parse(codexLbDoctorRepair.stdout);
|
|
1185
1326
|
const codexLbDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1186
1327
|
const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1187
|
-
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"
|
|
1328
|
+
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"browser"') || codexLbDoctorAuth.includes('sk-test') || hasTopLevelCodexLbSelected(codexLbDoctorConfig) || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex') || !hasCodexUnstableFeatureWarningSuppression(codexLbDoctorConfig)) throw new Error('selftest: doctor codex-lb');
|
|
1188
1329
|
const codexLbContext7Bin = path.join(tmp, 'codex-lb-context7-bin');
|
|
1189
1330
|
await ensureDir(codexLbContext7Bin);
|
|
1190
1331
|
await writeTextAtomic(path.join(codexLbContext7Bin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 99.0.0"; exit 0; fi\nif [ "$CODEX_LB_API_KEY" ]; then echo "context7 leaked CODEX_LB_API_KEY" >&2; exit 77; fi\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then echo ""; exit 0; fi\nif [ "$1" = "mcp" ] && [ "$2" = "add" ]; then echo "context7 added"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
@@ -1206,23 +1347,82 @@ export async function selftestCodexLb(tmp) {
|
|
|
1206
1347
|
});
|
|
1207
1348
|
if (codexLbContext7Postinstall.code !== 0 || String(`${codexLbContext7Postinstall.stdout}\n${codexLbContext7Postinstall.stderr}`).includes('leaked CODEX_LB_API_KEY')) throw new Error('selftest: Context7 key leak');
|
|
1208
1349
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_API_KEY='unterminated\n");
|
|
1209
|
-
const codexLbLoginCallsBeforeMalformed =
|
|
1350
|
+
const codexLbLoginCallsBeforeMalformed = await codexLbLoginCallCount(codexLbHome);
|
|
1210
1351
|
const codexLbMalformedPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1211
1352
|
cwd: tmp,
|
|
1212
|
-
env:
|
|
1213
|
-
...codexLbEnvForSelftest,
|
|
1214
|
-
SKS_POSTINSTALL_NO_BOOTSTRAP: '1',
|
|
1215
|
-
SKS_SKIP_POSTINSTALL_SHIM: '1',
|
|
1216
|
-
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1217
|
-
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1218
|
-
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1219
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1220
|
-
},
|
|
1353
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1221
1354
|
timeoutMs: 15000,
|
|
1222
1355
|
maxOutputBytes: 128 * 1024
|
|
1223
1356
|
});
|
|
1224
|
-
const codexLbLoginCallsAfterMalformed =
|
|
1357
|
+
const codexLbLoginCallsAfterMalformed = await codexLbLoginCallCount(codexLbHome);
|
|
1225
1358
|
if (codexLbMalformedPostinstall.code !== 0 || !String(codexLbMalformedPostinstall.stdout || '').includes('codex-lb auth: stored key missing') || codexLbLoginCallsAfterMalformed !== codexLbLoginCallsBeforeMalformed) throw new Error('selftest: bad codex-lb env');
|
|
1359
|
+
await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
|
|
1360
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), '[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
|
|
1361
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy"}\n');
|
|
1362
|
+
const codexLbLoginCallsBeforeLegacyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1363
|
+
const codexLbLegacyPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1364
|
+
cwd: tmp,
|
|
1365
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1366
|
+
timeoutMs: 15000,
|
|
1367
|
+
maxOutputBytes: 128 * 1024
|
|
1368
|
+
});
|
|
1369
|
+
if (codexLbLegacyPostinstall.code !== 0) throw new Error(`selftest: legacy codex-lb postinstall restore exited ${codexLbLegacyPostinstall.code}: ${codexLbLegacyPostinstall.stderr}`);
|
|
1370
|
+
const codexLbLegacyPostinstallEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1371
|
+
const codexLbLegacyPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1372
|
+
const codexLbLoginCallsAfterLegacyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1373
|
+
if (!String(codexLbLegacyPostinstall.stdout || '').includes('codex-lb auth: restored from existing Codex login cache') || !codexLbLegacyPostinstallEnv.includes("CODEX_LB_API_KEY='sk-legacy'") || !codexLbLegacyPostinstallEnv.includes("CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'") || !codexLbLegacyPostinstallAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyPostinstallAuth.includes('sk-legacy') || codexLbLoginCallsAfterLegacyPostinstall !== codexLbLoginCallsBeforeLegacyPostinstall) throw new Error('selftest: legacy codex-lb postinstall restore');
|
|
1374
|
+
await fsp.rm(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), { force: true });
|
|
1375
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model_provider = "codex-lb"\n\n[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "https://lb.example.test/backend-api/codex"\nwire_api = "responses"\nsupports_websockets = true\nrequires_openai_auth = true\n');
|
|
1376
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-legacy-doctor"}\n');
|
|
1377
|
+
const codexLbLegacyDoctorProject = tmpdir();
|
|
1378
|
+
await ensureDir(path.join(codexLbLegacyDoctorProject, '.git'));
|
|
1379
|
+
await writeTextAtomic(path.join(codexLbLegacyDoctorProject, 'package.json'), '{"name":"codex-lb-legacy-doctor-project","version":"0.0.0"}\n');
|
|
1380
|
+
const codexLbLegacyDoctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
|
|
1381
|
+
cwd: codexLbLegacyDoctorProject,
|
|
1382
|
+
env: { ...codexLbEnvForSelftest, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-legacy-doctor-global'), SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' },
|
|
1383
|
+
timeoutMs: 30000,
|
|
1384
|
+
maxOutputBytes: 256 * 1024
|
|
1385
|
+
});
|
|
1386
|
+
if (codexLbLegacyDoctorRepair.code !== 0) throw new Error(`selftest: legacy doctor --fix codex-lb restore exited ${codexLbLegacyDoctorRepair.code}: ${codexLbLegacyDoctorRepair.stderr}`);
|
|
1387
|
+
const codexLbLegacyDoctorJson = JSON.parse(codexLbLegacyDoctorRepair.stdout);
|
|
1388
|
+
const codexLbLegacyDoctorEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1389
|
+
const codexLbLegacyDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1390
|
+
const codexLbLegacyDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1391
|
+
if (!codexLbLegacyDoctorJson.repair?.codex_lb?.ok || !codexLbLegacyDoctorJson.repair.codex_lb.legacy_auth_migrated || !codexLbLegacyDoctorEnv.includes("CODEX_LB_API_KEY='sk-legacy-doctor'") || !codexLbLegacyDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbLegacyDoctorAuth.includes('sk-legacy-doctor') || hasTopLevelCodexLbSelected(codexLbLegacyDoctorConfig) || !codexLbLegacyDoctorConfig.includes('env_key = "CODEX_LB_API_KEY"')) throw new Error('selftest: legacy doctor codex-lb restore');
|
|
1392
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\n");
|
|
1393
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nservice_tier = "fast"\n');
|
|
1394
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-env-only"}\n');
|
|
1395
|
+
const codexLbLoginCallsBeforeEnvOnlyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1396
|
+
const codexLbEnvOnlyPostinstall = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1397
|
+
cwd: tmp,
|
|
1398
|
+
env: codexLbPostinstallEnv(codexLbEnvForSelftest),
|
|
1399
|
+
timeoutMs: 15000,
|
|
1400
|
+
maxOutputBytes: 128 * 1024
|
|
1401
|
+
});
|
|
1402
|
+
if (codexLbEnvOnlyPostinstall.code !== 0) throw new Error(`selftest: env-only codex-lb postinstall restore exited ${codexLbEnvOnlyPostinstall.code}: ${codexLbEnvOnlyPostinstall.stderr}`);
|
|
1403
|
+
const codexLbEnvOnlyPostinstallEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1404
|
+
const codexLbEnvOnlyPostinstallConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1405
|
+
const codexLbEnvOnlyPostinstallAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1406
|
+
const codexLbLoginCallsAfterEnvOnlyPostinstall = await codexLbLoginCallCount(codexLbHome);
|
|
1407
|
+
if (!String(codexLbEnvOnlyPostinstall.stdout || '').includes('codex-lb auth: restored from existing Codex login cache') || !codexLbEnvOnlyPostinstallEnv.includes("CODEX_LB_API_KEY='sk-env-only'") || !codexLbEnvOnlyPostinstallConfig.includes('env_key = "CODEX_LB_API_KEY"') || !codexLbEnvOnlyPostinstallAuth.includes('sk-env-only') || codexLbLoginCallsAfterEnvOnlyPostinstall !== codexLbLoginCallsBeforeEnvOnlyPostinstall) throw new Error('selftest: env-only codex-lb postinstall restore');
|
|
1408
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_BASE_URL='https://lb.example.test/backend-api/codex'\n");
|
|
1409
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nservice_tier = "fast"\n');
|
|
1410
|
+
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"apikey","key":"sk-env-only-doctor"}\n');
|
|
1411
|
+
const codexLbEnvOnlyDoctorProject = tmpdir();
|
|
1412
|
+
await ensureDir(path.join(codexLbEnvOnlyDoctorProject, '.git'));
|
|
1413
|
+
await writeTextAtomic(path.join(codexLbEnvOnlyDoctorProject, 'package.json'), '{"name":"codex-lb-env-only-doctor-project","version":"0.0.0"}\n');
|
|
1414
|
+
const codexLbEnvOnlyDoctorRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'doctor', '--fix', '--json'], {
|
|
1415
|
+
cwd: codexLbEnvOnlyDoctorProject,
|
|
1416
|
+
env: { ...codexLbEnvForSelftest, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-env-only-doctor-global'), SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1' },
|
|
1417
|
+
timeoutMs: 30000,
|
|
1418
|
+
maxOutputBytes: 256 * 1024
|
|
1419
|
+
});
|
|
1420
|
+
if (codexLbEnvOnlyDoctorRepair.code !== 0) throw new Error(`selftest: env-only doctor --fix codex-lb restore exited ${codexLbEnvOnlyDoctorRepair.code}: ${codexLbEnvOnlyDoctorRepair.stderr}`);
|
|
1421
|
+
const codexLbEnvOnlyDoctorJson = JSON.parse(codexLbEnvOnlyDoctorRepair.stdout);
|
|
1422
|
+
const codexLbEnvOnlyDoctorEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1423
|
+
const codexLbEnvOnlyDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1424
|
+
const codexLbEnvOnlyDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1425
|
+
if (!codexLbEnvOnlyDoctorJson.repair?.codex_lb?.ok || !codexLbEnvOnlyDoctorJson.repair.codex_lb.legacy_auth_migrated || !codexLbEnvOnlyDoctorEnv.includes("CODEX_LB_API_KEY='sk-env-only-doctor'") || !codexLbEnvOnlyDoctorConfig.includes('env_key = "CODEX_LB_API_KEY"') || !codexLbEnvOnlyDoctorAuth.includes('sk-env-only-doctor')) throw new Error('selftest: env-only doctor codex-lb restore');
|
|
1226
1426
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'), "export CODEX_LB_API_KEY='sk-test'\n");
|
|
1227
1427
|
const codexLbMissingCli = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1228
1428
|
cwd: tmp,
|
|
@@ -1235,12 +1435,13 @@ export async function selftestCodexLb(tmp) {
|
|
|
1235
1435
|
SKS_SKIP_POSTINSTALL_CONTEXT7: '1',
|
|
1236
1436
|
SKS_SKIP_POSTINSTALL_GETDESIGN: '1',
|
|
1237
1437
|
SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1',
|
|
1238
|
-
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0'
|
|
1438
|
+
SKS_SKIP_POSTINSTALL_CODEX_LB_AUTH: '0',
|
|
1439
|
+
SKS_SKIP_CODEX_LB_LAUNCH_ENV: '1'
|
|
1239
1440
|
},
|
|
1240
1441
|
timeoutMs: 15000,
|
|
1241
1442
|
maxOutputBytes: 128 * 1024
|
|
1242
1443
|
});
|
|
1243
|
-
if (codexLbMissingCli.code !== 0 || !String(codexLbMissingCli.stdout || '').includes('codex-lb auth:
|
|
1444
|
+
if (codexLbMissingCli.code !== 0 || !String(codexLbMissingCli.stdout || '').includes('codex-lb auth: preserved') || String(codexLbMissingCli.stdout || '').includes('codex_missing')) throw new Error('selftest: codex-lb provider auth should not require Codex CLI login');
|
|
1244
1445
|
const codexLbNotConfiguredHome = path.join(tmp, 'codex-lb-not-configured-home');
|
|
1245
1446
|
const codexLbNotConfigured = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], {
|
|
1246
1447
|
cwd: tmp,
|
|
@@ -1260,7 +1461,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1260
1461
|
});
|
|
1261
1462
|
if (codexLbNotConfigured.code !== 0 || String(codexLbNotConfigured.stdout || '').includes('codex-lb auth:')) throw new Error('selftest: postinstall should stay quiet when codex-lb is not configured');
|
|
1262
1463
|
const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
1263
|
-
if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
|
|
1464
|
+
if (!String(codexLbStatusText.stdout || '').includes('Repair provider auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
|
|
1264
1465
|
const nonInteractiveLaunchChainCalls = [];
|
|
1265
1466
|
const nonInteractiveLaunch = await maybePromptCodexLbSetupForLaunch([], {
|
|
1266
1467
|
home: codexLbHome,
|
|
@@ -1326,7 +1527,7 @@ function hasTopLevelCodexModeLock(text = '') {
|
|
|
1326
1527
|
const lines = String(text || '').split('\n');
|
|
1327
1528
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
1328
1529
|
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
1329
|
-
return /(^|\n)\s*
|
|
1530
|
+
return /(^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(\n|$)/.test(top) || /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
|
|
1330
1531
|
}
|
|
1331
1532
|
|
|
1332
1533
|
function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
package/src/cli/main.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { bumpProjectVersion, disableVersionGitHook, runVersionPreCommit, version
|
|
|
22
22
|
import { rustInfo } from '../core/rust-accelerator.mjs';
|
|
23
23
|
import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../core/gx-renderer.mjs';
|
|
24
24
|
import { defaultEvaluationScenario, runEvaluationBenchmark } from '../core/evaluation.mjs';
|
|
25
|
-
import { buildResearchPrompt, evaluateResearchGate, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
|
|
25
|
+
import { buildResearchPrompt, evaluateResearchGate, isDatedResearchPaperArtifact, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
|
|
26
26
|
import { evaluateRecallPulseFixtures, readMissionStatusLedger, writeRecallPulseArtifacts } from '../core/recallpulse.mjs';
|
|
27
27
|
import {
|
|
28
28
|
PPT_AUDIENCE_STRATEGY_ARTIFACT,
|
|
@@ -155,11 +155,19 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
155
155
|
return { ...opts, session, codexArgs: [...(opts.codexArgs || []), '-c', 'model_provider="openai"'], codexLbBypassed: true };
|
|
156
156
|
}
|
|
157
157
|
if (!lb?.ok) return opts;
|
|
158
|
-
|
|
158
|
+
const nextOpts = withCodexLbProviderArgs(opts);
|
|
159
|
+
if (explicitSession) return nextOpts;
|
|
159
160
|
const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
|
|
160
161
|
console.log(`codex-lb active for this launch: ${lb.env_path || lb.base_url || 'configured'}`);
|
|
161
162
|
console.log(`Using fresh tmux session: ${session}`);
|
|
162
|
-
return { ...
|
|
163
|
+
return { ...nextOpts, session, codexLbFreshSession: true };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function withCodexLbProviderArgs(opts = {}) {
|
|
167
|
+
const codexArgs = [...(opts.codexArgs || [])];
|
|
168
|
+
const hasProviderOverride = codexArgs.some((arg) => /model_provider\s*=/.test(String(arg || '')));
|
|
169
|
+
if (!hasProviderOverride) codexArgs.push('-c', 'model_provider="codex-lb"');
|
|
170
|
+
return { ...opts, codexArgs };
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
function help(args = []) {
|
|
@@ -1141,7 +1149,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1141
1149
|
console.log(`Env file: ${status.env_file ? status.env_path : 'missing'}`);
|
|
1142
1150
|
if (status.base_url) console.log(`Base URL: ${status.base_url}`);
|
|
1143
1151
|
if (!status.ok) console.log('\nRun: sks codex-lb setup --host <domain> --api-key <key>');
|
|
1144
|
-
else console.log('\nRepair auth: sks codex-lb repair');
|
|
1152
|
+
else console.log('\nRepair provider auth: sks codex-lb repair');
|
|
1145
1153
|
return;
|
|
1146
1154
|
}
|
|
1147
1155
|
if (sub === 'health' || sub === 'verify-chain' || sub === 'chain') {
|
|
@@ -1168,7 +1176,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1168
1176
|
process.exitCode = 1;
|
|
1169
1177
|
return;
|
|
1170
1178
|
}
|
|
1171
|
-
console.log('codex-lb auth repaired for Codex CLI.');
|
|
1179
|
+
console.log('codex-lb provider auth repaired for Codex CLI/App environment.');
|
|
1172
1180
|
console.log(`Config: ${result.config_path}`);
|
|
1173
1181
|
console.log(`Key env: ${result.env_path}`);
|
|
1174
1182
|
return;
|
|
@@ -1185,7 +1193,7 @@ async function codexLbCommand(action = 'status', args = []) {
|
|
|
1185
1193
|
const result = await configureCodexLb({ host, apiKey });
|
|
1186
1194
|
if (json) return console.log(JSON.stringify(result, null, 2));
|
|
1187
1195
|
if (!result.ok) {
|
|
1188
|
-
console.error(`codex-lb setup failed: ${result.status}`);
|
|
1196
|
+
console.error(`codex-lb setup failed: ${result.status}${result.error ? `: ${result.error}` : ''}`);
|
|
1189
1197
|
process.exitCode = 1;
|
|
1190
1198
|
return;
|
|
1191
1199
|
}
|
|
@@ -1771,7 +1779,7 @@ async function doctor(args) {
|
|
|
1771
1779
|
const cleanup = removed.length ? ` removed stale generated skill shadow(s): ${removed.join(', ')}` : '';
|
|
1772
1780
|
console.log(`Global $ repair: ${globalSkillsRepair.status} ${globalSkillsRepair.root || ''}${cleanup}`.trimEnd());
|
|
1773
1781
|
}
|
|
1774
|
-
if (codexLbRepair?.ok) console.log(`codex-lb repair: ${codexLbRepair.config_repaired ? 'config+auth resynced' : 'auth resynced'} from stored env`);
|
|
1782
|
+
if (codexLbRepair?.ok) console.log(`codex-lb repair: ${codexLbRepair.config_repaired ? 'config+provider auth resynced' : 'provider auth resynced'} from stored env`);
|
|
1775
1783
|
else if (codexLbRepair && codexLbRepair.status !== 'missing_env_key') console.log(`codex-lb repair: skipped (${codexLbRepair.status})`);
|
|
1776
1784
|
if (flag(args, '--fix') && result.harness_conflicts.hard_block) console.log('Repair: skipped because another Codex harness needs human-approved removal first');
|
|
1777
1785
|
console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
|
|
@@ -3946,17 +3954,21 @@ async function selftest() {
|
|
|
3946
3954
|
const researchPlan = await writeResearchPlan(researchDir, researchMission.prompt, {});
|
|
3947
3955
|
if (researchPlan.methodology !== 'genius-scout-council-frontier-discovery-loop' || researchPlan.web_research_policy?.mode !== 'layered_source_retrieval_and_triangulation') throw new Error('selftest: research plan contract');
|
|
3948
3956
|
if (researchPlan.execution_policy?.default_max_cycles !== 12 || researchPlan.mutation_policy?.implementation_allowed !== false || !String(researchPlan.research_council?.debate_policy?.rule || '').includes('every scout records final agreement')) throw new Error('selftest: research consensus/no-code contract');
|
|
3949
|
-
if (!researchPlan.research_council?.scouts?.every((scout) => scout.display_name && scout.persona && scout.persona_boundary && scout.reasoning_effort === 'xhigh')) throw new Error('selftest: research scout persona contract missing from plan');
|
|
3957
|
+
if (!researchPlan.research_council?.scouts?.every((scout) => scout.agent_name && scout.display_name && scout.persona && scout.persona_boundary && scout.reasoning_effort === 'xhigh') || !researchPlan.research_council.scouts.some((scout) => scout.agent_name === 'Einstein Scout')) throw new Error('selftest: research scout persona contract missing from plan');
|
|
3958
|
+
const researchPaperArtifact = researchPlan.artifacts?.research_paper;
|
|
3959
|
+
if (!isDatedResearchPaperArtifact(researchPaperArtifact) || researchPaperArtifact === 'research-paper.md') throw new Error('selftest: research paper artifact filename is not dated and titled');
|
|
3950
3960
|
const researchPrompt = buildResearchPrompt({ id: researchMission.id, mission: researchMission, plan: researchPlan, cycle: 1, previous: '' });
|
|
3951
|
-
if (!researchPrompt.includes('NO-CODE-MUTATION POLICY') || !researchPrompt.includes('not a fixed three-cycle run') || !researchPrompt.includes('unanimous_consensus=true')) throw new Error('selftest: research prompt missing no-code unanimous consensus policy');
|
|
3961
|
+
if (!researchPrompt.includes('NO-CODE-MUTATION POLICY') || !researchPrompt.includes('not a fixed three-cycle run') || !researchPrompt.includes('unanimous_consensus=true') || !researchPrompt.includes('agent_name') || !researchPrompt.includes(researchPaperArtifact)) throw new Error('selftest: research prompt missing no-code unanimous consensus policy');
|
|
3952
3962
|
const rArts = researchPlan.required_artifacts || [];
|
|
3953
3963
|
for (const a of [rss, 'source-ledger.json', 'scout-ledger.json', 'debate-ledger.json', 'falsification-ledger.json']) if (!rArts.includes(a) || !(await exists(path.join(researchDir, a)))) throw new Error('selftest: research artifact');
|
|
3954
|
-
if (!rArts.includes('research-paper.md') || !rArts.includes(gos)) throw new Error('selftest: research paper');
|
|
3964
|
+
if (!rArts.includes(researchPaperArtifact) || rArts.includes('research-paper.md') || !rArts.includes(gos)) throw new Error('selftest: research paper');
|
|
3955
3965
|
const initialResearchGate = await evaluateResearchGate(researchDir);
|
|
3956
3966
|
if (initialResearchGate.passed || ['web_search_pass_missing', 'eureka_missing', 'debate_exchanges_missing', 'research_paper_missing', 'consensus_iteration_missing', 'unanimous_consensus_missing'].some((r) => !initialResearchGate.reasons.includes(r))) throw new Error('selftest: research gate');
|
|
3957
3967
|
const researchGate = await writeMockResearchResult(researchDir, researchPlan);
|
|
3958
3968
|
if (!researchGate.passed) throw new Error('selftest: mock research gate did not pass');
|
|
3969
|
+
if (!(await exists(path.join(researchDir, researchPaperArtifact))) || await exists(path.join(researchDir, 'research-paper.md'))) throw new Error('selftest: mock research paper filename did not use dated title artifact');
|
|
3959
3970
|
const rm = researchGate.metrics || {};
|
|
3971
|
+
if (rm.research_paper_artifact !== researchPaperArtifact) throw new Error('selftest: research gate did not report dated paper artifact');
|
|
3960
3972
|
if (rm.scout_persona_contract_ok !== true || (rm.scout_persona_issues || []).length) throw new Error('selftest: research scout persona contract did not pass');
|
|
3961
3973
|
if (['independent_scouts', 'xhigh_scouts', 'eureka_moments', 'debate_participants', 'genius_opinion_summaries'].some((m) => rm[m] < 5) || ['counterevidence_sources', 'falsification_cases', 'triangulation_checks'].some((m) => rm[m] < 1) || rm.paper_sections < 8 || rm.citation_coverage !== true || rm.source_layers_covered < 7 || rm.consensus_iterations < 1 || rm.unanimous_consensus !== true || rm.consensus_agreed_scouts < 5) throw new Error('selftest: research metrics');
|
|
3962
3974
|
await writeJsonAtomic(path.join(dir, 'done-gate.json'), { passed: true, unsupported_critical_claims: 0, database_safety_violation: false, database_safety_reviewed: true, visual_drift: 'low', wiki_drift: 'low', tests_required: false });
|
|
@@ -9,7 +9,7 @@ import { buildQuestionSchema, writeQuestions } from '../core/questions.mjs';
|
|
|
9
9
|
import { sealContract } from '../core/decision-contract.mjs';
|
|
10
10
|
import { buildQaLoopQuestionSchema, buildQaLoopPrompt, evaluateQaGate, qaStatus, writeMockQaResult, writeQaLoopArtifacts } from '../core/qa-loop.mjs';
|
|
11
11
|
import { containsUserQuestion, noQuestionContinuationReason } from '../core/no-question-guard.mjs';
|
|
12
|
-
import { RESEARCH_GENIUS_SUMMARY_ARTIFACT,
|
|
12
|
+
import { RESEARCH_GENIUS_SUMMARY_ARTIFACT, RESEARCH_SOURCE_SKILL_ARTIFACT, countGeniusOpinionSummaries, countResearchPaperSections, buildResearchPrompt, evaluateResearchGate, findResearchPaperArtifact, researchPaperArtifactForPlan, writeMockResearchResult, writeResearchPlan } from '../core/research.mjs';
|
|
13
13
|
import { storageReport, enforceRetention, pruneWikiArtifacts } from '../core/retention.mjs';
|
|
14
14
|
import { evaluateDoneGate } from '../core/hproof.mjs';
|
|
15
15
|
import { renderCartridge, validateCartridge, driftCartridge, snapshotCartridge } from '../core/gx-renderer.mjs';
|
|
@@ -76,11 +76,19 @@ function codexLbImmediateLaunchOpts(args = [], lb = {}, opts = {}) {
|
|
|
76
76
|
return { ...opts, session, codexArgs: [...(opts.codexArgs || []), '-c', 'model_provider="openai"'], codexLbBypassed: true };
|
|
77
77
|
}
|
|
78
78
|
if (!lb?.ok) return opts;
|
|
79
|
-
|
|
79
|
+
const nextOpts = withCodexLbProviderArgs(opts);
|
|
80
|
+
if (explicitSession) return nextOpts;
|
|
80
81
|
const session = sanitizeTmuxSessionName(`sks-codex-lb-${Date.now().toString(36)}-${defaultTmuxSessionName(root)}`);
|
|
81
82
|
console.log(`codex-lb active for this launch: ${lb.env_path || lb.base_url || 'configured'}`);
|
|
82
83
|
console.log(`Using fresh tmux session: ${session}`);
|
|
83
|
-
return { ...
|
|
84
|
+
return { ...nextOpts, session, codexLbFreshSession: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function withCodexLbProviderArgs(opts = {}) {
|
|
88
|
+
const codexArgs = [...(opts.codexArgs || [])];
|
|
89
|
+
const hasProviderOverride = codexArgs.some((arg) => /model_provider\s*=/.test(String(arg || '')));
|
|
90
|
+
if (!hasProviderOverride) codexArgs.push('-c', 'model_provider="codex-lb"');
|
|
91
|
+
return { ...opts, codexArgs };
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
export async function madHighCommand(args = [], deps = {}) {
|
|
@@ -491,7 +499,7 @@ async function researchPrepare(args) {
|
|
|
491
499
|
console.log(`Methodology: ${plan.methodology}`);
|
|
492
500
|
console.log(`Plan: ${path.relative(root, path.join(dir, 'research-plan.md'))}`);
|
|
493
501
|
console.log(`Pipeline: ${path.relative(root, path.join(dir, PIPELINE_PLAN_ARTIFACT))}`);
|
|
494
|
-
console.log(`Paper: ${
|
|
502
|
+
console.log(`Paper: ${researchPaperArtifactForPlan(plan)}`);
|
|
495
503
|
console.log(`Genius summary: ${RESEARCH_GENIUS_SUMMARY_ARTIFACT}`);
|
|
496
504
|
console.log(`Source skill: ${RESEARCH_SOURCE_SKILL_ARTIFACT}`);
|
|
497
505
|
console.log('Ledgers: source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json');
|
|
@@ -612,7 +620,9 @@ async function researchStatus(args) {
|
|
|
612
620
|
const falsificationLedger = await readJson(path.join(dir, 'falsification-ledger.json'), null);
|
|
613
621
|
const sourceSkillText = await readText(path.join(dir, RESEARCH_SOURCE_SKILL_ARTIFACT), '');
|
|
614
622
|
const geniusSummaryText = await readText(path.join(dir, RESEARCH_GENIUS_SUMMARY_ARTIFACT), '');
|
|
615
|
-
const
|
|
623
|
+
const plan = await readJson(path.join(dir, 'research-plan.json'), null);
|
|
624
|
+
const paperArtifact = await findResearchPaperArtifact(dir, plan);
|
|
625
|
+
const paperText = paperArtifact.exists ? await readText(paperArtifact.path, '') : '';
|
|
616
626
|
const scoutRows = Array.isArray(scoutLedger?.scouts) ? scoutLedger.scouts : [];
|
|
617
627
|
const sourceLayerRows = Array.isArray(sourceLedger?.source_layers) ? sourceLedger.source_layers : [];
|
|
618
628
|
const sourceLayersCovered = sourceLayerRows.filter((layer) => layer.status === 'covered' && ((Array.isArray(layer.source_ids) && layer.source_ids.length) || (Array.isArray(layer.counterevidence_ids) && layer.counterevidence_ids.length))).length;
|
|
@@ -635,6 +645,7 @@ async function researchStatus(args) {
|
|
|
635
645
|
unanimous_consensus: gate?.metrics?.unanimous_consensus ?? gate?.unanimous_consensus ?? debateLedger?.unanimous_consensus ?? false,
|
|
636
646
|
research_source_skill_present: Boolean(sourceSkillText.trim()),
|
|
637
647
|
genius_opinion_summary_present: Boolean(geniusSummaryText.trim()),
|
|
648
|
+
research_paper_artifact: paperArtifact.name,
|
|
638
649
|
paper_present: Boolean(paperText.trim()),
|
|
639
650
|
paper_sections: countResearchPaperSections(paperText),
|
|
640
651
|
falsification_cases: falsificationLedger?.cases?.length ?? null
|
package/src/core/codex-app.mjs
CHANGED
|
@@ -401,6 +401,7 @@ async function codexFastModeConfigStatus(opts = {}) {
|
|
|
401
401
|
if (!config.text) continue;
|
|
402
402
|
const topLevel = topLevelToml(config.text);
|
|
403
403
|
if (/(^|\n)\s*model_reasoning_effort\s*=/.test(topLevel)) blockers.push(`${config.scope}:top_level_model_reasoning_effort`);
|
|
404
|
+
if (/(^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevel)) blockers.push(`${config.scope}:top_level_codex_lb_provider`);
|
|
404
405
|
if (/(^|\n)\s*fast_default_opt_out\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(tomlTable(config.text, 'notice'))) blockers.push(`${config.scope}:fast_default_opt_out`);
|
|
405
406
|
}
|
|
406
407
|
const merged = configs.map((config) => config.text).join('\n');
|
package/src/core/fsx.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.8.
|
|
8
|
+
export const PACKAGE_VERSION = '0.8.5';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/init.mjs
CHANGED
|
@@ -48,7 +48,7 @@ export function hasTopLevelCodexModeLock(text = '') {
|
|
|
48
48
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
49
49
|
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
50
50
|
const model = top.match(/^model\s*=\s*"([^"]+)"/m)?.[1];
|
|
51
|
-
return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top);
|
|
51
|
+
return (Boolean(model) && model !== 'gpt-5.5') || /^model_reasoning_effort\s*=/m.test(top) || /^model_provider\s*=\s*"codex-lb"/m.test(top);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export function hasDeprecatedCodexHooksFeatureFlag(text = '') {
|
|
@@ -502,6 +502,7 @@ function installPolicy(scope, commandPrefix) {
|
|
|
502
502
|
|
|
503
503
|
function mergeManagedCodexConfigToml(existingContent = '') {
|
|
504
504
|
let next = removeLegacyTopLevelCodexModeLocks(String(existingContent || '').trimEnd());
|
|
505
|
+
next = removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
|
|
505
506
|
next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
|
|
506
507
|
next = removeTomlTableKey(next, 'features', 'codex_hooks');
|
|
507
508
|
next = upsertTopLevelTomlString(next, 'model', 'gpt-5.5');
|
|
@@ -546,13 +547,15 @@ async function mergeGlobalCodexConfigIfAvailable(configText = '', configPath = '
|
|
|
546
547
|
const globalConfig = await readText(globalConfigPath, '');
|
|
547
548
|
let next = mergeGlobalMcpServers(configText, globalConfig);
|
|
548
549
|
next = mergeGlobalCodexAppRuntimeTables(next, globalConfig);
|
|
549
|
-
if (selectedRe.test(next) && /\[model_providers\.codex-lb\]/.test(next))
|
|
550
|
+
if (selectedRe.test(next) && /\[model_providers\.codex-lb\]/.test(next)) {
|
|
551
|
+
return `${removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb').trim()}\n`;
|
|
552
|
+
}
|
|
550
553
|
const envPath = path.join(home, '.codex', 'sks-codex-lb.env');
|
|
551
554
|
if (!(await exists(envPath))) return next;
|
|
552
555
|
const envText = await readText(envPath, '');
|
|
553
556
|
const baseUrl = globalConfig.match(/(^|\n)\[model_providers\.codex-lb\][\s\S]*?\n\s*base_url\s*=\s*"([^"]+)"/)?.[2] || parseCodexLbEnvBaseUrl(envText);
|
|
554
557
|
if (!parseCodexLbEnvKey(envText) || !baseUrl || (!selectedRe.test(globalConfig) && !parseCodexLbEnvBaseUrl(envText))) return next;
|
|
555
|
-
next =
|
|
558
|
+
next = removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
|
|
556
559
|
next = upsertTomlTable(next, 'model_providers.codex-lb', `[model_providers.codex-lb]\nname = "OpenAI"\nbase_url = "${baseUrl}"\nwire_api = "responses"\nenv_key = "CODEX_LB_API_KEY"\nsupports_websockets = true\nrequires_openai_auth = true`);
|
|
557
560
|
return `${next.trim()}\n`;
|
|
558
561
|
}
|
|
@@ -612,6 +615,14 @@ function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
|
612
615
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
613
616
|
}
|
|
614
617
|
|
|
618
|
+
function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
|
|
619
|
+
const lines = String(text || '').split('\n');
|
|
620
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
621
|
+
const end = firstTable === -1 ? lines.length : firstTable;
|
|
622
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?$`);
|
|
623
|
+
return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
624
|
+
}
|
|
625
|
+
|
|
615
626
|
function upsertTopLevelTomlString(text, key, value) {
|
|
616
627
|
const line = `${key} = "${value}"`;
|
|
617
628
|
const lines = String(text || '').split('\n');
|
package/src/core/research.mjs
CHANGED
|
@@ -17,11 +17,62 @@ export const RESEARCH_PAPER_SECTION_GROUPS = Object.freeze([
|
|
|
17
17
|
['references', 'sources']
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
function cleanResearchArtifactDate(value = '') {
|
|
21
|
+
const match = String(value || '').match(/\d{4}-\d{2}-\d{2}/);
|
|
22
|
+
return match ? match[0] : nowIso().slice(0, 10);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function researchTitleSlug(prompt = '') {
|
|
26
|
+
const cleaned = String(prompt || '')
|
|
27
|
+
.normalize('NFKC')
|
|
28
|
+
.replace(/[`"'<>]/g, ' ')
|
|
29
|
+
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
30
|
+
.replace(/^-+|-+$/g, '')
|
|
31
|
+
.toLowerCase();
|
|
32
|
+
const slug = cleaned.split('-').filter(Boolean).slice(0, 10).join('-').slice(0, 90).replace(/-+$/g, '');
|
|
33
|
+
return slug || 'research';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function researchPaperArtifactName(prompt = '', createdAt = nowIso(), opts = {}) {
|
|
37
|
+
const titleSource = opts.title || opts.paperTitle || prompt;
|
|
38
|
+
return `${cleanResearchArtifactDate(createdAt)}-${researchTitleSlug(titleSource)}-research-paper.md`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isDatedResearchPaperArtifact(name = '') {
|
|
42
|
+
return /^\d{4}-\d{2}-\d{2}-[^\s/\\]+-research-paper\.md$/u.test(String(name || ''));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function researchPaperArtifactForPlan(plan = null) {
|
|
46
|
+
const artifact = plan?.artifacts?.research_paper || plan?.paper_artifact;
|
|
47
|
+
return artifact ? path.basename(String(artifact)) : RESEARCH_PAPER_ARTIFACT;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function findResearchPaperArtifact(dir, plan = null, opts = {}) {
|
|
51
|
+
const preferred = researchPaperArtifactForPlan(plan);
|
|
52
|
+
const allowLegacyFallback = opts.allowLegacyFallback === true || preferred === RESEARCH_PAPER_ARTIFACT;
|
|
53
|
+
const names = [...new Set([preferred, allowLegacyFallback ? RESEARCH_PAPER_ARTIFACT : null].filter(Boolean))];
|
|
54
|
+
for (const name of names) {
|
|
55
|
+
const file = path.join(dir, name);
|
|
56
|
+
if (await exists(file)) return { name, path: file, exists: true, preferred: name === preferred, legacy: name === RESEARCH_PAPER_ARTIFACT };
|
|
57
|
+
}
|
|
58
|
+
return { name: preferred, path: path.join(dir, preferred), exists: false, preferred: true, legacy: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function researchScoutAgentName(scout = {}) {
|
|
62
|
+
return String(scout.agent_name || scout.display_name || scout.label || scout.id || 'Research Scout').trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const RESEARCH_SCOUT_COUNCIL = Object.freeze(RESEARCH_SCOUT_PERSONA_CONTRACT.map((scout) => {
|
|
66
|
+
const displayName = scout.display_name || scout.label || scout.id;
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
...scout,
|
|
69
|
+
display_name: displayName,
|
|
70
|
+
label: displayName,
|
|
71
|
+
agent_name: displayName,
|
|
72
|
+
codex_agent_name: displayName,
|
|
73
|
+
required_outputs: scout.required_outputs
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
25
76
|
|
|
26
77
|
export const RESEARCH_SOURCE_LAYERS = Object.freeze([
|
|
27
78
|
{
|
|
@@ -86,12 +137,21 @@ export const RESEARCH_SOURCE_LAYER_IDS = Object.freeze(RESEARCH_SOURCE_LAYERS.ma
|
|
|
86
137
|
|
|
87
138
|
export function createResearchPlan(prompt, opts = {}) {
|
|
88
139
|
const depth = opts.depth || 'frontier';
|
|
140
|
+
const createdAt = nowIso();
|
|
141
|
+
const paperArtifact = researchPaperArtifactName(prompt, createdAt, opts);
|
|
89
142
|
return {
|
|
90
143
|
schema_version: 1,
|
|
91
144
|
prompt,
|
|
92
145
|
depth,
|
|
93
|
-
created_at:
|
|
146
|
+
created_at: createdAt,
|
|
94
147
|
methodology: 'genius-scout-council-frontier-discovery-loop',
|
|
148
|
+
paper_artifact: paperArtifact,
|
|
149
|
+
artifacts: {
|
|
150
|
+
research_paper: paperArtifact,
|
|
151
|
+
legacy_research_paper: RESEARCH_PAPER_ARTIFACT,
|
|
152
|
+
genius_opinion_summary: RESEARCH_GENIUS_SUMMARY_ARTIFACT,
|
|
153
|
+
research_source_skill: RESEARCH_SOURCE_SKILL_ARTIFACT
|
|
154
|
+
},
|
|
95
155
|
objective: 'Find the shortest useful mechanism that can be falsified or applied, grounded in maximum available source retrieval rather than broad summary.',
|
|
96
156
|
execution_policy: {
|
|
97
157
|
normal_run: 'real_long_running_research_until_unanimous_scout_consensus',
|
|
@@ -165,6 +225,10 @@ export function createResearchPlan(prompt, opts = {}) {
|
|
|
165
225
|
allowed_write_scope: 'route-local mission artifacts only',
|
|
166
226
|
rule: 'Normal Research must not modify repository source, package, docs, config, or generated harness files. It may write only artifacts under its own .sneakoscope/missions/<mission-id>/ directory.'
|
|
167
227
|
},
|
|
228
|
+
artifact_policy: {
|
|
229
|
+
research_paper: paperArtifact,
|
|
230
|
+
rule: 'Write the final manuscript to the dated topic-specific research_paper artifact from this plan, not the legacy generic filename.'
|
|
231
|
+
},
|
|
168
232
|
rules: [
|
|
169
233
|
'Do not modify code or project source files during Research. Research writes only route-local mission artifacts; implementation belongs to $Team or another execution route.',
|
|
170
234
|
'Do not claim novelty without a novelty ledger entry.',
|
|
@@ -175,7 +239,7 @@ export function createResearchPlan(prompt, opts = {}) {
|
|
|
175
239
|
'Maximize safe web/source search as layered source retrieval and record queries, source layers, citations, quality notes, triangulation checks, and blockers in source-ledger.json.',
|
|
176
240
|
`Create ${RESEARCH_SOURCE_SKILL_ARTIFACT} as a route-local source collection skill before synthesis; do not edit generated .agents/skills during the research run.`,
|
|
177
241
|
'Actively seek disconfirming evidence before synthesis.',
|
|
178
|
-
|
|
242
|
+
`Turn the surviving research result into ${paperArtifact} with paper-style sections and references.`,
|
|
179
243
|
`End every run with ${RESEARCH_GENIUS_SUMMARY_ARTIFACT}, summarizing each genius-lens scout's final opinion, strongest evidence, disagreement, and changed mind.`,
|
|
180
244
|
'Keep unsupported source-free claims as hypotheses only.',
|
|
181
245
|
'Prefer the smallest testable mechanism or implementation probe, but do not stop source gathering early for speed when the research question needs a longer pass.',
|
|
@@ -194,7 +258,7 @@ export function createResearchPlan(prompt, opts = {}) {
|
|
|
194
258
|
],
|
|
195
259
|
required_artifacts: [
|
|
196
260
|
'research-report.md',
|
|
197
|
-
|
|
261
|
+
paperArtifact,
|
|
198
262
|
RESEARCH_GENIUS_SUMMARY_ARTIFACT,
|
|
199
263
|
RESEARCH_SOURCE_SKILL_ARTIFACT,
|
|
200
264
|
'source-ledger.json',
|
|
@@ -214,6 +278,7 @@ export function researchPlanMarkdown(plan) {
|
|
|
214
278
|
lines.push(`Prompt: ${plan.prompt}`);
|
|
215
279
|
lines.push(`Depth: ${plan.depth}`);
|
|
216
280
|
lines.push(`Methodology: ${plan.methodology}`);
|
|
281
|
+
lines.push(`Research paper: ${researchPaperArtifactForPlan(plan)}`);
|
|
217
282
|
if (plan.execution_policy) {
|
|
218
283
|
lines.push(`Execution: ${plan.execution_policy.normal_run}; default cycle timeout ${plan.execution_policy.default_cycle_timeout_minutes} minutes`);
|
|
219
284
|
if (plan.execution_policy.default_max_cycles) lines.push(`Consensus loop: repeat until unanimous scout consensus; default safety cap ${plan.execution_policy.default_max_cycles} cycles`);
|
|
@@ -227,7 +292,7 @@ export function researchPlanMarkdown(plan) {
|
|
|
227
292
|
if (plan.research_council?.scouts?.length) {
|
|
228
293
|
lines.push('## Genius Scout Council');
|
|
229
294
|
lines.push(`Policy: ${plan.research_council.policy}`);
|
|
230
|
-
for (const scout of plan.research_council.scouts) lines.push(`- ${scout
|
|
295
|
+
for (const scout of plan.research_council.scouts) lines.push(`- ${researchScoutAgentName(scout)}: ${scout.persona || scout.role} - ${scout.mandate} (${scout.persona_boundary || 'persona-inspired lens only'})`);
|
|
231
296
|
lines.push('');
|
|
232
297
|
}
|
|
233
298
|
if (plan.web_research_policy) {
|
|
@@ -381,6 +446,7 @@ export function defaultScoutLedger(plan = null) {
|
|
|
381
446
|
created_at: nowIso(),
|
|
382
447
|
scouts: scouts.map((scout) => ({
|
|
383
448
|
id: scout.id,
|
|
449
|
+
agent_name: researchScoutAgentName(scout),
|
|
384
450
|
display_name: scout.display_name || scout.label || scout.id,
|
|
385
451
|
historical_inspiration: scout.historical_inspiration || null,
|
|
386
452
|
persona: scout.persona || scout.role,
|
|
@@ -417,10 +483,13 @@ export function defaultDebateLedger(plan = null) {
|
|
|
417
483
|
created_at: nowIso(),
|
|
418
484
|
mode: 'vigorous_evidence_bound_debate_until_unanimous_consensus',
|
|
419
485
|
required_participants: scouts.map((scout) => scout.id),
|
|
486
|
+
participant_display_names: scouts.map((scout) => researchScoutAgentName(scout)),
|
|
420
487
|
consensus_iterations: 0,
|
|
421
488
|
unanimous_consensus: false,
|
|
422
489
|
scout_agreements: scouts.map((scout) => ({
|
|
423
490
|
scout_id: scout.id,
|
|
491
|
+
agent_name: researchScoutAgentName(scout),
|
|
492
|
+
display_name: scout.display_name || scout.label || scout.id,
|
|
424
493
|
agrees: false,
|
|
425
494
|
final_position: '',
|
|
426
495
|
source_ids: []
|
|
@@ -506,6 +575,7 @@ export function defaultResearchGate() {
|
|
|
506
575
|
return {
|
|
507
576
|
passed: false,
|
|
508
577
|
report_present: false,
|
|
578
|
+
research_paper_artifact: null,
|
|
509
579
|
paper_present: false,
|
|
510
580
|
paper_sections: 0,
|
|
511
581
|
genius_opinion_summary_present: false,
|
|
@@ -548,8 +618,10 @@ export async function evaluateResearchGate(dir) {
|
|
|
548
618
|
const gate = await readJson(path.join(dir, 'research-gate.json'), defaultResearchGate());
|
|
549
619
|
const plan = await readJson(path.join(dir, 'research-plan.json'), null);
|
|
550
620
|
const reportPresent = await exists(path.join(dir, 'research-report.md'));
|
|
551
|
-
const
|
|
552
|
-
const
|
|
621
|
+
const paperArtifact = await findResearchPaperArtifact(dir, plan);
|
|
622
|
+
const paperPresent = paperArtifact.exists;
|
|
623
|
+
const paperText = paperPresent ? await readText(paperArtifact.path, '') : '';
|
|
624
|
+
const paperSections = paperPresent ? countResearchPaperSections(paperText) : 0;
|
|
553
625
|
const geniusSummaryPresent = await exists(path.join(dir, RESEARCH_GENIUS_SUMMARY_ARTIFACT));
|
|
554
626
|
const geniusSummaryCount = geniusSummaryPresent ? countGeniusOpinionSummaries(await readText(path.join(dir, RESEARCH_GENIUS_SUMMARY_ARTIFACT), '')) : 0;
|
|
555
627
|
const sourceSkillPresent = await exists(path.join(dir, RESEARCH_SOURCE_SKILL_ARTIFACT));
|
|
@@ -624,6 +696,8 @@ export async function evaluateResearchGate(dir) {
|
|
|
624
696
|
passed: gate.passed === true && reasons.length === 0,
|
|
625
697
|
reasons,
|
|
626
698
|
metrics: {
|
|
699
|
+
research_paper_artifact: paperArtifact.name,
|
|
700
|
+
paper_present: paperPresent || gate.paper_present === true,
|
|
627
701
|
web_search_passes: webSearchPasses,
|
|
628
702
|
paper_sections: Math.max(Number(gate.paper_sections || 0), paperSections),
|
|
629
703
|
genius_opinion_summary_present: geniusSummaryPresent || gate.genius_opinion_summary_present === true,
|
|
@@ -651,13 +725,18 @@ export async function evaluateResearchGate(dir) {
|
|
|
651
725
|
citation_coverage: citationCoverage,
|
|
652
726
|
web_search_blockers: searchBlockers.length
|
|
653
727
|
},
|
|
654
|
-
gate
|
|
728
|
+
gate: {
|
|
729
|
+
...gate,
|
|
730
|
+
research_paper_artifact: paperArtifact.name,
|
|
731
|
+
paper_present: paperPresent || gate.paper_present === true
|
|
732
|
+
}
|
|
655
733
|
};
|
|
656
734
|
await writeJsonAtomic(path.join(dir, 'research-gate.evaluated.json'), result);
|
|
657
735
|
return result;
|
|
658
736
|
}
|
|
659
737
|
|
|
660
738
|
export async function writeMockResearchResult(dir, plan) {
|
|
739
|
+
const paperArtifact = researchPaperArtifactForPlan(plan);
|
|
661
740
|
const mockLayerSources = RESEARCH_SOURCE_LAYERS.map((layer, index) => ({
|
|
662
741
|
id: `mock-source-${index + 1}`,
|
|
663
742
|
layer: layer.id,
|
|
@@ -751,6 +830,7 @@ export async function writeMockResearchResult(dir, plan) {
|
|
|
751
830
|
...defaultScoutLedger(plan),
|
|
752
831
|
scouts: RESEARCH_SCOUT_COUNCIL.map((scout) => ({
|
|
753
832
|
id: scout.id,
|
|
833
|
+
agent_name: researchScoutAgentName(scout),
|
|
754
834
|
display_name: scout.display_name || scout.label,
|
|
755
835
|
historical_inspiration: scout.historical_inspiration || null,
|
|
756
836
|
persona: scout.persona || scout.role,
|
|
@@ -790,10 +870,13 @@ export async function writeMockResearchResult(dir, plan) {
|
|
|
790
870
|
created_at: nowIso(),
|
|
791
871
|
mode: 'vigorous_evidence_bound_debate_until_unanimous_consensus',
|
|
792
872
|
required_participants: RESEARCH_SCOUT_COUNCIL.map((scout) => scout.id),
|
|
873
|
+
participant_display_names: RESEARCH_SCOUT_COUNCIL.map((scout) => researchScoutAgentName(scout)),
|
|
793
874
|
consensus_iterations: 2,
|
|
794
875
|
unanimous_consensus: true,
|
|
795
876
|
scout_agreements: RESEARCH_SCOUT_COUNCIL.map((scout) => ({
|
|
796
877
|
scout_id: scout.id,
|
|
878
|
+
agent_name: researchScoutAgentName(scout),
|
|
879
|
+
display_name: scout.display_name || scout.label,
|
|
797
880
|
agrees: true,
|
|
798
881
|
final_position: 'Agrees to keep the falsifiable, source-cited research mechanism as the surviving claim.',
|
|
799
882
|
source_ids: ['mock-source-1', 'mock-counter-1']
|
|
@@ -868,11 +951,12 @@ export async function writeMockResearchResult(dir, plan) {
|
|
|
868
951
|
await writeJsonAtomic(path.join(dir, 'novelty-ledger.json'), ledger);
|
|
869
952
|
await writeTextAtomic(path.join(dir, RESEARCH_GENIUS_SUMMARY_ARTIFACT), `${geniusSummary}\n`);
|
|
870
953
|
await writeTextAtomic(path.join(dir, 'research-report.md'), `# SKS Research Report\n\nPrompt: ${plan.prompt}\n\n## Scout Council Synthesis\n\nThe mock council keeps one cited methodological insight: a research mode should force layered, falsifiable novelty rather than summarize known material from one corpus [mock-source-1].\n\n## Source Coverage\n\nThis is a selftest fixture. It records mock coverage for academic literature, official data, standards, news, public discourse, developer knowledge, and counterevidence layers, but does not perform live web browsing in --mock mode.\n\n## Candidate Insight\n\nA useful research run must produce source-cited, cross-layer triangulated, falsifiable novelty with scout findings and a cheap probe.\n\n## Falsification\n\nThe claim is weak if no new testable prediction, counterevidence source, cross-layer check, or experiment is produced [mock-counter-1].\n\n## Next Test\n\nCompare this mode against a summary-only run and score candidate insights, falsification passes, citation coverage, source-layer coverage, triangulation checks, and testability.\n`);
|
|
871
|
-
await writeTextAtomic(path.join(dir,
|
|
954
|
+
await writeTextAtomic(path.join(dir, paperArtifact), `# Research Paper: ${plan.prompt}\n\n## Abstract\nA source-cited research run should produce cross-layer, falsifiable novelty rather than only summarize known material.\n\n## Introduction\nThe mock topic is evaluated as a research workflow outcome with layered source coverage [mock-source-1].\n\n## Methodology\nFive xhigh scouts produce Eureka ideas, debate, triangulate source layers, and falsify the strongest claim.\n\n## Findings\nThe surviving finding is that useful research needs cited novelty, source-layer coverage, cross-layer triangulation, and a cheap decisive probe.\n\n## Discussion\nThe debate favors gate-backed evidence over narrative confidence, and treats public discourse as signal rather than truth.\n\n## Limitations and Falsification\nThe claim fails without sources, counterevidence, triangulation checks, or testable predictions [mock-counter-1].\n\n## Conclusion and Next Experiment\nCompare this loop against a summary-only baseline and score testable insights.\n\n## References\n- [mock-source-1] Mock academic literature coverage.\n- [mock-source-2] Mock official government and leading-institution knowledge coverage.\n- [mock-source-3] Mock standards and primary documents coverage.\n- [mock-source-4] Mock current news and global reporting coverage.\n- [mock-source-5] Mock public discourse coverage.\n- [mock-source-6] Mock developer and practitioner knowledge coverage.\n- [mock-source-7] Mock counterevidence and fact-checking coverage.\n- [mock-counter-1] Mock overclaim counterexample.\n`);
|
|
872
955
|
await writeJsonAtomic(path.join(dir, 'research-gate.json'), {
|
|
873
956
|
...defaultResearchGate(),
|
|
874
957
|
passed: true,
|
|
875
958
|
report_present: true,
|
|
959
|
+
research_paper_artifact: paperArtifact,
|
|
876
960
|
paper_present: true,
|
|
877
961
|
paper_sections: RESEARCH_PAPER_SECTION_GROUPS.length,
|
|
878
962
|
genius_opinion_summary_present: true,
|
|
@@ -902,12 +986,14 @@ export async function writeMockResearchResult(dir, plan) {
|
|
|
902
986
|
falsification_cases: 1,
|
|
903
987
|
testable_predictions: 1,
|
|
904
988
|
citation_coverage: true,
|
|
905
|
-
evidence: ['mock research report',
|
|
989
|
+
evidence: ['mock research report', `mock research paper: ${paperArtifact}`, 'mock genius opinion summary', 'mock research source skill', 'mock layered source ledger', 'mock scout ledger', 'mock debate ledger', 'mock novelty ledger', 'mock falsification ledger'],
|
|
906
990
|
notes: ['mock mode records the new contract but does not call a model or perform live web browsing']
|
|
907
991
|
});
|
|
908
992
|
return evaluateResearchGate(dir);
|
|
909
993
|
}
|
|
910
994
|
|
|
911
995
|
export function buildResearchPrompt({ id, mission, plan, cycle, previous }) {
|
|
912
|
-
|
|
996
|
+
const paperArtifact = researchPaperArtifactForPlan(plan);
|
|
997
|
+
const scoutAgentNames = (plan?.research_council?.scouts || RESEARCH_SCOUT_COUNCIL).map((scout) => researchScoutAgentName(scout)).join(', ');
|
|
998
|
+
return `You are running SKS Research Mode.\nMISSION: ${id}\nTOPIC: ${mission.prompt}\nCYCLE: ${cycle}\nMODE: Genius Scout Council + frontier discovery loop. Use maximum reasoning depth available under the current Codex profile.\nLONG-RUN REAL-RESEARCH POLICY: Normal Research is allowed to take one or two hours when the question requires it. Do real source gathering and evidence comparison; do not shortcut into mock, fixture, or summary-only output. If live source access is unavailable, write the blocker and keep the gate unpassed.\nNO-CODE-MUTATION POLICY: Do not edit repository source, package metadata, docs, config, generated skills, or harness files. Write only route-local artifacts under .sneakoscope/missions/${id}/. If a needed implementation change is discovered, record it as a recommendation or blocker for a later execution route.\nNO-QUESTION LOCK: Do not ask the user. Resolve scope from research-plan.json and current project evidence.\nSAFETY: Destructive database operations and unsafe external actions are forbidden. Prefer read-only inspection, local files, and cited public sources.\nPERSONA POLICY: Use Einstein/Feynman/Turing/von Neumann-inspired scout lenses only as cognitive roles. Do not impersonate, roleplay private identity, or speak as the historical people.\nSCOUT PERSONA POLICY: Every Research scout row must include agent_name, display_name, persona, persona_boundary, reasoning_effort: "xhigh", service_tier when available, falsifiers, cheap_probes, and challenge_or_response. Use these agent_name values exactly: ${scoutAgentNames}. Persona names are cognitive lenses, not impersonations.\nSCOUT EFFORT POLICY: Every Research scout agent must use reasoning_effort=xhigh. Record effort: "xhigh" for every scout in scout-ledger.json. Any lower-effort scout output must keep research-gate.json unpassed.\nEUREKA POLICY: Every scout must literally write "Eureka!" and one non-obvious, source-linked idea before debate.\nCONSENSUS LOOP POLICY: This is not a fixed three-cycle run. Repeat source-gathering, scout Eureka ideas, debate, falsification, and synthesis pressure until every scout records final agreement with the surviving mechanism. If unanimous agreement is not reached, keep research-gate.json unpassed and continue until the explicit max-cycle safety cap pauses the run.\nDEBATE POLICY: The scouts must debate vigorously but stay evidence-bound. Every scout must challenge or respond at least once, and debate-ledger.json must record exchanges, consensus_iterations, unanimous_consensus, and per-scout agreements before synthesis.\nPAPER POLICY: After the report and ledgers, write ${paperArtifact} as a concise manuscript with Abstract, Introduction, Methodology, Findings/Results, Discussion, Limitations/Falsification, Conclusion/Next Experiment, and References.\nSOURCE SKILL POLICY: Create or update ${RESEARCH_SOURCE_SKILL_ARTIFACT} as a route-local source collection skill before synthesis. It must name the selected source layers, query routes, quality fields, blockers, and cross-layer triangulation checks. Do not edit generated .agents/skills during the research run.\nWEB/SOURCE POLICY: Run layered source retrieval across every safely available layer before synthesis: latest public papers, official government or leading-institution data, standards or primary docs, current news including BBC/CNN/GDELT-style sources when relevant, public discourse including X/Twitter and Reddit when available, developer/practitioner sources such as Stack Overflow/Stack Exchange/GitHub, and counterevidence or fact-checking sources. Treat public discourse as signal, not truth. If a layer cannot be searched, record the blocker in source-ledger.json and do not pass the gate.\nRESEARCH PLAN:\n${JSON.stringify(plan, null, 2)}\n\nOBJECTIVE: Produce genuinely useful candidate discoveries: non-obvious hypotheses, mechanisms, predictions, or experiments. Do not merely summarize. Mark uncertainty clearly.\n\nREQUIRED PROCESS:\n1. Source skill first: create ${RESEARCH_SOURCE_SKILL_ARTIFACT} with source layers, query templates, quality fields, blockers, and triangulation rules.\n2. Layered source search: create source-ledger.json with source_layers, queries, source ids, source quality notes, counterevidence sources, triangulation.cross_layer_checks, citation coverage, and blockers.\n3. Independent xhigh scouts: create scout-ledger.json with agent_name/display_name/persona/persona_boundary, effort=xhigh, reasoning_effort=xhigh, a literal Eureka! idea, findings, source_ids, falsifiers, cheap_probes, and challenge_or_response for every scout lens.\n4. Debate to agreement: create debate-ledger.json with evidence-bound challenge/response exchanges involving every scout, consensus_iterations >= 1, unanimous_consensus=true only when all scouts agree, and scout_agreements for every scout.\n5. Falsification: create falsification-ledger.json with attacks, missing evidence, source conflicts, and decisive next tests.\n6. Synthesis: write research-report.md and novelty-ledger.json only after cited scout findings, Eureka ideas, unanimous debate agreement, cross-layer triangulation, and falsification are recorded.\n7. Paper: write ${paperArtifact} as a paper-style manuscript with source-ledger references and limitations.\n\nREQUIRED OUTPUT FILES in .sneakoscope/missions/${id}/:\n- research-report.md: concise report with framing, source coverage, scout synthesis, debate synthesis, hypotheses, falsification, predictions, and next experiments. Cite source-ledger ids for factual claims.\n- ${paperArtifact}: paper manuscript with Abstract, Introduction, Methodology, Findings/Results, Discussion, Limitations/Falsification, Conclusion/Next Experiment, and References using source-ledger ids.\n- ${RESEARCH_SOURCE_SKILL_ARTIFACT}: route-local source collection skill; it is evidence for the Skill Creator step and must not mutate generated .agents/skills.\n- source-ledger.json: layered web/source queries, source ids, source priority, source quality notes, counterevidence sources, citation coverage, triangulation checks, and blockers.\n- scout-ledger.json: one entry per scout lens with agent_name, display_name, persona, persona_boundary, effort, reasoning_effort, service_tier, eureka, query_set, findings, source_ids, falsifiers, cheap_probes, and challenge_or_response.\n- debate-ledger.json: evidence-bound challenge/response exchanges, participants, changed minds, unresolved conflicts, consensus_iterations, unanimous_consensus, and scout_agreements for every scout.\n- novelty-ledger.json: entries with claim, novelty, confidence, falsifiability, evidence source ids, falsifiers, next_experiment.\n- falsification-ledger.json: attacks/counterexamples/source conflicts, result, and next_decisive_tests.\n- research-gate.json: set passed only when all ledgers exist, ${RESEARCH_SOURCE_SKILL_ARTIFACT} exists, ${paperArtifact} exists with required paper sections, layered web/source retrieval covered every required source layer, at least one cross-layer triangulation check exists, all scouts have agent_name/display_name/persona/persona_boundary, all scouts have effort=xhigh, all scouts have literal Eureka! ideas, every scout participated in debate, consensus_iterations >= 1, unanimous_consensus=true with every scout agreement recorded, at least one counterevidence source exists, citation coverage is complete, at least one insight survived falsification, at least one testable prediction exists, and unsupported breakthrough claims are zero.\n\nPrevious cycle tail:\n${String(previous || '').slice(-2500)}\n`;
|
|
913
999
|
}
|
package/src/core/routes.mjs
CHANGED
|
@@ -523,8 +523,8 @@ export const COMMAND_CATALOG = [
|
|
|
523
523
|
{ name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
|
|
524
524
|
{ name: 'deps', usage: 'sks deps check|install [tmux|codex|context7|all] [--yes]', description: 'Check or guided-install Node/npm PATH, Codex CLI/App, Context7, Browser tooling, Computer Use, tmux, and Homebrew on macOS.' },
|
|
525
525
|
{ name: 'codex-app', usage: 'sks codex-app [check|open|remote-control]', description: 'Check Codex App install and first-party MCP/plugin readiness, then show app setup files, examples, and Codex CLI 0.130.0+ remote-control availability.' },
|
|
526
|
-
{ name: 'codex-lb', usage: 'sks codex-lb status|health|repair|setup --host <domain> --api-key <key>', description: 'Configure, health-check, or repair codex-lb
|
|
527
|
-
{ name: 'auth', usage: 'sks auth status|health|repair|setup --host <domain> --api-key <key>', description: 'Shortcut for codex-lb auth status, health, repair, and setup commands.' },
|
|
526
|
+
{ name: 'codex-lb', usage: 'sks codex-lb status|health|repair|setup --host <domain> --api-key <key>', description: 'Configure, health-check, or repair codex-lb provider auth by writing ~/.codex/config.toml, restoring CODEX_LB_API_KEY env auth from stored or legacy login-cache state, and preserving the shared Codex login cache unless explicitly requested.' },
|
|
527
|
+
{ name: 'auth', usage: 'sks auth status|health|repair|setup --host <domain> --api-key <key>', description: 'Shortcut for codex-lb provider auth status, health, repair, and setup commands.' },
|
|
528
528
|
{ name: 'openclaw', usage: 'sks openclaw install|path|print [--dir path] [--force] [--json]', description: 'Generate an OpenClaw skill package so OpenClaw agents can discover and use local SKS workflows.' },
|
|
529
529
|
{ name: 'tmux', usage: 'sks | sks tmux open|check|status [--workspace name]', description: 'Open the default SKS tmux runtime with bare sks, or use tmux subcommands for explicit launch/check/status.' },
|
|
530
530
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|