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 +32 -2
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +178 -3
- package/src/cli/main.mjs +190 -28
- package/src/cli/maintenance-commands.mjs +22 -7
- package/src/cli/recallpulse-command.mjs +157 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +37 -8
- package/src/core/init.mjs +14 -19
- package/src/core/recallpulse.mjs +1215 -0
- package/src/core/research.mjs +35 -46
- package/src/core/routes.mjs +5 -4
- package/src/core/team-live.mjs +81 -12
- package/src/core/tmux-ui.mjs +230 -45
- package/src/core/version-manager.mjs +61 -31
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.
|
|
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
|
-
|
|
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
|
|