sneakoscope 6.0.2 → 6.1.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 +5 -3
- package/config/codex-releases/{rust-v0.142.0.json → rust-v0.144.1.json} +6 -6
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/install-helpers.js +255 -440
- package/dist/cli/router.js +53 -4
- package/dist/commands/codex-app.js +8 -3
- package/dist/commands/codex-lb.js +169 -40
- package/dist/commands/codex.js +6 -5
- package/dist/commands/doctor.js +60 -26
- package/dist/commands/versioning.js +7 -0
- package/dist/config/skills-manifest.json +57 -57
- package/dist/core/agents/agent-central-ledger.js +9 -0
- package/dist/core/agents/agent-cleanup-executor.js +334 -41
- package/dist/core/agents/agent-cleanup.js +20 -1
- package/dist/core/agents/agent-command-surface.js +2 -1
- package/dist/core/agents/agent-effort-policy.js +46 -33
- package/dist/core/agents/agent-janitor.js +9 -10
- package/dist/core/agents/agent-namespace-safety.js +56 -0
- package/dist/core/agents/agent-orchestrator.js +26 -4
- package/dist/core/agents/agent-output-validator.js +1 -1
- package/dist/core/agents/agent-proof-evidence.js +16 -2
- package/dist/core/agents/agent-role-config.js +3 -6
- package/dist/core/agents/agent-roster.js +2 -2
- package/dist/core/agents/agent-runner-codex-exec.js +2 -0
- package/dist/core/agents/agent-runner-process.js +1 -0
- package/dist/core/agents/fast-mode-policy.js +3 -5
- package/dist/core/agents/native-cli-session-swarm.js +18 -1
- package/dist/core/agents/native-worker-backend-router.js +180 -108
- package/dist/core/agents/parallel-write-fixture.js +15 -10
- package/dist/core/agents/runtime-proof-summary.js +21 -3
- package/dist/core/agents/work-partition/repo-inventory.js +68 -7
- package/dist/core/code-structure.js +5 -4
- package/dist/core/codex/codex-config-guard.js +31 -9
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +51 -20
- package/dist/core/codex-app/codex-app-restart.js +38 -8
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +11 -3
- package/dist/core/codex-app/sks-menubar.js +100 -40
- package/dist/core/codex-app.js +60 -17
- package/dist/core/codex-compat/codex-hook-warning-detector.js +10 -1
- package/dist/core/codex-compat/codex-release-manifest.js +6 -6
- package/dist/core/codex-control/codex-0139-doctor-real-probe.js +7 -1
- package/dist/core/codex-control/codex-0139-image-path-real-probe.js +14 -8
- package/dist/core/codex-control/codex-0139-probe-runner.js +103 -28
- package/dist/core/codex-control/codex-0139-real-probe-summary.js +7 -3
- package/dist/core/codex-control/codex-0139-real-probes.js +21 -6
- package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +39 -13
- package/dist/core/codex-control/codex-0139-web-search-probe.js +11 -8
- package/dist/core/codex-control/{codex-0142-capability.js → codex-0144-capability.js} +22 -22
- package/dist/core/codex-control/codex-0144-collab-agent-real-probe.js +63 -0
- package/dist/core/codex-control/codex-model-metadata.js +8 -4
- package/dist/core/codex-control/codex-multi-agent-event-normalizer.js +5 -1
- package/dist/core/codex-control/codex-reliability-shield.js +4 -1
- package/dist/core/codex-control/codex-sdk-adapter.js +53 -6
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
- package/dist/core/codex-control/codex-sdk-sandbox-policy.js +6 -3
- package/dist/core/codex-lb/codex-lb-env.js +107 -0
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +478 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +22 -22
- package/dist/core/codex-native/codex-native-feature-matrix.js +2 -2
- package/dist/core/codex-native/native-capability-postcheck.js +16 -8
- package/dist/core/codex-native/native-capability-repair-matrix.js +76 -27
- package/dist/core/codex-plugins/codex-plugin-json.js +67 -21
- package/dist/core/codex-plugins/codex-plugin-repair.js +118 -0
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +306 -0
- package/dist/core/commands/agent-command.js +19 -0
- package/dist/core/commands/basic-cli.js +45 -24
- package/dist/core/commands/gc-command.js +27 -4
- package/dist/core/commands/image-ux-review-command.js +55 -24
- package/dist/core/commands/mad-sks-command.js +152 -132
- package/dist/core/commands/naruto-command.js +41 -10
- package/dist/core/commands/pipeline-command.js +1 -0
- package/dist/core/commands/wiki-command.js +179 -83
- package/dist/core/computer-use-live-evidence.js +166 -3
- package/dist/core/computer-use-status.js +5 -2
- package/dist/core/daemon/sksd-hook-daemon-entrypoint.js +2 -2
- package/dist/core/daemon/sksd-hook-daemon.js +171 -28
- package/dist/core/daemon/sksd-hook-dispatch.js +2 -2
- package/dist/core/dfix/verification-selector.js +6 -4
- package/dist/core/doctor/browser-use-repair.js +54 -13
- package/dist/core/doctor/codex-0139-doctor.js +3 -2
- package/dist/core/doctor/computer-use-repair.js +28 -39
- package/dist/core/doctor/doctor-codex-startup-repair.js +19 -9
- package/dist/core/doctor/doctor-context7-repair.js +1 -1
- package/dist/core/doctor/doctor-dirty-planner.js +1 -1
- package/dist/core/doctor/doctor-native-capability-repair.js +74 -3
- package/dist/core/doctor/imagegen-repair.js +47 -12
- package/dist/core/doctor/legacy-global-hook-cleanup.js +179 -0
- package/dist/core/feature-registry.js +25 -31
- package/dist/core/fsx.js +54 -6
- package/dist/core/harness-conflicts.js +1 -1
- package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +5 -4
- package/dist/core/hooks-runtime/hook-invocation-dedupe.js +147 -0
- package/dist/core/hooks-runtime/hook-io.js +6 -0
- package/dist/core/hooks-runtime.js +17 -2
- package/dist/core/image/image-artifact-path-contract.js +8 -6
- package/dist/core/image/image-artifact-registry.js +5 -1
- package/dist/core/image-ux-review/imagegen-adapter.js +5 -2
- package/dist/core/imagegen/imagegen-capability.js +10 -13
- package/dist/core/imagegen/require-imagegen.js +37 -11
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -5
- package/dist/core/naruto/naruto-work-graph.js +70 -4
- package/dist/core/naruto/naruto-write-e2e.js +3 -4
- package/dist/core/ops/reporting.js +3 -0
- package/dist/core/pipeline-internals/runtime-core.js +12 -7
- package/dist/core/pipeline-internals/runtime-gates.js +17 -1
- package/dist/core/ppt/style-tokens.js +1 -1
- package/dist/core/ppt-review/index.js +1 -1
- package/dist/core/ppt-review/slide-exporter.js +23 -17
- package/dist/core/ppt-review/slide-imagegen-review.js +1 -1
- package/dist/core/ppt-review/slide-issue-extraction.js +4 -2
- package/dist/core/product-design-plugin.js +2 -2
- package/dist/core/proof/route-finalizer.js +1 -1
- package/dist/core/proof-field.js +3 -3
- package/dist/core/provider/model-router.js +68 -3
- package/dist/core/release/gate-manifest.js +18 -12
- package/dist/core/release/gate-pack-manifest.js +1 -1
- package/dist/core/release/package-size-budget.js +2 -0
- package/dist/core/release/release-gate-affected-selector.js +1 -1
- package/dist/core/release/release-gate-dag.js +43 -3
- package/dist/core/release/release-gate-hermetic-env.js +28 -17
- package/dist/core/release/release-real-contract.js +416 -0
- package/dist/core/retention/retention-budget.js +25 -6
- package/dist/core/retention.js +980 -101
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/evidence.js +4 -4
- package/dist/core/routes/model-mode-router.js +3 -0
- package/dist/core/routes/ppt-policy.js +1 -1
- package/dist/core/routes.js +11 -12
- package/dist/core/secret-redaction.js +4 -0
- package/dist/core/triwiki/code-pack.js +8 -2
- package/dist/core/triwiki/triwiki-cache-key.js +108 -29
- package/dist/core/triwiki-attention.js +13 -6
- package/dist/core/triwiki-provenance.js +274 -0
- package/dist/core/triwiki-runtime.js +21 -7
- package/dist/core/triwiki-wrongness/wrongness-ledger.js +178 -59
- package/dist/core/triwiki-wrongness/wrongness-retrieval.js +11 -2
- package/dist/core/triwiki-wrongness/wrongness-trust-policy.js +2 -0
- 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 +69 -25
- package/dist/core/version.js +1 -1
- package/dist/core/wiki-coordinate.js +128 -12
- package/dist/core/work-order-ledger.js +168 -9
- package/dist/core/zellij/zellij-command.js +12 -1
- package/dist/core/zellij/zellij-dashboard-pane.js +3 -3
- package/dist/core/zellij/zellij-launcher.js +15 -14
- package/dist/core/zellij/zellij-pane-proof.js +32 -6
- package/dist/core/zellij/zellij-worker-pane-manager.js +5 -3
- package/dist/scripts/agent-fast-mode-default-check.js +48 -29
- package/dist/scripts/agent-role-config-repair-check.js +25 -4
- package/dist/scripts/agent-wiki-context-proof-check.js +16 -2
- package/dist/scripts/all-feature-deep-completion-check.js +14 -4
- package/dist/scripts/build-dist.js +17 -5
- package/dist/scripts/canonical-test-runner.js +188 -0
- package/dist/scripts/check-command-module-budget.js +38 -16
- package/dist/scripts/check-legacy-free.js +1 -1
- package/dist/scripts/codex-0139-interrupt-agent-check.js +5 -1
- package/dist/scripts/{codex-0142-app-server-v2-check.js → codex-0144-app-server-v2-check.js} +11 -10
- package/dist/scripts/{codex-0142-binary-identity-check.js → codex-0144-binary-identity-check.js} +4 -4
- package/dist/scripts/codex-0144-capability-check.js +21 -0
- package/dist/scripts/{codex-0139-real-probes-check.js → codex-0144-core-real-probes-check.js} +19 -5
- package/dist/scripts/{codex-0142-doctor-wiring-check.js → codex-0144-doctor-wiring-check.js} +6 -6
- package/dist/scripts/{codex-0142-manifest-check.js → codex-0144-manifest-check.js} +11 -3
- package/dist/scripts/{codex-0142-policy-check.js → codex-0144-policy-check.js} +2 -2
- package/dist/scripts/{codex-0142-thread-store-check.js → codex-0144-thread-store-check.js} +3 -3
- package/dist/scripts/codex-app-provider-model-ui-check.js +57 -8
- package/dist/scripts/codex-app-ui-preservation-check.js +35 -4
- package/dist/scripts/codex-control-side-effect-scope-check.js +2 -2
- package/dist/scripts/codex-effort-auto-discovery-check.js +1 -1
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +1 -0
- package/dist/scripts/{codex-lb-gpt55-fast-profile-check.js → codex-lb-gpt56-fast-profile-check.js} +56 -5
- package/dist/scripts/codex-model-metadata-check.js +1 -1
- package/dist/scripts/codex-sdk-sandbox-policy-check.js +5 -1
- package/dist/scripts/codex-sdk-version-compat-check.js +2 -1
- package/dist/scripts/dfix-fixture-check.js +1 -1
- package/dist/scripts/docs-truthfulness-check.js +1 -1
- package/dist/scripts/doctor-codex-startup-repair-check.js +35 -10
- package/dist/scripts/doctor-context7-repair-check.js +2 -1
- package/dist/scripts/doctor-fix-proves-codex-read-check.js +1 -1
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +34 -7
- package/dist/scripts/doctor-imagegen-repair-check.js +14 -4
- package/dist/scripts/doctor-startup-config-repair-blackbox.js +1 -1
- package/dist/scripts/doctor-startup-config-repair-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +2 -2
- package/dist/scripts/install-update-preserves-config-check.js +1 -1
- package/dist/scripts/legacy-gate-inventory-check.js +7 -5
- package/dist/scripts/legacy-strong-inventory-check.js +7 -5
- package/dist/scripts/legacy-upgrade-matrix-check.js +5 -5
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +175 -14
- package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +166 -6
- package/dist/scripts/mutation-callsite-coverage-check.js +0 -11
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +88 -50
- package/dist/scripts/npm-publish-performance-check.js +2 -1
- package/dist/scripts/package-published-contract-check.js +9 -0
- package/dist/scripts/packlist-performance-check.js +5 -2
- package/dist/scripts/postinstall-safe-side-effects-check.js +8 -3
- package/dist/scripts/ppt-no-mock-as-real-check.js +3 -1
- package/dist/scripts/product-design-plugin-routing-check.js +1 -1
- package/dist/scripts/release-check-stamp.js +182 -2
- package/dist/scripts/release-dag-full-coverage-check.js +7 -7
- package/dist/scripts/release-dynamic-presets-check.js +8 -0
- package/dist/scripts/release-gate-dag-runner-check.js +3 -2
- package/dist/scripts/release-gate-dag-runner.js +24 -11
- package/dist/scripts/release-gate-existence-audit.js +8 -9
- package/dist/scripts/release-gate-script-parity-check.js +1 -1
- package/dist/scripts/release-real-check.js +368 -157
- package/dist/scripts/release-runtime-truth-matrix-check.js +22 -0
- package/dist/scripts/route-proof-artifact-structure-check.js +1 -0
- package/dist/scripts/scheduler-batch-dispatch-check.js +1 -1
- package/dist/scripts/search-visibility-gate-lib.js +1 -1
- package/dist/scripts/seo-geo-route-identity-check.js +30 -4
- package/dist/scripts/sizecheck.js +7 -2
- package/dist/scripts/sks-1-11-gate-lib.js +53 -6
- package/dist/scripts/sks-menubar-install-check.js +7 -0
- package/dist/scripts/sks-uninstall-regression-check.js +1 -1
- package/dist/scripts/sksd-daemon-check.js +16 -6
- package/dist/scripts/update-default-command-check.js +4 -2
- package/dist/scripts/write-build-manifest.js +2 -1
- package/dist/scripts/zellij-layout-valid-check.js +31 -6
- package/dist/scripts/zellij-pane-proof-check.js +33 -2
- package/dist/scripts/zellij-real-session-cleanup-check.js +122 -2
- package/dist/scripts/zellij-real-session-launch-check.js +76 -5
- package/dist/scripts/zellij-slot-pane-renderer-check.js +4 -4
- package/package.json +27 -30
- package/schemas/codex/{app-server-0.142 → app-server-0.144}/codex_app_server_protocol.v2.schemas.json +1146 -781
- package/schemas/codex/codex-0139-real-probe-result.schema.json +39 -5
- package/schemas/codex/ppt-slide-issue-ledger.schema.json +2 -1
- package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +0 -107
- package/dist/scripts/agent-native-release-gate.js +0 -274
- package/dist/scripts/codex-0142-capability-check.js +0 -21
- package/dist/scripts/command-performance-scorecard-check.js +0 -204
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +0 -212
- package/dist/scripts/dollar-performance-scorecard-check.js +0 -257
- package/dist/scripts/gate-timing-check.js +0 -16
- package/dist/scripts/ops-maturity-scorecard-check.js +0 -12
- package/dist/scripts/performance-baseline-check.js +0 -12
- package/dist/scripts/performance-improvement-report-check.js +0 -58
- package/dist/scripts/release-parallel-check.js +0 -389
- package/dist/scripts/release-runner-efficiency-check.js +0 -15
- package/dist/scripts/retention-long-run-smoke-check.js +0 -299
- package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
|
@@ -64,6 +64,7 @@ export async function writeCodexConfigGuarded(input) {
|
|
|
64
64
|
return result;
|
|
65
65
|
}
|
|
66
66
|
if (next === ensureTrailingNewline(before)) {
|
|
67
|
+
await writeTextAtomic(configPath, next, { mode: 0o600 });
|
|
67
68
|
const result = { ok: true, status: 'present', config_path: configPath, backup_path: null, changed: false, repaired_keys: preserved.keys, forbidden_top_level: forbiddenTopLevel };
|
|
68
69
|
if (preserved.keys.length || forbiddenTopLevel.length) {
|
|
69
70
|
await recordCodexConfigGuard(root, input.reportPath, {
|
|
@@ -80,7 +81,7 @@ export async function writeCodexConfigGuarded(input) {
|
|
|
80
81
|
}
|
|
81
82
|
const backupPath = before.trim() ? await backupCodexConfig(configPath, before, input.backupTag || cause) : null;
|
|
82
83
|
await ensureDir(path.dirname(configPath));
|
|
83
|
-
await writeTextAtomic(configPath, next);
|
|
84
|
+
await writeTextAtomic(configPath, next, { mode: 0o600 });
|
|
84
85
|
const afterSnapshot = await snapshotForConfig(root, configPath).catch(() => null);
|
|
85
86
|
const diff = beforeSnapshot && afterSnapshot ? diffCodexAppUiSnapshots(beforeSnapshot, afterSnapshot) : null;
|
|
86
87
|
const result = {
|
|
@@ -161,15 +162,36 @@ export function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
|
161
162
|
return true;
|
|
162
163
|
if (!/^\s*(?:model|model_reasoning_effort)\s*=/.test(line))
|
|
163
164
|
return true;
|
|
164
|
-
return !
|
|
165
|
+
return !hasSksModeLockProvenance(lines, index);
|
|
165
166
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n'));
|
|
166
167
|
}
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return
|
|
172
|
-
|
|
168
|
+
function hasSksModeLockProvenance(lines, index) {
|
|
169
|
+
const current = String(lines[index] || '');
|
|
170
|
+
const inlineComment = current.includes('#') ? current.slice(current.indexOf('#')) : '';
|
|
171
|
+
if (isSksModeLockMarker(inlineComment))
|
|
172
|
+
return true;
|
|
173
|
+
const allowedManagedKeys = new Set([
|
|
174
|
+
'service_tier', 'model', 'model_reasoning_effort', 'model_provider',
|
|
175
|
+
'approval_policy', 'sandbox_mode', 'web_search', 'notify', 'preferred_auth_method'
|
|
176
|
+
]);
|
|
177
|
+
const lowerBound = Math.max(0, index - 16);
|
|
178
|
+
for (let cursor = index - 1; cursor >= lowerBound; cursor -= 1) {
|
|
179
|
+
const candidate = String(lines[cursor] || '').trim();
|
|
180
|
+
if (!candidate)
|
|
181
|
+
continue;
|
|
182
|
+
if (candidate.startsWith('#')) {
|
|
183
|
+
if (isSksModeLockMarker(candidate))
|
|
184
|
+
return true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const key = candidate.match(/^([A-Za-z0-9_-]+)\s*=/)?.[1] || '';
|
|
188
|
+
if (!allowedManagedKeys.has(key))
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
function isSksModeLockMarker(value = '') {
|
|
194
|
+
return /^#\s*(?:SKS|Sneakoscope)\b.*(?:moved machine-local Codex config|forced fast UI|managed (?:Codex )?(?:model|reasoning)|codex-lb (?:model|reasoning))/i.test(String(value || '').trim());
|
|
173
195
|
}
|
|
174
196
|
function mergeLostFastUiKeys(before, nextInput) {
|
|
175
197
|
let next = String(nextInput || '');
|
|
@@ -301,7 +323,7 @@ async function snapshotForConfig(root, configPath) {
|
|
|
301
323
|
async function backupCodexConfig(configPath, text, tag) {
|
|
302
324
|
try {
|
|
303
325
|
const backupPath = `${configPath}.sks-${tag}-${Date.now().toString(36)}.bak`;
|
|
304
|
-
await writeTextAtomic(backupPath, text);
|
|
326
|
+
await writeTextAtomic(backupPath, text, { mode: 0o600 });
|
|
305
327
|
await cleanupCodexConfigBackups(configPath, { keepPerTag: 3, maxAgeMs: 30 * 24 * 60 * 60 * 1000 }).catch(() => undefined);
|
|
306
328
|
return backupPath;
|
|
307
329
|
}
|
|
@@ -4,8 +4,12 @@ import { ensureDir, nowIso, readText, sha256, writeJsonAtomic, writeTextAtomic }
|
|
|
4
4
|
import { codexHome, scanProjectLocalForbiddenKeys, snapshotCodexAppUiState } from './codex-app-ui-state-snapshot.js';
|
|
5
5
|
import { assertCodexAppUiMutationAllowed } from './codex-app-ui-clobber-guard.js';
|
|
6
6
|
import { codexProviderModelUiStatus } from '../codex-app.js';
|
|
7
|
+
import { cleanupCodexConfigBackups } from '../codex/codex-config-toml.js';
|
|
7
8
|
export const CODEX_APP_FAST_UI_REPAIR_SCHEMA = 'sks.codex-app-fast-ui-repair.v1';
|
|
8
|
-
|
|
9
|
+
// `service_tier = "fast"` is the current Codex Desktop default-selection
|
|
10
|
+
// contract. Removing it would silently turn Fast mode off, not restore the
|
|
11
|
+
// selector. Only legacy SKS model/effort pins lock the native picker.
|
|
12
|
+
const FAST_UI_TOP_LEVEL_RE = /^\s*(?:model|model_reasoning_effort)\s*=/;
|
|
9
13
|
const FAST_UI_FEATURE_LINE_RE = /^\s*fast_mode\s*=/;
|
|
10
14
|
const FAST_UI_LEGACY_TABLES = new Set(['user.fast_mode', 'profiles.sks-fast-high']);
|
|
11
15
|
const SKS_CAUSED_RE = /(?:SKS|Sneakoscope|codex-lb|sks-mad|sks fast)/i;
|
|
@@ -20,6 +24,7 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
20
24
|
const actions = [];
|
|
21
25
|
const detectedProjectLocalForbiddenKeys = [];
|
|
22
26
|
const unsafeReasons = [];
|
|
27
|
+
let permissionsHardened = 0;
|
|
23
28
|
let detectedSksCausedMutation = false;
|
|
24
29
|
for (const candidate of candidates) {
|
|
25
30
|
const text = await readText(candidate.file, null);
|
|
@@ -27,34 +32,39 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
27
32
|
actions.push({ scope: candidate.scope, file: displayPath(candidate.file), status: 'missing', changed: false });
|
|
28
33
|
continue;
|
|
29
34
|
}
|
|
35
|
+
if (input.apply === true)
|
|
36
|
+
permissionsHardened += await hardenCodexConfigPermissions(candidate.file);
|
|
30
37
|
const repaired = candidate.mode === 'project_forbidden_keys'
|
|
31
38
|
? stripProjectLocalForbiddenKeys(text)
|
|
32
39
|
: stripSksCausedHostOwnedLines(text);
|
|
40
|
+
const candidateUnsafeReasons = detectUnsafeFastUiRepair(text);
|
|
41
|
+
unsafeReasons.push(...candidateUnsafeReasons);
|
|
33
42
|
if (candidate.mode === 'project_forbidden_keys')
|
|
34
43
|
detectedProjectLocalForbiddenKeys.push(...repaired.removedKeys);
|
|
35
44
|
if (candidate.mode === 'sks_caused_host_owned_keys') {
|
|
36
|
-
const unsafe = detectUnsafeFastUiRepair(text);
|
|
37
|
-
unsafeReasons.push(...unsafe);
|
|
38
45
|
if (repaired.text !== text)
|
|
39
46
|
detectedSksCausedMutation = true;
|
|
40
47
|
}
|
|
41
48
|
if (repaired.text === text) {
|
|
42
|
-
actions.push({ scope: candidate.scope, file: displayPath(candidate.file), status:
|
|
49
|
+
actions.push({ scope: candidate.scope, file: displayPath(candidate.file), status: candidateUnsafeReasons.length ? 'requires_confirmation' : 'ok', changed: false, removed_keys: repaired.removedKeys });
|
|
43
50
|
continue;
|
|
44
51
|
}
|
|
45
52
|
const backupPath = `${candidate.file}.codex-app-ui-repair-${Date.now().toString(36)}.bak`;
|
|
46
|
-
|
|
53
|
+
const applyAllowed = input.apply === true && (candidateUnsafeReasons.length === 0 || input.force === true);
|
|
54
|
+
if (applyAllowed) {
|
|
47
55
|
await ensureDir(path.dirname(candidate.file));
|
|
48
|
-
await fs.writeFile(backupPath, text, 'utf8');
|
|
56
|
+
await fs.writeFile(backupPath, text, { encoding: 'utf8', mode: 0o600 });
|
|
57
|
+
await fs.chmod(backupPath, 0o600);
|
|
49
58
|
assertCodexAppUiMutationAllowed({ kind: 'codex_app_ui_state', scope: 'codex-app-ui-repair', backupPath });
|
|
50
|
-
await writeTextAtomic(candidate.file, repaired.text);
|
|
59
|
+
await writeTextAtomic(candidate.file, repaired.text, { mode: 0o600 });
|
|
60
|
+
await cleanupCodexConfigBackups(candidate.file, { keepPerTag: 3, maxAgeMs: 30 * 24 * 60 * 60 * 1000 }).catch(() => undefined);
|
|
51
61
|
}
|
|
52
62
|
actions.push({
|
|
53
63
|
scope: candidate.scope,
|
|
54
64
|
file: displayPath(candidate.file),
|
|
55
|
-
status: input.apply ? '
|
|
65
|
+
status: applyAllowed ? 'repaired' : input.apply ? 'requires_confirmation' : 'planned',
|
|
56
66
|
changed: true,
|
|
57
|
-
backup_path:
|
|
67
|
+
backup_path: applyAllowed ? displayPath(backupPath) : null,
|
|
58
68
|
before_hash: sha256(text),
|
|
59
69
|
after_hash: sha256(repaired.text),
|
|
60
70
|
removed_keys: repaired.removedKeys
|
|
@@ -63,17 +73,24 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
63
73
|
const after = await snapshotCodexAppUiState(resolvedRoot, { codexHome: home });
|
|
64
74
|
const providerModelUi = await codexProviderModelUiStatus({
|
|
65
75
|
cwd: resolvedRoot,
|
|
76
|
+
env: input.env,
|
|
66
77
|
home: path.dirname(home),
|
|
67
78
|
configPath: path.join(home, 'config.toml'),
|
|
68
79
|
codexLbEnvPath: path.join(home, 'sks-codex-lb.env')
|
|
69
80
|
});
|
|
70
81
|
const changed = actions.some((action) => action.changed);
|
|
82
|
+
const applied = actions.some((action) => action.status === 'repaired');
|
|
83
|
+
const pending = actions.some((action) => action.changed && action.status !== 'repaired');
|
|
71
84
|
const requiresConfirmation = unsafeReasons.length > 0 && input.force !== true;
|
|
85
|
+
const selectedProviderBlockers = Array.isArray(providerModelUi.selected_provider_blockers)
|
|
86
|
+
? providerModelUi.selected_provider_blockers
|
|
87
|
+
: [];
|
|
72
88
|
const safeAutoApply = changed && !requiresConfirmation;
|
|
73
89
|
const manual = changed && !input.apply;
|
|
74
90
|
const blockers = [
|
|
75
91
|
...(requiresConfirmation ? ['codex_app_fast_ui_repair_requires_confirmation'] : []),
|
|
76
92
|
...(manual && !safeAutoApply ? ['codex_app_fast_ui_repair_requires_explicit_apply'] : []),
|
|
93
|
+
...selectedProviderBlockers.map((blocker) => `selected_provider:${blocker}`),
|
|
77
94
|
...(after.indicators.secret_leak_suspected ? ['codex_app_ui_repair_secret_leak_suspected'] : [])
|
|
78
95
|
];
|
|
79
96
|
const report = {
|
|
@@ -86,12 +103,15 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
86
103
|
detected_sks_caused_mutation: detectedSksCausedMutation,
|
|
87
104
|
detected_project_local_forbidden_keys: [...new Set(detectedProjectLocalForbiddenKeys)],
|
|
88
105
|
unsafe_repair_reasons: [...new Set(unsafeReasons)],
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
permissions_hardened: permissionsHardened,
|
|
107
|
+
fast_selector: pending ? 'manual_action_required' : applied ? 'repaired' : before.indicators.fast_selector === 'maybe_hidden_or_locked' ? 'manual_action_required' : 'ok',
|
|
108
|
+
provider_selector: selectedProviderBlockers.length ? 'manual_action_required' : 'selected_provider_ready',
|
|
91
109
|
provider_model_ui: providerModelUi,
|
|
92
110
|
provider_actions: providerModelUi.ui_actions || [],
|
|
93
111
|
provider_blockers: providerModelUi.blockers || [],
|
|
94
|
-
|
|
112
|
+
selected_provider_blockers: selectedProviderBlockers,
|
|
113
|
+
optional_provider_blockers: providerModelUi.optional_provider_blockers || [],
|
|
114
|
+
host_owned_config: applied && !pending ? 'repaired_with_backup' : changed ? 'preserved_until_explicit_apply' : 'preserved',
|
|
95
115
|
actions,
|
|
96
116
|
before_fast_selector: before.indicators.fast_selector,
|
|
97
117
|
after_fast_selector: after.indicators.fast_selector,
|
|
@@ -102,16 +122,27 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
102
122
|
await writeJsonAtomic(input.reportPath, report);
|
|
103
123
|
return report;
|
|
104
124
|
}
|
|
125
|
+
async function hardenCodexConfigPermissions(configFile) {
|
|
126
|
+
const dir = path.dirname(configFile);
|
|
127
|
+
const base = path.basename(configFile);
|
|
128
|
+
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
129
|
+
const targets = [configFile, ...entries
|
|
130
|
+
.filter((entry) => entry.isFile() && entry.name.startsWith(`${base}.`) && entry.name.endsWith('.bak'))
|
|
131
|
+
.map((entry) => path.join(dir, entry.name))];
|
|
132
|
+
let hardened = 0;
|
|
133
|
+
for (const target of targets) {
|
|
134
|
+
const stat = await fs.lstat(target).catch(() => null);
|
|
135
|
+
if (!stat?.isFile() || stat.isSymbolicLink())
|
|
136
|
+
continue;
|
|
137
|
+
if ((stat.mode & 0o777) !== 0o600) {
|
|
138
|
+
await fs.chmod(target, 0o600);
|
|
139
|
+
hardened += 1;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return hardened;
|
|
143
|
+
}
|
|
105
144
|
function detectUnsafeFastUiRepair(text) {
|
|
106
145
|
const reasons = [];
|
|
107
|
-
const lines = text.split(/\r?\n/);
|
|
108
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
109
|
-
const line = lines[i] || '';
|
|
110
|
-
const serviceTier = line.match(/^\s*service_tier\s*=\s*"(standard|flex)"\s*(?:#.*)?$/i)?.[1];
|
|
111
|
-
const sksMarked = SKS_CAUSED_RE.test(line) || SKS_CAUSED_RE.test(lines[i - 1] || '') || SKS_CAUSED_RE.test(lines[i + 1] || '');
|
|
112
|
-
if (serviceTier && !sksMarked)
|
|
113
|
-
reasons.push(`user_selected_service_tier_${serviceTier.toLowerCase()}`);
|
|
114
|
-
}
|
|
115
146
|
if (hasOddUnescapedQuotes(text))
|
|
116
147
|
reasons.push('unparseable_config_requires_manual_review');
|
|
117
148
|
return [...new Set(reasons)];
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { exists, runProcess, which } from '../fsx.js';
|
|
2
2
|
export async function restartCodexApp(opts = {}) {
|
|
3
3
|
const env = opts.env || process.env;
|
|
4
|
-
const appName = String(opts.appName || env.SKS_CODEX_APP_NAME || '
|
|
4
|
+
const appName = String(opts.appName || env.SKS_CODEX_APP_NAME || 'ChatGPT');
|
|
5
|
+
const bundleId = String(opts.bundleId || env.SKS_CODEX_APP_BUNDLE_ID || 'com.openai.codex');
|
|
5
6
|
if (opts.enabled === false || env.SKS_SKIP_CODEX_APP_RESTART === '1') {
|
|
6
7
|
return skipped(appName, 'disabled');
|
|
7
8
|
}
|
|
8
|
-
if (process.platform !== 'darwin')
|
|
9
|
+
if ((opts.platform || process.platform) !== 'darwin')
|
|
9
10
|
return skipped(appName, 'not_macos');
|
|
10
11
|
const run = opts.runProcessImpl || runProcess;
|
|
11
|
-
const osascript = await which('osascript').catch(() => null) || await exists('/usr/bin/osascript').then((ok) => ok ? '/usr/bin/osascript' : null).catch(() => null);
|
|
12
|
-
const open = await which('open').catch(() => null) || await exists('/usr/bin/open').then((ok) => ok ? '/usr/bin/open' : null).catch(() => null);
|
|
12
|
+
const osascript = opts.osascriptPath || await which('osascript').catch(() => null) || await exists('/usr/bin/osascript').then((ok) => ok ? '/usr/bin/osascript' : null).catch(() => null);
|
|
13
|
+
const open = opts.openPath || await which('open').catch(() => null) || await exists('/usr/bin/open').then((ok) => ok ? '/usr/bin/open' : null).catch(() => null);
|
|
13
14
|
if (!osascript || !open) {
|
|
14
15
|
return {
|
|
15
16
|
schema: 'sks.codex-app-restart.v1',
|
|
@@ -22,25 +23,54 @@ export async function restartCodexApp(opts = {}) {
|
|
|
22
23
|
]
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
|
-
const
|
|
26
|
+
const appTarget = bundleId ? `application id ${JSON.stringify(bundleId)}` : `application ${JSON.stringify(appName)}`;
|
|
27
|
+
const quit = await run(osascript, ['-e', `tell ${appTarget} to quit`], { timeoutMs: 5000, maxOutputBytes: 8192 }).catch((err) => ({
|
|
26
28
|
code: 1,
|
|
27
29
|
stdout: '',
|
|
28
30
|
stderr: err?.message || String(err)
|
|
29
31
|
}));
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
+
const quitOk = quit.code === 0 || /not running|Can't get application|application isn't running/i.test(String(quit.stderr || quit.stdout || ''));
|
|
33
|
+
const exitTimeoutMs = Math.max(250, Number(opts.exitTimeoutMs ?? env.SKS_CODEX_APP_EXIT_TIMEOUT_MS ?? 5000));
|
|
34
|
+
const pollMs = Math.max(50, Number(opts.pollMs ?? 100));
|
|
35
|
+
let exited = !quitOk;
|
|
36
|
+
let attempts = 0;
|
|
37
|
+
const deadline = Date.now() + exitTimeoutMs;
|
|
38
|
+
while (quitOk && Date.now() < deadline) {
|
|
39
|
+
attempts += 1;
|
|
40
|
+
const probe = await run(osascript, ['-e', `${appTarget} is running`], { timeoutMs: 1000, maxOutputBytes: 1024 }).catch(() => null);
|
|
41
|
+
if (probe?.code === 0 && String(probe.stdout || '').trim().toLowerCase() === 'false') {
|
|
42
|
+
exited = true;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
await sleep(pollMs);
|
|
46
|
+
}
|
|
47
|
+
if (quitOk && !exited) {
|
|
48
|
+
return {
|
|
49
|
+
schema: 'sks.codex-app-restart.v1',
|
|
50
|
+
ok: false,
|
|
51
|
+
status: 'blocked',
|
|
52
|
+
app_name: appName,
|
|
53
|
+
bundle_id: bundleId,
|
|
54
|
+
quit: { ok: true, code: quit.code, error: null },
|
|
55
|
+
exit_wait: { ok: false, attempts, error: 'codex_app_exit_timeout' },
|
|
56
|
+
blockers: ['codex_app_exit_timeout']
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
await sleep(Number(opts.delayMs ?? env.SKS_CODEX_APP_RESTART_DELAY_MS ?? 150));
|
|
60
|
+
const launched = await run(open, bundleId ? ['-b', bundleId] : ['-a', appName], { timeoutMs: 10000, maxOutputBytes: 8192 }).catch((err) => ({
|
|
32
61
|
code: 1,
|
|
33
62
|
stdout: '',
|
|
34
63
|
stderr: err?.message || String(err)
|
|
35
64
|
}));
|
|
36
|
-
const quitOk = quit.code === 0 || /not running|Can't get application|application isn't running/i.test(String(quit.stderr || quit.stdout || ''));
|
|
37
65
|
const openOk = launched.code === 0;
|
|
38
66
|
return {
|
|
39
67
|
schema: 'sks.codex-app-restart.v1',
|
|
40
68
|
ok: quitOk && openOk,
|
|
41
69
|
status: quitOk && openOk ? 'restarted' : 'blocked',
|
|
42
70
|
app_name: appName,
|
|
71
|
+
bundle_id: bundleId,
|
|
43
72
|
quit: { ok: quitOk, code: quit.code, error: quitOk ? null : String(quit.stderr || quit.stdout || '').trim() },
|
|
73
|
+
exit_wait: { ok: exited, attempts, error: exited ? null : 'codex_app_exit_timeout' },
|
|
44
74
|
open: { ok: openOk, code: launched.code, error: openOk ? null : String(launched.stderr || launched.stdout || '').trim() },
|
|
45
75
|
blockers: [
|
|
46
76
|
...(quitOk ? [] : ['codex_app_quit_failed']),
|
|
@@ -12,7 +12,7 @@ export const PROJECT_LOCAL_FORBIDDEN_CODEX_KEYS = [
|
|
|
12
12
|
'otel'
|
|
13
13
|
];
|
|
14
14
|
const HOST_OWNED_KEY_RE = /^(?:model|model_reasoning_effort|openai_base_url|chatgpt_base_url|apps_mcp_product_sku|model_provider|model_providers(?:\.|$)|notify|profile|profiles(?:\.|$)|experimental_realtime_ws_base_url|otel(?:\.|$)|features\.fast_mode|service_tier|user\.fast_mode(?:\.|$))/;
|
|
15
|
-
const SECRET_KEY_RE = /(?:key|token|secret|password|credential|cookie|authorization|bearer|refresh|access)/i;
|
|
15
|
+
const SECRET_KEY_RE = /(?:key|token|secret|password|credential|cookie|authorization|auth|bearer|refresh|access|headers?|env)/i;
|
|
16
16
|
export function codexHome(input) {
|
|
17
17
|
return path.resolve(String(input?.codexHome || process.env.CODEX_HOME || path.join(os.homedir(), '.codex')));
|
|
18
18
|
}
|
|
@@ -41,8 +41,6 @@ export async function snapshotCodexAppUiState(root = process.cwd(), input = {})
|
|
|
41
41
|
return true;
|
|
42
42
|
if ((signal.key_path === 'model' || signal.key_path === 'model_reasoning_effort') && signal.sks_related)
|
|
43
43
|
return true;
|
|
44
|
-
if (signal.key_path === 'service_tier' && signal.sks_related)
|
|
45
|
-
return true;
|
|
46
44
|
return false;
|
|
47
45
|
});
|
|
48
46
|
return {
|
|
@@ -98,6 +96,9 @@ export function scanTomlSignals(text) {
|
|
|
98
96
|
const tables = [];
|
|
99
97
|
let table = null;
|
|
100
98
|
const lines = text.split(/\r?\n/);
|
|
99
|
+
const firstTableIndex = lines.findIndex((line) => /^\s*\[/.test(line));
|
|
100
|
+
const topLevelLines = firstTableIndex === -1 ? lines : lines.slice(0, firstTableIndex);
|
|
101
|
+
const sksManagedTopLevel = topLevelLines.some((line) => /(?:SKS|Sneakoscope|codex-lb|sks fast)/i.test(line));
|
|
101
102
|
lines.forEach((lineText, index) => {
|
|
102
103
|
const tableMatch = lineText.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
|
|
103
104
|
if (tableMatch?.[1]) {
|
|
@@ -124,6 +125,7 @@ export function scanTomlSignals(text) {
|
|
|
124
125
|
fast_ui_related: /(?:fast_mode|service_tier|model_reasoning_effort|^model$)/i.test(keyPath) || /(?:fast|priority|default)/i.test(value),
|
|
125
126
|
provider_related: /(?:provider|base_url|auth|profile|openai|chatgpt|codex-lb)/i.test(lowerPath),
|
|
126
127
|
sks_related: /(?:sks|sneakoscope|codex-lb)/i.test(lineText)
|
|
128
|
+
|| (table == null && sksManagedTopLevel && (key === 'model' || key === 'model_reasoning_effort'))
|
|
127
129
|
});
|
|
128
130
|
});
|
|
129
131
|
return { signals, tables: [...new Set(tables)] };
|
|
@@ -252,7 +254,13 @@ function hostOwnedFingerprint(snapshot) {
|
|
|
252
254
|
function redactValuePreview(keyPath, value) {
|
|
253
255
|
if (SECRET_KEY_RE.test(keyPath))
|
|
254
256
|
return '<redacted>';
|
|
257
|
+
if (/^\s*\{/.test(value))
|
|
258
|
+
return '<redacted-object>';
|
|
259
|
+
if (/^\s*\[/.test(value))
|
|
260
|
+
return '<redacted-array>';
|
|
255
261
|
const normalized = value.replace(/^['"]|['"]$/g, '');
|
|
262
|
+
if (/^[a-z][a-z0-9+.-]*:\/\/[^/@\s]+@/i.test(normalized))
|
|
263
|
+
return '<redacted-url-credentials>';
|
|
256
264
|
if (SECRET_KEY_RE.test(normalized))
|
|
257
265
|
return '<redacted>';
|
|
258
266
|
return normalized.length > 80 ? `${normalized.slice(0, 77)}...` : normalized;
|
|
@@ -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,22 +1291,32 @@ ${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
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1294
|
+
guard code == 0, let json = json else {
|
|
1295
|
+
self.fastModeOnItem.state = .off
|
|
1296
|
+
self.fastModeOffItem.state = .off
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
guard let global = json["global"] as? [String: Any], let desktopFast = global["on"] as? Bool else {
|
|
1300
|
+
self.fastModeOnItem.state = .off
|
|
1301
|
+
self.fastModeOffItem.state = .off
|
|
1302
|
+
return
|
|
1303
|
+
}
|
|
1304
|
+
let fastMode = json["fast_mode"] as? Bool == true
|
|
1305
|
+
let serviceTier = (json["service_tier"] as? String)?.lowercased()
|
|
1306
|
+
let projectFast = fastMode || serviceTier == "fast" || serviceTier == "priority"
|
|
1307
|
+
self.fastModeOnItem.state = projectFast && desktopFast ? .on : .off
|
|
1308
|
+
self.fastModeOffItem.state = !projectFast && !desktopFast ? .on : .off
|
|
1259
1309
|
}
|
|
1260
1310
|
}
|
|
1261
1311
|
|
|
1262
1312
|
@objc func useCodexLb() {
|
|
1263
|
-
runSksBackground(["codex-lb", "use-codex-lb", "--json"], title: "Use codex-lb") { [weak self] code, _ in
|
|
1313
|
+
runSksBackground(["codex-lb", "use-codex-lb", "--restart-app", "--json"], title: "Use codex-lb") { [weak self] code, _ in
|
|
1264
1314
|
if code == 0 { self?.updateAuthModeChecks() }
|
|
1265
1315
|
}
|
|
1266
1316
|
}
|
|
1267
1317
|
|
|
1268
1318
|
@objc func useChatGptOAuth() {
|
|
1269
|
-
runSksBackground(["codex-lb", "use-oauth", "--json"], title: "Use ChatGPT OAuth") { [weak self] code, _ in
|
|
1319
|
+
runSksBackground(["codex-lb", "use-oauth", "--restart-app", "--json"], title: "Use ChatGPT OAuth") { [weak self] code, _ in
|
|
1270
1320
|
if code == 0 { self?.updateAuthModeChecks() }
|
|
1271
1321
|
}
|
|
1272
1322
|
}
|
|
@@ -1274,14 +1324,14 @@ ${codexLifecycleSource}
|
|
|
1274
1324
|
@objc func setCodexLbDomainAndKey() {
|
|
1275
1325
|
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
1326
|
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
|
|
1327
|
+
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
1328
|
if code == 0 { self?.updateAuthModeChecks() }
|
|
1279
1329
|
}
|
|
1280
1330
|
}
|
|
1281
1331
|
|
|
1282
1332
|
@objc func setOpenRouterKey() {
|
|
1283
1333
|
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")
|
|
1334
|
+
runSksBackground(["codex-app", "set-openrouter-key", "--api-key-stdin", "--restart-app", "--json"], title: "Set OpenRouter Key", stdinText: key + "\\n")
|
|
1285
1335
|
}
|
|
1286
1336
|
|
|
1287
1337
|
@objc func fastModeOn() {
|
|
@@ -1312,36 +1362,46 @@ ${codexLifecycleSource}
|
|
|
1312
1362
|
let urlString = "http://127.0.0.1:4477"
|
|
1313
1363
|
runProcess("/usr/bin/curl", ["-fsS", "--max-time", "1", urlString]) { [weak self] code, _ in
|
|
1314
1364
|
DispatchQueue.main.async {
|
|
1315
|
-
if code
|
|
1365
|
+
if code == 0 {
|
|
1366
|
+
if let url = URL(string: urlString) { NSWorkspace.shared.open(url) }
|
|
1367
|
+
} else {
|
|
1316
1368
|
self?.startSksDetached(["ui"], title: "SKS Dashboard")
|
|
1369
|
+
self?.waitForDashboard(urlString, attempts: 20)
|
|
1317
1370
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
func waitForDashboard(_ urlString: String, attempts: Int) {
|
|
1376
|
+
runProcess("/usr/bin/curl", ["-fsS", "--max-time", "1", urlString]) { [weak self] code, _ in
|
|
1377
|
+
if code == 0 {
|
|
1378
|
+
DispatchQueue.main.async {
|
|
1379
|
+
if let url = URL(string: urlString) { NSWorkspace.shared.open(url) }
|
|
1380
|
+
}
|
|
1381
|
+
} else if attempts > 1 {
|
|
1382
|
+
DispatchQueue.global().asyncAfter(deadline: .now() + 0.25) {
|
|
1383
|
+
self?.waitForDashboard(urlString, attempts: attempts - 1)
|
|
1384
|
+
}
|
|
1385
|
+
} else {
|
|
1386
|
+
DispatchQueue.main.async {
|
|
1387
|
+
showAlert("SKS Dashboard failed", informative: "Dashboard did not become ready at \(urlString). See View Last Log.")
|
|
1320
1388
|
}
|
|
1321
1389
|
}
|
|
1322
1390
|
}
|
|
1323
1391
|
}
|
|
1324
1392
|
|
|
1325
1393
|
@objc func openCodexSettings() {
|
|
1326
|
-
runProcess("/usr/bin/open", ["codex://settings"])
|
|
1394
|
+
runProcess("/usr/bin/open", ["codex://settings"]) { code, output in
|
|
1395
|
+
if code != 0 {
|
|
1396
|
+
DispatchQueue.main.async {
|
|
1397
|
+
showAlert("Open Codex Settings failed", informative: humanizeSksFailure(redactSecrets(output)))
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1327
1401
|
}
|
|
1328
1402
|
|
|
1329
1403
|
@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
|
-
}
|
|
1404
|
+
runSksBackground(["codex-app", "restart", "--json"], title: "Restart Codex")
|
|
1345
1405
|
}
|
|
1346
1406
|
|
|
1347
1407
|
@objc func viewLastLog() {
|