sneakoscope 8.0.0 → 8.0.2
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 +13 -2
- package/config/skills-hash-ledger.v1.json +63 -0
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +14 -3
- package/dist/bin/sks-dispatch.js +1 -1
- package/dist/cli/codex-lb-setup-warning-output.js +17 -0
- package/dist/cli/command-manifest-lite.js +2 -13
- package/dist/cli/install-helpers-codex-lb-config.js +475 -8
- package/dist/cli/install-helpers-install-support.js +1 -1
- package/dist/cli/install-helpers.js +764 -68
- package/dist/cli/router.js +5 -2
- package/dist/commands/codex-lb.js +77 -47
- package/dist/commands/doctor-profile.js +1 -0
- package/dist/commands/doctor.js +165 -58
- package/dist/config/skills-manifest.json +193 -97
- package/dist/core/codex/codex-config-guard.js +70 -16
- package/dist/core/codex/oauth-callback-port-diagnostic.js +121 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -2
- package/dist/core/codex-app/menubar/constants.js +3 -0
- package/dist/core/codex-app/menubar/installer.js +32 -9
- package/dist/core/codex-app/menubar/launch-agent.js +58 -6
- package/dist/core/codex-app/menubar/migration.js +17 -6
- package/dist/core/codex-app/menubar/status.js +15 -3
- package/dist/core/codex-app.js +15 -20
- package/dist/core/codex-control/codex-lb-launch-recovery.js +26 -0
- package/dist/core/codex-lb/capability-runner.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +52 -41
- package/dist/core/codex-lb/codex-lb-setup.js +85 -8
- package/dist/core/codex-lb/desktop-center-credentials.js +474 -39
- package/dist/core/codex-lb/desktop-controller.js +118 -4
- package/dist/core/codex-lb/legacy-migration.js +6 -3
- package/dist/core/codex-native/codex-native-feature-broker.js +5 -1
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/codex-native/managed-skill-generation-lock.js +13 -0
- package/dist/core/codex-native/sks-skill-paths.js +97 -10
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +1 -0
- package/dist/core/commands/basic-cli.js +54 -4
- package/dist/core/commands/mad-sks-command.js +1 -1
- package/dist/core/commands/menubar-command.js +24 -8
- package/dist/core/commands/naruto-command.js +42 -217
- package/dist/core/doctor/codex-config-syntax-repair.js +368 -0
- package/dist/core/doctor/doctor-native-capability-repair.js +12 -5
- package/dist/core/doctor/doctor-readiness-matrix.js +51 -4
- package/dist/core/doctor/skill-legacy-surface.js +0 -1
- package/dist/core/doctor/sks-menubar-doctor.js +21 -0
- package/dist/core/fsx.js +7 -14
- package/dist/core/hooks-runtime/hook-context.js +30 -16
- package/dist/core/hooks-runtime/managed-skill-admission.js +17 -0
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +891 -30
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +99 -6
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +3 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-evidence.js +1 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-guards.js +4 -4
- package/dist/core/hooks-runtime/subagent-skill-availability.js +9 -7
- package/dist/core/hooks-runtime/subagent-stop-hook.js +19 -0
- package/dist/core/hooks-runtime.js +41 -14
- package/dist/core/init/skills.js +270 -11
- package/dist/core/mission.js +20 -0
- package/dist/core/naruto/hardware-capacity-probe.js +3 -1
- package/dist/core/naruto/naruto-concurrency-governor.js +9 -3
- package/dist/core/pipeline-internals/runtime-core.js +68 -9
- package/dist/core/pipeline-internals/runtime-gates.js +10 -2
- package/dist/core/preflight/parallel-preflight-engine.js +18 -4
- package/dist/core/release/package-dist-snapshot.js +50 -8
- package/dist/core/routes.js +1 -1
- package/dist/core/safety/command-contract/contracts.js +10 -13
- package/dist/core/safety/command-contract/json-schema.js +38 -1
- package/dist/core/subagents/agent-catalog.js +37 -13
- package/dist/core/subagents/model-policy.js +14 -9
- package/dist/core/subagents/naruto-command-args.js +237 -0
- package/dist/core/subagents/naruto-command-input-contract.js +123 -0
- package/dist/core/subagents/naruto-help-contract.js +35 -2
- package/dist/core/subagents/naruto-launch-diagnostics.js +35 -0
- package/dist/core/subagents/official-subagent-config.js +30 -3
- package/dist/core/subagents/official-subagent-preparation.js +30 -5
- package/dist/core/subagents/official-subagent-prompt.js +4 -3
- package/dist/core/subagents/official-subagent-runner.js +12 -0
- package/dist/core/subagents/subagent-evidence.js +10 -0
- package/dist/core/subagents/thread-budget.js +1 -1
- package/dist/core/subagents/wave-lifecycle.js +41 -11
- package/dist/core/subagents/wave-parent-guidance.js +10 -3
- package/dist/core/update/update-migration-state.js +694 -63
- package/dist/core/update/update-operation.js +498 -32
- package/dist/core/update-check.js +990 -509
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -0
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -1
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +1 -1
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +127 -12
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +7 -2
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +287 -84
- package/dist/native/sks-menubar/Sources/ProcessExecutionState.swift +45 -0
- package/dist/native/sks-menubar/Sources/ProcessIdentityGuard.swift +196 -0
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +169 -0
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +23 -5
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +113 -24
- package/dist/native/sks-menubar/Sources/main.swift +12 -0
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +14 -0
- package/dist/native/sks-menubar/Tests/OperationCoordinatorTests.swift +174 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +4 -0
- package/dist/scripts/current-surface-update-e2e-check.js +24 -5
- package/dist/scripts/lib/ensure-dist-fresh.js +12 -8
- package/dist/scripts/mutation-callsite-analysis.js +381 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +127 -62
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/skills-manifest-continuity-check.js +19 -1
- package/package.json +5 -3
- package/runtime-required-scripts.json +5 -0
- package/scripts/build-clean-atomic.mjs +323 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import {
|
|
3
|
+
import { constants as fsConstants } from 'node:fs';
|
|
4
|
+
import fsp from 'node:fs/promises';
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
6
|
+
import { assertTestHomeWriteAllowed, ensureDir, readText, writeBinaryAtomic } from '../core/fsx.js';
|
|
4
7
|
import { GLM_CODEX_CONFIG_PROVIDER_ID, GLM_52_OPENROUTER_MODEL, OPENROUTER_AUTH_COMMAND, OPENROUTER_AUTH_REFRESH_INTERVAL_MS, OPENROUTER_AUTH_TIMEOUT_MS, openRouterAuthCommandArgs, OPENROUTER_DEFAULT_PROFILE_ID, RETIRED_GLM_DESKTOP_CONFIG_PROFILE_IDS } from '../core/codex-app/openrouter-provider.js';
|
|
5
8
|
import { openRouterSecretPaths, resolveOpenRouterApiKey } from '../core/providers/openrouter/openrouter-secret-store.js';
|
|
6
9
|
import { reconcileRetiredSksConfigText } from '../core/auto-review.js';
|
|
@@ -290,17 +293,481 @@ export function detectCodexLbSetupDrift(state = {}) {
|
|
|
290
293
|
drift.push('shell_profile_changed_despite_skip');
|
|
291
294
|
return drift;
|
|
292
295
|
}
|
|
293
|
-
export async function captureCodexLbSetupWriteState({ home, configPath, envPath, shellProfile } = {}) {
|
|
296
|
+
export async function captureCodexLbSetupWriteState({ home, configPath, envPath, metadataPath, shellProfile } = {}) {
|
|
294
297
|
const profileFiles = profileFilesForDrift(home, shellProfile);
|
|
298
|
+
const paths = [configPath, envPath, metadataPath, ...profileFiles].filter(Boolean);
|
|
299
|
+
const files = await Promise.all(paths.map(captureSetupFile));
|
|
300
|
+
const hashesByPath = new Map(await Promise.all(files.map(async (file) => [file.path, await capturedSetupFileHash(file)])));
|
|
301
|
+
const hashForPath = (file) => file ? hashesByPath.get(file) || 'missing' : 'missing';
|
|
295
302
|
return {
|
|
296
|
-
configHash:
|
|
297
|
-
envHash:
|
|
298
|
-
|
|
303
|
+
configHash: hashForPath(configPath),
|
|
304
|
+
envHash: hashForPath(envPath),
|
|
305
|
+
metadataHash: hashForPath(metadataPath),
|
|
306
|
+
profileHash: profileFiles.map(hashForPath).join('|'),
|
|
307
|
+
files,
|
|
308
|
+
stateHash: await sha256Text(JSON.stringify(files))
|
|
299
309
|
};
|
|
300
310
|
}
|
|
301
|
-
async function
|
|
302
|
-
const
|
|
303
|
-
|
|
311
|
+
export async function writeCodexLbSetupFileIfUnchanged(input) {
|
|
312
|
+
const file = path.resolve(input.file);
|
|
313
|
+
if (path.resolve(String(input.expected.path || '')) !== file) {
|
|
314
|
+
return {
|
|
315
|
+
ok: false,
|
|
316
|
+
status: 'write_failed',
|
|
317
|
+
installed: false,
|
|
318
|
+
expected_after: {
|
|
319
|
+
path: file,
|
|
320
|
+
existed: true,
|
|
321
|
+
kind: 'regular',
|
|
322
|
+
bytes_base64: Buffer.from(input.text).toString('base64'),
|
|
323
|
+
mode: Number(input.mode) & 0o777
|
|
324
|
+
},
|
|
325
|
+
error: 'setup_snapshot_path_mismatch'
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const expected = { ...input.expected, path: file };
|
|
329
|
+
const mode = Number(input.mode) & 0o777;
|
|
330
|
+
const expectedAfter = {
|
|
331
|
+
path: file,
|
|
332
|
+
existed: true,
|
|
333
|
+
kind: 'regular',
|
|
334
|
+
bytes_base64: Buffer.from(input.text).toString('base64'),
|
|
335
|
+
mode
|
|
336
|
+
};
|
|
337
|
+
if (expected.existed === true && expected.kind !== 'regular') {
|
|
338
|
+
return {
|
|
339
|
+
ok: false,
|
|
340
|
+
status: 'unsafe_setup_write_target',
|
|
341
|
+
installed: false,
|
|
342
|
+
expected_after: expectedAfter
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
assertTestHomeWriteAllowed(file);
|
|
346
|
+
await ensureDir(path.dirname(file));
|
|
347
|
+
const token = `${Date.now().toString(36)}-${process.pid}-${randomBytes(6).toString('hex')}`;
|
|
348
|
+
const claimedPath = `${file}.sks-setup-claimed-${token}`;
|
|
349
|
+
const candidatePath = `${file}.sks-setup-candidate-${token}`;
|
|
350
|
+
let claimed = false;
|
|
351
|
+
let installed = false;
|
|
352
|
+
try {
|
|
353
|
+
await writeBinaryAtomic(candidatePath, Buffer.from(input.text), { mode });
|
|
354
|
+
const observed = await captureSetupFile(file);
|
|
355
|
+
if (!setupFileStatesEqual(observed, expected)) {
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
status: 'concurrent_change_detected',
|
|
359
|
+
installed: false,
|
|
360
|
+
expected_after: expectedAfter
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (setupFileStatesEqual(expected, expectedAfter)) {
|
|
364
|
+
return {
|
|
365
|
+
ok: true,
|
|
366
|
+
status: 'present',
|
|
367
|
+
installed: false,
|
|
368
|
+
expected_after: expectedAfter
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (expected.existed === true) {
|
|
372
|
+
try {
|
|
373
|
+
await fsp.rename(file, claimedPath);
|
|
374
|
+
claimed = true;
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return {
|
|
378
|
+
ok: false,
|
|
379
|
+
status: 'concurrent_change_detected',
|
|
380
|
+
installed: false,
|
|
381
|
+
expected_after: expectedAfter
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const claimedState = await captureSetupFile(claimedPath);
|
|
385
|
+
if (!setupFileStatesEqual(claimedState, { ...expected, path: claimedPath })) {
|
|
386
|
+
const recovered = await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState);
|
|
387
|
+
claimed = !recovered;
|
|
388
|
+
const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
|
|
389
|
+
return {
|
|
390
|
+
ok: false,
|
|
391
|
+
status: hardened ? 'concurrent_change_detected' : 'write_failed',
|
|
392
|
+
installed: false,
|
|
393
|
+
expected_after: expectedAfter,
|
|
394
|
+
...(claimed ? { recovery_path: claimedPath } : {}),
|
|
395
|
+
...(hardened ? {} : { error: 'setup_recovery_mode_hardening_failed' })
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
await input.beforeReplacement?.({ path: file });
|
|
400
|
+
try {
|
|
401
|
+
await fsp.link(candidatePath, file);
|
|
402
|
+
installed = true;
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
if (claimed) {
|
|
406
|
+
const claimedState = await captureSetupFile(claimedPath).catch(() => null);
|
|
407
|
+
const recovered = claimedState
|
|
408
|
+
? await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState)
|
|
409
|
+
: false;
|
|
410
|
+
claimed = !recovered;
|
|
411
|
+
}
|
|
412
|
+
const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
status: hardened ? 'concurrent_change_detected' : 'write_failed',
|
|
416
|
+
installed: false,
|
|
417
|
+
expected_after: expectedAfter,
|
|
418
|
+
...(claimed ? { recovery_path: claimedPath } : {}),
|
|
419
|
+
...(hardened ? {} : { error: 'setup_recovery_mode_hardening_failed' })
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const committed = await captureSetupFile(file);
|
|
423
|
+
if (!setupFileStatesEqual(committed, expectedAfter)) {
|
|
424
|
+
const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
|
|
425
|
+
return {
|
|
426
|
+
ok: false,
|
|
427
|
+
status: hardened ? 'concurrent_change_detected' : 'write_failed',
|
|
428
|
+
installed: true,
|
|
429
|
+
expected_after: expectedAfter,
|
|
430
|
+
...(claimed ? { recovery_path: claimedPath } : {}),
|
|
431
|
+
...(hardened ? {} : { error: 'setup_recovery_mode_hardening_failed' })
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (claimed && !await hardenCodexLbSetupRecoveryPath(claimedPath)) {
|
|
435
|
+
return {
|
|
436
|
+
ok: false,
|
|
437
|
+
status: 'write_failed',
|
|
438
|
+
installed: true,
|
|
439
|
+
expected_after: expectedAfter,
|
|
440
|
+
recovery_path: claimedPath,
|
|
441
|
+
error: 'setup_recovery_mode_hardening_failed'
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
ok: true,
|
|
446
|
+
status: 'written',
|
|
447
|
+
installed: true,
|
|
448
|
+
expected_after: expectedAfter,
|
|
449
|
+
...(claimed ? { recovery_path: claimedPath } : {})
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
if (claimed && !installed) {
|
|
454
|
+
const claimedState = await captureSetupFile(claimedPath).catch(() => null);
|
|
455
|
+
const recovered = claimedState
|
|
456
|
+
? await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState)
|
|
457
|
+
: false;
|
|
458
|
+
claimed = !recovered;
|
|
459
|
+
}
|
|
460
|
+
const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
status: 'write_failed',
|
|
464
|
+
installed,
|
|
465
|
+
expected_after: expectedAfter,
|
|
466
|
+
...(claimed ? { recovery_path: claimedPath } : {}),
|
|
467
|
+
error: [
|
|
468
|
+
error instanceof Error ? error.message : String(error),
|
|
469
|
+
...(hardened ? [] : ['setup_recovery_mode_hardening_failed'])
|
|
470
|
+
].join(':')
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
finally {
|
|
474
|
+
await fsp.rm(candidatePath, { force: true }).catch(() => undefined);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
export async function hardenCodexLbSetupRecoveryPath(file) {
|
|
478
|
+
let handle = null;
|
|
479
|
+
try {
|
|
480
|
+
handle = await fsp.open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
481
|
+
const before = await handle.stat();
|
|
482
|
+
if (!before.isFile())
|
|
483
|
+
return false;
|
|
484
|
+
await handle.chmod(0o600);
|
|
485
|
+
const hardened = await handle.stat();
|
|
486
|
+
const pathStat = await fsp.lstat(file);
|
|
487
|
+
return hardened.isFile()
|
|
488
|
+
&& (hardened.mode & 0o777) === 0o600
|
|
489
|
+
&& pathStat.isFile()
|
|
490
|
+
&& !pathStat.isSymbolicLink()
|
|
491
|
+
&& pathStat.dev === hardened.dev
|
|
492
|
+
&& pathStat.ino === hardened.ino;
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
finally {
|
|
498
|
+
await handle?.close().catch(() => undefined);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
export async function removeCodexLbSetupRecoveryPath(file) {
|
|
502
|
+
if (!path.basename(file).includes('.sks-setup-claimed-'))
|
|
503
|
+
return false;
|
|
504
|
+
let handle = null;
|
|
505
|
+
try {
|
|
506
|
+
handle = await fsp.open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
507
|
+
const opened = await handle.stat();
|
|
508
|
+
const pathStat = await fsp.lstat(file);
|
|
509
|
+
if (!opened.isFile()
|
|
510
|
+
|| !pathStat.isFile()
|
|
511
|
+
|| pathStat.isSymbolicLink()
|
|
512
|
+
|| pathStat.dev !== opened.dev
|
|
513
|
+
|| pathStat.ino !== opened.ino)
|
|
514
|
+
return false;
|
|
515
|
+
await fsp.unlink(file);
|
|
516
|
+
return await fsp.lstat(file).then(() => false, (error) => error.code === 'ENOENT');
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
finally {
|
|
522
|
+
await handle?.close().catch(() => undefined);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
export async function restoreCodexLbSetupWriteState(state, expectedCurrentState, opts = {}) {
|
|
526
|
+
const restored = [];
|
|
527
|
+
const removed = [];
|
|
528
|
+
const blockers = [];
|
|
529
|
+
const recovery = [];
|
|
530
|
+
const expectedByPath = new Map((Array.isArray(expectedCurrentState?.files) ? expectedCurrentState.files : [])
|
|
531
|
+
.map((entry) => [String(entry?.path || ''), entry]));
|
|
532
|
+
for (const entry of Array.isArray(state?.files) ? [...state.files].reverse() : []) {
|
|
533
|
+
const file = String(entry?.path || '');
|
|
534
|
+
if (!file)
|
|
535
|
+
continue;
|
|
536
|
+
try {
|
|
537
|
+
const expected = expectedByPath.get(file);
|
|
538
|
+
if (!expected) {
|
|
539
|
+
blockers.push(`setup_rollback_missing_expected_state:${file}`);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (expected && setupFileStatesEqual(entry, expected))
|
|
543
|
+
continue;
|
|
544
|
+
const commit = await commitSetupRollbackIfUnchanged({
|
|
545
|
+
file,
|
|
546
|
+
before: entry,
|
|
547
|
+
expected,
|
|
548
|
+
...(opts.beforeReplacement ? { beforeReplacement: opts.beforeReplacement } : {})
|
|
549
|
+
});
|
|
550
|
+
if (commit.recoveryPath)
|
|
551
|
+
recovery.push(commit.recoveryPath);
|
|
552
|
+
if (!commit.ok) {
|
|
553
|
+
blockers.push(`${commit.failed ? 'setup_rollback_failed' : 'setup_rollback_conflict'}:${file}`);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (commit.restored)
|
|
557
|
+
restored.push(file);
|
|
558
|
+
if (commit.removed)
|
|
559
|
+
removed.push(file);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
blockers.push(`setup_rollback_failed:${file}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return { ok: blockers.length === 0, restored, removed, blockers, recovery };
|
|
566
|
+
}
|
|
567
|
+
function setupFileStatesEqual(left, right) {
|
|
568
|
+
return left?.existed === right?.existed
|
|
569
|
+
&& String(left?.kind || (left?.existed ? 'regular' : 'missing')) === String(right?.kind || (right?.existed ? 'regular' : 'missing'))
|
|
570
|
+
&& String(left?.bytes_base64 || '') === String(right?.bytes_base64 || '')
|
|
571
|
+
&& (left?.existed !== true || Number(left?.mode) === Number(right?.mode));
|
|
572
|
+
}
|
|
573
|
+
async function captureSetupFile(file) {
|
|
574
|
+
let pathStat;
|
|
575
|
+
try {
|
|
576
|
+
pathStat = await fsp.lstat(file);
|
|
577
|
+
}
|
|
578
|
+
catch (error) {
|
|
579
|
+
if (error?.code === 'ENOENT') {
|
|
580
|
+
return { path: file, existed: false, kind: 'missing', bytes_base64: '', mode: null };
|
|
581
|
+
}
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
if (pathStat.isSymbolicLink()) {
|
|
585
|
+
return { path: file, existed: true, kind: 'symlink', bytes_base64: '', mode: pathStat.mode & 0o777 };
|
|
586
|
+
}
|
|
587
|
+
if (!pathStat.isFile()) {
|
|
588
|
+
return { path: file, existed: true, kind: 'non_regular', bytes_base64: '', mode: pathStat.mode & 0o777 };
|
|
589
|
+
}
|
|
590
|
+
let handle = null;
|
|
591
|
+
try {
|
|
592
|
+
handle = await fsp.open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
593
|
+
const stat = await handle.stat();
|
|
594
|
+
if (!stat.isFile()) {
|
|
595
|
+
return { path: file, existed: true, kind: 'non_regular', bytes_base64: '', mode: stat.mode & 0o777 };
|
|
596
|
+
}
|
|
597
|
+
const bytes = await handle.readFile();
|
|
598
|
+
return {
|
|
599
|
+
path: file,
|
|
600
|
+
existed: true,
|
|
601
|
+
kind: 'regular',
|
|
602
|
+
bytes_base64: bytes.toString('base64'),
|
|
603
|
+
mode: stat.mode & 0o777
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
if (error?.code === 'ENOENT') {
|
|
608
|
+
return { path: file, existed: false, kind: 'missing', bytes_base64: '', mode: null };
|
|
609
|
+
}
|
|
610
|
+
if (error?.code === 'ELOOP') {
|
|
611
|
+
const stat = await fsp.lstat(file).catch(() => null);
|
|
612
|
+
return {
|
|
613
|
+
path: file,
|
|
614
|
+
existed: true,
|
|
615
|
+
kind: 'symlink',
|
|
616
|
+
bytes_base64: '',
|
|
617
|
+
mode: stat ? stat.mode & 0o777 : null
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
throw error;
|
|
621
|
+
}
|
|
622
|
+
finally {
|
|
623
|
+
await handle?.close().catch(() => undefined);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async function commitSetupRollbackIfUnchanged(input) {
|
|
627
|
+
const { file, before, expected } = input;
|
|
628
|
+
await ensureDir(path.dirname(file));
|
|
629
|
+
const token = `${Date.now().toString(36)}-${process.pid}-${randomBytes(6).toString('hex')}`;
|
|
630
|
+
const claimedPath = `${file}.sks-rollback-claimed-${token}`;
|
|
631
|
+
const candidatePath = `${file}.sks-rollback-candidate-${token}`;
|
|
632
|
+
let claimed = false;
|
|
633
|
+
try {
|
|
634
|
+
if (expected.existed === true) {
|
|
635
|
+
const currentStat = await fsp.lstat(file).catch((error) => {
|
|
636
|
+
if (error.code === 'ENOENT')
|
|
637
|
+
return null;
|
|
638
|
+
throw error;
|
|
639
|
+
});
|
|
640
|
+
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile()) {
|
|
641
|
+
return { ok: false, restored: false, removed: false };
|
|
642
|
+
}
|
|
643
|
+
try {
|
|
644
|
+
await fsp.rename(file, claimedPath);
|
|
645
|
+
claimed = true;
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
return { ok: false, restored: false, removed: false };
|
|
649
|
+
}
|
|
650
|
+
const claimedState = await captureSetupFile(claimedPath);
|
|
651
|
+
if (!setupFileStatesEqual(claimedState, { ...expected, path: claimedPath })) {
|
|
652
|
+
const recovered = await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState);
|
|
653
|
+
claimed = !recovered;
|
|
654
|
+
return {
|
|
655
|
+
ok: false,
|
|
656
|
+
restored: false,
|
|
657
|
+
removed: false,
|
|
658
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
const current = await captureSetupFile(file);
|
|
664
|
+
if (!setupFileStatesEqual(current, expected)) {
|
|
665
|
+
return { ok: false, restored: false, removed: false };
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
await input.beforeReplacement?.({ path: file });
|
|
669
|
+
if (before.existed === true) {
|
|
670
|
+
if (String(before.kind || 'regular') !== 'regular') {
|
|
671
|
+
if (claimed) {
|
|
672
|
+
const claimedState = await captureSetupFile(claimedPath);
|
|
673
|
+
const recovered = await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState);
|
|
674
|
+
claimed = !recovered;
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
restored: false,
|
|
679
|
+
removed: false,
|
|
680
|
+
failed: true,
|
|
681
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const bytes = Buffer.from(String(before.bytes_base64 || ''), 'base64');
|
|
685
|
+
await writeBinaryAtomic(candidatePath, bytes, { mode: Number(before.mode || 0o600) });
|
|
686
|
+
try {
|
|
687
|
+
await fsp.link(candidatePath, file);
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
return {
|
|
691
|
+
ok: false,
|
|
692
|
+
restored: false,
|
|
693
|
+
removed: false,
|
|
694
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
const committed = await captureSetupFile(file);
|
|
698
|
+
if (!setupFileStatesEqual(committed, before)) {
|
|
699
|
+
return {
|
|
700
|
+
ok: false,
|
|
701
|
+
restored: false,
|
|
702
|
+
removed: false,
|
|
703
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
ok: true,
|
|
708
|
+
restored: true,
|
|
709
|
+
removed: false,
|
|
710
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
const committed = await captureSetupFile(file);
|
|
714
|
+
if (!setupFileStatesEqual(committed, before)) {
|
|
715
|
+
return {
|
|
716
|
+
ok: false,
|
|
717
|
+
restored: false,
|
|
718
|
+
removed: false,
|
|
719
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
ok: true,
|
|
724
|
+
restored: false,
|
|
725
|
+
removed: true,
|
|
726
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
if (claimed) {
|
|
731
|
+
const claimedState = await captureSetupFile(claimedPath).catch(() => null);
|
|
732
|
+
const recovered = claimedState
|
|
733
|
+
? await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState)
|
|
734
|
+
: false;
|
|
735
|
+
claimed = !recovered;
|
|
736
|
+
}
|
|
737
|
+
return {
|
|
738
|
+
ok: false,
|
|
739
|
+
restored: false,
|
|
740
|
+
removed: false,
|
|
741
|
+
failed: true,
|
|
742
|
+
...(claimed ? { recoveryPath: claimedPath } : {})
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
finally {
|
|
746
|
+
await fsp.rm(candidatePath, { force: true }).catch(() => undefined);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async function restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState) {
|
|
750
|
+
try {
|
|
751
|
+
if (claimedState?.kind === 'regular') {
|
|
752
|
+
await fsp.link(claimedPath, file);
|
|
753
|
+
}
|
|
754
|
+
else if (claimedState?.kind === 'symlink') {
|
|
755
|
+
await fsp.symlink(await fsp.readlink(claimedPath), file);
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
await fsp.unlink(claimedPath);
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async function capturedSetupFileHash(file) {
|
|
768
|
+
if (file.kind !== 'regular')
|
|
769
|
+
return 'missing';
|
|
770
|
+
return await sha256Text(Buffer.from(file.bytes_base64, 'base64').toString('utf8'));
|
|
304
771
|
}
|
|
305
772
|
function profileFilesForDrift(home, shellProfile) {
|
|
306
773
|
const targets = {
|
|
@@ -15,7 +15,7 @@ import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
|
|
|
15
15
|
import { restartCodexApp } from '../core/codex-app/codex-app-restart.js';
|
|
16
16
|
import { cleanupMacLaunchSecretEnvironment } from '../core/codex-app/menubar/index.js';
|
|
17
17
|
import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
|
|
18
|
-
import { loadCodexLbEnv,
|
|
18
|
+
import { loadCodexLbEnv, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
|
|
19
19
|
import { codexLbToolCatalogPath, ensureCodexLbToolCatalog } from '../core/codex-lb/codex-lb-tool-catalog.js';
|
|
20
20
|
import { codexLbToolOutputRecoveryNotChecked, codexLbToolOutputRecoveryNotSelected, codexLbToolOutputRecoveryOverrideAcknowledged, probeCodexLbToolOutputRecovery } from '../core/codex-lb/codex-lb-tool-output-recovery.js';
|
|
21
21
|
import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES, GLM_52_OPENROUTER_MODEL } from '../core/codex-app/openrouter-provider.js';
|