sneakoscope 6.0.2 → 6.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +5 -0
- package/dist/bin/sks-dispatch.js +2 -0
- package/dist/cli/install-helpers.js +41 -6
- package/dist/commands/codex-app.js +8 -3
- package/dist/commands/codex-lb.js +37 -12
- package/dist/config/skills-manifest.json +57 -57
- package/dist/core/agents/agent-effort-policy.js +46 -33
- package/dist/core/agents/agent-orchestrator.js +1 -1
- package/dist/core/agents/agent-proof-evidence.js +16 -2
- package/dist/core/agents/agent-roster.js +2 -2
- package/dist/core/agents/native-cli-session-swarm.js +18 -1
- package/dist/core/agents/native-worker-backend-router.js +179 -108
- package/dist/core/agents/parallel-write-fixture.js +15 -10
- package/dist/core/agents/runtime-proof-summary.js +21 -3
- package/dist/core/codex-app/codex-app-restart.js +38 -8
- package/dist/core/codex-app/sks-menubar.js +92 -37
- package/dist/core/codex-app.js +26 -4
- package/dist/core/codex-control/codex-model-metadata.js +8 -4
- package/dist/core/codex-lb/codex-lb-env.js +78 -0
- package/dist/core/commands/basic-cli.js +15 -0
- package/dist/core/commands/naruto-command.js +37 -9
- package/dist/core/naruto/naruto-write-e2e.js +3 -4
- package/dist/core/provider/model-router.js +64 -3
- package/dist/core/release/package-size-budget.js +2 -0
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/model-mode-router.js +3 -0
- package/dist/core/routes.js +7 -9
- package/dist/core/update/update-migration-state.js +1 -1
- package/dist/core/update/update-notice.js +23 -0
- package/dist/core/update-check.js +46 -10
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-app-provider-model-ui-check.js +17 -2
- package/dist/scripts/doctor-fix-proves-codex-read-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +2 -2
- package/dist/scripts/legacy-upgrade-matrix-check.js +1 -1
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +2 -1
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +88 -50
- package/dist/scripts/npm-publish-performance-check.js +2 -1
- package/dist/scripts/packlist-performance-check.js +5 -2
- package/dist/scripts/release-dag-full-coverage-check.js +7 -7
- package/dist/scripts/release-gate-existence-audit.js +8 -9
- package/dist/scripts/scheduler-batch-dispatch-check.js +1 -1
- package/dist/scripts/sizecheck.js +7 -2
- package/dist/scripts/sks-menubar-install-check.js +7 -0
- package/dist/scripts/update-default-command-check.js +4 -2
- package/package.json +3 -16
|
@@ -444,7 +444,7 @@ async function resolveCodexBundleId(input) {
|
|
|
444
444
|
}
|
|
445
445
|
export async function smokeSksMenuBarAction(actionScriptPath) {
|
|
446
446
|
if (!(await exists(actionScriptPath)))
|
|
447
|
-
return { ok: false, code: null, output: null, versionDetected: false, executable: false };
|
|
447
|
+
return { ok: false, code: null, output: null, versionDetected: false, detectedVersion: null, executable: false };
|
|
448
448
|
// The Swift app runs the script directly (which requires the executable bit), so the smoke
|
|
449
449
|
// check must invoke it the same way. Running it via `/bin/zsh <script>` — as this check used
|
|
450
450
|
// to — succeeds even when +x is missing, which let doctor/status report a healthy action
|
|
@@ -456,6 +456,7 @@ export async function smokeSksMenuBarAction(actionScriptPath) {
|
|
|
456
456
|
code: null,
|
|
457
457
|
output: 'action script is not executable (missing +x); the menu bar app cannot run it',
|
|
458
458
|
versionDetected: false,
|
|
459
|
+
detectedVersion: null,
|
|
459
460
|
executable: false
|
|
460
461
|
};
|
|
461
462
|
}
|
|
@@ -464,12 +465,14 @@ export async function smokeSksMenuBarAction(actionScriptPath) {
|
|
|
464
465
|
maxOutputBytes: 16 * 1024
|
|
465
466
|
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
466
467
|
const output = String(`${result.stdout || ''}\n${result.stderr || ''}`).trim();
|
|
467
|
-
const
|
|
468
|
+
const detectedVersion = output.match(/\b(?:sks|sneakoscope)?\s*v?(\d+\.\d+\.\d+)\b/i)?.[1] || null;
|
|
469
|
+
const versionDetected = Boolean(detectedVersion);
|
|
468
470
|
return {
|
|
469
471
|
ok: result.code === 0 && versionDetected,
|
|
470
472
|
code: result.code,
|
|
471
473
|
output: output ? output.slice(0, 700) : null,
|
|
472
474
|
versionDetected,
|
|
475
|
+
detectedVersion,
|
|
473
476
|
executable: true
|
|
474
477
|
};
|
|
475
478
|
}
|
|
@@ -505,6 +508,10 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
505
508
|
warning: config.codex_bundle_id ? null : 'codex_sync_disabled'
|
|
506
509
|
};
|
|
507
510
|
const buildStamp = await readJson(paths.build_stamp_path, null);
|
|
511
|
+
const actionIntegrity = evaluateActionScriptIntegrity(actionText, buildStamp);
|
|
512
|
+
const expectedActionVersion = buildStamp?.package_version || PACKAGE_VERSION;
|
|
513
|
+
const actionVersionMatchesExpected = actionSmoke.detectedVersion === expectedActionVersion
|
|
514
|
+
&& actionSmoke.detectedVersion === PACKAGE_VERSION;
|
|
508
515
|
const launchd = await inspectLaunchdService(opts.env);
|
|
509
516
|
const signature = await inspectSignature(paths.app_path, opts.env);
|
|
510
517
|
const blockers = [];
|
|
@@ -517,6 +524,12 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
517
524
|
blockers.push('action_script_not_executable');
|
|
518
525
|
if (installed && !actionSmoke.ok)
|
|
519
526
|
blockers.push('action_script_smoke_failed');
|
|
527
|
+
if (installed && actionSmoke.ok && !actionVersionMatchesExpected)
|
|
528
|
+
blockers.push('action_target_version_mismatch');
|
|
529
|
+
if (installed && !buildStamp)
|
|
530
|
+
blockers.push('build_stamp_missing');
|
|
531
|
+
if (installed && buildStamp && !actionIntegrity.script_hash_matches_stamp)
|
|
532
|
+
blockers.push('action_script_hash_mismatch');
|
|
520
533
|
if (installed && signature.checked && !signature.ok)
|
|
521
534
|
warnings.push('codesign_identifier_unexpected');
|
|
522
535
|
if (!codexSync.ok)
|
|
@@ -539,8 +552,12 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
539
552
|
smoke_code: actionSmoke.code,
|
|
540
553
|
smoke_output: actionSmoke.output,
|
|
541
554
|
version_detected: actionSmoke.versionDetected,
|
|
555
|
+
detected_version: actionSmoke.detectedVersion,
|
|
556
|
+
expected_version: expectedActionVersion,
|
|
557
|
+
version_matches_expected: actionVersionMatchesExpected,
|
|
558
|
+
...actionIntegrity,
|
|
542
559
|
executable: actionSmoke.executable,
|
|
543
|
-
ok: actionSmoke.ok
|
|
560
|
+
ok: actionSmoke.ok && actionVersionMatchesExpected && actionIntegrity.script_hash_matches_stamp
|
|
544
561
|
},
|
|
545
562
|
codex_sync: codexSync,
|
|
546
563
|
build_stamp: buildStamp,
|
|
@@ -551,6 +568,15 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
551
568
|
next_actions: blockers.length || warnings.length ? defaultNextActions() : ['sks menubar status --json']
|
|
552
569
|
};
|
|
553
570
|
}
|
|
571
|
+
export function evaluateActionScriptIntegrity(actionText, buildStamp) {
|
|
572
|
+
const scriptSha256 = actionText ? sha256(actionText) : null;
|
|
573
|
+
const expectedScriptSha256 = buildStamp?.action_script_sha256 || null;
|
|
574
|
+
return {
|
|
575
|
+
script_sha256: scriptSha256,
|
|
576
|
+
expected_script_sha256: expectedScriptSha256,
|
|
577
|
+
script_hash_matches_stamp: Boolean(scriptSha256 && expectedScriptSha256 && scriptSha256 === expectedScriptSha256)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
554
580
|
export async function restartSksMenuBar(opts = {}) {
|
|
555
581
|
const env = opts.env || process.env;
|
|
556
582
|
const paths = sksMenuBarPaths(opts.home || env.HOME, opts.root);
|
|
@@ -757,6 +783,14 @@ run_node_entry() {
|
|
|
757
783
|
exec "$node_bin" "$entry" "$@"
|
|
758
784
|
}
|
|
759
785
|
|
|
786
|
+
# The installer pins the entry that produced this menu build. Prefer it over
|
|
787
|
+
# PATH/global copies so a newly updated 6.0.3 companion cannot execute stale
|
|
788
|
+
# 6.0.2 auth/restart logic. Fall back only when the pinned file or Node runtime
|
|
789
|
+
# is unavailable.
|
|
790
|
+
if [ -f "$SKS_ENTRY" ]; then
|
|
791
|
+
run_node_entry "$SKS_ENTRY" "$@" || true
|
|
792
|
+
fi
|
|
793
|
+
|
|
760
794
|
SKS_BIN="$(/bin/zsh -lc 'command -v sks' 2>/dev/null | /usr/bin/head -n 1 || true)"
|
|
761
795
|
if [ -n "$SKS_BIN" ] && [ -x "$SKS_BIN" ]; then
|
|
762
796
|
exec "$SKS_BIN" "$@"
|
|
@@ -772,7 +806,6 @@ for entry in "$HOME"/.nvm/versions/node/*/lib/node_modules/sneakoscope/dist/bin/
|
|
|
772
806
|
run_node_entry "$entry" "$@" || true
|
|
773
807
|
fi
|
|
774
808
|
done
|
|
775
|
-
run_node_entry "$SKS_ENTRY" "$@" || true
|
|
776
809
|
notify_sks_missing
|
|
777
810
|
echo "SKS command not found. Run npm install -g sneakoscope or sks doctor --fix, then try again." >&2
|
|
778
811
|
exit 127
|
|
@@ -1176,9 +1209,10 @@ ${codexLifecycleSource}
|
|
|
1176
1209
|
let cachePath = NSString(string: "~/.sneakoscope/cache/update-notice.json").expandingTildeInPath
|
|
1177
1210
|
guard let data = FileManager.default.contents(atPath: cachePath) else { return false }
|
|
1178
1211
|
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return false }
|
|
1179
|
-
if let
|
|
1180
|
-
|
|
1181
|
-
|
|
1212
|
+
if let latest = json["latest_version"] as? String, !latest.isEmpty {
|
|
1213
|
+
return latest.compare(packageVersion, options: .numeric) == .orderedDescending
|
|
1214
|
+
}
|
|
1215
|
+
return json["update_available"] as? Bool == true
|
|
1182
1216
|
}
|
|
1183
1217
|
|
|
1184
1218
|
func runSksCapture(_ args: [String], title: String, stdinText: String? = nil, notify: Bool = true, completion: ((Int32, String) -> Void)? = nil) {
|
|
@@ -1238,12 +1272,18 @@ ${codexLifecycleSource}
|
|
|
1238
1272
|
runSksSilent(["codex-lb", "status", "--json"]) { [weak self] code, output in
|
|
1239
1273
|
guard let self = self else { return }
|
|
1240
1274
|
let json = parseJsonObject(output)
|
|
1241
|
-
let
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1275
|
+
guard code == 0, let json = json else {
|
|
1276
|
+
self.codexLbItem.state = .off
|
|
1277
|
+
self.oauthItem.state = .off
|
|
1278
|
+
return
|
|
1279
|
+
}
|
|
1280
|
+
let selected = json["selected"] as? Bool == true
|
|
1281
|
+
let providerContract = json["provider_contract_ok"] as? Bool == true
|
|
1282
|
+
let authMode = (json["auth_mode"] as? String)?.lowercased() ?? ""
|
|
1283
|
+
let codexLbActive = selected && providerContract && authMode == "apikey"
|
|
1284
|
+
let oauthActive = !selected && (authMode.contains("chatgpt") || authMode.contains("oauth") || authMode.contains("browser"))
|
|
1245
1285
|
self.codexLbItem.state = codexLbActive ? .on : .off
|
|
1246
|
-
self.oauthItem.state =
|
|
1286
|
+
self.oauthItem.state = oauthActive ? .on : .off
|
|
1247
1287
|
}
|
|
1248
1288
|
}
|
|
1249
1289
|
|
|
@@ -1251,8 +1291,13 @@ ${codexLifecycleSource}
|
|
|
1251
1291
|
runSksSilent(["fast-mode", "status", "--json"]) { [weak self] code, output in
|
|
1252
1292
|
guard let self = self else { return }
|
|
1253
1293
|
let json = parseJsonObject(output)
|
|
1254
|
-
let
|
|
1255
|
-
|
|
1294
|
+
guard code == 0, let json = json else {
|
|
1295
|
+
self.fastModeOnItem.state = .off
|
|
1296
|
+
self.fastModeOffItem.state = .off
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
let fastMode = json["fast_mode"] as? Bool == true
|
|
1300
|
+
let serviceTier = (json["service_tier"] as? String)?.lowercased()
|
|
1256
1301
|
let fastActive = code == 0 && (fastMode || serviceTier == "fast" || serviceTier == "priority")
|
|
1257
1302
|
self.fastModeOnItem.state = fastActive ? .on : .off
|
|
1258
1303
|
self.fastModeOffItem.state = fastActive ? .off : .on
|
|
@@ -1260,13 +1305,13 @@ ${codexLifecycleSource}
|
|
|
1260
1305
|
}
|
|
1261
1306
|
|
|
1262
1307
|
@objc func useCodexLb() {
|
|
1263
|
-
runSksBackground(["codex-lb", "use-codex-lb", "--json"], title: "Use codex-lb") { [weak self] code, _ in
|
|
1308
|
+
runSksBackground(["codex-lb", "use-codex-lb", "--restart-app", "--json"], title: "Use codex-lb") { [weak self] code, _ in
|
|
1264
1309
|
if code == 0 { self?.updateAuthModeChecks() }
|
|
1265
1310
|
}
|
|
1266
1311
|
}
|
|
1267
1312
|
|
|
1268
1313
|
@objc func useChatGptOAuth() {
|
|
1269
|
-
runSksBackground(["codex-lb", "use-oauth", "--json"], title: "Use ChatGPT OAuth") { [weak self] code, _ in
|
|
1314
|
+
runSksBackground(["codex-lb", "use-oauth", "--restart-app", "--json"], title: "Use ChatGPT OAuth") { [weak self] code, _ in
|
|
1270
1315
|
if code == 0 { self?.updateAuthModeChecks() }
|
|
1271
1316
|
}
|
|
1272
1317
|
}
|
|
@@ -1274,14 +1319,14 @@ ${codexLifecycleSource}
|
|
|
1274
1319
|
@objc func setCodexLbDomainAndKey() {
|
|
1275
1320
|
guard let domain = promptText(title: "Set codex-lb Domain", message: "Enter just your codex-lb domain or base URL — the /backend-api/codex path is added automatically.", placeholder: "lb.example.com") else { return }
|
|
1276
1321
|
guard let key = promptText(title: "Set codex-lb Key", message: "Enter your codex-lb API key.", placeholder: "sk-clb-...", secure: true) else { return }
|
|
1277
|
-
runSksBackground(["codex-lb", "setup", "--host", domain, "--api-key-stdin", "--yes", "--json"], title: "Set codex-lb", stdinText: key + "\\n") { [weak self] code, _ in
|
|
1322
|
+
runSksBackground(["codex-lb", "setup", "--host", domain, "--api-key-stdin", "--yes", "--restart-app", "--json"], title: "Set codex-lb", stdinText: key + "\\n") { [weak self] code, _ in
|
|
1278
1323
|
if code == 0 { self?.updateAuthModeChecks() }
|
|
1279
1324
|
}
|
|
1280
1325
|
}
|
|
1281
1326
|
|
|
1282
1327
|
@objc func setOpenRouterKey() {
|
|
1283
1328
|
guard let key = promptText(title: "Set OpenRouter Key", message: "Enter your OpenRouter API key.", placeholder: "sk-or-v1-...", secure: true) else { return }
|
|
1284
|
-
runSksBackground(["codex-app", "set-openrouter-key", "--api-key-stdin", "--json"], title: "Set OpenRouter Key", stdinText: key + "\\n")
|
|
1329
|
+
runSksBackground(["codex-app", "set-openrouter-key", "--api-key-stdin", "--restart-app", "--json"], title: "Set OpenRouter Key", stdinText: key + "\\n")
|
|
1285
1330
|
}
|
|
1286
1331
|
|
|
1287
1332
|
@objc func fastModeOn() {
|
|
@@ -1312,36 +1357,46 @@ ${codexLifecycleSource}
|
|
|
1312
1357
|
let urlString = "http://127.0.0.1:4477"
|
|
1313
1358
|
runProcess("/usr/bin/curl", ["-fsS", "--max-time", "1", urlString]) { [weak self] code, _ in
|
|
1314
1359
|
DispatchQueue.main.async {
|
|
1315
|
-
if code
|
|
1360
|
+
if code == 0 {
|
|
1361
|
+
if let url = URL(string: urlString) { NSWorkspace.shared.open(url) }
|
|
1362
|
+
} else {
|
|
1316
1363
|
self?.startSksDetached(["ui"], title: "SKS Dashboard")
|
|
1364
|
+
self?.waitForDashboard(urlString, attempts: 20)
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
func waitForDashboard(_ urlString: String, attempts: Int) {
|
|
1371
|
+
runProcess("/usr/bin/curl", ["-fsS", "--max-time", "1", urlString]) { [weak self] code, _ in
|
|
1372
|
+
if code == 0 {
|
|
1373
|
+
DispatchQueue.main.async {
|
|
1374
|
+
if let url = URL(string: urlString) { NSWorkspace.shared.open(url) }
|
|
1375
|
+
}
|
|
1376
|
+
} else if attempts > 1 {
|
|
1377
|
+
DispatchQueue.global().asyncAfter(deadline: .now() + 0.25) {
|
|
1378
|
+
self?.waitForDashboard(urlString, attempts: attempts - 1)
|
|
1317
1379
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1380
|
+
} else {
|
|
1381
|
+
DispatchQueue.main.async {
|
|
1382
|
+
showAlert("SKS Dashboard failed", informative: "Dashboard did not become ready at \(urlString). See View Last Log.")
|
|
1320
1383
|
}
|
|
1321
1384
|
}
|
|
1322
1385
|
}
|
|
1323
1386
|
}
|
|
1324
1387
|
|
|
1325
1388
|
@objc func openCodexSettings() {
|
|
1326
|
-
runProcess("/usr/bin/open", ["codex://settings"])
|
|
1389
|
+
runProcess("/usr/bin/open", ["codex://settings"]) { code, output in
|
|
1390
|
+
if code != 0 {
|
|
1391
|
+
DispatchQueue.main.async {
|
|
1392
|
+
showAlert("Open Codex Settings failed", informative: humanizeSksFailure(redactSecrets(output)))
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1327
1396
|
}
|
|
1328
1397
|
|
|
1329
1398
|
@objc func restartCodex() {
|
|
1330
|
-
|
|
1331
|
-
if let bundle = codexBundleId, app.bundleIdentifier == bundle { return true }
|
|
1332
|
-
return app.localizedName == "Codex"
|
|
1333
|
-
}
|
|
1334
|
-
for app in running {
|
|
1335
|
-
app.terminate()
|
|
1336
|
-
}
|
|
1337
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
|
|
1338
|
-
if let bundle = codexBundleId, let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundle) {
|
|
1339
|
-
NSWorkspace.shared.open(url)
|
|
1340
|
-
showNotification("Restart Codex", "requested")
|
|
1341
|
-
} else {
|
|
1342
|
-
showAlert("Restart Codex failed", informative: "Codex app bundle could not be resolved.")
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1399
|
+
runSksBackground(["codex-app", "restart", "--json"], title: "Restart Codex")
|
|
1345
1400
|
}
|
|
1346
1401
|
|
|
1347
1402
|
@objc func viewLastLog() {
|
package/dist/core/codex-app.js
CHANGED
|
@@ -9,7 +9,7 @@ import { PRODUCT_DESIGN_AUTO_INSTALL_ENV, ensureProductDesignPluginInstalled, pr
|
|
|
9
9
|
import { GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from './codex-app/glm-model-profile.js';
|
|
10
10
|
import { GLM_52_OPENROUTER_MODEL } from './providers/glm/glm-52-settings.js';
|
|
11
11
|
import { resolveOpenRouterApiKey } from './providers/openrouter/openrouter-secret-store.js';
|
|
12
|
-
import { codexLbEnvPath, parseShellEnvValue } from './codex-lb/codex-lb-env.js';
|
|
12
|
+
import { codexLbEnvPath, loadCodexLbEnv, parseShellEnvValue, readCodexLbModelCatalog } from './codex-lb/codex-lb-env.js';
|
|
13
13
|
export const CODEX_APP_DOCS_URL = 'https://developers.openai.com/codex/app/features';
|
|
14
14
|
export const CODEX_CHANGELOG_URL = 'https://developers.openai.com/codex/changelog';
|
|
15
15
|
export const CODEX_ACCESS_TOKENS_DOCS_URL = 'https://developers.openai.com/codex/enterprise/access-tokens';
|
|
@@ -35,6 +35,8 @@ export function codexAppCandidatePaths(home = os.homedir(), env = process.env) {
|
|
|
35
35
|
if (env.SKS_CODEX_APP_PATH)
|
|
36
36
|
candidates.push(env.SKS_CODEX_APP_PATH);
|
|
37
37
|
if (process.platform === 'darwin') {
|
|
38
|
+
candidates.push('/Applications/ChatGPT.app');
|
|
39
|
+
candidates.push(path.join(home || '', 'Applications', 'ChatGPT.app'));
|
|
38
40
|
candidates.push('/Applications/Codex.app');
|
|
39
41
|
candidates.push(path.join(home || '', 'Applications', 'Codex.app'));
|
|
40
42
|
}
|
|
@@ -545,7 +547,8 @@ export function codexAppGuidance({ appInstalled, codex, mcpList, featureList, re
|
|
|
545
547
|
lines.push(`Run: ${providerModelUi.glm.key_command}; then ${providerModelUi.glm.install_command}; restart Codex App if the model picker was already open.`);
|
|
546
548
|
}
|
|
547
549
|
if (providerModelUi?.codex_lb?.key_entry_visible && providerModelUi.codex_lb.key_present && providerModelUi.codex_lb.provider_present && providerModelUi.codex_lb.provider_contract_ok && providerModelUi.codex_lb.base_url_present) {
|
|
548
|
-
lines.push(`codex-lb key entry path is
|
|
550
|
+
lines.push(`codex-lb key entry path is configured; provider catalog ${providerModelUi.codex_lb.expected_models_present ? 'contains GPT-5.6 Sol/Terra/Luna' : 'is not verified'}. Update command: ${providerModelUi.codex_lb.set_key_command}.`);
|
|
551
|
+
lines.push('Provider/config readiness does not by itself prove the already-running Desktop picker; auth/profile actions restart ChatGPT/Codex so it reloads model/list.');
|
|
549
552
|
}
|
|
550
553
|
else if (providerModelUi?.codex_lb?.key_entry_visible) {
|
|
551
554
|
lines.push(`codex-lb key entry path is visible, but setup is incomplete: ${providerModelUi.codex_lb.blockers.join(', ') || 'setup required'}.`);
|
|
@@ -938,6 +941,14 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
938
941
|
&& hasTomlBoolean(codexLbProvider, 'supports_websockets', true)
|
|
939
942
|
&& hasTomlBoolean(codexLbProvider, 'requires_openai_auth', true);
|
|
940
943
|
const codexLbSelectedDefault = /(?:^|\n)\s*model_provider\s*=\s*"codex-lb"\s*(?:#.*)?(?=\n|$)/.test(topLevelToml(globalConfig));
|
|
944
|
+
let codexLbModelCatalog = opts.codexLbModelCatalog || null;
|
|
945
|
+
if (!codexLbModelCatalog && codexLbProviderContractOk && codexLbApiKeySource !== 'missing' && codexLbBaseUrlSource !== 'missing') {
|
|
946
|
+
const loadedEnv = await loadCodexLbEnv({ home, envPath: codexLbEnvFilePath }).catch(() => null);
|
|
947
|
+
codexLbModelCatalog = loadedEnv ? await readCodexLbModelCatalog({ loadedEnv }).catch(() => null) : null;
|
|
948
|
+
}
|
|
949
|
+
const expectedCodexLbModels = ['gpt-5.6-luna', 'gpt-5.6-terra', 'gpt-5.6-sol'];
|
|
950
|
+
const catalogModels = Array.isArray(codexLbModelCatalog?.models) ? codexLbModelCatalog.models.map(String) : [];
|
|
951
|
+
const expectedCodexLbModelsPresent = expectedCodexLbModels.every((model) => catalogModels.includes(model));
|
|
941
952
|
const glmBlockers = [
|
|
942
953
|
...(glmProviderPresent ? [] : ['glm_openrouter_provider_missing']),
|
|
943
954
|
...glmProfileBlockers,
|
|
@@ -947,7 +958,10 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
947
958
|
...(codexLbProviderPresent ? [] : ['codex_lb_provider_missing']),
|
|
948
959
|
...(codexLbProviderPresent && !codexLbProviderContractOk ? ['codex_lb_provider_contract_drift'] : []),
|
|
949
960
|
...(codexLbApiKeySource !== 'missing' ? [] : ['codex_lb_api_key_missing']),
|
|
950
|
-
...(codexLbBaseUrlSource !== 'missing' ? [] : ['codex_lb_base_url_missing'])
|
|
961
|
+
...(codexLbBaseUrlSource !== 'missing' ? [] : ['codex_lb_base_url_missing']),
|
|
962
|
+
...(codexLbProviderContractOk && codexLbApiKeySource !== 'missing' && codexLbBaseUrlSource !== 'missing' && !expectedCodexLbModelsPresent
|
|
963
|
+
? ['codex_lb_gpt_5_6_catalog_unverified']
|
|
964
|
+
: [])
|
|
951
965
|
];
|
|
952
966
|
const blockers = [...glmBlockers, ...codexLbBlockers];
|
|
953
967
|
const keyCommand = 'sks codex-app set-openrouter-key --api-key-stdin';
|
|
@@ -958,6 +972,8 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
958
972
|
schema: 'sks.codex-app-provider-model-ui.v1',
|
|
959
973
|
ok: blockers.length === 0,
|
|
960
974
|
checked: true,
|
|
975
|
+
verification_scope: 'config_and_provider_catalog',
|
|
976
|
+
desktop_picker_verified: false,
|
|
961
977
|
status: blockers.length === 0 ? 'ready' : 'setup_required',
|
|
962
978
|
config_paths: {
|
|
963
979
|
codex_config: globalConfigPath,
|
|
@@ -988,6 +1004,12 @@ export async function codexProviderModelUiStatus(opts = {}) {
|
|
|
988
1004
|
api_key_source: codexLbApiKeySource,
|
|
989
1005
|
base_url_present: codexLbBaseUrlSource !== 'missing',
|
|
990
1006
|
base_url_source: codexLbBaseUrlSource,
|
|
1007
|
+
model_catalog_checked: Boolean(codexLbModelCatalog),
|
|
1008
|
+
model_catalog_ok: codexLbModelCatalog?.ok === true,
|
|
1009
|
+
model_catalog_models: catalogModels,
|
|
1010
|
+
expected_models: expectedCodexLbModels,
|
|
1011
|
+
expected_models_present: expectedCodexLbModelsPresent,
|
|
1012
|
+
model_catalog_blockers: codexLbModelCatalog?.blockers || [],
|
|
991
1013
|
setup_command: setupCommand,
|
|
992
1014
|
set_key_command: setKeyCommand,
|
|
993
1015
|
status_command: 'sks codex-lb status',
|
|
@@ -1066,7 +1088,7 @@ function providerModelUiSummary(status = {}) {
|
|
|
1066
1088
|
if (!status?.checked)
|
|
1067
1089
|
return ' not checked';
|
|
1068
1090
|
if (status.ok)
|
|
1069
|
-
return '
|
|
1091
|
+
return ' config/catalog ready (live picker reload required)';
|
|
1070
1092
|
return ` setup ${(status.blockers || []).join(', ') || 'required'}`;
|
|
1071
1093
|
}
|
|
1072
1094
|
function sksNativeMenuSummary(status = {}) {
|
|
@@ -57,19 +57,23 @@ async function readCodexCliMetadata(model) {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
function normalizePayload(payload, fallbackModel, source) {
|
|
60
|
-
const catalog = Array.isArray(payload?.models) ? payload.models : null;
|
|
60
|
+
const catalog = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload?.models) ? payload.models : null;
|
|
61
61
|
const reportedSelection = String(payload?.selected_model || payload?.current_model || payload?.active_model || '').trim();
|
|
62
62
|
const requestedModel = String(fallbackModel || reportedSelection).trim();
|
|
63
63
|
const row = catalog
|
|
64
64
|
? requestedModel
|
|
65
|
-
? catalog.find((candidate) => String(candidate?.id || candidate?.model || candidate?.name || '') === requestedModel) || null
|
|
65
|
+
? catalog.find((candidate) => String(candidate?.id || candidate?.model || candidate?.slug || candidate?.name || '') === requestedModel) || null
|
|
66
66
|
: null
|
|
67
67
|
: payload?.model_metadata || payload?.metadata || payload;
|
|
68
68
|
if (!row) {
|
|
69
69
|
return metadata(requestedModel, [], 'medium', source, [requestedModel ? 'codex_model_not_found_in_advertised_catalog' : 'codex_model_selection_unknown']);
|
|
70
70
|
}
|
|
71
|
-
const
|
|
72
|
-
|
|
71
|
+
const effortRows = row?.supportedReasoningEfforts || row?.supported_reasoning_levels || row?.supported_reasoning_efforts || [];
|
|
72
|
+
const structuredEfforts = Array.isArray(effortRows)
|
|
73
|
+
? effortRows.map((entry) => entry?.reasoningEffort || entry?.effort || entry)
|
|
74
|
+
: [];
|
|
75
|
+
const efforts = normalizeAdvertisedEfforts(structuredEfforts.length ? structuredEfforts : row?.advertised_efforts || row?.advertisedEfforts || row?.reasoning_efforts || row?.reasoningEfforts || payload?.advertised_efforts);
|
|
76
|
+
return metadata(String(row?.model || row?.id || row?.slug || row?.name || requestedModel), efforts, row?.default_reasoning_level || row?.defaultReasoningLevel || row?.default_effort || row?.defaultEffort || payload?.default_effort || 'medium', source, efforts.length ? [] : ['codex_model_metadata_efforts_missing']);
|
|
73
77
|
}
|
|
74
78
|
function metadata(model, efforts, defaultEffort, source, blockers) {
|
|
75
79
|
const advertised = normalizeAdvertisedEfforts(efforts);
|
|
@@ -29,6 +29,84 @@ export async function readLbHealth(home = process.env.HOME || os.homedir()) {
|
|
|
29
29
|
updated_at: typeof raw.updated_at === 'string' ? raw.updated_at : null
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
export async function readCodexLbModelCatalog(opts = {}) {
|
|
33
|
+
const loaded = opts.loadedEnv || await loadCodexLbEnv();
|
|
34
|
+
if (!loaded.configured || !loaded.base_url || !loaded.secret_api_key) {
|
|
35
|
+
return {
|
|
36
|
+
schema: 'sks.codex-lb-model-catalog.v1',
|
|
37
|
+
ok: false,
|
|
38
|
+
status: 'blocked',
|
|
39
|
+
models: [],
|
|
40
|
+
model_efforts: {},
|
|
41
|
+
http_status: null,
|
|
42
|
+
blockers: loaded.missing.length ? loaded.missing.map((item) => `codex_lb_missing:${item}`) : ['codex_lb_not_configured']
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const fetchImpl = opts.fetchImpl || fetch;
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetchImpl(`${loaded.base_url}/models`, {
|
|
48
|
+
headers: { Authorization: `Bearer ${loaded.secret_api_key}` },
|
|
49
|
+
signal: AbortSignal.timeout(Math.max(250, Number(opts.timeoutMs || 5000)))
|
|
50
|
+
});
|
|
51
|
+
const payload = await response.json().catch(() => null);
|
|
52
|
+
const models = normalizeCodexLbModelCatalogPayload(payload);
|
|
53
|
+
const apiEfforts = normalizeCodexModelEffortCatalogPayload(payload);
|
|
54
|
+
const cachePath = path.join(String(process.env.CODEX_HOME || path.join(os.homedir(), '.codex')), 'models_cache.json');
|
|
55
|
+
const cachePayload = await readJson(cachePath, null).catch(() => null);
|
|
56
|
+
const cachedEfforts = normalizeCodexModelEffortCatalogPayload(cachePayload);
|
|
57
|
+
const modelEfforts = Object.fromEntries(models.map((model) => [model, apiEfforts[model] || cachedEfforts[model] || []]));
|
|
58
|
+
const ok = response.ok && models.length > 0;
|
|
59
|
+
return {
|
|
60
|
+
schema: 'sks.codex-lb-model-catalog.v1',
|
|
61
|
+
ok,
|
|
62
|
+
status: ok ? 'ready' : 'blocked',
|
|
63
|
+
models,
|
|
64
|
+
model_efforts: modelEfforts,
|
|
65
|
+
http_status: response.status,
|
|
66
|
+
blockers: [
|
|
67
|
+
...(response.ok ? [] : [`codex_lb_models_http_${response.status}`]),
|
|
68
|
+
...(models.length ? [] : ['codex_lb_model_catalog_empty'])
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return {
|
|
74
|
+
schema: 'sks.codex-lb-model-catalog.v1',
|
|
75
|
+
ok: false,
|
|
76
|
+
status: 'blocked',
|
|
77
|
+
models: [],
|
|
78
|
+
model_efforts: {},
|
|
79
|
+
http_status: null,
|
|
80
|
+
blockers: ['codex_lb_model_catalog_unavailable']
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function normalizeCodexModelEffortCatalogPayload(payload) {
|
|
85
|
+
const rows = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload?.models) ? payload.models : [];
|
|
86
|
+
const result = {};
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
const model = String(row?.id || row?.model || row?.slug || row?.name || '').trim();
|
|
89
|
+
if (!model)
|
|
90
|
+
continue;
|
|
91
|
+
const effortRows = row?.supportedReasoningEfforts
|
|
92
|
+
|| row?.supported_reasoning_levels
|
|
93
|
+
|| row?.supported_reasoning_efforts
|
|
94
|
+
|| row?.reasoning_efforts
|
|
95
|
+
|| [];
|
|
96
|
+
const efforts = (Array.isArray(effortRows) ? effortRows : [])
|
|
97
|
+
.map((entry) => String(entry?.reasoningEffort || entry?.effort || entry || '').trim().toLowerCase())
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
result[model] = [...new Set(efforts)];
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
export function normalizeCodexLbModelCatalogPayload(payload) {
|
|
104
|
+
const rows = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload?.models) ? payload.models : [];
|
|
105
|
+
const models = rows
|
|
106
|
+
.map((row) => String(row?.id || row?.model || row?.slug || row?.name || '').trim())
|
|
107
|
+
.filter(Boolean);
|
|
108
|
+
return [...new Set(models)];
|
|
109
|
+
}
|
|
32
110
|
export function normalizeCodexLbBaseUrl(input = '') {
|
|
33
111
|
let host = String(input || '').trim();
|
|
34
112
|
if (!host)
|
|
@@ -15,6 +15,7 @@ import { hooksExplainReport } from '../../cli/feature-commands.js';
|
|
|
15
15
|
import { writeSelftestRouteProof } from '../proof/selftest-proof-fixtures.js';
|
|
16
16
|
import { createMission } from '../mission.js';
|
|
17
17
|
import { formatSksUpdateCheckText, runSksUpdateCheck, runSksUpdateNow } from '../update-check.js';
|
|
18
|
+
import { persistSksUpdateNoticeFromVersions } from '../update/update-notice.js';
|
|
18
19
|
import { withSecretPreservationGuard } from '../config/config-migration-journal.js';
|
|
19
20
|
export async function helpCommand(args = []) {
|
|
20
21
|
const topic = args[0];
|
|
@@ -121,6 +122,12 @@ For implementation work, use Codex App prompt routes such as $Naruto, $Goal, $QA
|
|
|
121
122
|
}
|
|
122
123
|
export async function updateCheckCommand(args = []) {
|
|
123
124
|
const result = await runSksUpdateCheck();
|
|
125
|
+
await persistSksUpdateNoticeFromVersions({
|
|
126
|
+
packageName: result.package,
|
|
127
|
+
currentVersion: result.current,
|
|
128
|
+
latestVersion: result.latest,
|
|
129
|
+
error: result.error
|
|
130
|
+
}).catch(() => undefined);
|
|
124
131
|
if (flag(args, '--json'))
|
|
125
132
|
return printJson(result);
|
|
126
133
|
cliUi.banner('update');
|
|
@@ -151,6 +158,14 @@ export async function updateCommand(sub = 'now', args = []) {
|
|
|
151
158
|
timeoutMs: 10 * 60 * 1000,
|
|
152
159
|
maxOutputBytes: 128 * 1024
|
|
153
160
|
}));
|
|
161
|
+
if (!flag(effectiveArgs, '--dry-run')) {
|
|
162
|
+
await persistSksUpdateNoticeFromVersions({
|
|
163
|
+
packageName: result.package,
|
|
164
|
+
currentVersion: result.new_version || result.from,
|
|
165
|
+
latestVersion: result.latest,
|
|
166
|
+
error: result.ok ? null : result.error
|
|
167
|
+
}).catch(() => undefined);
|
|
168
|
+
}
|
|
154
169
|
if (flag(effectiveArgs, '--json'))
|
|
155
170
|
return printJson(result);
|
|
156
171
|
cliUi.banner('update');
|
|
@@ -42,10 +42,26 @@ const NARUTO_ROUTE = '$Naruto';
|
|
|
42
42
|
// writes). The standard 20-agent ceiling is lifted only for this route.
|
|
43
43
|
export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
44
44
|
const args = Array.isArray(commandOrArgs) ? commandOrArgs : maybeArgs;
|
|
45
|
-
//
|
|
45
|
+
// Normal Naruto is a GPT-5.6-family-only route. Keep the historical GLM
|
|
46
|
+
// implementation reachable only through the explicitly separate
|
|
47
|
+
// `sks --mad --glm --naruto` global mode; a command-local --glm flag must
|
|
48
|
+
// never bypass the Luna/Terra/Sol catalog and backend guards below.
|
|
46
49
|
if (args.includes('--glm')) {
|
|
47
|
-
const
|
|
48
|
-
|
|
50
|
+
const result = {
|
|
51
|
+
schema: 'sks.naruto-command-result.v1',
|
|
52
|
+
ok: false,
|
|
53
|
+
mode: 'NARUTO',
|
|
54
|
+
status: 'blocked',
|
|
55
|
+
reason: 'naruto_gpt_5_6_family_only_glm_override_forbidden',
|
|
56
|
+
blockers: ['naruto_gpt_5_6_family_only_glm_override_forbidden'],
|
|
57
|
+
hint: 'Use normal sks naruto for Luna/Terra/Sol. The separate legacy GLM route requires: sks --mad --glm --naruto.'
|
|
58
|
+
};
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
if (args.includes('--json'))
|
|
61
|
+
console.log(JSON.stringify(result, null, 2));
|
|
62
|
+
else
|
|
63
|
+
console.error(`$Naruto blocked: ${result.reason}. ${result.hint}`);
|
|
64
|
+
return result;
|
|
49
65
|
}
|
|
50
66
|
const parsed = parseNarutoArgs(args);
|
|
51
67
|
if (!parsed.json)
|
|
@@ -84,13 +100,21 @@ async function narutoRun(parsed) {
|
|
|
84
100
|
const writeCapable = parsed.readonly !== true && parsed.writeMode !== 'off';
|
|
85
101
|
const patchEnvelopeBasePath = '.sneakoscope/naruto/patch-envelopes';
|
|
86
102
|
const explicitPromptPaths = writeCapable ? extractNarutoPromptPaths(parsed.prompt) : [];
|
|
87
|
-
|
|
103
|
+
// Keep an empty target list when the prompt does not name a concrete file.
|
|
104
|
+
// buildNarutoWorkGraph then derives one patch-envelope path per work item;
|
|
105
|
+
// passing the base directory as a target would collapse every write lease
|
|
106
|
+
// onto the same path and force an avoidable serial merge bottleneck.
|
|
107
|
+
const targetPaths = explicitPromptPaths;
|
|
108
|
+
const fixtureLeaseTargetsAllowed = parsed.backend === 'fake' || parsed.mock;
|
|
88
109
|
const placeholderGuard = checkPromptPlaceholders({
|
|
89
110
|
prompt: parsed.prompt,
|
|
90
111
|
writeCapable,
|
|
91
|
-
targetPaths: writeCapable
|
|
112
|
+
targetPaths: writeCapable
|
|
113
|
+
? (targetPaths.length ? targetPaths : fixtureLeaseTargetsAllowed ? [patchEnvelopeBasePath] : [])
|
|
114
|
+
: []
|
|
92
115
|
});
|
|
93
116
|
if (!placeholderGuard.ok) {
|
|
117
|
+
process.exitCode = 1;
|
|
94
118
|
return emit(parsed, {
|
|
95
119
|
schema: NARUTO_RESULT_SCHEMA,
|
|
96
120
|
ok: false,
|
|
@@ -98,7 +122,10 @@ async function narutoRun(parsed) {
|
|
|
98
122
|
action: 'run',
|
|
99
123
|
status: 'blocked',
|
|
100
124
|
prompt_placeholder_guard: placeholderGuard,
|
|
101
|
-
blockers: placeholderGuard.blockers
|
|
125
|
+
blockers: placeholderGuard.blockers,
|
|
126
|
+
hint: placeholderGuard.blockers.includes('write_capable_prompt_target_paths_empty')
|
|
127
|
+
? 'Name the target source path(s), or run Naruto read-only discovery before starting a write-capable worker swarm.'
|
|
128
|
+
: null
|
|
102
129
|
}, () => {
|
|
103
130
|
console.log('$Naruto blocked before work graph creation: unresolved prompt placeholder or empty write target path.');
|
|
104
131
|
for (const blocker of placeholderGuard.blockers)
|
|
@@ -137,7 +164,7 @@ async function narutoRun(parsed) {
|
|
|
137
164
|
// system-safe number so naruto never spawns the whole count at once unless an
|
|
138
165
|
// explicit operator override asks for a higher target.
|
|
139
166
|
const localWorker = await resolveNarutoLocalWorkerMode(parsed);
|
|
140
|
-
const schedulerBackend =
|
|
167
|
+
const schedulerBackend = parsed.backend;
|
|
141
168
|
const safe = systemSafeNarutoConcurrency({ backend: schedulerBackend });
|
|
142
169
|
const baseWorkGraph = buildNarutoWorkGraph({
|
|
143
170
|
prompt: parsed.prompt,
|
|
@@ -1176,7 +1203,7 @@ async function resolveNarutoLocalWorkerMode(parsed) {
|
|
|
1176
1203
|
description: parsed.prompt,
|
|
1177
1204
|
write_paths: parsed.readonly ? [] : ['<lease-scoped-worker-path>']
|
|
1178
1205
|
}, { route: NARUTO_ROUTE, agent: { role: parsed.readonly ? 'collector' : 'implementer' } });
|
|
1179
|
-
const
|
|
1206
|
+
const localAutoSelectCandidate = parsed.backend === 'codex-sdk'
|
|
1180
1207
|
&& parsed.backendExplicit !== true
|
|
1181
1208
|
&& parsed.noOllama !== true
|
|
1182
1209
|
&& config?.ok === true
|
|
@@ -1189,13 +1216,14 @@ async function resolveNarutoLocalWorkerMode(parsed) {
|
|
|
1189
1216
|
model: config?.model || null,
|
|
1190
1217
|
requested_backend: parsed.backend,
|
|
1191
1218
|
backend_explicit: parsed.backendExplicit,
|
|
1192
|
-
auto_select_eligible:
|
|
1219
|
+
auto_select_eligible: false,
|
|
1193
1220
|
worker_only: true,
|
|
1194
1221
|
no_strategy_planning_design: true,
|
|
1195
1222
|
policy,
|
|
1196
1223
|
blockers: [
|
|
1197
1224
|
...(config?.blockers || (config ? [] : ['ollama_worker_config_unavailable'])),
|
|
1198
1225
|
...(policy.blockers || []),
|
|
1226
|
+
...(localAutoSelectCandidate ? ['naruto_gpt_5_6_family_only_local_auto_select_forbidden'] : []),
|
|
1199
1227
|
...(parsed.backendExplicit ? ['backend_explicit'] : []),
|
|
1200
1228
|
...(parsed.noOllama ? ['no_ollama_requested'] : [])
|
|
1201
1229
|
]
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import os from 'node:os';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import { fileURLToPath } from 'node:url';
|
|
5
4
|
import { applyNarutoPatchEnvelopes } from './naruto-parallel-patch-apply.js';
|
|
6
5
|
import { realisticNarutoRealWriteProofFixture, validateNarutoRealWriteProof } from './naruto-real-write-proof.js';
|
|
7
|
-
import { ensureDir, runProcess, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
6
|
+
import { ensureDir, runProcess, tmpdir, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
8
7
|
export async function runNarutoWriteE2e(mode) {
|
|
9
8
|
if (mode === 'real-codex')
|
|
10
9
|
return runRealCodexWriteE2e();
|
|
11
10
|
return runHermeticWriteE2e();
|
|
12
11
|
}
|
|
13
12
|
export async function runHermeticWriteE2e() {
|
|
14
|
-
const tempRoot =
|
|
13
|
+
const tempRoot = tmpdir('naruto-write-e2e-');
|
|
15
14
|
const mergeArtifactPath = path.join(tempRoot, '.sneakoscope', 'naruto-write-e2e-parent-merge.json');
|
|
16
15
|
const blockers = [];
|
|
17
16
|
let cleanup = { ok: false, temp_root_removed: false, blockers: [] };
|
|
@@ -284,7 +283,7 @@ async function probeRealCodexRuntime() {
|
|
|
284
283
|
return { ok: result.code === 0, blockers: result.code === 0 ? [] : [`codex_version_exit_${result.code}`] };
|
|
285
284
|
}
|
|
286
285
|
async function runRealNarutoCommandWriteE2e() {
|
|
287
|
-
const tempRoot =
|
|
286
|
+
const tempRoot = tmpdir('naruto-real-write-e2e-');
|
|
288
287
|
const mergeArtifactPath = path.join(tempRoot, '.sneakoscope', 'naruto-real-write-e2e-parent-merge.json');
|
|
289
288
|
const blockers = [];
|
|
290
289
|
let cleanup = { ok: false, temp_root_removed: false, blockers: [] };
|