sneakoscope 0.7.75 → 0.8.0

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 CHANGED
@@ -40,6 +40,25 @@ sks selftest --mock
40
40
 
41
41
  `sks` adds a tmux Codex CLI runtime, Codex App `$` commands, Team/QA/PPT/Research/DB/GX/Wiki routes, OpenClaw skill generation, Context7-gated current docs, TriWiki context packs, DB safety, design SSOT policy, skill dreaming, release checks, and Honest Mode.
42
42
 
43
+ ## 0.8.0 Massive Upgrade
44
+
45
+ Sneakoscope 0.8.0 introduces the RecallPulse planning spine: a report-only active-recall layer that records what the pipeline should remember before a stage proceeds. RecallPulse maps TriWiki into L1/L2/L3 cache behavior, writes durable status ledgers instead of relying on ephemeral hook text, suppresses duplicate reminder loops, and emits RouteProofCapsule plus EvidenceEnvelope artifacts for later gate comparison. These artifacts are evidence surfaces first; speed or accuracy gains remain benchmark-gated until scored evals prove them.
46
+
47
+ Inspect the new report-only artifacts with:
48
+
49
+ ```sh
50
+ sks recallpulse run latest
51
+ sks recallpulse status latest --json
52
+ sks recallpulse eval latest --json
53
+ sks recallpulse governance latest --json
54
+ sks recallpulse checklist latest --json
55
+ sks recallpulse checklist latest --task T001 --apply --evidence src/core/recallpulse.mjs
56
+ ```
57
+
58
+ Research scouts now use named persona-inspired cognitive lenses: Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout. They are not impersonations; each scout ledger row must carry `display_name`, `persona`, `persona_boundary`, `reasoning_effort=xhigh`, a literal `Eureka!` idea, falsifiers, cheap probes, and debate participation evidence.
59
+
60
+ For existing 0.7.x users, the visible change is new report-only evidence, not a route personality rewrite. Team still feels like Team, DFix stays ultralight, DB remains conservative, QA-LOOP still dogfoods, PPT stays information-first, imagegen still requires real raster evidence, and Honest Mode remains the final truth pass. The original strong reminder idea became neutral RecallPulse so user-facing prompts stay short, professional, and non-repetitive; hook messages can point at status, but `mission-status-ledger.json` is the durable source when app-visible text disappears. The planning source is `docs/RECALLPULSE_0_8_0_TASKS.md`, and implementation is designed to land in safe task-sized slices before any enforcement promotion.
61
+
43
62
  ## Requirements
44
63
 
45
64
  - Node.js `>=20.11`
