sneakoscope 0.8.0 → 0.8.4
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 +3 -3
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +59 -22
- package/src/cli/main.mjs +57 -26
- package/src/cli/maintenance-commands.mjs +114 -11
- package/src/core/codex-app.mjs +182 -11
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +82 -1
- package/src/core/init.mjs +56 -10
- package/src/core/pipeline.mjs +3 -3
- package/src/core/research.mjs +185 -29
- package/src/core/routes.mjs +1 -1
package/README.md
CHANGED
|
@@ -212,7 +212,7 @@ sks qa-loop prepare "http://localhost:3000"
|
|
|
212
212
|
sks qa-loop run latest --max-cycles 2
|
|
213
213
|
sks goal create "persist this migration workflow"
|
|
214
214
|
sks research prepare "evaluate this approach"
|
|
215
|
-
sks research run latest --max-cycles
|
|
215
|
+
sks research run latest --max-cycles 12 --cycle-timeout-minutes 120
|
|
216
216
|
sks research status latest
|
|
217
217
|
sks recallpulse run latest
|
|
218
218
|
sks recallpulse status latest --json
|
|
@@ -235,7 +235,7 @@ sks skill-dream run --json
|
|
|
235
235
|
sks code-structure scan --json
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
-
`sks research` prepares a named genius-lens scout council, requires every scout to run at `xhigh`, records one literal `Eureka!` idea per scout, runs an evidence-bound debate, and
|
|
238
|
+
`sks research` prepares a named genius-lens scout council, requires every scout to run at `xhigh`, records one literal `Eureka!` idea per scout, runs an evidence-bound debate, and creates `research-source-skill.md` as a route-local source collection skill before synthesis. Research is not a code-change route: real runs may write only their own mission artifacts under `.sneakoscope/missions/<id>/`, and source/package/docs/config mutations block the run with `research-code-mutation-blocker.json`. The required Research persona lenses are Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout; they are cognitive roles, not impersonations, and `scout-ledger.json` must include `display_name`, `persona`, `persona_boundary`, `reasoning_effort`, falsifiers, cheap probes, and `challenge_or_response`. Normal Research is not a fixed three-cycle flow: it repeats source gathering, Eureka ideas, debate, falsification, and synthesis pressure until every scout records final agreement, or pauses at the explicit max-cycle safety cap with an unpassed gate. `debate-ledger.json` must include `consensus_iterations`, `unanimous_consensus`, and per-scout agreements; `research-gate.json` cannot pass until unanimous consensus is true for all scouts. Normal Research is intentionally allowed to take one or two hours when the problem needs it; `--mock` is only for selftests or dry harness checks, and a real run blocks with `research-blocker.json` instead of silently substituting mock output when the Codex execution path is unavailable. The source layer contract separates latest papers, official/government or leading-institution sources, standards/primary docs, current news such as BBC/CNN/GDELT-style sources, public discourse such as X/Reddit, developer/practitioner knowledge such as Stack Overflow/GitHub, traditional background sources, and counterevidence/fact-checking; `source-ledger.json` must record layer coverage, source quality, blockers, citations, and cross-layer triangulation. Context7 is optional for `$Research` and only becomes relevant when the research topic specifically depends on package, API, framework, or SDK documentation. Research runs require `research-report.md`, `research-paper.md`, `genius-opinion-summary.md`, `research-source-skill.md`, `source-ledger.json`, `scout-ledger.json`, `debate-ledger.json`, `novelty-ledger.json`, `falsification-ledger.json`, and `research-gate.json` so they stay source-backed, adversarially checked, falsifiable, paper-ready, and clear about every scout lens opinion. `research status` reports source entries, source-layer coverage, triangulation checks, counterevidence, xhigh scout count, Eureka moments, debate exchanges, consensus iterations, unanimous consensus, paper presence/sections, genius-opinion summary coverage, scout findings, and falsification cases alongside the gate.
|
|
239
239
|
|
|
240
240
|
`sks recallpulse` is the 0.8.0 report-only RecallPulse utility. It writes `recallpulse-decision.json`, `mission-status-ledger.json`, `route-proof-capsule.json`, `evidence-envelope.json`, `recallpulse-governance-report.json`, `recallpulse-task-goal-ledger.json`, and `recallpulse-eval-report.json` for the current mission. RecallPulse does not replace route gates, Honest Mode, DB safety, imagegen evidence, or TriWiki validation; it records cache hits, hydration needs, duplicate suppression, route-governance risks, and final-summary-ready durable status so later releases can promote only measured improvements. Checklist updates are sequential: every `Txxx` row is treated as a child `$Goal` checkpoint, and `sks recallpulse checklist ... --task T001 --apply` refuses out-of-order checks unless explicitly overridden.
|
|
241
241
|
|
|
@@ -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. 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
|
|
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.4",
|
|
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",
|
|
@@ -10,6 +10,16 @@ import { initProject, installSkills } from '../core/init.mjs';
|
|
|
10
10
|
import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext7ConfigText, RECOMMENDED_SKILLS } from '../core/routes.mjs';
|
|
11
11
|
import { codexLaunchCommand, platformTmuxInstallHint, tmuxReadiness } from '../core/tmux-ui.mjs';
|
|
12
12
|
|
|
13
|
+
const DEFAULT_CODEX_APP_PLUGINS = [
|
|
14
|
+
['browser', 'openai-bundled'],
|
|
15
|
+
['chrome', 'openai-bundled'],
|
|
16
|
+
['computer-use', 'openai-bundled'],
|
|
17
|
+
['latex', 'openai-bundled'],
|
|
18
|
+
['documents', 'openai-primary-runtime'],
|
|
19
|
+
['presentations', 'openai-primary-runtime'],
|
|
20
|
+
['spreadsheets', 'openai-primary-runtime']
|
|
21
|
+
];
|
|
22
|
+
|
|
13
23
|
export async function postinstall({ bootstrap }) {
|
|
14
24
|
const installRoot = path.resolve(process.env.INIT_CWD || process.cwd());
|
|
15
25
|
const conflictScan = await scanHarnessConflicts(installRoot);
|
|
@@ -159,10 +169,10 @@ async function capturePostinstallCodexLbConfigSnapshot(home = process.env.HOME |
|
|
|
159
169
|
async function restorePostinstallCodexLbConfigSnapshot(snapshot) {
|
|
160
170
|
if (!snapshot?.base_url) return { status: 'skipped', reason: 'no_snapshot' };
|
|
161
171
|
const current = await readText(snapshot.config_path, '');
|
|
162
|
-
|
|
172
|
+
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, snapshot.base_url));
|
|
173
|
+
if (next === ensureTrailingNewline(current) && codexLbProviderBaseUrl(current)) {
|
|
163
174
|
return { status: 'present', config_path: snapshot.config_path };
|
|
164
175
|
}
|
|
165
|
-
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, snapshot.base_url));
|
|
166
176
|
await writeTextAtomic(snapshot.config_path, next);
|
|
167
177
|
return { status: 'restored', config_path: snapshot.config_path };
|
|
168
178
|
}
|
|
@@ -202,10 +212,10 @@ export async function codexLbStatus(opts = {}) {
|
|
|
202
212
|
const envText = envExists ? await readText(envPath, '') : '';
|
|
203
213
|
const envKeyConfigured = Boolean(parseCodexLbEnvKey(envText));
|
|
204
214
|
const providerConfigured = /\[model_providers\.codex-lb\]/.test(config);
|
|
205
|
-
const selected =
|
|
215
|
+
const selected = hasTopLevelCodexLbSelected(config);
|
|
206
216
|
const baseUrl = codexLbProviderBaseUrl(config) || parseCodexLbEnvBaseUrl(envText) || null;
|
|
207
217
|
return {
|
|
208
|
-
ok:
|
|
218
|
+
ok: providerConfigured && envKeyConfigured && Boolean(baseUrl),
|
|
209
219
|
config_path: configPath,
|
|
210
220
|
env_path: envPath,
|
|
211
221
|
provider_configured: providerConfigured,
|
|
@@ -350,10 +360,10 @@ function codexLbProviderBaseUrl(text = '') {
|
|
|
350
360
|
export async function repairCodexLbAuth(opts = {}) {
|
|
351
361
|
let status = await codexLbStatus(opts);
|
|
352
362
|
let configRepaired = false;
|
|
353
|
-
|
|
363
|
+
const currentConfig = await readText(status.config_path, '');
|
|
364
|
+
if (status.env_key_configured && status.base_url && (!status.ok || status.selected || hasTopLevelCodexModeLock(currentConfig))) {
|
|
354
365
|
await ensureDir(path.dirname(status.config_path));
|
|
355
|
-
const
|
|
356
|
-
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(current, status.base_url));
|
|
366
|
+
const next = normalizeCodexFastModeUiConfig(upsertCodexLbConfig(currentConfig, status.base_url));
|
|
357
367
|
await writeTextAtomic(status.config_path, next);
|
|
358
368
|
configRepaired = true;
|
|
359
369
|
status = await codexLbStatus(opts);
|
|
@@ -450,7 +460,7 @@ async function syncCodexApiKeyLogin(apiKey, opts = {}) {
|
|
|
450
460
|
}
|
|
451
461
|
|
|
452
462
|
function upsertCodexLbConfig(text = '', baseUrl) {
|
|
453
|
-
let next =
|
|
463
|
+
let next = removeTopLevelTomlKeyIfValue(text, 'model_provider', 'codex-lb');
|
|
454
464
|
const block = [
|
|
455
465
|
'[model_providers.codex-lb]',
|
|
456
466
|
'name = "OpenAI"',
|
|
@@ -488,11 +498,18 @@ export function normalizeCodexFastModeUiConfig(text = '') {
|
|
|
488
498
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
489
499
|
next = upsertTopLevelTomlBoolean(next, 'suppress_unstable_features_warning', true);
|
|
490
500
|
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
501
|
+
next = upsertTomlTableKey(next, 'features', 'remote_control = true');
|
|
491
502
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
492
503
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
493
504
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
494
505
|
next = upsertTomlTableKey(next, 'features', 'codex_git_commit = true');
|
|
495
506
|
next = upsertTomlTableKey(next, 'features', 'computer_use = true');
|
|
507
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use = true');
|
|
508
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use_external = true');
|
|
509
|
+
next = upsertTomlTableKey(next, 'features', 'image_generation = true');
|
|
510
|
+
next = upsertTomlTableKey(next, 'features', 'in_app_browser = true');
|
|
511
|
+
next = upsertTomlTableKey(next, 'features', 'guardian_approval = true');
|
|
512
|
+
next = upsertTomlTableKey(next, 'features', 'tool_suggest = true');
|
|
496
513
|
next = upsertTomlTableKey(next, 'features', 'apps = true');
|
|
497
514
|
next = upsertTomlTableKey(next, 'features', 'plugins = true');
|
|
498
515
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
|
|
@@ -503,24 +520,41 @@ export function normalizeCodexFastModeUiConfig(text = '') {
|
|
|
503
520
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'approval_policy = "on-request"');
|
|
504
521
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'sandbox_mode = "workspace-write"');
|
|
505
522
|
next = upsertTomlTableKey(next, 'profiles.sks-fast-high', 'model_reasoning_effort = "high"');
|
|
523
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'model = "gpt-5.5"');
|
|
524
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'service_tier = "fast"');
|
|
525
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'approval_policy = "on-request"');
|
|
526
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'sandbox_mode = "workspace-write"');
|
|
527
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research-xhigh', 'model_reasoning_effort = "xhigh"');
|
|
528
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'model = "gpt-5.5"');
|
|
529
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'service_tier = "fast"');
|
|
530
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'approval_policy = "never"');
|
|
531
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'sandbox_mode = "workspace-write"');
|
|
532
|
+
next = upsertTomlTableKey(next, 'profiles.sks-research', 'model_reasoning_effort = "xhigh"');
|
|
533
|
+
for (const [name, marketplace] of DEFAULT_CODEX_APP_PLUGINS) {
|
|
534
|
+
const table = `plugins."${name}@${marketplace}"`;
|
|
535
|
+
next = upsertTomlTable(next, table, `[${table}]\nenabled = true`);
|
|
536
|
+
}
|
|
506
537
|
return ensureTrailingNewline(next);
|
|
507
538
|
}
|
|
508
539
|
|
|
509
540
|
function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
510
|
-
const legacy = {
|
|
511
|
-
model_reasoning_effort: new Set(['high'])
|
|
512
|
-
};
|
|
513
541
|
const lines = String(text || '').split('\n');
|
|
514
542
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
515
543
|
const end = firstTable === -1 ? lines.length : firstTable;
|
|
516
544
|
return lines.filter((line, index) => {
|
|
517
545
|
if (index >= end) return true;
|
|
518
|
-
|
|
519
|
-
if (!match) return true;
|
|
520
|
-
return !legacy[match[1]]?.has(match[2]);
|
|
546
|
+
return !/^\s*model_reasoning_effort\s*=/.test(line);
|
|
521
547
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
522
548
|
}
|
|
523
549
|
|
|
550
|
+
function removeTopLevelTomlKeyIfValue(text = '', key = '', value = '') {
|
|
551
|
+
const lines = String(text || '').split('\n');
|
|
552
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
553
|
+
const end = firstTable === -1 ? lines.length : firstTable;
|
|
554
|
+
const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"${escapeRegExp(value)}"\\s*(?:#.*)?$`);
|
|
555
|
+
return lines.filter((line, index) => index >= end || !keyPattern.test(line)).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
556
|
+
}
|
|
557
|
+
|
|
524
558
|
function removeTomlTableKey(text, table, key) {
|
|
525
559
|
const lines = String(text || '').trimEnd().split('\n');
|
|
526
560
|
if (lines.length === 1 && lines[0] === '') return '';
|
|
@@ -1056,7 +1090,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1056
1090
|
const codexLbFakeCodex = path.join(codexLbFakeBin, 'codex');
|
|
1057
1091
|
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");
|
|
1058
1092
|
await fsp.chmod(codexLbFakeCodex, 0o755);
|
|
1059
|
-
await writeTextAtomic(path.join(codexLbHome, '.codex', 'config.toml'), 'model = "gpt-5.5"\nmodel_reasoning_effort = "
|
|
1093
|
+
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');
|
|
1060
1094
|
const codexLbEnvForSelftest = { HOME: codexLbHome, SKS_GLOBAL_ROOT: path.join(tmp, 'codex-lb-global'), PATH: `${codexLbFakeBin}${path.delimiter}${process.env.PATH || ''}` };
|
|
1061
1095
|
const codexLbSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'setup', '--host', 'lb.example.test', '--api-key', 'sk-test', '--json'], {
|
|
1062
1096
|
cwd: tmp,
|
|
@@ -1069,18 +1103,18 @@ export async function selftestCodexLb(tmp) {
|
|
|
1069
1103
|
const codexLbConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1070
1104
|
const codexLbEnv = await safeReadText(path.join(codexLbHome, '.codex', 'sks-codex-lb.env'));
|
|
1071
1105
|
const codexLbAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1072
|
-
if (!codexLbSetupJson.ok || codexLbSetupJson.base_url !== 'https://lb.example.test/backend-api/codex' ||
|
|
1106
|
+
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'") || !/(\"auth_mode\"\s*:\s*\"apikey\")/.test(codexLbAuth)) throw new Error('selftest: codex-lb setup');
|
|
1073
1107
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
1074
1108
|
await initProject(codexLbHome, { installScope: 'global', force: true, repair: true });
|
|
1075
1109
|
const codexLbRepairSetupConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1076
|
-
if (
|
|
1110
|
+
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');
|
|
1077
1111
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbRepairSetupConfig)) throw new Error('selftest: init codex-lb did not suppress Codex unstable feature warning');
|
|
1078
1112
|
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`);
|
|
1079
1113
|
const ptmp = path.join(tmp, 'codex-lb-project-config'), prevHome = process.env.HOME;
|
|
1080
1114
|
try { process.env.HOME = codexLbHome; await initProject(ptmp, { installScope: 'global' }); }
|
|
1081
1115
|
finally { if (prevHome === undefined) delete process.env.HOME; else process.env.HOME = prevHome; }
|
|
1082
1116
|
const pcfg = await safeReadText(path.join(ptmp, '.codex', 'config.toml'));
|
|
1083
|
-
if (
|
|
1117
|
+
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');
|
|
1084
1118
|
if (!hasCodexUnstableFeatureWarningSuppression(pcfg)) throw new Error('selftest: project codex-lb config did not suppress Codex unstable feature warning');
|
|
1085
1119
|
await writeTextAtomic(path.join(codexLbHome, '.codex', 'auth.json'), '{"auth_mode":"browser"}\n');
|
|
1086
1120
|
const codexLbRepair = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'auth', 'repair', '--json'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
|
|
@@ -1141,7 +1175,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1141
1175
|
const codexLbPostBootstrapConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1142
1176
|
const codexLbLoginCallsAfterBootstrap = (await safeReadText(path.join(codexLbHome, '.codex', 'login-calls.log'))).trim().split(/\r?\n/).filter(Boolean).length;
|
|
1143
1177
|
if (!codexLbPostBootstrapAuth.includes('"auth_mode":"apikey"') || !codexLbPostBootstrapAuth.includes('sk-test') || codexLbLoginCallsAfterBootstrap <= codexLbLoginCallsBeforeBootstrap) throw new Error('selftest: postinstall drift auth');
|
|
1144
|
-
if (
|
|
1178
|
+
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');
|
|
1145
1179
|
const doctorProject = tmpdir();
|
|
1146
1180
|
await ensureDir(path.join(doctorProject, '.git'));
|
|
1147
1181
|
await writeTextAtomic(path.join(doctorProject, 'package.json'), '{"name":"codex-lb-doctor-project","version":"0.0.0"}\n');
|
|
@@ -1158,7 +1192,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1158
1192
|
const codexLbDoctorJson = JSON.parse(codexLbDoctorRepair.stdout);
|
|
1159
1193
|
const codexLbDoctorAuth = await safeReadText(path.join(codexLbHome, '.codex', 'auth.json'));
|
|
1160
1194
|
const codexLbDoctorConfig = await safeReadText(path.join(codexLbHome, '.codex', 'config.toml'));
|
|
1161
|
-
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbDoctorAuth.includes('sk-test') ||
|
|
1195
|
+
if (!codexLbDoctorJson.repair?.codex_lb?.ok || !codexLbDoctorJson.repair.codex_lb.config_repaired || !codexLbDoctorJson.codex_lb?.ok || !codexLbDoctorAuth.includes('"auth_mode":"apikey"') || !codexLbDoctorAuth.includes('sk-test') || hasTopLevelCodexLbSelected(codexLbDoctorConfig) || !codexLbDoctorConfig.includes('https://lb.example.test/backend-api/codex') || !hasCodexUnstableFeatureWarningSuppression(codexLbDoctorConfig)) throw new Error('selftest: doctor codex-lb');
|
|
1162
1196
|
const codexLbContext7Bin = path.join(tmp, 'codex-lb-context7-bin');
|
|
1163
1197
|
await ensureDir(codexLbContext7Bin);
|
|
1164
1198
|
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');
|
|
@@ -1285,7 +1319,7 @@ export async function selftestCodexLb(tmp) {
|
|
|
1285
1319
|
}
|
|
1286
1320
|
);
|
|
1287
1321
|
if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true) throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
|
|
1288
|
-
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
|
|
1322
|
+
if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
|
|
1289
1323
|
if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
|
|
1290
1324
|
const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
|
|
1291
1325
|
if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest: tmux launch command does not source codex-lb env file');
|
|
@@ -1297,7 +1331,10 @@ export async function selftestCodexLb(tmp) {
|
|
|
1297
1331
|
}
|
|
1298
1332
|
|
|
1299
1333
|
function hasTopLevelCodexModeLock(text = '') {
|
|
1300
|
-
|
|
1334
|
+
const lines = String(text || '').split('\n');
|
|
1335
|
+
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
1336
|
+
const top = (firstTable === -1 ? lines : lines.slice(0, firstTable)).join('\n');
|
|
1337
|
+
return /(^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(\n|$)/.test(top) || /(^|\n)\s*model_reasoning_effort\s*=/.test(top);
|
|
1301
1338
|
}
|
|
1302
1339
|
|
|
1303
1340
|
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 { 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 = []) {
|
|
@@ -1277,7 +1285,7 @@ async function depsStatus(root = null, opts = {}) {
|
|
|
1277
1285
|
codex_cli: { ok: Boolean(codex.bin), bin: codex.bin || null, version: codex.version || null },
|
|
1278
1286
|
codex_app: app,
|
|
1279
1287
|
context7,
|
|
1280
|
-
browser_use: { ok: app.mcp.has_browser_use, cache: app.plugins.browser_use_cache },
|
|
1288
|
+
browser_use: { ok: Boolean(app.features?.browser_tool_ready || app.mcp.has_browser_use), cache: app.plugins.browser_use_cache, source: app.features?.browser_tool_source || app.mcp.browser_use_source || null },
|
|
1281
1289
|
computer_use: { ok: app.mcp.has_computer_use, cache: app.plugins.computer_use_cache },
|
|
1282
1290
|
tmux: { ok: Boolean(tmux.ok), bin: tmux.bin || null, version: tmux.version || null, min_version: tmux.min_version || '3.0', current_session: Boolean(tmux.current_session), install_hint: tmux.ok ? null : platformTmuxInstallHint(), error: tmux.error || null },
|
|
1283
1291
|
homebrew: process.platform === 'darwin' ? { ok: Boolean(brew), bin: brew, required_for_tmux_install: homebrewNeeded } : { ok: null, bin: null, required_for_tmux_install: false },
|
|
@@ -1307,7 +1315,7 @@ function printDepsStatus(status) {
|
|
|
1307
1315
|
console.log(`Codex App: ${status.codex_app.app.installed ? 'ok' : 'missing'}`);
|
|
1308
1316
|
console.log(`Image Gen: ${status.codex_app.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1309
1317
|
console.log(`Context7: ${status.context7.ok ? 'ok' : 'missing'}`);
|
|
1310
|
-
console.log(`Browser
|
|
1318
|
+
console.log(`Browser: ${status.browser_use.ok ? `ok${status.browser_use.source ? ` (${status.browser_use.source})` : ''}` : 'missing'}`);
|
|
1311
1319
|
console.log(`Computer Use:${status.computer_use.ok ? ' ok' : ' missing'}`);
|
|
1312
1320
|
console.log(`tmux: ${tmuxStatusKind(status.tmux)} ${status.tmux.version || status.tmux.error || ''}`.trimEnd());
|
|
1313
1321
|
if (process.platform === 'darwin') console.log(`Homebrew: ${status.homebrew.ok ? 'ok' : 'missing'} ${status.homebrew.bin || ''}`.trimEnd());
|
|
@@ -1637,7 +1645,7 @@ async function setup(args) {
|
|
|
1637
1645
|
else console.log('Git: .gitignore ignores SKS generated files');
|
|
1638
1646
|
console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
|
|
1639
1647
|
console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
|
|
1640
|
-
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser
|
|
1648
|
+
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser=${appRuntime.features?.browser_tool_ready ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1641
1649
|
console.log(`Prompt: intent-first routing, $Answer fact-check route, $DFix ultralight Direct Fix route, $PPT HTML/PDF presentation route, Context7 gate`);
|
|
1642
1650
|
console.log(`Skills: .agents/skills`);
|
|
1643
1651
|
console.log(`Next: sks context7 check; sks selftest --mock; sks commands; sks dollar-commands`);
|
|
@@ -1777,7 +1785,7 @@ async function doctor(args) {
|
|
|
1777
1785
|
console.log(`Rust acc.: ${rust.available ? rust.version : 'optional-missing'}`);
|
|
1778
1786
|
console.log(`State: ${result.sneakoscope.ok ? 'ok' : 'missing .sneakoscope'}`);
|
|
1779
1787
|
console.log(`Context7: ${result.context7.ok ? 'ok' : 'missing MCP config'} project=${result.context7.project.ok ? 'ok' : 'missing'} global=${result.context7.global.ok ? 'ok' : 'missing'}`);
|
|
1780
|
-
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser
|
|
1788
|
+
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser=${appRuntime.features?.browser_tool_ready ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'} Image Gen=${appRuntime.features?.image_generation ? 'ok' : 'missing'}`);
|
|
1781
1789
|
console.log(`tmux: ${tmuxStatusKind(result.runtime.tmux)} ${result.runtime.tmux.version || result.runtime.tmux.error || ''}`.trimEnd());
|
|
1782
1790
|
console.log(`Guard: ${result.harness_guard.ok ? 'ok' : 'blocked'}${result.harness_guard.source_exception ? ' source-exception' : ''}`);
|
|
1783
1791
|
console.log(`Version: ${result.versioning.ok ? 'ok' : 'missing'}${result.versioning.enabled ? ` ${result.versioning.package_version || ''}` : ` ${result.versioning.reason || 'disabled'}`}`);
|
|
@@ -1963,6 +1971,12 @@ async function safeReadText(file, fallback = '') {
|
|
|
1963
1971
|
}
|
|
1964
1972
|
|
|
1965
1973
|
async function resolveMissionId(root, arg) { return (!arg || arg === 'latest') ? findLatestMission(root) : arg; }
|
|
1974
|
+
|
|
1975
|
+
function hasResearchProfileConfig(text = '') {
|
|
1976
|
+
return /\[profiles\.sks-research-xhigh\][\s\S]*?model = "gpt-5\.5"[\s\S]*?model_reasoning_effort = "xhigh"/.test(text)
|
|
1977
|
+
&& /\[profiles\.sks-research\][\s\S]*?model = "gpt-5\.5"[\s\S]*?approval_policy = "never"[\s\S]*?model_reasoning_effort = "xhigh"/.test(text);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1966
1980
|
function readMaxCycles(args, fallback) {
|
|
1967
1981
|
const i = args.indexOf('--max-cycles');
|
|
1968
1982
|
const raw = i >= 0 && args[i + 1] ? Number(args[i + 1]) : Number(fallback);
|
|
@@ -2012,6 +2026,8 @@ async function selftest() {
|
|
|
2012
2026
|
if (trippedStop) throw new Error('selftest: compliance loop guard did not terminally trip');
|
|
2013
2027
|
const loopBlocker = await readJson(path.join(loopMission.dir, 'hard-blocker.json'), null);
|
|
2014
2028
|
if (loopBlocker?.reason !== 'compliance_loop_guard_tripped') throw new Error('selftest: compliance loop guard did not write hard blocker');
|
|
2029
|
+
const hardBlockerUnblocked = await evaluateStop(tmp, loopState, { last_assistant_message: 'done' });
|
|
2030
|
+
if (hardBlockerUnblocked?.decision === 'block' && !String(hardBlockerUnblocked.reason || '').includes('reflection')) throw new Error('selftest: hard blocker did not unblock incomplete active gate');
|
|
2015
2031
|
const clarificationMission = await createMission(tmp, { mode: 'team', prompt: 'visible question gate selftest' });
|
|
2016
2032
|
await writeTextAtomic(path.join(clarificationMission.dir, 'questions.md'), '# Questions\n\n1. GOAL_PRECISE: What should be changed?\n');
|
|
2017
2033
|
await writeJsonAtomic(path.join(clarificationMission.dir, 'required-answers.schema.json'), { slots: [{ id: 'GOAL_PRECISE', question: 'What should be changed?' }] });
|
|
@@ -2167,6 +2183,7 @@ async function selftest() {
|
|
|
2167
2183
|
const doctorGlobalCodexConfig = await safeReadText(path.join(doctorGlobalHome, '.codex', 'config.toml'));
|
|
2168
2184
|
if (!doctorGlobalRepairJson.repair?.global_codex_config) throw new Error('selftest: doctor global config repair missing');
|
|
2169
2185
|
assertCodexWarn(doctorGlobalCodexConfig, 'doctor global config');
|
|
2186
|
+
if (missingGeneratedCodexAppFeatureFlags(doctorGlobalCodexConfig).length || hasDeprecatedCodexHooksFeatureFlag(doctorGlobalCodexConfig) || !hasResearchProfileConfig(doctorGlobalCodexConfig)) throw new Error('selftest: doctor global config repair did not restore Codex App feature flags and Research xhigh profiles');
|
|
2170
2187
|
for (const name of stalePluginSkillNames) {
|
|
2171
2188
|
if (await exists(path.join(doctorGlobalHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest: doctor --fix did not remove global generated ${name} plugin shadow skill`);
|
|
2172
2189
|
}
|
|
@@ -2237,6 +2254,7 @@ async function selftest() {
|
|
|
2237
2254
|
const postinstallNoMarkerConfig = await safeReadText(path.join(postinstallNoMarkerGlobalRoot, '.codex', 'config.toml'));
|
|
2238
2255
|
if (missingGeneratedCodexAppFeatureFlags(postinstallNoMarkerConfig).length || hasDeprecatedCodexHooksFeatureFlag(postinstallNoMarkerConfig)) throw new Error('selftest: no-marker flags');
|
|
2239
2256
|
assertCodexWarn(postinstallNoMarkerConfig, 'postinstall global runtime config');
|
|
2257
|
+
if (!hasResearchProfileConfig(postinstallNoMarkerConfig)) throw new Error('selftest: postinstall global runtime config did not restore Research xhigh profiles');
|
|
2240
2258
|
if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest: no-marker postinstall polluted install cwd');
|
|
2241
2259
|
if (await exists(path.join(postinstallNoMarkerGlobalRoot, '.gitignore'))) throw new Error('selftest: global runtime bootstrap without project git wrote shared .gitignore');
|
|
2242
2260
|
const bootstrapJsonTmp = tmpdir();
|
|
@@ -2271,12 +2289,15 @@ async function selftest() {
|
|
|
2271
2289
|
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest: MAD tmux attach args are not stable by session name');
|
|
2272
2290
|
const defaultFastHighPlan = await buildTmuxLaunchPlan({ root: tmp, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2273
2291
|
if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="high"') throw new Error('selftest: default sks tmux launch is not fast-high');
|
|
2274
|
-
const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.
|
|
2275
|
-
if (forcedModelPlan.codexArgs.includes('gpt-5.
|
|
2276
|
-
const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-
|
|
2277
|
-
if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.
|
|
2278
|
-
const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-
|
|
2279
|
-
if (codexExecArgs.join(' ').includes('gpt-5.
|
|
2292
|
+
const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.0-forbidden', SKS_CODEX_FAST_HIGH: '0', SKS_CODEX_REASONING: 'medium' }, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2293
|
+
if (forcedModelPlan.codexArgs.includes('gpt-5.0-forbidden') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" -c model_reasoning_effort="medium"') throw new Error('selftest: sks tmux launch allowed a non-GPT-5.5 model override');
|
|
2294
|
+
const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-forbidden-model', '--model', 'gpt-5.0-forbidden', '-c', 'model="gpt-5.0-forbidden"', '-c', 'model_reasoning_effort="low"'], tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
|
|
2295
|
+
if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.0-forbidden') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c service_tier="fast" --profile legacy-forbidden-model -c model_reasoning_effort="low"') throw new Error('selftest: explicit tmux model override was not forced back to GPT-5.5');
|
|
2296
|
+
const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-forbidden-model', extraArgs: ['--model=gpt-5.0-forbidden', '--config', 'model = "gpt-5.0-forbidden"', '-c', 'model_reasoning_effort="medium"'] });
|
|
2297
|
+
if (codexExecArgs.join(' ').includes('gpt-5.0-forbidden') || !codexExecArgs.includes('gpt-5.5') || codexExecArgs.includes('--model=gpt-5.0-forbidden')) throw new Error('selftest: codex exec args allowed a non-GPT-5.5 model override');
|
|
2298
|
+
const researchExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'research exec selftest', profile: 'sks-research', extraArgs: ['-c', 'service_tier="fast"', '-c', 'model_reasoning_effort="xhigh"'] });
|
|
2299
|
+
const researchExecJoined = researchExecArgs.join(' ');
|
|
2300
|
+
if (!researchExecJoined.includes('--profile sks-research') || !researchExecJoined.includes('--model gpt-5.5') || !researchExecJoined.includes('service_tier="fast"') || !researchExecJoined.includes('model_reasoning_effort="xhigh"')) throw new Error('selftest: research exec args did not force GPT-5.5 fast xhigh execution');
|
|
2280
2301
|
await selftestCodexLb(tmp);
|
|
2281
2302
|
if (!shouldAutoAttachTmux(['--mad'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest: MAD tmux launch does not auto-attach in an interactive terminal');
|
|
2282
2303
|
if (shouldAutoAttachTmux(['--mad', '--json'], {}, { stdin: { isTTY: true }, stdout: { isTTY: true } })) throw new Error('selftest: MAD tmux json mode should not auto-attach');
|
|
@@ -2330,14 +2351,14 @@ async function selftest() {
|
|
|
2330
2351
|
if (remoteControlStatus.code !== 0) throw new Error(`selftest: Codex remote-control status exited ${remoteControlStatus.code}: ${remoteControlStatus.stderr}`);
|
|
2331
2352
|
const remoteControlJson = JSON.parse(remoteControlStatus.stdout);
|
|
2332
2353
|
if (!remoteControlJson.ok || remoteControlJson.min_version !== '0.130.0' || !String(remoteControlJson.command || '').includes('remote-control')) throw new Error('selftest: Codex remote-control status did not report 0.130.0 readiness');
|
|
2333
|
-
const remoteControlLaunch = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-app', 'remote-control', '--', '--model', 'gpt-5.
|
|
2354
|
+
const remoteControlLaunch = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-app', 'remote-control', '--', '--model', 'gpt-5.0-forbidden', '-c', 'model="gpt-5.0-forbidden"', '--example'], {
|
|
2334
2355
|
cwd: globalCwd,
|
|
2335
2356
|
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, PATH: remoteControlBin },
|
|
2336
2357
|
timeoutMs: 15000,
|
|
2337
2358
|
maxOutputBytes: 64 * 1024
|
|
2338
2359
|
});
|
|
2339
2360
|
const remoteControlLaunchText = `${remoteControlLaunch.stdout}\n${remoteControlLaunch.stderr}`;
|
|
2340
|
-
if (remoteControlLaunch.code !== 0 || remoteControlLaunchText.includes('gpt-5.
|
|
2361
|
+
if (remoteControlLaunch.code !== 0 || remoteControlLaunchText.includes('gpt-5.0-forbidden') || remoteControlLaunchText.includes('--model') || !remoteControlLaunchText.includes('-c model="gpt-5.5"')) throw new Error('selftest: Codex remote-control passthrough did not force GPT-5.5 with config syntax');
|
|
2341
2362
|
const remoteControlOldBin = path.join(tmp, 'remote-control-old-bin');
|
|
2342
2363
|
await ensureDir(remoteControlOldBin);
|
|
2343
2364
|
await writeTextAtomic(path.join(remoteControlOldBin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 0.129.0"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
@@ -2848,10 +2869,10 @@ async function selftest() {
|
|
|
2848
2869
|
if (hookTeamState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookTeamState.implementation_allowed === false || !hookTeamState.team_plan_ready) throw new Error('selftest: $Team hook did not prepare direct Team mission');
|
|
2849
2870
|
if (!hookTeamState.pipeline_plan_ready || !(await exists(path.join(missionDir(hookTeamTmp, hookTeamState.mission_id), PIPELINE_PLAN_ARTIFACT)))) throw new Error('selftest: $Team hook did not write a pipeline plan');
|
|
2850
2871
|
if (!(await exists(path.join(missionDir(hookTeamTmp, hookTeamState.mission_id), 'team-plan.json')))) throw new Error('selftest: Team plan was not created directly');
|
|
2851
|
-
const hookForbiddenModelResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team should be blocked before route work', model: 'gpt-5.5', metadata: { client: { modelId: 'gpt-5.
|
|
2872
|
+
const hookForbiddenModelResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team should be blocked before route work', model: 'gpt-5.5', metadata: { client: { modelId: 'gpt-5.0-forbidden' } } }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
|
|
2852
2873
|
if (hookForbiddenModelResult.code !== 0) throw new Error(`selftest: forbidden model hook exited ${hookForbiddenModelResult.code}: ${hookForbiddenModelResult.stderr}`);
|
|
2853
2874
|
const hookForbiddenModelJson = JSON.parse(hookForbiddenModelResult.stdout);
|
|
2854
|
-
if (hookForbiddenModelJson.decision !== 'block' || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.5') || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.
|
|
2875
|
+
if (hookForbiddenModelJson.decision !== 'block' || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.5') || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.0-forbidden')) throw new Error('selftest: hook did not block forbidden client model metadata');
|
|
2855
2876
|
const hookTeamPendingResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 새 작업으로 넘어가' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
|
|
2856
2877
|
if (hookTeamPendingResult.code !== 0) throw new Error(`selftest: pending clarification hook exited ${hookTeamPendingResult.code}: ${hookTeamPendingResult.stderr}`);
|
|
2857
2878
|
const hookTeamPendingJson = JSON.parse(hookTeamPendingResult.stdout);
|
|
@@ -3024,7 +3045,8 @@ async function selftest() {
|
|
|
3024
3045
|
if (missingCodexConfigFlags.length || hasDeprecatedCodexHooksFeatureFlag(codexConfigText)) throw new Error(`selftest: generated Codex App feature flags missing or deprecated: ${missingCodexConfigFlags.join(', ')}`);
|
|
3025
3046
|
assertCodexWarn(codexConfigText, 'generated Codex App config');
|
|
3026
3047
|
if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest: Context7 MCP not configured');
|
|
3027
|
-
if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest: GPT-5.5 reasoning profiles not configured');
|
|
3048
|
+
if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-research]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest: GPT-5.5 reasoning profiles not configured');
|
|
3049
|
+
if (!hasResearchProfileConfig(codexConfigText)) throw new Error('selftest: generated Research xhigh profiles not configured');
|
|
3028
3050
|
if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest: generated sks-mad-high profile is not full access');
|
|
3029
3051
|
if (!codexConfigText.includes('[agents.analysis_scout]')) throw new Error('selftest: analysis_scout agent not configured');
|
|
3030
3052
|
if (!codexConfigText.includes('[agents.team_consensus]')) throw new Error('selftest: team_consensus agent not configured');
|
|
@@ -3037,20 +3059,22 @@ async function selftest() {
|
|
|
3037
3059
|
assertCodexWarn(preservedConfig, 'merged Codex config');
|
|
3038
3060
|
if (preservedConfig.includes('fast_default_opt_out = true') || !preservedConfig.includes('keep = true')) throw new Error('selftest: Codex config merge did not remove stale Fast opt-out notice while preserving other notice keys');
|
|
3039
3061
|
const missingPreservedFlags = missingGeneratedCodexAppFeatureFlags(preservedConfig);
|
|
3040
|
-
if (missingPreservedFlags.length || hasDeprecatedCodexHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('custom_preview = true') || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error(`selftest: Codex config merge did not add required app feature flags, preserve existing feature flags, or remove deprecated codex_hooks: ${missingPreservedFlags.join(', ')}`);
|
|
3062
|
+
if (missingPreservedFlags.length || hasDeprecatedCodexHooksFeatureFlag(preservedConfig) || !preservedConfig.includes('custom_preview = true') || !preservedConfig.includes('[profiles.sks-fast-high]') || !hasResearchProfileConfig(preservedConfig)) throw new Error(`selftest: Codex config merge did not add required app feature flags, Research profiles, preserve existing feature flags, or remove deprecated codex_hooks: ${missingPreservedFlags.join(', ')}`);
|
|
3041
3063
|
if (hasTopLevelCodexModeLock(preservedConfig)) throw new Error('selftest: Codex config merge left top-level legacy model/reasoning locks that hide Fast mode UI');
|
|
3042
3064
|
const appFeatureTmp = tmpdir();
|
|
3043
3065
|
const fakeCodexApp = path.join(appFeatureTmp, 'Codex.app');
|
|
3044
3066
|
const fakeCodexBinDir = path.join(appFeatureTmp, 'bin');
|
|
3045
3067
|
await ensureDir(fakeCodexApp);
|
|
3046
3068
|
await ensureDir(fakeCodexBinDir);
|
|
3069
|
+
await ensureDir(path.join(appFeatureTmp, '.codex'));
|
|
3070
|
+
await writeTextAtomic(path.join(appFeatureTmp, '.codex', 'config.toml'), codexConfigText);
|
|
3047
3071
|
const fakeCodex = path.join(fakeCodexBinDir, 'codex');
|
|
3048
|
-
await writeTextAtomic(fakeCodex, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\ncodex_git_commit under development true\ncomputer_use stable true\nfast_mode stable true\nhooks stable true\nimage_generation stable true\nplugins stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3072
|
+
await writeTextAtomic(fakeCodex, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\nbrowser_use stable true\nbrowser_use_external stable true\ncodex_git_commit under development true\ncomputer_use stable true\nfast_mode stable true\nguardian_approval stable true\nhooks stable true\nimage_generation stable true\nin_app_browser stable true\nplugins stable true\nremote_control under development true\ntool_suggest stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3049
3073
|
await fsp.chmod(fakeCodex, 0o755);
|
|
3050
3074
|
const codexAppFeatureStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodex, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
3051
|
-
if (!codexAppFeatureStatus.ok || !codexAppFeatureStatus.features?.required_flags_ok || !codexAppFeatureStatus.features?.codex_git_commit) throw new Error('selftest: codex-app check did not accept required app feature flags
|
|
3075
|
+
if (!codexAppFeatureStatus.ok || !codexAppFeatureStatus.features?.required_flags_ok || !codexAppFeatureStatus.features?.codex_git_commit || !codexAppFeatureStatus.features?.remote_control || !codexAppFeatureStatus.features?.fast_mode_config?.ok) throw new Error('selftest: codex-app check did not accept required app feature flags, remote_control, and unlocked Fast UI config');
|
|
3052
3076
|
const fakeCodexMissing = path.join(fakeCodexBinDir, 'codex-missing-git-commit');
|
|
3053
|
-
await writeTextAtomic(fakeCodexMissing, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\ncodex_git_commit under development false\ncomputer_use stable true\nfast_mode stable true\nhooks stable true\nimage_generation stable true\nplugins stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3077
|
+
await writeTextAtomic(fakeCodexMissing, '#!/bin/sh\nif [ "$1" = "mcp" ] && [ "$2" = "list" ]; then printf "%s\\n" "computer-use enabled" "browser-use enabled"; exit 0; fi\nif [ "$1" = "features" ] && [ "$2" = "list" ]; then cat <<EOF\napps stable true\nbrowser_use stable true\nbrowser_use_external stable true\ncodex_git_commit under development false\ncomputer_use stable true\nfast_mode stable true\nguardian_approval stable true\nhooks stable true\nimage_generation stable true\nin_app_browser stable true\nplugins stable true\nremote_control under development true\ntool_suggest stable true\nEOF\nexit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
|
|
3054
3078
|
await fsp.chmod(fakeCodexMissing, 0o755);
|
|
3055
3079
|
const codexAppMissingFeatureStatus = await codexAppIntegrationStatus({ codex: { bin: fakeCodexMissing, version: 'codex-cli 99.0.0' }, home: appFeatureTmp, env: { SKS_CODEX_APP_PATH: fakeCodexApp } });
|
|
3056
3080
|
if (codexAppMissingFeatureStatus.ok || codexAppMissingFeatureStatus.features?.required_flags_ok || codexAppMissingFeatureStatus.features?.codex_git_commit) throw new Error('selftest: codex-app check did not block disabled codex_git_commit feature flag');
|
|
@@ -3929,17 +3953,24 @@ async function selftest() {
|
|
|
3929
3953
|
const { dir: researchDir, mission: researchMission } = await createMission(tmp, { mode: 'research', prompt: '새로운 코드 리뷰 방법론 연구' });
|
|
3930
3954
|
const researchPlan = await writeResearchPlan(researchDir, researchMission.prompt, {});
|
|
3931
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');
|
|
3932
|
-
if (
|
|
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');
|
|
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');
|
|
3960
|
+
const researchPrompt = buildResearchPrompt({ id: researchMission.id, mission: researchMission, plan: researchPlan, cycle: 1, previous: '' });
|
|
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');
|
|
3933
3962
|
const rArts = researchPlan.required_artifacts || [];
|
|
3934
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');
|
|
3935
|
-
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');
|
|
3936
3965
|
const initialResearchGate = await evaluateResearchGate(researchDir);
|
|
3937
|
-
if (initialResearchGate.passed || ['web_search_pass_missing', 'eureka_missing', 'debate_exchanges_missing', 'research_paper_missing'].some((r) => !initialResearchGate.reasons.includes(r))) throw new Error('selftest: research gate');
|
|
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');
|
|
3938
3967
|
const researchGate = await writeMockResearchResult(researchDir, researchPlan);
|
|
3939
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');
|
|
3940
3970
|
const rm = researchGate.metrics || {};
|
|
3971
|
+
if (rm.research_paper_artifact !== researchPaperArtifact) throw new Error('selftest: research gate did not report dated paper artifact');
|
|
3941
3972
|
if (rm.scout_persona_contract_ok !== true || (rm.scout_persona_issues || []).length) throw new Error('selftest: research scout persona contract did not pass');
|
|
3942
|
-
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) throw new Error('selftest: research metrics');
|
|
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');
|
|
3943
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 });
|
|
3944
3975
|
const gate = await evaluateDoneGate(tmp, id);
|
|
3945
3976
|
if (!gate.passed) throw new Error('selftest: done gate');
|