sneakoscope 8.0.2 → 8.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 +47 -23
- package/config/skills-hash-ledger.v1.json +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +55 -13
- package/dist/bin/sks-dispatch.js +1 -1
- package/dist/cli/command-manifest-lite.js +4 -0
- package/dist/cli/command-registry.js +4 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +187 -0
- package/dist/cli/install-helpers-codex-lb-config.js +74 -8
- package/dist/cli/install-helpers.js +89 -44
- package/dist/commands/codex-lb.js +117 -19
- package/dist/commands/doctor.js +697 -37
- package/dist/config/skills-manifest.json +3 -2
- package/dist/core/agents/agent-runner-codex-exec.js +5 -3
- package/dist/core/codex/codex-config-guard.js +238 -52
- package/dist/core/codex-adapter.js +5 -3
- package/dist/core/codex-app/menubar/constants.js +10 -1
- package/dist/core/codex-app/menubar/global-install.js +288 -0
- package/dist/core/codex-app/menubar/installer.js +79 -14
- package/dist/core/codex-app/menubar/launch-agent.js +267 -45
- package/dist/core/codex-app/menubar/paths.js +3 -0
- package/dist/core/codex-app/menubar/rollback.js +55 -7
- package/dist/core/codex-app/menubar/status.js +75 -4
- package/dist/core/codex-control/codex-0139-image-path-real-probe.js +3 -1
- package/dist/core/codex-control/codex-0139-web-search-probe.js +3 -1
- package/dist/core/codex-control/codex-app-server-runtime-env.js +13 -4
- package/dist/core/codex-control/codex-app-server-v2-client.js +3 -3
- package/dist/core/codex-exec-output-schema.js +4 -1
- package/dist/core/codex-lb/codex-lb-env.js +63 -72
- package/dist/core/codex-lb/codex-lb-setup.js +15 -2
- package/dist/core/codex-lb/desktop-center-credentials.js +517 -58
- package/dist/core/codex-lb/desktop-controller.js +153 -24
- package/dist/core/codex-lb/routing-truth.js +327 -0
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/commands/basic-cli.js +7 -1
- package/dist/core/commands/qa-loop-command.js +3 -1
- package/dist/core/commands/telegram-command.js +241 -0
- package/dist/core/config-adopt/config-adopt-command.js +35 -0
- package/dist/core/config-adopt/config-adopt.js +346 -0
- package/dist/core/config-adopt/index.js +3 -0
- package/dist/core/doctor/command-alias-cleanup.js +4 -1
- package/dist/core/doctor/current-project-guidance-nested.js +37 -6
- package/dist/core/doctor/current-project-guidance.js +11 -6
- package/dist/core/doctor/doctor-readiness-matrix.js +14 -0
- package/dist/core/doctor/retired-managed-residue-private.js +1 -1
- package/dist/core/doctor/sks-menubar-doctor.js +4 -0
- package/dist/core/feature-fixtures.js +8 -0
- package/dist/core/hooks-runtime/hook-context.js +9 -0
- package/dist/core/hooks-runtime/managed-skill-admission.js +54 -4
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +69 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +2 -2
- package/dist/core/hooks-runtime/subagent-skill-availability-evidence.js +3 -3
- package/dist/core/hooks-runtime/subagent-skill-availability-guards.js +43 -5
- package/dist/core/hooks-runtime/subagent-skill-availability.js +64 -30
- package/dist/core/hooks-runtime/subagent-stop-hook.js +15 -2
- package/dist/core/hooks-runtime.js +148 -4
- package/dist/core/imagegen/imagegen-capability.js +46 -0
- package/dist/core/imagegen/require-imagegen.js +2 -2
- package/dist/core/init/skills.js +17 -3
- package/dist/core/install/installed-package-smoke.js +8 -2
- package/dist/core/naruto/hardware-capacity-probe.js +9 -3
- package/dist/core/naruto/naruto-concurrency-governor.js +68 -24
- package/dist/core/perf/import-graph-budget.js +0 -1
- package/dist/core/product-design-app-server.js +13 -1
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-pack-receipt.js +1 -2
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +1 -1
- package/dist/core/security/private-credential-file.js +209 -0
- package/dist/core/subagents/agent-catalog.js +17 -32
- package/dist/core/subagents/naruto-help-contract.js +5 -5
- package/dist/core/subagents/official-subagent-config.js +16 -6
- package/dist/core/subagents/official-subagent-preparation.js +34 -15
- package/dist/core/subagents/official-subagent-prompt.js +4 -4
- package/dist/core/subagents/official-subagent-runner.js +27 -4
- package/dist/core/subagents/thread-budget.js +19 -12
- package/dist/core/subagents/wave-lifecycle.js +18 -6
- package/dist/core/telegram/access.js +54 -0
- package/dist/core/telegram/client.js +144 -0
- package/dist/core/telegram/confirmation.js +37 -0
- package/dist/core/telegram/doctor.js +151 -0
- package/dist/core/telegram/index.js +9 -0
- package/dist/core/telegram/keychain.js +265 -0
- package/dist/core/telegram/liveness.js +109 -0
- package/dist/core/telegram/poller.js +90 -0
- package/dist/core/telegram/redaction.js +15 -0
- package/dist/core/telegram/types.js +1 -0
- package/dist/core/update/update-diagnostics.js +52 -0
- package/dist/core/update/update-migration-state.js +5 -1
- package/dist/core/update/update-operation.js +2 -1
- package/dist/core/update/update-stage-diagnostics.js +38 -0
- package/dist/core/update/update-status.js +2 -9
- package/dist/core/update-check.js +147 -31
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +23 -4
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +13 -0
- package/dist/native/sks-menubar/Sources/AppIdentity.swift +7 -0
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +3 -2
- package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +115 -0
- package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +17 -0
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +190 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +100 -59
- package/dist/native/sks-menubar/Sources/SingletonInstanceGuard.swift +328 -0
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileStore.swift +320 -0
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileSupport.swift +303 -0
- package/dist/native/sks-menubar/Sources/TelegramProcessGateway.swift +238 -0
- package/dist/native/sks-menubar/Sources/TelegramRuntimeSupport.swift +56 -0
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
- package/dist/native/sks-menubar/Sources/TelegramTransport.swift +474 -0
- package/dist/native/sks-menubar/Sources/main.swift +15 -1
- package/dist/native/sks-menubar/Tests/OperationCoordinatorTests.swift +4 -3
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +129 -0
- package/dist/native/sks-menubar/Tests/SingletonInstanceGuardTests.swift +183 -0
- package/dist/native/sks-menubar/Tests/TelegramRuntimeSmokeTests.swift +267 -0
- package/dist/native/sks-menubar/Tests/TelegramRuntimeTests.swift +222 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +1 -1
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +1 -1
- package/dist/scripts/current-command-surface-check.js +2 -4
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +1 -2
- package/package.json +1 -1
|
@@ -4,16 +4,16 @@ import path from 'node:path';
|
|
|
4
4
|
import { exists, PACKAGE_VERSION, readJson, readText, sha256 } from '../../fsx.js';
|
|
5
5
|
import { smokeSksMenuBarAction } from './action-runner.js';
|
|
6
6
|
import { isCodexAppRunningByBundleId, readMenuBarConfig } from './config.js';
|
|
7
|
-
import { inspectLaunchdService,
|
|
7
|
+
import { inspectLaunchdService, inspectRunningMenuBarProcess, removeLaunchAgent, restartLaunchAgent, waitForRunningMenuBarVersion } from './launch-agent.js';
|
|
8
8
|
import { sksMenuBarPaths } from './paths.js';
|
|
9
9
|
import { inspectInstalledResources } from './resources.js';
|
|
10
10
|
import { inspectMenuBarArtifactSet } from './rollback.js';
|
|
11
11
|
import { inspectSignature } from './signature.js';
|
|
12
12
|
import { cleanupRetiredRemoteBridgeLaunchAgent, quarantineRetiredRemoteBridgeBindings } from './migration.js';
|
|
13
|
+
import { inspectProjectMenuBarCanonicalState } from './global-install.js';
|
|
13
14
|
export async function inspectSksMenuBarStatus(opts = {}) {
|
|
14
15
|
const paths = sksMenuBarPaths(opts.home || opts.env?.HOME, opts.root);
|
|
15
16
|
const installed = await exists(paths.executable_path);
|
|
16
|
-
const running = installed ? await isMenuBarProcessRunning(paths.executable_path, opts.env) : false;
|
|
17
17
|
const actionText = await readText(paths.action_script_path, '');
|
|
18
18
|
const nodeBin = shellAssignment(actionText, 'NODE_BIN');
|
|
19
19
|
const sksEntry = shellAssignment(actionText, 'SKS_ENTRY');
|
|
@@ -25,6 +25,23 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
25
25
|
const config = await readMenuBarConfig(paths.config_path);
|
|
26
26
|
const codexRunning = config.codex_bundle_id ? await isCodexAppRunningByBundleId(config.codex_bundle_id, opts.env) : null;
|
|
27
27
|
const launchd = await inspectLaunchdService(opts.env);
|
|
28
|
+
const runningProcess = await inspectRunningMenuBarProcess(paths, launchd.pid, opts.env);
|
|
29
|
+
const running = runningProcess.ok;
|
|
30
|
+
const installedVersion = buildStamp?.package_version || null;
|
|
31
|
+
const persistedVersionProbe = await readJson(paths.version_probe_path, null);
|
|
32
|
+
let versionProbe = evaluatePersistedMenuBarVersionProbe({
|
|
33
|
+
probe: persistedVersionProbe,
|
|
34
|
+
expectedVersion,
|
|
35
|
+
runningProcess,
|
|
36
|
+
persistedPath: paths.version_probe_path
|
|
37
|
+
});
|
|
38
|
+
if (!versionProbe.ok
|
|
39
|
+
&& runningProcess.ok
|
|
40
|
+
&& runningProcess.package_version === expectedVersion
|
|
41
|
+
&& (versionProbe.error === 'menubar_version_probe_missing'
|
|
42
|
+
|| versionProbe.error === 'menubar_version_probe_stale')) {
|
|
43
|
+
versionProbe = await waitForRunningMenuBarVersion(paths, expectedVersion, launchd.pid, opts.env);
|
|
44
|
+
}
|
|
28
45
|
const legacyVerification = buildStamp?.legacy_v1
|
|
29
46
|
? await inspectMenuBarArtifactSet({
|
|
30
47
|
appPath: paths.app_path,
|
|
@@ -36,15 +53,30 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
36
53
|
: null;
|
|
37
54
|
const signature = legacyVerification?.signature || await inspectSignature(paths.app_path, opts.env);
|
|
38
55
|
const resources = legacyVerification?.resources || await inspectInstalledResources({ resourcesDir: paths.resources_path, buildStamp });
|
|
56
|
+
const canonicalState = await inspectProjectMenuBarCanonicalState({
|
|
57
|
+
paths,
|
|
58
|
+
root: paths.root,
|
|
59
|
+
...(opts.env ? { env: opts.env } : {})
|
|
60
|
+
});
|
|
39
61
|
const blockers = [];
|
|
40
62
|
const warnings = [];
|
|
41
|
-
const runtime = evaluateMenuBarRuntimeReadiness({
|
|
63
|
+
const runtime = evaluateMenuBarRuntimeReadiness({
|
|
64
|
+
installed,
|
|
65
|
+
running,
|
|
66
|
+
verifiedProcessMatchesExpectedVersion: runningProcess.ok
|
|
67
|
+
&& runningProcess.package_version === expectedVersion,
|
|
68
|
+
launchd
|
|
69
|
+
});
|
|
42
70
|
if (!installed)
|
|
43
71
|
blockers.push('menubar_app_missing');
|
|
44
72
|
if (runtime.blocker)
|
|
45
73
|
blockers.push(runtime.blocker);
|
|
46
74
|
if (runtime.warning)
|
|
47
75
|
warnings.push(runtime.warning);
|
|
76
|
+
if (installed && launchd.checked && launchd.ok && !runningProcess.ok)
|
|
77
|
+
blockers.push('menubar_runtime_identity_unverified');
|
|
78
|
+
if (installed && launchd.checked && launchd.ok && !versionProbe.ok)
|
|
79
|
+
blockers.push(versionProbe.error || 'menubar_version_probe_failed');
|
|
48
80
|
if (installed && !actionSmoke.executable)
|
|
49
81
|
blockers.push('action_script_not_executable');
|
|
50
82
|
if (installed && !actionSmoke.ok)
|
|
@@ -63,11 +95,21 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
63
95
|
blockers.push('menubar_install_transaction_pending');
|
|
64
96
|
if (await exists(paths.rollback_transaction_path))
|
|
65
97
|
blockers.push('menubar_rollback_transaction_pending');
|
|
98
|
+
blockers.push(...canonicalState.blockers);
|
|
99
|
+
if (canonicalState.verified_duplicates.length > 0)
|
|
100
|
+
blockers.push('menubar_canonical_only_verified_duplicate_present');
|
|
101
|
+
if (canonicalState.unverified_collisions.length > 0)
|
|
102
|
+
blockers.push('menubar_canonical_only_unverified_duplicate_present');
|
|
103
|
+
warnings.push(...canonicalState.warnings);
|
|
66
104
|
if (!config.codex_bundle_id)
|
|
67
105
|
warnings.push('codex_sync_disabled');
|
|
68
106
|
return {
|
|
69
107
|
schema: 'sks.menubar-status.v1', ok: blockers.length === 0, platform: process.platform,
|
|
70
|
-
installed, running,
|
|
108
|
+
installed, running, installed_version: installedVersion, running_process: runningProcess,
|
|
109
|
+
menubar_version_probe: versionProbe,
|
|
110
|
+
duplicate_install_candidates: canonicalState.candidate_paths,
|
|
111
|
+
paths,
|
|
112
|
+
launchd,
|
|
71
113
|
action_target: {
|
|
72
114
|
node_bin: nodeBin,
|
|
73
115
|
node_exists: nodeBin ? await isExecutable(nodeBin) : false,
|
|
@@ -94,10 +136,39 @@ export async function inspectSksMenuBarStatus(opts = {}) {
|
|
|
94
136
|
next_actions: blockers.length ? defaultNextActions() : ['sks menubar status --json']
|
|
95
137
|
};
|
|
96
138
|
}
|
|
139
|
+
export function evaluatePersistedMenuBarVersionProbe(input) {
|
|
140
|
+
const base = {
|
|
141
|
+
schema: 'sks.menubar-version-probe.v1',
|
|
142
|
+
checked: true,
|
|
143
|
+
expected_version: input.expectedVersion,
|
|
144
|
+
running_version: input.runningProcess.package_version,
|
|
145
|
+
pid: input.runningProcess.pid,
|
|
146
|
+
generated_at: input.probe?.generated_at || new Date(0).toISOString(),
|
|
147
|
+
persisted_path: input.persistedPath
|
|
148
|
+
};
|
|
149
|
+
if (!input.probe || input.probe.schema !== 'sks.menubar-version-probe.v1') {
|
|
150
|
+
return { ...base, ok: false, error: 'menubar_version_probe_missing' };
|
|
151
|
+
}
|
|
152
|
+
if (!input.runningProcess.ok) {
|
|
153
|
+
return { ...base, ok: false, error: input.runningProcess.error || 'menubar_runtime_identity_unverified' };
|
|
154
|
+
}
|
|
155
|
+
if (input.probe.expected_version !== input.expectedVersion
|
|
156
|
+
|| input.probe.running_version !== input.runningProcess.package_version
|
|
157
|
+
|| input.probe.pid !== input.runningProcess.pid) {
|
|
158
|
+
return { ...base, ok: false, error: 'menubar_version_probe_stale' };
|
|
159
|
+
}
|
|
160
|
+
if (input.runningProcess.package_version !== input.expectedVersion) {
|
|
161
|
+
return { ...base, ok: false, error: 'menubar_running_version_mismatch' };
|
|
162
|
+
}
|
|
163
|
+
return { ...base, generated_at: input.probe.generated_at, ok: true, error: null };
|
|
164
|
+
}
|
|
97
165
|
export function evaluateMenuBarRuntimeReadiness(input) {
|
|
98
166
|
if (!input.installed || !input.launchd.checked || input.launchd.ok) {
|
|
99
167
|
return { blocker: null, warning: null };
|
|
100
168
|
}
|
|
169
|
+
if (input.running && input.verifiedProcessMatchesExpectedVersion === true) {
|
|
170
|
+
return { blocker: null, warning: 'launchd_not_running_process_active' };
|
|
171
|
+
}
|
|
101
172
|
return {
|
|
102
173
|
blocker: 'launchd_not_running',
|
|
103
174
|
warning: input.running ? 'launchd_not_running_process_active' : null
|
|
@@ -4,6 +4,7 @@ import { buildCodexExecArgs, findCodexBinary, runCodexExec } from '../codex-adap
|
|
|
4
4
|
import { ensureDir, runProcess, writeBinaryAtomic } from '../fsx.js';
|
|
5
5
|
import { buildImageArtifactPathContract } from '../image/image-artifact-path-contract.js';
|
|
6
6
|
import { codex0139ProbeTail, skippedCodex0139Probe } from './codex-0139-real-probes.js';
|
|
7
|
+
import { prepareCodexAppServerRuntimeEnv } from './codex-app-server-runtime-env.js';
|
|
7
8
|
const ONE_BY_ONE_PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4//8/AAX+Av4N70a4AAAAAElFTkSuQmCC', 'base64');
|
|
8
9
|
export async function runCodex0139ImageReferencedPathRealProbe(input) {
|
|
9
10
|
const started = Date.now();
|
|
@@ -55,6 +56,7 @@ export async function runCodex0139ImageReferencedPathRealProbe(input) {
|
|
|
55
56
|
].join(' ');
|
|
56
57
|
const extraArgs = ['-c', 'mcp_servers={}', '--image', inputB, '--skip-git-repo-check', '--ephemeral'];
|
|
57
58
|
const args = buildCodexExecArgs({ root: tempDir, prompt, outputFile, json: true, extraArgs });
|
|
59
|
+
const runtimeEnv = await prepareCodexAppServerRuntimeEnv({ env: input.env || process.env });
|
|
58
60
|
const result = await runCodexExec({
|
|
59
61
|
root: tempDir,
|
|
60
62
|
recoveryRoot: input.root,
|
|
@@ -65,7 +67,7 @@ export async function runCodex0139ImageReferencedPathRealProbe(input) {
|
|
|
65
67
|
timeoutMs: input.timeoutMs || 60000,
|
|
66
68
|
maxBufferBytes: 512 * 1024,
|
|
67
69
|
codexBin,
|
|
68
|
-
env:
|
|
70
|
+
env: runtimeEnv,
|
|
69
71
|
...(typeof input.recoveryFetch === 'function' ? { recoveryFetch: input.recoveryFetch } : {}),
|
|
70
72
|
...(typeof input.runProcessImpl === 'function' ? { runProcessImpl: input.runProcessImpl } : {})
|
|
71
73
|
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { buildCodexExecArgs, findCodexBinary, runCodexExec } from '../codex-adapter.js';
|
|
4
4
|
import { ensureDir, runProcess, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
5
5
|
import { codex0139ProbeTail, skippedCodex0139Probe } from './codex-0139-real-probes.js';
|
|
6
|
+
import { prepareCodexAppServerRuntimeEnv } from './codex-app-server-runtime-env.js';
|
|
6
7
|
export async function runCodex0139WebSearchRealProbe(input) {
|
|
7
8
|
const started = Date.now();
|
|
8
9
|
if (!input.allowNetwork) {
|
|
@@ -18,6 +19,7 @@ export async function runCodex0139WebSearchRealProbe(input) {
|
|
|
18
19
|
const outputFile = path.join(tempDir, 'last-message.txt');
|
|
19
20
|
const prompt = 'In code mode, use standalone web search to find the title of https://example.com. Return JSON {"used_web_search":true,"answer":"...","sources":[...]}.';
|
|
20
21
|
const args = buildCodexExecArgs({ root: tempDir, prompt, outputFile, json: true, extraArgs: ['-c', 'mcp_servers={}'] });
|
|
22
|
+
const runtimeEnv = await prepareCodexAppServerRuntimeEnv({ env: input.env || process.env });
|
|
21
23
|
const result = await runCodexExec({
|
|
22
24
|
root: tempDir,
|
|
23
25
|
recoveryRoot: input.root,
|
|
@@ -30,7 +32,7 @@ export async function runCodex0139WebSearchRealProbe(input) {
|
|
|
30
32
|
stdoutFile: path.join(tempDir, 'codex.stdout.log'),
|
|
31
33
|
stderrFile: path.join(tempDir, 'codex.stderr.log'),
|
|
32
34
|
codexBin,
|
|
33
|
-
env:
|
|
35
|
+
env: runtimeEnv,
|
|
34
36
|
...(typeof input.recoveryFetch === 'function' ? { recoveryFetch: input.recoveryFetch } : {}),
|
|
35
37
|
...(typeof input.runProcessImpl === 'function' ? { runProcessImpl: input.runProcessImpl } : {})
|
|
36
38
|
}).catch((err) => ({
|
|
@@ -7,12 +7,21 @@ export const CODEX_APP_SERVER_PROVIDER_CREDENTIALS_UNAVAILABLE = 'codex_app_serv
|
|
|
7
7
|
export const CODEX_APP_SERVER_CONFIG_READ_FAILED = 'codex_app_server_config_read_failed';
|
|
8
8
|
export class CodexAppServerRuntimeEnvError extends Error {
|
|
9
9
|
code;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
operatorActions;
|
|
11
|
+
constructor(code, operatorActions = []) {
|
|
12
|
+
super(operatorActions.length > 0 ? `${code}: ${operatorActions.join(' ')}` : code);
|
|
12
13
|
this.code = code;
|
|
14
|
+
this.operatorActions = operatorActions;
|
|
13
15
|
this.name = 'CodexAppServerRuntimeEnvError';
|
|
14
16
|
}
|
|
15
17
|
}
|
|
18
|
+
export function codexAppServerProviderCredentialGuidance(home) {
|
|
19
|
+
return [
|
|
20
|
+
`Store the key in ${path.join(home, '.codex', 'sks-codex-lb.env')} (owner-only mode 0600).`,
|
|
21
|
+
'Run: sks codex-lb setup --host <domain> --api-key-stdin --yes',
|
|
22
|
+
'Alternatively, provide CODEX_LB_API_KEY in the launching environment.'
|
|
23
|
+
];
|
|
24
|
+
}
|
|
16
25
|
export function codexAppServerExecutablePath(input) {
|
|
17
26
|
const home = path.resolve(input.home || process.env.HOME || os.homedir());
|
|
18
27
|
const candidates = [
|
|
@@ -75,10 +84,10 @@ export async function prepareCodexAppServerRuntimeEnv(input = {}) {
|
|
|
75
84
|
});
|
|
76
85
|
}
|
|
77
86
|
catch {
|
|
78
|
-
throw new CodexAppServerRuntimeEnvError(CODEX_APP_SERVER_PROVIDER_CREDENTIALS_UNAVAILABLE);
|
|
87
|
+
throw new CodexAppServerRuntimeEnvError(CODEX_APP_SERVER_PROVIDER_CREDENTIALS_UNAVAILABLE, codexAppServerProviderCredentialGuidance(home));
|
|
79
88
|
}
|
|
80
89
|
if (!loaded?.configured || !loaded.secret_api_key || !loaded.base_url) {
|
|
81
|
-
throw new CodexAppServerRuntimeEnvError(CODEX_APP_SERVER_PROVIDER_CREDENTIALS_UNAVAILABLE);
|
|
90
|
+
throw new CodexAppServerRuntimeEnvError(CODEX_APP_SERVER_PROVIDER_CREDENTIALS_UNAVAILABLE, codexAppServerProviderCredentialGuidance(home));
|
|
82
91
|
}
|
|
83
92
|
env.CODEX_LB_API_KEY = loaded.secret_api_key;
|
|
84
93
|
env.CODEX_LB_BASE_URL = loaded.base_url;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { nowIso, PACKAGE_VERSION } from '../fsx.js';
|
|
3
3
|
import { resolveCodexRuntime } from '../codex-runtime/resolve-codex-runtime.js';
|
|
4
|
+
import { prepareCodexAppServerRuntimeEnv } from './codex-app-server-runtime-env.js';
|
|
4
5
|
export class CodexAppServerRequestError extends Error {
|
|
5
6
|
method;
|
|
6
7
|
kind;
|
|
@@ -333,13 +334,12 @@ export async function createCodexAppServerV2Client(options = {}) {
|
|
|
333
334
|
});
|
|
334
335
|
if (!runtime.identity)
|
|
335
336
|
throw new Error(`Codex runtime not found: ${runtime.blockers.join(',')}`);
|
|
336
|
-
const
|
|
337
|
+
const runtimeEnv = await prepareCodexAppServerRuntimeEnv({ env: options.env || process.env });
|
|
338
|
+
const clientOptions = { command: runtime.identity.realpath, env: runtimeEnv };
|
|
337
339
|
if (options.args !== undefined)
|
|
338
340
|
clientOptions.args = options.args;
|
|
339
341
|
if (options.cwd !== undefined)
|
|
340
342
|
clientOptions.cwd = options.cwd;
|
|
341
|
-
if (options.env !== undefined)
|
|
342
|
-
clientOptions.env = options.env;
|
|
343
343
|
if (options.timeoutMs !== undefined)
|
|
344
344
|
clientOptions.timeoutMs = options.timeoutMs;
|
|
345
345
|
if (options.maxFrameBytes !== undefined)
|
|
@@ -4,6 +4,7 @@ import { ensureDir, exists, packageRoot, readJson, runProcess, which } from './f
|
|
|
4
4
|
import { codexVersionPolicy, compareSemverLike, parseCodexVersionText } from './codex-compat/codex-version-policy.js';
|
|
5
5
|
import { validateJsonSchemaRecursive } from './json-schema-validator.js';
|
|
6
6
|
import { inspectCodexLbCliLaunchRecovery } from './codex-control/codex-lb-launch-recovery.js';
|
|
7
|
+
import { prepareCodexAppServerRuntimeEnv } from './codex-control/codex-app-server-runtime-env.js';
|
|
7
8
|
export async function detectCodexExecOutputSchemaSyntax(opts = {}) {
|
|
8
9
|
const codexBin = opts.codexBin || await which('codex').catch(() => null);
|
|
9
10
|
if (!codexBin) {
|
|
@@ -169,7 +170,9 @@ export async function buildCodexExecResumeOutputSchemaArgs(input) {
|
|
|
169
170
|
}
|
|
170
171
|
export async function runCodexExecResumeWithOutputSchema(input, opts = {}) {
|
|
171
172
|
const root = opts.cwd || packageRoot();
|
|
172
|
-
const env = opts.
|
|
173
|
+
const env = await (opts.prepareCodexRuntimeEnvImpl || prepareCodexAppServerRuntimeEnv)({
|
|
174
|
+
env: opts.env || process.env
|
|
175
|
+
});
|
|
173
176
|
const toolOutputRecovery = await inspectCodexLbCliLaunchRecovery({
|
|
174
177
|
root,
|
|
175
178
|
env,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
+
import fsp from 'node:fs/promises';
|
|
3
4
|
import { exists, readJson, readText } from '../fsx.js';
|
|
4
5
|
import { escapeRegExp } from '../text/regex.js';
|
|
6
|
+
import { PrivateCredentialFileError, readPrivateCredentialFile } from '../security/private-credential-file.js';
|
|
5
7
|
export const CODEX_LB_SECURE_KEYCHAIN_SERVICE = 'com.sneakoscope.codex-lb.api-key.v2';
|
|
6
8
|
export const CODEX_LB_LEGACY_KEYCHAIN_SERVICE = 'sks-codex-lb';
|
|
7
9
|
export function codexLbEnvPath(home = process.env.HOME || os.homedir()) {
|
|
@@ -158,15 +160,13 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
158
160
|
if (opts.allowProjectSecrets)
|
|
159
161
|
sourcePriority.push('project-local');
|
|
160
162
|
const processEnv = pickEnv(opts.processEnv || process.env);
|
|
161
|
-
const envFile = await readEnvFile(envPaths[0]);
|
|
162
|
-
const projectLocal = opts.allowProjectSecrets
|
|
163
|
+
const envFile = await readEnvFile(envPaths[0], { privateFile: true });
|
|
164
|
+
const projectLocal = opts.allowProjectSecrets
|
|
165
|
+
? await readEnvFile(path.join(opts.root || process.cwd(), '.env'))
|
|
166
|
+
: {};
|
|
163
167
|
const metadata = await readCodexLbCredentialMetadata(metadataPath);
|
|
164
|
-
const keychain = await keychainReadIsNeeded(opts, metadata, [envFile.apiKey, processEnv.apiKey, projectLocal.apiKey])
|
|
165
|
-
? await readMacKeychain(opts)
|
|
166
|
-
: { checked: false, available: false, status: 'skipped_not_needed', apiKey: '' };
|
|
167
168
|
const apiKeyCandidates = [
|
|
168
169
|
{ source: 'env-file', apiKey: envFile.apiKey },
|
|
169
|
-
{ source: 'keychain', apiKey: keychain.apiKey },
|
|
170
170
|
{ source: 'process.env', apiKey: processEnv.apiKey },
|
|
171
171
|
...(opts.allowProjectSecrets ? [{ source: 'project-local', apiKey: String(projectLocal.apiKey || '') }] : [])
|
|
172
172
|
].filter((candidate) => Boolean(candidate.apiKey));
|
|
@@ -174,9 +174,7 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
174
174
|
...candidate,
|
|
175
175
|
sha256: await sha256Full(candidate.apiKey)
|
|
176
176
|
})));
|
|
177
|
-
const selectedApiKey =
|
|
178
|
-
? candidateFingerprints.find((candidate) => candidate.sha256 === metadata.apiKeySha256) || candidateFingerprints[0]
|
|
179
|
-
: candidateFingerprints[0];
|
|
177
|
+
const selectedApiKey = candidateFingerprints[0];
|
|
180
178
|
const keySource = selectedApiKey?.source || 'missing';
|
|
181
179
|
const apiKey = selectedApiKey?.apiKey || '';
|
|
182
180
|
const configuredBaseUrl = normalizeCodexLbBaseUrl(envFile.baseUrl || processEnv.baseUrl || projectLocal.baseUrl || '');
|
|
@@ -187,20 +185,23 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
187
185
|
apiKeySha256,
|
|
188
186
|
configuredBaseUrl
|
|
189
187
|
});
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
: keychain.status === 'keychain_acl_helper_unavailable'
|
|
195
|
-
? ['codex_lb_keychain_acl_helper_unavailable']
|
|
196
|
-
: [];
|
|
188
|
+
const resolutionBlockers = [
|
|
189
|
+
...envFile.blockers,
|
|
190
|
+
...(apiKey ? [] : ['codex_lb_api_key_missing'])
|
|
191
|
+
];
|
|
197
192
|
const baseUrl = binding.present && metadata.baseUrl ? metadata.baseUrl : configuredBaseUrl;
|
|
198
|
-
const apiKeyUsable = Boolean(apiKey) && binding.blockers.length === 0 &&
|
|
193
|
+
const apiKeyUsable = Boolean(apiKey) && binding.blockers.length === 0 && resolutionBlockers.length === 0;
|
|
199
194
|
const missing = [
|
|
200
195
|
...(apiKey ? [] : ['CODEX_LB_API_KEY']),
|
|
201
196
|
...(baseUrl ? [] : ['CODEX_LB_BASE_URL']),
|
|
202
197
|
...(binding.blockers.length ? ['CODEX_LB_CREDENTIAL_BINDING'] : []),
|
|
203
|
-
...(
|
|
198
|
+
...(envFile.blockers.length ? ['CODEX_LB_CREDENTIAL_FILE'] : [])
|
|
199
|
+
];
|
|
200
|
+
const guidance = [
|
|
201
|
+
...envFile.guidance,
|
|
202
|
+
...(!apiKey
|
|
203
|
+
? [`Configure CODEX_LB_API_KEY with \`sks codex-lb setup --host <domain> --api-key-stdin --yes\`, put it in ${envPaths[0]} (mode 0600), or export CODEX_LB_API_KEY.`]
|
|
204
|
+
: [])
|
|
204
205
|
];
|
|
205
206
|
return {
|
|
206
207
|
schema: 'sks.codex-lb-env.v1',
|
|
@@ -217,32 +218,20 @@ export async function loadCodexLbEnv(opts = {}) {
|
|
|
217
218
|
fingerprint: apiKeySha256 ? apiKeySha256.slice(0, 16) : null
|
|
218
219
|
},
|
|
219
220
|
secret_api_key: apiKeyUsable ? apiKey : null,
|
|
221
|
+
blockers: [...new Set([...binding.blockers, ...resolutionBlockers])],
|
|
222
|
+
guidance: [...new Set(guidance)],
|
|
220
223
|
credential_binding: {
|
|
221
224
|
...binding,
|
|
222
|
-
blockers: [...binding.blockers, ...
|
|
225
|
+
blockers: [...new Set([...binding.blockers, ...resolutionBlockers])]
|
|
223
226
|
},
|
|
224
227
|
env_paths: envPaths,
|
|
225
228
|
keychain: {
|
|
226
|
-
checked:
|
|
227
|
-
available:
|
|
228
|
-
status:
|
|
229
|
+
checked: false,
|
|
230
|
+
available: false,
|
|
231
|
+
status: 'not_used'
|
|
229
232
|
}
|
|
230
233
|
};
|
|
231
234
|
}
|
|
232
|
-
async function keychainReadIsNeeded(opts, metadata, nonKeychainKeys) {
|
|
233
|
-
if (opts.forceKeychainRead === true)
|
|
234
|
-
return true;
|
|
235
|
-
const candidates = nonKeychainKeys.map((key) => String(key || '').trim()).filter(Boolean);
|
|
236
|
-
if (candidates.length === 0)
|
|
237
|
-
return true;
|
|
238
|
-
if (!metadata.valid || !metadata.apiKeySha256)
|
|
239
|
-
return false;
|
|
240
|
-
for (const candidate of candidates) {
|
|
241
|
-
if ((await sha256Full(candidate)) === metadata.apiKeySha256)
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
return true;
|
|
245
|
-
}
|
|
246
235
|
async function readCodexLbCredentialMetadata(file) {
|
|
247
236
|
if (!(await exists(file)))
|
|
248
237
|
return { present: false, valid: false, baseUrl: '', apiKeySha256: '' };
|
|
@@ -322,44 +311,46 @@ function evaluateCodexLbCredentialBinding(input) {
|
|
|
322
311
|
blockers: []
|
|
323
312
|
};
|
|
324
313
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
314
|
+
async function readEnvFile(file, opts = {}) {
|
|
315
|
+
let text = '';
|
|
316
|
+
const blockers = [];
|
|
317
|
+
const guidance = [];
|
|
318
|
+
if (opts.privateFile) {
|
|
319
|
+
let pathExists = false;
|
|
320
|
+
try {
|
|
321
|
+
await fsp.lstat(file);
|
|
322
|
+
pathExists = true;
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (error?.code !== 'ENOENT') {
|
|
326
|
+
blockers.push('codex_lb_env_file_read_failed');
|
|
327
|
+
guidance.push(`Replace the unreadable credential file at ${file} with \`sks codex-lb setup --host <domain> --api-key-stdin --yes\`.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (!pathExists) {
|
|
331
|
+
return { apiKey: '', baseUrl: '', blockers, guidance };
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
text = (await readPrivateCredentialFile(path.dirname(file), file, 'codex_lb_env_file')).bytes.toString('utf8');
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (!(error instanceof PrivateCredentialFileError) || error.code !== 'missing') {
|
|
338
|
+
const code = error instanceof PrivateCredentialFileError ? error.code : 'read_failed';
|
|
339
|
+
blockers.push(`codex_lb_env_file_${code}`);
|
|
340
|
+
guidance.push(code === 'mode_not_0600'
|
|
341
|
+
? `Run \`chmod 600 ${file}\` or \`sks doctor --fix\`, then retry.`
|
|
342
|
+
: `Replace the unsafe credential file at ${file} with \`sks codex-lb setup --host <domain> --api-key-stdin --yes\`.`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
text = await readText(file, '');
|
|
348
|
+
}
|
|
360
349
|
return {
|
|
361
350
|
apiKey: parseShellEnvValue(text, 'CODEX_LB_API_KEY'),
|
|
362
|
-
baseUrl: parseShellEnvValue(text, 'CODEX_LB_BASE_URL')
|
|
351
|
+
baseUrl: parseShellEnvValue(text, 'CODEX_LB_BASE_URL'),
|
|
352
|
+
blockers,
|
|
353
|
+
guidance
|
|
363
354
|
};
|
|
364
355
|
}
|
|
365
356
|
function pickEnv(env) {
|
|
@@ -63,7 +63,9 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
|
|
|
63
63
|
actions.push({
|
|
64
64
|
type: 'write_cli_provider',
|
|
65
65
|
target: configPath,
|
|
66
|
-
effect:
|
|
66
|
+
effect: answers.use_as_default_provider === true
|
|
67
|
+
? 'atomically write and select the codex-lb provider using CODEX_LB_API_KEY without changing auth.json'
|
|
68
|
+
: 'write an unselected CLI-only codex-lb provider using CODEX_LB_API_KEY without changing Desktop OAuth'
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
71
|
if (answers.write_env_file) {
|
|
@@ -77,7 +79,18 @@ export function buildCodexLbSetupPlan(answers, opts = {}) {
|
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
if (answers.sync_launchctl) {
|
|
80
|
-
|
|
82
|
+
const cliProviderSelected = desktopMode === 'cli-provider'
|
|
83
|
+
&& answers.use_as_default_provider === true;
|
|
84
|
+
actions.push({
|
|
85
|
+
type: 'sync_launchctl',
|
|
86
|
+
target: 'macOS launchctl user environment',
|
|
87
|
+
effect: cliProviderSelected
|
|
88
|
+
? 'set CODEX_LB_API_KEY and CODEX_LB_BASE_URL from the canonical owner-only env file for selected CLI-provider mode'
|
|
89
|
+
: 'remove CODEX_LB_API_KEY outside selected CLI-provider mode and reconcile the non-secret base URL for the chosen mode',
|
|
90
|
+
command: cliProviderSelected
|
|
91
|
+
? 'launchctl setenv CODEX_LB_API_KEY <canonical-env-file-value>; launchctl setenv CODEX_LB_BASE_URL ...; launchctl unsetenv OPENROUTER_API_KEY'
|
|
92
|
+
: 'launchctl unsetenv CODEX_LB_API_KEY OPENROUTER_API_KEY; reconcile CODEX_LB_BASE_URL for the chosen mode'
|
|
93
|
+
});
|
|
81
94
|
}
|
|
82
95
|
if (answers.install_shell_profile !== 'skip') {
|
|
83
96
|
actions.push({ type: 'install_shell_profile_snippet', target: profileTargets(home, answers.install_shell_profile).join(', '), effect: `install managed shell snippet for ${answers.install_shell_profile}` });
|