@@ -150,11 +169,12 @@ For [codex-lb](https://github.com/Soju06/codex-lb), start the server, create a d
150
169
 
151
170
  ```sh
152
171
  sks codex-lb setup --host https://your-codex-lb.example.com --api-key "sk-clb-..."
172
+ sks codex-lb health
153
173
  sks codex-lb repair
154
174
  sks
155
175
  ```
156
176
 
157
- Bare `sks` can also prompt for codex-lb auth; SKS stores the base URL/key in `~/.codex/sks-codex-lb.env`, syncs `codex login --with-api-key`, and loads it in tmux.
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 --with-api-key`, and loads it in tmux. 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.
158
178
 
159
179
  If Codex CLI 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>`.
160
180
 
@@ -194,6 +214,10 @@ sks goal create "persist this migration workflow"
194
214
  sks research prepare "evaluate this approach"
195
215
  sks research run latest --max-cycles 3 --cycle-timeout-minutes 120
196
216
  sks research status latest
217
+ sks recallpulse run latest
218
+ sks recallpulse status latest --json
219
+ sks recallpulse governance latest --json
220
+ sks recallpulse checklist latest --json
197
221
  sks db scan --json
198
222
  sks wiki refresh
199
223
  sks wiki sweep latest --json
@@ -211,7 +235,9 @@ sks skill-dream run --json
211
235
  sks code-structure scan --json
212
236
  ```
213
237
 
214
- `sks research` prepares a genius-lens scout council, requires every scout to run at `xhigh`, records one literal `Eureka!` idea per scout, runs an evidence-bound debate, and now creates `research-source-skill.md` as a route-local source collection skill before synthesis. 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, 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, paper presence/sections, genius-opinion summary coverage, scout findings, and falsification cases alongside the gate.
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 now creates `research-source-skill.md` as a route-local source collection skill before synthesis. 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 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, 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, paper presence/sections, genius-opinion summary coverage, scout findings, and falsification cases alongside the gate.
239
+
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.
215
241
 
216
242
  `sks pipeline plan` shows the active route lane, kept/skipped stages, verification commands, and no-unrequested-fallback invariant. `sks proof-field scan` is the lightweight rubric for small changes; risky or broad signals return to the full Team/Honest path.
217
243
 
@@ -278,6 +304,8 @@ Default setup adds these generated SKS paths to the project `.gitignore`; `--loc
278
304
 
279
305
  Use `sks dollar-commands` to confirm that terminal discovery and Codex App prompt commands agree.
280
306
 
307
+ SKS does not install Git pre-commit hooks. Release metadata is changed only by explicit commands such as `sks versioning bump`, and `sks versioning hook` is intentionally blocked so Codex App commit/push flows stay unobstructed.
308
+
281
309
  TriWiki is intentionally sparse: `sks wiki sweep` records demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim into future prompts. `sks harness fixture` validates the broader Harness Growth Factory contract: deliberate forgetting fixtures, skill card metadata, experiment schema, tool-error taxonomy, permission profiles, MultiAgentV2 defaults, and tmux cockpit view coverage. `sks code-structure scan` flags handwritten files above 1000/2000/3000-line thresholds so new logic can be extracted before command files become harder to maintain.
282
310
 
283
311
  ## OpenClaw Agent Usage
@@ -416,6 +444,8 @@ npm run publish:dry
416
444
 
417
445
  `release:check` runs audit, changelog, syntax, selftest, size, and registry checks. `publish:dry` runs that same gate and then performs an npm dry-run publish against the public registry.
418
446
 
447
+ Version bumps are manual. Run `sks versioning bump` only when preparing release metadata; SKS will not create `.git/hooks/pre-commit` or auto-bump during ordinary commits.
448
+
419
449
  ## License
420
450
 
421
451
  MIT
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.75",
4
+ "version": "0.8.0",
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",
@@ -217,6 +217,126 @@ export async function codexLbStatus(opts = {}) {
217
217
  };
218
218
  }
219
219
 
220
+ function codexLbResponsesEndpoint(baseUrl = '') {
221
+ const base = String(baseUrl || '').trim().replace(/\/+$/, '');
222
+ if (!base) return '';
223
+ return /\/responses$/i.test(base) ? base : `${base}/responses`;
224
+ }
225
+
226
+ function codexLbChainCheckEnabled(env = process.env) {
227
+ return env.SKS_CODEX_LB_CHAIN_CHECK !== '0' && env.SKS_SKIP_CODEX_LB_CHAIN_CHECK !== '1';
228
+ }
229
+
230
+ function isPreviousResponseNotFound(payload = {}) {
231
+ const error = payload?.error || payload?.response?.error || payload;
232
+ const text = typeof error === 'string'
233
+ ? error
234
+ : [error?.type, error?.code, error?.message, error?.param, JSON.stringify(error || {})].filter(Boolean).join(' ');
235
+ return /previous_response_not_found|previous_response_id.*not found|previous_response_id/i.test(text);
236
+ }
237
+
238
+ function parseCodexLbSseEvents(text = '') {
239
+ const events = [];
240
+ for (const line of String(text || '').split(/\r?\n/)) {
241
+ if (!line.startsWith('data:')) continue;
242
+ const data = line.slice(5).trim();
243
+ if (!data || data === '[DONE]') continue;
244
+ try {
245
+ events.push(JSON.parse(data));
246
+ } catch {}
247
+ }
248
+ return events;
249
+ }
250
+
251
+ function codexLbResponseId(payload = {}) {
252
+ if (typeof payload?.id === 'string' && payload.id) return payload.id;
253
+ if (typeof payload?.response?.id === 'string' && payload.response.id) return payload.response.id;
254
+ if (typeof payload?.data?.id === 'string' && payload.data.id) return payload.data.id;
255
+ if (typeof payload?.data?.response?.id === 'string' && payload.data.response.id) return payload.data.response.id;
256
+ return null;
257
+ }
258
+
259
+ function codexLbResponseError(json, events = []) {
260
+ if (json?.error) return json;
261
+ for (const event of events) {
262
+ if (event?.error || event?.response?.error || event?.type === 'response.failed' || event?.type === 'error') return event;
263
+ }
264
+ return null;
265
+ }
266
+
267
+ async function fetchCodexLbResponse(fetchImpl, endpoint, apiKey, body, timeoutMs) {
268
+ const controller = new AbortController();
269
+ const timer = setTimeout(() => controller.abort(), timeoutMs).unref?.();
270
+ try {
271
+ const response = await fetchImpl(endpoint, {
272
+ method: 'POST',
273
+ headers: {
274
+ authorization: `Bearer ${apiKey}`,
275
+ 'content-type': 'application/json'
276
+ },
277
+ body: JSON.stringify(body),
278
+ signal: controller.signal
279
+ });
280
+ const text = await response.text();
281
+ let json = null;
282
+ try { json = text ? JSON.parse(text) : null; } catch {}
283
+ const events = json ? [] : parseCodexLbSseEvents(text);
284
+ const responseId = codexLbResponseId(json) || events.map((event) => codexLbResponseId(event)).find(Boolean) || null;
285
+ const errorPayload = codexLbResponseError(json, events);
286
+ return { ok: response.ok && !errorPayload, status: response.status, json, text, events, response_id: responseId, error_payload: errorPayload };
287
+ } catch (err) {
288
+ return { ok: false, status: 0, json: null, text: err.name === 'AbortError' ? 'request timed out' : err.message, events: [], response_id: null, error_payload: null };
289
+ } finally {
290
+ clearTimeout(timer);
291
+ }
292
+ }
293
+
294
+ export async function checkCodexLbResponseChain(status = {}, opts = {}) {
295
+ const env = opts.env || process.env;
296
+ if (!codexLbChainCheckEnabled(env) && !opts.force) return { ok: true, status: 'skipped', skipped: true, reason: 'SKS_CODEX_LB_CHAIN_CHECK=0' };
297
+ const endpoint = codexLbResponsesEndpoint(opts.baseUrl || status.base_url);
298
+ if (!endpoint) return { ok: false, status: 'missing_base_url', chain_unhealthy: true };
299
+ const apiKey = opts.apiKey || parseCodexLbEnvKey(await readText(opts.envPath || status.env_path || codexLbEnvPath(opts.home || env.HOME || os.homedir()), ''));
300
+ if (!apiKey) return { ok: false, status: 'missing_env_key', chain_unhealthy: true };
301
+ const fetchImpl = opts.fetch || globalThis.fetch;
302
+ if (typeof fetchImpl !== 'function') return { ok: true, status: 'skipped', skipped: true, reason: 'fetch unavailable' };
303
+ const model = opts.model || env.SKS_CODEX_MODEL || 'gpt-5.5';
304
+ const timeoutMs = Number(opts.timeoutMs || env.SKS_CODEX_LB_CHAIN_CHECK_TIMEOUT_MS || 8000);
305
+ const baseBody = {
306
+ model,
307
+ instructions: 'You are running a short SKS codex-lb response-chain health check.',
308
+ input: 'SKS codex-lb response-chain health check. Reply with OK.',
309
+ stream: true,
310
+ store: true,
311
+ parallel_tool_calls: false,
312
+ tool_choice: 'auto',
313
+ reasoning: { effort: 'low' }
314
+ };
315
+ const first = await fetchCodexLbResponse(fetchImpl, endpoint, apiKey, baseBody, timeoutMs);
316
+ if (!first.ok || !first.response_id) {
317
+ return {
318
+ ok: false,
319
+ status: first.ok ? 'missing_response_id' : 'first_request_failed',
320
+ chain_unhealthy: true,
321
+ endpoint,
322
+ http_status: first.status,
323
+ error: redactSecretText(first.error_payload?.error?.message || first.error_payload?.response?.error?.message || first.text || 'codex-lb first Responses request failed', [apiKey])
324
+ };
325
+ }
326
+ const second = await fetchCodexLbResponse(fetchImpl, endpoint, apiKey, { ...baseBody, previous_response_id: first.response_id }, timeoutMs);
327
+ if (second.ok) return { ok: true, status: 'chain_ok', endpoint, response_id: first.response_id, chained_response_id: second.response_id || null, http_status: second.status };
328
+ const previousMissing = isPreviousResponseNotFound(second.error_payload || second.json || second.text);
329
+ return {
330
+ ok: false,
331
+ status: previousMissing ? 'previous_response_not_found' : 'second_request_failed',
332
+ chain_unhealthy: true,
333
+ endpoint,
334
+ response_id: first.response_id,
335
+ http_status: second.status,
336
+ error: redactSecretText(second.error_payload?.error?.message || second.error_payload?.response?.error?.message || second.text || 'codex-lb chained Responses request failed', [apiKey])
337
+ };
338
+ }
339
+
220
340
  function hasTopLevelCodexLbSelected(text = '') {
221
341
  const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
222
342
  return /(^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevel);
@@ -283,13 +403,18 @@ export async function ensureCodexLbAuthDuringInstall(opts = {}) {
283
403
 
284
404
  export async function maybePromptCodexLbSetupForLaunch(args = [], opts = {}) {
285
405
  if (args.includes('--json') || args.includes('--skip-codex-lb') || process.env.SKS_SKIP_CODEX_LB_PROMPT === '1') return { status: 'skipped' };
286
- if (!canAskYesNo()) return { status: 'non_interactive' };
287
406
  const status = await codexLbStatus(opts);
288
407
  if (status.ok) {
289
408
  const codexLogin = await ensureCodexLbLoginFromEnv(status, opts);
290
409
  if (codexLogin.status === 'synced') console.log('codex-lb auth synced with Codex CLI.');
291
- return { status: 'present', ...status, codex_login: codexLogin };
410
+ const chainHealth = await checkCodexLbResponseChain(status, opts);
411
+ if (!chainHealth.ok && chainHealth.chain_unhealthy) {
412
+ console.log(`codex-lb response chain check failed (${chainHealth.status}); bypassing codex-lb for this launch.`);
413
+ return { status: 'chain_unhealthy', ...status, ok: false, codex_login: codexLogin, chain_health: chainHealth, bypass_codex_lb: true };
414
+ }
415
+ return { status: 'present', ...status, codex_login: codexLogin, chain_health: chainHealth };
292
416
  }
417
+ if (!canAskYesNo()) return { status: 'non_interactive', codex_lb: status };
293
418
  const useCodexLb = (await askPostinstallQuestion('\nAuthenticate and route Codex through codex-lb? [y/N] ')).trim();
294
419
  if (!/^(y|yes|예|네|응)$/i.test(useCodexLb)) return { status: 'continued_to_codex' };
295
420
  const host = (await askPostinstallQuestion('codex-lb host domain [http://127.0.0.1:2455]: ')).trim() || 'http://127.0.0.1:2455';
@@ -1110,6 +1235,56 @@ export async function selftestCodexLb(tmp) {
1110
1235
  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');
1111
1236
  const codexLbStatusText = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-lb', 'status'], { cwd: tmp, env: codexLbEnvForSelftest, timeoutMs: 15000, maxOutputBytes: 64 * 1024 });
1112
1237
  if (!String(codexLbStatusText.stdout || '').includes('Repair auth: sks codex-lb repair')) throw new Error('selftest: codex-lb status did not advertise repair command');
1238
+ const nonInteractiveLaunchChainCalls = [];
1239
+ const nonInteractiveLaunch = await maybePromptCodexLbSetupForLaunch([], {
1240
+ home: codexLbHome,
1241
+ apiKey: 'sk-test',
1242
+ codexBin: path.join(codexLbFakeBin, 'codex'),
1243
+ timeoutMs: 1000,
1244
+ fetch: async (url, init) => {
1245
+ nonInteractiveLaunchChainCalls.push({ url, body: JSON.parse(init.body) });
1246
+ return new Response(JSON.stringify({ id: nonInteractiveLaunchChainCalls.length === 1 ? 'resp_noninteractive_1' : 'resp_noninteractive_2' }), { status: 200, headers: { 'content-type': 'application/json' } });
1247
+ }
1248
+ });
1249
+ if (!nonInteractiveLaunch.ok || nonInteractiveLaunch.status !== 'present' || nonInteractiveLaunch.chain_health?.status !== 'chain_ok' || nonInteractiveLaunchChainCalls.length !== 2 || nonInteractiveLaunchChainCalls[1].body.previous_response_id !== 'resp_noninteractive_1') throw new Error('selftest: non-interactive codex-lb launch path did not run response-chain preflight');
1250
+ const nonInteractiveBrokenLaunch = await maybePromptCodexLbSetupForLaunch([], {
1251
+ home: codexLbHome,
1252
+ apiKey: 'sk-test',
1253
+ codexBin: path.join(codexLbFakeBin, 'codex'),
1254
+ timeoutMs: 1000,
1255
+ fetch: async (_url, init) => {
1256
+ const body = JSON.parse(init.body);
1257
+ if (!body.previous_response_id) return new Response(JSON.stringify({ id: 'resp_noninteractive_broken' }), { status: 200, headers: { 'content-type': 'application/json' } });
1258
+ return new Response(JSON.stringify({ error: { type: 'invalid_request_error', code: 'previous_response_not_found', message: 'Previous response not found.', param: 'previous_response_id' } }), { status: 400, headers: { 'content-type': 'application/json' } });
1259
+ }
1260
+ });
1261
+ if (nonInteractiveBrokenLaunch.status !== 'chain_unhealthy' || nonInteractiveBrokenLaunch.bypass_codex_lb !== true || nonInteractiveBrokenLaunch.chain_health?.status !== 'previous_response_not_found') throw new Error('selftest: non-interactive codex-lb launch path did not bypass on previous_response_not_found');
1262
+ const chainCalls = [];
1263
+ const okChain = await checkCodexLbResponseChain(
1264
+ { base_url: 'https://lb.example.test/backend-api/codex', env_path: path.join(codexLbHome, '.codex', 'sks-codex-lb.env') },
1265
+ {
1266
+ apiKey: 'sk-test',
1267
+ timeoutMs: 1000,
1268
+ fetch: async (url, init) => {
1269
+ chainCalls.push({ url, body: JSON.parse(init.body) });
1270
+ return new Response(JSON.stringify({ id: chainCalls.length === 1 ? 'resp_selftest_1' : 'resp_selftest_2' }), { status: 200, headers: { 'content-type': 'application/json' } });
1271
+ }
1272
+ }
1273
+ );
1274
+ if (!okChain.ok || okChain.status !== 'chain_ok' || chainCalls.length !== 2 || !String(chainCalls[0].url).endsWith('/backend-api/codex/responses') || chainCalls[1].body.previous_response_id !== 'resp_selftest_1') throw new Error('selftest: codex-lb response chain health check did not verify previous_response_id continuity');
1275
+ const brokenChain = await checkCodexLbResponseChain(
1276
+ { base_url: 'https://lb.example.test/backend-api/codex', env_path: path.join(codexLbHome, '.codex', 'sks-codex-lb.env') },
1277
+ {
1278
+ apiKey: 'sk-test',
1279
+ timeoutMs: 1000,
1280
+ fetch: async (_url, init) => {
1281
+ const body = JSON.parse(init.body);
1282
+ if (!body.previous_response_id) return new Response(JSON.stringify({ id: 'resp_missing_selftest' }), { status: 200, headers: { 'content-type': 'application/json' } });
1283
+ return new Response(JSON.stringify({ error: { type: 'invalid_request_error', code: 'previous_response_not_found', message: 'Previous response not found.', param: 'previous_response_id' } }), { status: 400, headers: { 'content-type': 'application/json' } });
1284
+ }
1285
+ }
1286
+ );
1287
+ 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');
1113
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');
1114
1289
  if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig)) throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
1115
1290
  const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
@@ -1117,7 +1292,7 @@ export async function selftestCodexLb(tmp) {
1117
1292
  if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest: tmux launch command without args did not force GPT-5.5');
1118
1293
  if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest: tmux launch command does not include the animated SKS logo intro');
1119
1294
  const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'maintenance-commands.mjs'));
1120
- if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest: MAD launch does not sync codex-lb auth and fresh-session launch options');
1295
+ if (!madLaunchSource.includes('const lb = await deps.maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb') || !madLaunchSource.includes('bypass_codex_lb') || !madLaunchSource.includes('model_provider="openai"') || !madLaunchSource.includes('codexLbFreshSession: true')) throw new Error('selftest: MAD launch does not sync codex-lb auth and fresh-session launch options');
1121
1296
 
1122
1297
  }
1123
1298