sneakoscope 8.1.2 → 8.2.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 +96 -58
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/bin/fast-inline.js +5 -51
- package/dist/cli/command-manifest-lite.js +2 -1
- package/dist/cli/command-registry.js +2 -1
- package/dist/cli/global-mode-router.js +1 -1
- package/dist/cli/install-helpers-codex-lb-config.js +112 -914
- package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
- package/dist/cli/install-helpers-install-support.js +4 -20
- package/dist/cli/install-helpers.js +36 -2716
- package/dist/cli/router.js +9 -9
- package/dist/commands/bridge-command-output.js +111 -0
- package/dist/commands/bridge.js +320 -0
- package/dist/commands/codex-app.js +39 -123
- package/dist/commands/doctor-helpers.js +380 -0
- package/dist/commands/doctor-profile.js +3 -0
- package/dist/commands/doctor.js +114 -873
- package/dist/commands/mad-sks.js +0 -4
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/agents/agent-effort-policy.js +11 -21
- package/dist/core/agents/agent-recursion-guard.js +26 -0
- package/dist/core/agents/agent-runner-codex-exec.js +7 -9
- package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
- package/dist/core/architecture-hardening/state/state-service.js +49 -13
- package/dist/core/auto-review.js +15 -9
- package/dist/core/codex/codex-config-guard.js +8 -0
- package/dist/core/codex-adapter.js +8 -10
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
- package/dist/core/codex-app/codex-app-launcher.js +10 -12
- package/dist/core/codex-app/codex-app-restart.js +10 -12
- package/dist/core/codex-app/menubar/constants.js +2 -5
- package/dist/core/codex-app/menubar/global-install.js +3 -2
- package/dist/core/codex-app/menubar/installer/result.js +23 -0
- package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
- package/dist/core/codex-app/menubar/installer.js +5 -96
- package/dist/core/codex-app/menubar/migration.js +1 -42
- package/dist/core/codex-app/openrouter-provider.js +0 -78
- package/dist/core/codex-app.js +97 -360
- package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
- package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
- package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
- package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
- package/dist/core/codex-control/codex-task-runner.js +16 -60
- package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
- package/dist/core/codex-exec-output-schema.js +11 -13
- package/dist/core/codex-lb/bridge-contracts.js +2 -0
- package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
- package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
- package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
- package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
- package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
- package/dist/core/codex-lb/capability-runner.js +505 -201
- package/dist/core/codex-lb/codex-lb-env.js +179 -12
- package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
- package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
- package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
- package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
- package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
- package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
- package/dist/core/codex-lb/combined-catalog.js +12 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
- package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
- package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
- package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
- package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
- package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
- package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
- package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
- package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
- package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
- package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
- package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
- package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
- package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
- package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
- package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
- package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
- package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
- package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
- package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
- package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
- package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
- package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
- package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
- package/dist/core/codex-lb/desktop-controller.js +2 -1730
- package/dist/core/codex-lb/desktop-service.js +462 -627
- package/dist/core/codex-lb/migration-receipt.js +560 -118
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
- package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
- package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
- package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
- package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
- package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
- package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
- package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
- package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
- package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
- package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
- package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
- package/dist/core/codex-lb/provider-credentials/types.js +1 -0
- package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
- package/dist/core/codex-lb/provider-credentials.js +5 -0
- package/dist/core/codex-lb/provider-registry.js +323 -0
- package/dist/core/codex-lb/provider-route-policy.js +118 -0
- package/dist/core/codex-lb/request-route-resolver.js +172 -0
- package/dist/core/codex-lb/route-index.js +148 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
- package/dist/core/commands/basic-cli.js +1 -1
- package/dist/core/commands/check-command.js +3 -10
- package/dist/core/commands/gate-result-contract.js +10 -14
- package/dist/core/commands/intent-normalization/normalizer.js +1 -1
- package/dist/core/commands/mad-sks-command.js +2 -6
- package/dist/core/commands/release-command.js +11 -1
- package/dist/core/commands/telegram-command.js +46 -6
- package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
- package/dist/core/doctor/skill-legacy-surface.js +5 -5
- package/dist/core/evidence/v2/evidence-key.js +30 -3
- package/dist/core/feature-fixtures.js +8 -1
- package/dist/core/feature-registry.js +3 -2
- package/dist/core/hooks-runtime/stop-finalization.js +11 -1
- package/dist/core/hooks-runtime/subagent-context.js +15 -1
- package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +73 -87
- package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
- package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
- package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
- package/dist/core/imagegen/imagegen-capability.js +43 -151
- package/dist/core/init/skills/filesystem.js +89 -0
- package/dist/core/init/skills/inventory.js +97 -0
- package/dist/core/init/skills/manifest.js +151 -0
- package/dist/core/init/skills.js +5 -325
- package/dist/core/init.js +0 -63
- package/dist/core/install/installed-package-smoke.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
- package/dist/core/policy/latest-version-guidance.js +15 -2
- package/dist/core/preflight/parallel-preflight-engine.js +27 -51
- package/dist/core/provider/provider-badge.js +9 -4
- package/dist/core/provider/provider-context.js +57 -107
- package/dist/core/providers/openrouter/openrouter-account.js +3 -1
- package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
- package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
- package/dist/core/release/gate-manifest.js +0 -1
- package/dist/core/release/main-push-guard.js +13 -0
- package/dist/core/release/main-push-receipt.js +11 -0
- package/dist/core/release/physical-release-gates.js +664 -61
- package/dist/core/release/publish-preflight.js +3 -3
- package/dist/core/release/release-gate-batch-runner.js +19 -6
- package/dist/core/release/release-gate-dag.js +33 -6
- package/dist/core/release/release-gate-node.js +1 -3
- package/dist/core/release/release-gate-output-contract.js +55 -0
- package/dist/core/release/release-pack-contract.js +1 -1
- package/dist/core/release/release-pack-receipt.js +209 -8
- package/dist/core/release/release-real-contract.js +1 -1
- package/dist/core/release/stage-publish.js +106 -28
- package/dist/core/retention/temp-lease.js +204 -0
- package/dist/core/retention.js +18 -195
- package/dist/core/routes.js +1 -2
- package/dist/core/safety/intent-contract/intent-contract.js +3 -3
- package/dist/core/subagents/naruto-host-credentials.js +6 -1
- package/dist/core/subagents/official-subagent-config.js +27 -9
- package/dist/core/subagents/official-subagent-runner.js +14 -31
- package/dist/core/subagents/wave-lifecycle.js +36 -8
- package/dist/core/subagents/wave-parent-guidance.js +2 -1
- package/dist/core/telegram/doctor.js +2 -2
- package/dist/core/telegram/liveness.js +10 -4
- package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
- package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
- package/dist/core/update/update-migration-state/session-state-split.js +247 -0
- package/dist/core/update/update-migration-state/simple-stages.js +46 -0
- package/dist/core/update/update-migration-state.js +4 -481
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
- package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
- package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
- package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
- package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
- package/dist/scripts/canonical-test-runner.js +7 -1
- package/dist/scripts/check-runtime-schemas.js +7 -2
- package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
- package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
- package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
- package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
- package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
- package/dist/scripts/desktop-bridge-transport-client.js +116 -0
- package/dist/scripts/desktop-bridge-unification-check.js +276 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
- package/dist/scripts/gate-policy-audit-check.js +52 -2
- package/dist/scripts/provider-badge-context-check.js +55 -12
- package/dist/scripts/publish-preflight.js +1 -2
- package/dist/scripts/release-affected-selector-check.js +1 -2
- package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
- package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
- package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
- package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
- package/dist/scripts/release-gate-batch-runner-check.js +9 -0
- package/dist/scripts/release-gate-dag-runner-check.js +6 -0
- package/dist/scripts/release-gate-dag-runner.js +4 -2
- package/dist/scripts/release-metadata-check.js +16 -3
- package/dist/scripts/release-pack-receipt.js +29 -6
- package/dist/scripts/release-physical-gates-check.js +39 -0
- package/dist/scripts/release-real-check.js +7 -5
- package/package.json +6 -8
- package/release-gates.v2.json +156 -157
- package/runtime-required-scripts.json +11 -11
- package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
- package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
- package/schemas/release/release-gate-node.schema.json +2 -3
- package/dist/cli/codex-lb-setup-warning-output.js +0 -17
- package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
- package/dist/commands/codex-lb.js +0 -1232
- package/dist/core/architecture-hardening/migration/migration.js +0 -111
- package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
- package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
- package/dist/core/codex-app/child-policy/child-policy.js +0 -35
- package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
- package/dist/core/codex-app/glm-model-profile.js +0 -1
- package/dist/core/codex-app/glm-profile-installer.js +0 -191
- package/dist/core/codex-app/glm-profile-schema.js +0 -42
- package/dist/core/codex-app/multi-provider-router.js +0 -391
- package/dist/core/codex-app/openrouter-activate.js +0 -405
- package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
- package/dist/core/codex-app/provider-child-policy.js +0 -64
- package/dist/core/codex-app/provider-mode.js +0 -91
- package/dist/core/codex-app/provider-session-policy.js +0 -85
- package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
- package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
- package/dist/core/codex-lb/codex-lb-setup.js +0 -293
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
- package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
- package/dist/core/codex-lb/desktop-mode.js +0 -32
- package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
- package/dist/core/codex-lb/legacy-migration.js +0 -576
- package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
- package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
- package/dist/core/codex-lb/routing-truth.js +0 -327
- package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
- package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
- package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +0 -231
|
@@ -1,93 +1,128 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { constants as fsConstants } from 'node:fs';
|
|
1
3
|
import fsp from 'node:fs/promises';
|
|
2
4
|
import os from 'node:os';
|
|
3
5
|
import path from 'node:path';
|
|
4
6
|
import { ensureDir, exists, runProcess, which, writeTextAtomic } from '../fsx.js';
|
|
5
|
-
import {
|
|
7
|
+
import { withFileLock } from '../locks/file-lock.js';
|
|
8
|
+
import { loadCodexLbEnv } from './codex-lb-env.js';
|
|
6
9
|
import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, DESKTOP_BRIDGE_LAUNCHD_LABEL, desktopBridgeConfigGeneration, desktopBridgeLaunchdPlistPath, desktopBridgeProcessExists, desktopBridgeStatePath, getDesktopBridgeStatus, preflightDesktopBridge, readDesktopBridgeState,
|
|
10
|
-
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
13
|
-
export const
|
|
14
|
-
export const CODEX_LB_DESKTOP_BRIDGE_SERVICE_SCHEMA = 'sks.codex-lb-desktop-bridge-service.v1';
|
|
10
|
+
import { cleanupRetiredDesktopBridgeRuntime, prepareRetiredDesktopBridgeRuntime, } from './desktop-bridge-migration/retired-runtime-cleanup.js';
|
|
11
|
+
import { canonicalizeBridgeModelId, normalizeBridgeUpstreamModelId, sha256Stable } from './route-index.js';
|
|
12
|
+
import { DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, DESKTOP_BRIDGE_LAUNCHD_LABEL, desktopBridgeConfigGeneration, desktopBridgeLaunchdPlistPath, desktopBridgeProcessExists, desktopBridgeStatePath, getDesktopBridgeStatus, preflightDesktopBridge, readDesktopBridgeState, safeBridgeErrorCode, writeDesktopBridgeLaunchdPlist, selectAvailableDesktopBridgePort, startPreparedDesktopBridge, DESKTOP_BRIDGE_STATE_SCHEMA, DesktopBridgeError, } from './desktop-bridge/index.js';
|
|
13
|
+
export const DEFAULT_DESKTOP_BRIDGE_HOST = '127.0.0.1';
|
|
14
|
+
export const DEFAULT_DESKTOP_BRIDGE_PORT = 49_152;
|
|
15
|
+
export const DESKTOP_BRIDGE_SETTINGS_SCHEMA = 'sks.desktop-bridge-settings.v2';
|
|
16
|
+
export const DESKTOP_BRIDGE_SERVICE_SCHEMA = 'sks.desktop-bridge-service.v2';
|
|
15
17
|
const DEFAULT_ALLOWED_ORIGINS = ['app://codex'];
|
|
16
|
-
const
|
|
17
|
-
const DEFAULT_IDLE_TIMEOUT_MS = 5 * 60_000;
|
|
18
|
-
const OPENROUTER_RESPONSES_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
18
|
+
const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
19
19
|
export function desktopBridgeServicePaths(home = process.env.HOME || os.homedir()) {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
settings_path: path.join(runtimeDir, 'codex-lb-desktop-bridge-settings.json'),
|
|
25
|
-
state_path: desktopBridgeStatePath(resolvedHome),
|
|
26
|
-
launch_agent_path: desktopBridgeLaunchdPlistPath(resolvedHome),
|
|
27
|
-
stdout_log_path: path.join(logDir, 'codex-lb-desktop-bridge.out.log'),
|
|
28
|
-
stderr_log_path: path.join(logDir, 'codex-lb-desktop-bridge.err.log')
|
|
29
|
-
};
|
|
20
|
+
const runtime = path.join(path.resolve(home), '.codex', 'sks');
|
|
21
|
+
const logs = path.join(runtime, 'logs');
|
|
22
|
+
return { settings_path: path.join(runtime, 'desktop-bridge-settings.json'), state_path: desktopBridgeStatePath(path.resolve(home)), client_capability_path: path.join(runtime, 'desktop-bridge-client-capability'), launch_agent_path: desktopBridgeLaunchdPlistPath(path.resolve(home)), stdout_log_path: path.join(logs, 'desktop-bridge.out.log'), stderr_log_path: path.join(logs, 'desktop-bridge.err.log') };
|
|
30
23
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
24
|
+
function fingerprint(secret) { return createHash('sha256').update(secret).digest('hex').slice(0, 16); }
|
|
25
|
+
function sha256(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
26
|
+
function generation(value) { return createHash('sha256').update(JSON.stringify(value)).digest('hex'); }
|
|
27
|
+
function validateClientCapability(value) {
|
|
28
|
+
const capability = String(value || '').trim();
|
|
29
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(capability))
|
|
30
|
+
throw new Error('desktop_bridge_client_capability_invalid');
|
|
31
|
+
return capability;
|
|
32
|
+
}
|
|
33
|
+
export async function readDesktopBridgeClientCapability(file) {
|
|
34
|
+
let handle;
|
|
35
|
+
try {
|
|
36
|
+
handle = await fsp.open(file, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code === 'ENOENT')
|
|
40
|
+
throw new Error('desktop_bridge_client_capability_missing');
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const stat = await handle.stat();
|
|
45
|
+
if (!stat.isFile() || stat.size > 256)
|
|
46
|
+
throw new Error('desktop_bridge_client_capability_file_invalid');
|
|
47
|
+
const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
48
|
+
if (expectedUid !== null && stat.uid !== expectedUid)
|
|
49
|
+
throw new Error('desktop_bridge_client_capability_owner_mismatch');
|
|
50
|
+
if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0)
|
|
51
|
+
throw new Error('desktop_bridge_client_capability_permissions_unsafe');
|
|
52
|
+
return validateClientCapability((await handle.readFile('utf8')).trim());
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await handle.close().catch(() => undefined);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function ensureDesktopBridgeClientCapability(file) {
|
|
59
|
+
await ensurePrivateDirectory(path.dirname(file));
|
|
60
|
+
return withFileLock(settingsLockOptions(file), async () => {
|
|
61
|
+
try {
|
|
62
|
+
return await readDesktopBridgeClientCapability(file);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (!(error instanceof Error) || error.message !== 'desktop_bridge_client_capability_missing')
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
const capability = randomBytes(32).toString('base64url');
|
|
69
|
+
await writeTextAtomic(file, `${capability}\n`, { mode: 0o600 });
|
|
70
|
+
if (process.platform !== 'win32')
|
|
71
|
+
await fsp.chmod(file, 0o600);
|
|
72
|
+
const verified = await readDesktopBridgeClientCapability(file);
|
|
73
|
+
if (verified !== capability)
|
|
74
|
+
throw new Error('desktop_bridge_client_capability_write_verification_failed');
|
|
75
|
+
return capability;
|
|
46
76
|
});
|
|
47
77
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
contract_version: ARCHITECTURE_HARDENING_CONTRACT_VERSION,
|
|
56
|
-
mode,
|
|
57
|
-
credential_class: credentialClassForMode(mode),
|
|
58
|
-
allowed_models: allowedModels,
|
|
59
|
-
child_policy_hash: '0'.repeat(64),
|
|
60
|
-
catalog_version: catalogVersion,
|
|
78
|
+
function disabledProvider(id) {
|
|
79
|
+
return { provider_id: id, enabled: false, base_url: id === 'openrouter' ? OPENROUTER_BASE_URL : 'https://invalid.codex-lb.local', allowed_origins: [id === 'openrouter' ? 'https://openrouter.ai' : 'https://invalid.codex-lb.local'], auth_transport: id === 'openrouter' ? 'openrouter-bearer' : 'x-codex-lb-api-key', credential_state: 'not_configured', credential_fingerprint: null, credential_generation: 'not-configured', source_catalog_generation: null };
|
|
80
|
+
}
|
|
81
|
+
function emptyProviderRegistry() {
|
|
82
|
+
const providers = {
|
|
83
|
+
'codex-lb': disabledProvider('codex-lb'),
|
|
84
|
+
openrouter: disabledProvider('openrouter')
|
|
61
85
|
};
|
|
62
|
-
const registered = settings.registered_child_models === undefined
|
|
63
|
-
? allowedModels
|
|
64
|
-
: settings.registered_child_models;
|
|
65
|
-
const child = createChildPolicySnapshot(seed, registered);
|
|
66
|
-
const policy = { ...seed, child_policy_hash: child.policy_hash };
|
|
67
86
|
return {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
requireSessionPin: settings.require_session_pin === true,
|
|
87
|
+
schema: 'sks.desktop-bridge-provider-registry.v1',
|
|
88
|
+
generation: sha256Stable(providers),
|
|
89
|
+
created_at: new Date(0).toISOString(),
|
|
90
|
+
providers
|
|
73
91
|
};
|
|
74
92
|
}
|
|
93
|
+
function emptyRoutingPolicy() {
|
|
94
|
+
const modelRoutes = {};
|
|
95
|
+
return {
|
|
96
|
+
schema: 'sks.bridge-routing-policy.v1',
|
|
97
|
+
default_provider_id: null,
|
|
98
|
+
fallback: 'none',
|
|
99
|
+
model_routes: modelRoutes,
|
|
100
|
+
catalog_generation: 'not-configured',
|
|
101
|
+
policy_generation: generation(modelRoutes),
|
|
102
|
+
changed_at: new Date(0).toISOString()
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function defaultDesktopBridgeServiceSettings(input = {}) {
|
|
106
|
+
const registry = input.provider_registry || emptyProviderRegistry();
|
|
107
|
+
const policy = input.route_policy || emptyRoutingPolicy();
|
|
108
|
+
return validateDesktopBridgeServiceSettings({
|
|
109
|
+
schema: DESKTOP_BRIDGE_SETTINGS_SCHEMA, listen_host: input.listen_host || DEFAULT_DESKTOP_BRIDGE_HOST,
|
|
110
|
+
listen_port: input.listen_port ?? DEFAULT_DESKTOP_BRIDGE_PORT, provider_registry: registry, route_policy: policy,
|
|
111
|
+
provider_session_pins: [...(input.provider_session_pins || [])], client_capability_sha256: input.client_capability_sha256 || '0'.repeat(64), allowed_origins: [...(input.allowed_origins || DEFAULT_ALLOWED_ORIGINS)],
|
|
112
|
+
connect_timeout_ms: input.connect_timeout_ms ?? 10_000, idle_timeout_ms: input.idle_timeout_ms ?? 300_000
|
|
113
|
+
});
|
|
114
|
+
}
|
|
75
115
|
export async function resolveDesktopBridgeActivationSettings(options = {}) {
|
|
76
116
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
77
117
|
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
118
|
+
const clientCapability = options.clientCapability
|
|
119
|
+
? validateClientCapability(options.clientCapability)
|
|
120
|
+
: await ensureDesktopBridgeClientCapability(paths.client_capability_path);
|
|
78
121
|
const persisted = await readDesktopBridgeServiceSettings(paths.settings_path);
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
?? persisted?.listen_port
|
|
84
|
-
?? await (options.selectAvailablePort || selectAvailableDesktopBridgePort)(listenHost);
|
|
85
|
-
return defaultDesktopBridgeServiceSettings({
|
|
86
|
-
...(persisted || {}),
|
|
87
|
-
...(options.settings || {}),
|
|
88
|
-
listen_host: listenHost,
|
|
89
|
-
listen_port: listenPort
|
|
90
|
-
});
|
|
122
|
+
const merged = { ...(persisted || {}), ...(options.settings || {}) };
|
|
123
|
+
const host = merged.listen_host || DEFAULT_DESKTOP_BRIDGE_HOST;
|
|
124
|
+
const port = merged.listen_port ?? await (options.selectAvailablePort || selectAvailableDesktopBridgePort)(host);
|
|
125
|
+
return defaultDesktopBridgeServiceSettings({ ...merged, ...(options.providerRegistry ? { provider_registry: options.providerRegistry } : {}), ...(options.routePolicy ? { route_policy: options.routePolicy } : {}), ...(options.providerSessionPins ? { provider_session_pins: [...options.providerSessionPins] } : {}), client_capability_sha256: sha256(clientCapability), listen_host: host, listen_port: port });
|
|
91
126
|
}
|
|
92
127
|
export async function readDesktopBridgeServiceSettings(file) {
|
|
93
128
|
let stat;
|
|
@@ -95,17 +130,16 @@ export async function readDesktopBridgeServiceSettings(file) {
|
|
|
95
130
|
stat = await fsp.lstat(file);
|
|
96
131
|
}
|
|
97
132
|
catch (error) {
|
|
98
|
-
if (
|
|
133
|
+
if (error.code === 'ENOENT')
|
|
99
134
|
return null;
|
|
100
135
|
throw error;
|
|
101
136
|
}
|
|
102
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
137
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
103
138
|
throw new Error('desktop_bridge_settings_not_regular_file');
|
|
104
|
-
}
|
|
105
139
|
if ((stat.mode & 0o077) !== 0)
|
|
106
140
|
throw new Error('desktop_bridge_settings_permissions_unsafe');
|
|
107
141
|
const raw = await fsp.readFile(file, 'utf8');
|
|
108
|
-
if (Buffer.byteLength(raw) >
|
|
142
|
+
if (Buffer.byteLength(raw) > 256 * 1024)
|
|
109
143
|
throw new Error('desktop_bridge_settings_too_large');
|
|
110
144
|
let parsed;
|
|
111
145
|
try {
|
|
@@ -117,636 +151,437 @@ export async function readDesktopBridgeServiceSettings(file) {
|
|
|
117
151
|
return validateDesktopBridgeServiceSettings(parsed);
|
|
118
152
|
}
|
|
119
153
|
export async function writeDesktopBridgeServiceSettings(file, settings) {
|
|
154
|
+
await withFileLock(settingsLockOptions(file), async () => {
|
|
155
|
+
await writeDesktopBridgeServiceSettingsUnlocked(file, settings);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function writeDesktopBridgeServiceSettingsUnlocked(file, settings) {
|
|
120
159
|
const validated = validateDesktopBridgeServiceSettings(settings);
|
|
121
|
-
|
|
160
|
+
const persisted = { schema: validated.schema, listen_host: validated.listen_host, listen_port: validated.listen_port, provider_registry: validated.provider_registry, route_policy: validated.route_policy, provider_session_pins: validated.provider_session_pins, client_capability_sha256: validated.client_capability_sha256, allowed_origins: validated.allowed_origins, connect_timeout_ms: validated.connect_timeout_ms, idle_timeout_ms: validated.idle_timeout_ms };
|
|
161
|
+
await writeTextAtomic(file, `${JSON.stringify(persisted, null, 2)}\n`, { mode: 0o600 });
|
|
122
162
|
await fsp.chmod(file, 0o600);
|
|
123
163
|
}
|
|
164
|
+
async function runtimeCredentials(settings, options) {
|
|
165
|
+
if (options.resolveProviderCredential) {
|
|
166
|
+
const sources = Object.fromEntries(['codex-lb', 'openrouter']
|
|
167
|
+
.filter((id) => settings.provider_registry.providers[id].credential_state === 'ready')
|
|
168
|
+
.map((id) => [id, 'injected-registry']));
|
|
169
|
+
return { registry: settings.provider_registry, resolver: options.resolveProviderCredential, sources, loaded: null };
|
|
170
|
+
}
|
|
171
|
+
const secrets = new Map();
|
|
172
|
+
let loaded = null;
|
|
173
|
+
try {
|
|
174
|
+
loaded = await loadCodexLbEnv({ home: options.home, ...(options.envPath ? { envPath: options.envPath } : {}), ...(options.metadataPath ? { metadataPath: options.metadataPath } : {}), ...(options.env ? { processEnv: options.env } : {}) });
|
|
175
|
+
if (loaded.configured && loaded.base_url && loaded.secret_api_key)
|
|
176
|
+
secrets.set('codex-lb', { value: loaded.secret_api_key, source: loaded.source, fingerprint: fingerprint(loaded.secret_api_key), generation: generation({ value: loaded.secret_api_key }) });
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
loaded = null;
|
|
180
|
+
}
|
|
181
|
+
const openrouter = await resolveOpenRouterApiKey({ env: options.env || process.env }).catch(() => ({ key: null, source: 'missing' }));
|
|
182
|
+
if (openrouter.key)
|
|
183
|
+
secrets.set('openrouter', { value: openrouter.key, source: openrouter.source === 'env' ? 'openrouter-env' : 'openrouter-user-secret-store', fingerprint: fingerprint(openrouter.key), generation: generation({ value: openrouter.key }) });
|
|
184
|
+
const providers = { ...settings.provider_registry.providers };
|
|
185
|
+
const adopted = new Map();
|
|
186
|
+
for (const id of ['codex-lb', 'openrouter']) {
|
|
187
|
+
const source = secrets.get(id);
|
|
188
|
+
const current = providers[id];
|
|
189
|
+
const endpointMatches = id !== 'codex-lb'
|
|
190
|
+
|| !loaded?.base_url
|
|
191
|
+
|| normalizeEndpointForBinding(loaded.base_url) === normalizeEndpointForBinding(current.base_url);
|
|
192
|
+
const identityMatches = Boolean(source
|
|
193
|
+
&& current.credential_fingerprint
|
|
194
|
+
&& source.fingerprint === current.credential_fingerprint
|
|
195
|
+
&& endpointMatches);
|
|
196
|
+
const credentialState = !source
|
|
197
|
+
? current.credential_state === 'ready' ? 'unavailable' : 'not_configured'
|
|
198
|
+
: current.credential_state === 'ready'
|
|
199
|
+
? identityMatches ? 'ready' : 'stale'
|
|
200
|
+
: current.credential_state === 'not_configured'
|
|
201
|
+
? 'configured_unverified'
|
|
202
|
+
: current.credential_state;
|
|
203
|
+
if (source && credentialState === 'ready')
|
|
204
|
+
adopted.set(id, source);
|
|
205
|
+
providers[id] = {
|
|
206
|
+
...current,
|
|
207
|
+
credential_state: credentialState,
|
|
208
|
+
credential_fingerprint: source?.fingerprint || null,
|
|
209
|
+
credential_generation: current.credential_generation
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const registry = { ...settings.provider_registry, generation: sha256Stable(providers), providers };
|
|
213
|
+
const resolver = options.resolveProviderCredential || (async (id, expected) => {
|
|
214
|
+
const secret = adopted.get(id);
|
|
215
|
+
if (!secret)
|
|
216
|
+
throw new Error(`${id.replace('-', '_')}_credential_unavailable`);
|
|
217
|
+
const profile = providers[id];
|
|
218
|
+
if (profile.credential_generation !== expected || profile.credential_fingerprint !== secret.fingerprint) {
|
|
219
|
+
throw new Error('bridge_provider_credential_generation_mismatch');
|
|
220
|
+
}
|
|
221
|
+
return { provider_id: id, value: secret.value, source: secret.source, fingerprint: secret.fingerprint, generation: expected };
|
|
222
|
+
});
|
|
223
|
+
return { registry, resolver, sources: Object.fromEntries([...adopted].map(([id, value]) => [id, value.source])), loaded };
|
|
224
|
+
}
|
|
225
|
+
function normalizeEndpointForBinding(value) {
|
|
226
|
+
try {
|
|
227
|
+
return new URL(value).toString().replace(/\/+$/, '');
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return String(value || '').trim().replace(/\/+$/, '');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
124
233
|
export async function resolveDesktopBridgeRuntimeConfig(options = {}) {
|
|
125
234
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
235
|
+
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
236
|
+
let settings = defaultDesktopBridgeServiceSettings({ ...((await readDesktopBridgeServiceSettings(paths.settings_path)) || {}), ...(options.settings || {}), ...(options.providerRegistry ? { provider_registry: options.providerRegistry } : {}), ...(options.routePolicy ? { route_policy: options.routePolicy } : {}), ...(options.providerSessionPins ? { provider_session_pins: [...options.providerSessionPins] } : {}) });
|
|
237
|
+
const clientCapability = options.clientCapability
|
|
238
|
+
? validateClientCapability(options.clientCapability)
|
|
239
|
+
: await readDesktopBridgeClientCapability(paths.client_capability_path);
|
|
240
|
+
if (sha256(clientCapability) !== settings.client_capability_sha256)
|
|
241
|
+
throw new Error('desktop_bridge_client_capability_mismatch');
|
|
242
|
+
const credentials = await runtimeCredentials(settings, { ...options, home });
|
|
243
|
+
settings = defaultDesktopBridgeServiceSettings({ ...settings, provider_registry: credentials.registry });
|
|
244
|
+
if (!Object.keys(settings.route_policy.model_routes).length)
|
|
245
|
+
throw new Error('catalog_model_route_missing');
|
|
246
|
+
const enabledRoutes = new Set(Object.values(settings.route_policy.model_routes).map((route) => route.provider_id));
|
|
247
|
+
if (![...enabledRoutes].some((id) => credentials.registry.providers[id].credential_state === 'ready'))
|
|
248
|
+
throw new Error('desktop_bridge_provider_credentials_unavailable');
|
|
249
|
+
const persistProviderSessionPins = options.persistProviderSessionPins
|
|
250
|
+
|| ((pins) => persistDesktopBridgeSessionPins(paths.settings_path, settings, pins));
|
|
251
|
+
const config = { providerRegistry: settings.provider_registry, routePolicy: settings.route_policy, providerSessionPins: settings.provider_session_pins, ...(options.resolveRequestRoute ? { resolveRequestRoute: options.resolveRequestRoute } : {}), persistProviderSessionPins, resolveProviderCredential: credentials.resolver, clientCapabilitySha256: settings.client_capability_sha256, listenHost: settings.listen_host, listenPort: settings.listen_port, allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, allowedOrigins: settings.allowed_origins, connectTimeoutMs: settings.connect_timeout_ms, idleTimeoutMs: settings.idle_timeout_ms };
|
|
252
|
+
const primary = credentials.sources['codex-lb'] || credentials.sources.openrouter;
|
|
253
|
+
if (!primary)
|
|
254
|
+
throw new Error('desktop_bridge_provider_credentials_unavailable');
|
|
255
|
+
return { config, settings, loaded_env: credentials.loaded, credential_source: primary, credential_sources: credentials.sources, paths };
|
|
256
|
+
}
|
|
257
|
+
async function persistDesktopBridgeSessionPins(file, expected, pins) {
|
|
258
|
+
await withFileLock(settingsLockOptions(file), async () => {
|
|
259
|
+
const current = await readDesktopBridgeServiceSettings(file);
|
|
260
|
+
if (!current
|
|
261
|
+
|| current.listen_host !== expected.listen_host
|
|
262
|
+
|| current.listen_port !== expected.listen_port
|
|
263
|
+
|| current.provider_registry.generation !== expected.provider_registry.generation
|
|
264
|
+
|| current.route_policy.policy_generation !== expected.route_policy.policy_generation
|
|
265
|
+
|| current.route_policy.catalog_generation !== expected.route_policy.catalog_generation) {
|
|
266
|
+
throw new DesktopBridgeError('session_pin_route_unavailable');
|
|
267
|
+
}
|
|
268
|
+
await writeDesktopBridgeServiceSettingsUnlocked(file, {
|
|
269
|
+
...current,
|
|
270
|
+
provider_session_pins: [...pins],
|
|
271
|
+
});
|
|
132
272
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const credentials = await resolveDesktopBridgeCredentials(settings, { ...options, home });
|
|
136
|
-
const architecture = desktopBridgeArchitecturePolicy(settings);
|
|
273
|
+
}
|
|
274
|
+
function settingsLockOptions(file) {
|
|
137
275
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
providerMode: settings.provider_mode,
|
|
142
|
-
allowedModels: settings.allowed_models,
|
|
143
|
-
providerPolicy: architecture.policy,
|
|
144
|
-
credentialReadiness: architecture.credential,
|
|
145
|
-
childPolicy: architecture.child,
|
|
146
|
-
sessionPins: architecture.sessionPins,
|
|
147
|
-
requireSessionPin: architecture.requireSessionPin,
|
|
148
|
-
remoteBaseUrl: credentials.remote_base_url,
|
|
149
|
-
gatewayKey: credentials.key,
|
|
150
|
-
gatewayAuthTransport: credentials.auth_transport,
|
|
151
|
-
allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES,
|
|
152
|
-
allowedOrigins: settings.allowed_origins,
|
|
153
|
-
connectTimeoutMs: settings.connect_timeout_ms,
|
|
154
|
-
idleTimeoutMs: settings.idle_timeout_ms
|
|
155
|
-
},
|
|
156
|
-
settings,
|
|
157
|
-
loaded_env: credentials.loaded_env,
|
|
158
|
-
credential_source: credentials.source,
|
|
159
|
-
paths
|
|
276
|
+
lockPath: `${path.resolve(file)}.lock`,
|
|
277
|
+
timeoutMs: 5_000,
|
|
278
|
+
staleMs: 30_000,
|
|
160
279
|
};
|
|
161
280
|
}
|
|
162
281
|
export async function desktopBridgeServiceStatus(options = {}) {
|
|
163
282
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
164
283
|
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
165
|
-
const service =
|
|
166
|
-
if ((options.platform || process.platform) !== 'darwin') {
|
|
167
|
-
return {
|
|
168
|
-
schema: CODEX_LB_DESKTOP_BRIDGE_SERVICE_SCHEMA,
|
|
169
|
-
ok: false,
|
|
170
|
-
supported: false,
|
|
171
|
-
installed: await exists(paths.launch_agent_path),
|
|
172
|
-
loaded: false,
|
|
173
|
-
running: false,
|
|
174
|
-
status: 'unsupported',
|
|
175
|
-
service,
|
|
176
|
-
paths,
|
|
177
|
-
state: await readDesktopBridgeState(paths.state_path).catch(() => null),
|
|
178
|
-
settings: await readDesktopBridgeServiceSettings(paths.settings_path).catch(() => null),
|
|
179
|
-
expected_config_generation: null,
|
|
180
|
-
credential_source: null,
|
|
181
|
-
blockers: ['desktop_bridge_service_requires_macos']
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
const installed = await exists(paths.launch_agent_path);
|
|
284
|
+
const service = launchService(options.uid);
|
|
185
285
|
const settings = await readDesktopBridgeServiceSettings(paths.settings_path).catch(() => null);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
286
|
+
if ((options.platform || process.platform) !== 'darwin')
|
|
287
|
+
return { schema: DESKTOP_BRIDGE_SERVICE_SCHEMA, ok: false, supported: false, installed: await exists(paths.launch_agent_path), loaded: false, running: false, status: 'unsupported', service, paths, state: await readDesktopBridgeState(paths.state_path).catch(() => null), settings, expected_config_generation: null, credential_source: null, blockers: ['desktop_bridge_service_requires_macos'] };
|
|
288
|
+
let expected = null;
|
|
289
|
+
let source = null;
|
|
290
|
+
let sources = {};
|
|
291
|
+
const blockers = [];
|
|
292
|
+
if (!settings)
|
|
293
|
+
blockers.push('desktop_bridge_settings_missing');
|
|
294
|
+
else
|
|
190
295
|
try {
|
|
191
296
|
const runtime = await resolveDesktopBridgeRuntimeConfig({ ...options, home, settingsPath: paths.settings_path });
|
|
192
|
-
|
|
193
|
-
|
|
297
|
+
expected = desktopBridgeConfigGeneration(runtime.config);
|
|
298
|
+
source = runtime.credential_source;
|
|
299
|
+
sources = runtime.credential_sources;
|
|
194
300
|
}
|
|
195
301
|
catch (error) {
|
|
196
|
-
|
|
302
|
+
blockers.push(safeServiceError(error));
|
|
197
303
|
}
|
|
198
|
-
}
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const blockers = [
|
|
206
|
-
...(settings ? [] : ['desktop_bridge_settings_missing']),
|
|
207
|
-
...credentialBlockers,
|
|
208
|
-
...(bridgeStatus.status === 'running' ? [] : [bridgeStatusBlocker(bridgeStatus)]),
|
|
209
|
-
...(launchd.loaded && !launchd.running ? ['desktop_bridge_launchd_not_running'] : [])
|
|
210
|
-
].filter(Boolean);
|
|
211
|
-
const running = bridgeStatus.status === 'running';
|
|
212
|
-
return {
|
|
213
|
-
schema: CODEX_LB_DESKTOP_BRIDGE_SERVICE_SCHEMA,
|
|
214
|
-
ok: running && blockers.length === 0,
|
|
215
|
-
supported: true,
|
|
216
|
-
installed,
|
|
217
|
-
loaded: launchd.loaded,
|
|
218
|
-
running,
|
|
219
|
-
status: !settings
|
|
220
|
-
? 'settings_missing'
|
|
221
|
-
: credentialBlockers.length
|
|
222
|
-
? 'credentials_unavailable'
|
|
223
|
-
: bridgeStatus.status,
|
|
224
|
-
service,
|
|
225
|
-
paths,
|
|
226
|
-
state: bridgeStatus.state,
|
|
227
|
-
settings,
|
|
228
|
-
expected_config_generation: expectedConfigGeneration,
|
|
229
|
-
credential_source: credentialSource,
|
|
230
|
-
blockers
|
|
231
|
-
};
|
|
304
|
+
const bridge = await getDesktopBridgeStatus({ statePath: paths.state_path, ...(expected ? { expectedConfigGeneration: expected } : {}), ...(options.processExists ? { processExists: options.processExists } : {}) });
|
|
305
|
+
const launchd = await inspectLaunchd(options, service);
|
|
306
|
+
if (bridge.status !== 'running')
|
|
307
|
+
blockers.push(bridgeStatusBlocker(bridge));
|
|
308
|
+
if (launchd.loaded && !launchd.running)
|
|
309
|
+
blockers.push('desktop_bridge_launchd_not_running');
|
|
310
|
+
return { schema: DESKTOP_BRIDGE_SERVICE_SCHEMA, ok: bridge.status === 'running' && blockers.length === 0, supported: true, installed: await exists(paths.launch_agent_path), loaded: launchd.loaded, running: bridge.status === 'running', status: !settings ? 'settings_missing' : blockers.some((b) => b.includes('credential')) ? 'credentials_unavailable' : bridge.status, service, paths, state: bridge.state, settings, expected_config_generation: expected, credential_source: source, credential_sources: sources, blockers: [...new Set(blockers.filter(Boolean))] };
|
|
232
311
|
}
|
|
233
312
|
export async function installAndStartDesktopBridgeService(options = {}) {
|
|
234
|
-
const platform = options.platform || process.platform;
|
|
235
313
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
236
|
-
const
|
|
237
|
-
if (platform !== 'darwin')
|
|
314
|
+
const platform = options.platform || process.platform;
|
|
315
|
+
if (platform !== 'darwin')
|
|
238
316
|
return desktopBridgeServiceStatus({ ...options, home, platform });
|
|
317
|
+
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
318
|
+
let retired;
|
|
319
|
+
try {
|
|
320
|
+
retired = await prepareRetiredDesktopBridgeRuntime({
|
|
321
|
+
home,
|
|
322
|
+
...(options.uid === undefined ? {} : { uid: options.uid }),
|
|
323
|
+
...(options.launchctl ? { launchctl: options.launchctl } : {}),
|
|
324
|
+
...(options.run ? { run: options.run } : {}),
|
|
325
|
+
});
|
|
239
326
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
327
|
+
catch (error) {
|
|
328
|
+
return failedStatus(paths, defaultDesktopBridgeServiceSettings(), launchService(options.uid), 'missing', safeServiceError(error));
|
|
329
|
+
}
|
|
330
|
+
const inheritedSettings = !(await exists(paths.settings_path)) && retired.settings
|
|
331
|
+
? retired.settings
|
|
332
|
+
: {};
|
|
333
|
+
const settings = await resolveDesktopBridgeActivationSettings({
|
|
334
|
+
...options, home, settingsPath: paths.settings_path,
|
|
335
|
+
settings: { ...inheritedSettings, ...(options.settings || {}) },
|
|
244
336
|
});
|
|
245
|
-
let
|
|
337
|
+
let runtime;
|
|
246
338
|
try {
|
|
247
|
-
|
|
339
|
+
runtime = await resolveDesktopBridgeRuntimeConfig({ ...options, home, settings: settings, settingsPath: paths.settings_path });
|
|
340
|
+
await preflightDesktopBridge(runtime.config);
|
|
248
341
|
}
|
|
249
342
|
catch (error) {
|
|
250
|
-
return
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (!catalog.ok || !catalog.models.length) {
|
|
273
|
-
return failedServiceStatus({
|
|
274
|
-
paths,
|
|
275
|
-
settings,
|
|
276
|
-
service: desktopBridgeLaunchService(options.uid),
|
|
277
|
-
status: 'credentials_unavailable',
|
|
278
|
-
credentialSource: credentials.source,
|
|
279
|
-
expectedConfigGeneration: null,
|
|
280
|
-
blocker: catalog.blockers[0] || 'codex_lb_model_catalog_unavailable'
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
settings = defaultDesktopBridgeServiceSettings({ ...settings, allowed_models: catalog.models });
|
|
284
|
-
}
|
|
285
|
-
if (!settings.allowed_models.length) {
|
|
286
|
-
return failedServiceStatus({
|
|
287
|
-
paths,
|
|
288
|
-
settings,
|
|
289
|
-
service: desktopBridgeLaunchService(options.uid),
|
|
290
|
-
status: 'credentials_unavailable',
|
|
291
|
-
credentialSource: credentials.source,
|
|
292
|
-
expectedConfigGeneration: null,
|
|
293
|
-
blocker: 'desktop_bridge_provider_model_allowlist_empty'
|
|
294
|
-
});
|
|
343
|
+
return failedStatus(paths, settings, launchService(options.uid), 'credentials_unavailable', safeServiceError(error));
|
|
344
|
+
}
|
|
345
|
+
const command = await resolveLaunchCommand(options);
|
|
346
|
+
if (!command)
|
|
347
|
+
return failedStatus(paths, settings, launchService(options.uid), 'settings_missing', 'desktop_bridge_sks_executable_missing');
|
|
348
|
+
await prepareDesktopBridgeServicePaths(paths);
|
|
349
|
+
await writeDesktopBridgeServiceSettings(paths.settings_path, { ...settings, provider_registry: runtime.config.providerRegistry, route_policy: runtime.config.routePolicy });
|
|
350
|
+
await writeDesktopBridgeLaunchdPlist(paths.launch_agent_path, { executablePath: command.executable, arguments: [...command.arguments, 'bridge', 'serve', '--settings', paths.settings_path, '--json'], stdoutPath: paths.stdout_log_path, stderrPath: paths.stderr_log_path });
|
|
351
|
+
const run = options.run || runProcess;
|
|
352
|
+
const ctl = options.launchctl || '/bin/launchctl';
|
|
353
|
+
const service = launchService(options.uid);
|
|
354
|
+
const domain = launchDomain(options.uid);
|
|
355
|
+
await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
|
|
356
|
+
await removeStaleState(paths.state_path, options.processExists);
|
|
357
|
+
const bootstrap = await run(ctl, ['bootstrap', domain, paths.launch_agent_path], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch((error) => failedProcess(error));
|
|
358
|
+
if (bootstrap.code !== 0 && !bootstrap.timedOut)
|
|
359
|
+
return failedStatus(paths, settings, service, 'missing', 'desktop_bridge_launchd_bootstrap_failed');
|
|
360
|
+
await run(ctl, ['kickstart', '-k', service], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch(() => undefined);
|
|
361
|
+
const status = await waitForBridge({ ...options, home });
|
|
362
|
+
if (!status.ok) {
|
|
363
|
+
await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
|
|
364
|
+
return status;
|
|
295
365
|
}
|
|
296
|
-
const config = {
|
|
297
|
-
listenHost: settings.listen_host,
|
|
298
|
-
listenPort: settings.listen_port,
|
|
299
|
-
providerMode: settings.provider_mode,
|
|
300
|
-
allowedModels: settings.allowed_models,
|
|
301
|
-
...desktopBridgeConfigArchitectureFields(settings),
|
|
302
|
-
remoteBaseUrl: credentials.remote_base_url,
|
|
303
|
-
gatewayKey: credentials.key,
|
|
304
|
-
gatewayAuthTransport: credentials.auth_transport,
|
|
305
|
-
allowedPathPrefixes: DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES,
|
|
306
|
-
allowedOrigins: settings.allowed_origins,
|
|
307
|
-
connectTimeoutMs: settings.connect_timeout_ms,
|
|
308
|
-
idleTimeoutMs: settings.idle_timeout_ms
|
|
309
|
-
};
|
|
310
366
|
try {
|
|
311
|
-
await
|
|
367
|
+
await cleanupRetiredDesktopBridgeRuntime(retired);
|
|
368
|
+
return status;
|
|
312
369
|
}
|
|
313
370
|
catch (error) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
service: desktopBridgeLaunchService(options.uid),
|
|
318
|
-
status: 'credentials_unavailable',
|
|
319
|
-
credentialSource: credentials.source,
|
|
320
|
-
expectedConfigGeneration: desktopBridgeConfigGeneration(config),
|
|
321
|
-
blocker: safeBridgeErrorCode(error)
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
const launchCommand = await resolveDesktopBridgeLaunchCommand(options);
|
|
325
|
-
if (!launchCommand) {
|
|
326
|
-
return failedServiceStatus({
|
|
327
|
-
paths,
|
|
328
|
-
settings,
|
|
329
|
-
service: desktopBridgeLaunchService(options.uid),
|
|
330
|
-
status: 'settings_missing',
|
|
331
|
-
credentialSource: credentials.source,
|
|
332
|
-
expectedConfigGeneration: desktopBridgeConfigGeneration(config),
|
|
333
|
-
blocker: 'desktop_bridge_sks_executable_missing'
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
await Promise.all([
|
|
337
|
-
ensureDir(path.dirname(paths.settings_path)),
|
|
338
|
-
ensureDir(path.dirname(paths.launch_agent_path)),
|
|
339
|
-
ensureDir(path.dirname(paths.stdout_log_path))
|
|
340
|
-
]);
|
|
341
|
-
await writeDesktopBridgeServiceSettings(paths.settings_path, settings);
|
|
342
|
-
const launchArguments = [
|
|
343
|
-
...launchCommand.arguments,
|
|
344
|
-
'codex-lb',
|
|
345
|
-
'bridge',
|
|
346
|
-
'serve',
|
|
347
|
-
'--settings',
|
|
348
|
-
paths.settings_path,
|
|
349
|
-
'--json'
|
|
350
|
-
];
|
|
351
|
-
const plist = renderDesktopBridgeLaunchdPlist({
|
|
352
|
-
executablePath: launchCommand.executable,
|
|
353
|
-
arguments: launchArguments,
|
|
354
|
-
stdoutPath: paths.stdout_log_path,
|
|
355
|
-
stderrPath: paths.stderr_log_path
|
|
356
|
-
});
|
|
357
|
-
await writeTextAtomic(paths.launch_agent_path, plist, { mode: 0o600 });
|
|
358
|
-
const run = options.run || runProcess;
|
|
359
|
-
const launchctl = options.launchctl || '/bin/launchctl';
|
|
360
|
-
const domain = desktopBridgeLaunchDomain(options.uid);
|
|
361
|
-
const service = desktopBridgeLaunchService(options.uid);
|
|
362
|
-
await run(launchctl, ['bootout', service], {
|
|
363
|
-
timeoutMs: 5_000,
|
|
364
|
-
maxOutputBytes: 16 * 1024
|
|
365
|
-
}).catch(() => undefined);
|
|
366
|
-
await run(launchctl, ['bootout', domain, paths.launch_agent_path], {
|
|
367
|
-
timeoutMs: 5_000,
|
|
368
|
-
maxOutputBytes: 16 * 1024
|
|
369
|
-
}).catch(() => undefined);
|
|
370
|
-
await removeStaleBridgeState(paths.state_path, options.processExists);
|
|
371
|
-
const bootstrap = await run(launchctl, ['bootstrap', domain, paths.launch_agent_path], {
|
|
372
|
-
timeoutMs: 10_000,
|
|
373
|
-
maxOutputBytes: 32 * 1024
|
|
374
|
-
}).catch((error) => failedProcess(error));
|
|
375
|
-
if (bootstrap.code !== 0 && !bootstrap.timedOut) {
|
|
376
|
-
return failedServiceStatus({
|
|
377
|
-
paths,
|
|
378
|
-
settings,
|
|
379
|
-
service,
|
|
380
|
-
status: 'missing',
|
|
381
|
-
credentialSource: credentials.source,
|
|
382
|
-
expectedConfigGeneration: desktopBridgeConfigGeneration(config),
|
|
383
|
-
blocker: 'desktop_bridge_launchd_bootstrap_failed'
|
|
384
|
-
});
|
|
371
|
+
await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
|
|
372
|
+
await removeStaleState(paths.state_path, options.processExists);
|
|
373
|
+
return failedStatus(paths, settings, service, 'missing', safeServiceError(error));
|
|
385
374
|
}
|
|
386
|
-
await run(launchctl, ['kickstart', '-k', service], {
|
|
387
|
-
timeoutMs: 10_000,
|
|
388
|
-
maxOutputBytes: 32 * 1024
|
|
389
|
-
}).catch(() => undefined);
|
|
390
|
-
const status = await waitForDesktopBridge({
|
|
391
|
-
...options,
|
|
392
|
-
home,
|
|
393
|
-
platform,
|
|
394
|
-
settingsPath: paths.settings_path,
|
|
395
|
-
statePath: paths.state_path,
|
|
396
|
-
launchAgentPath: paths.launch_agent_path,
|
|
397
|
-
stdoutLogPath: paths.stdout_log_path,
|
|
398
|
-
stderrLogPath: paths.stderr_log_path
|
|
399
|
-
});
|
|
400
|
-
if (!status.ok) {
|
|
401
|
-
await run(launchctl, ['bootout', service], {
|
|
402
|
-
timeoutMs: 5_000,
|
|
403
|
-
maxOutputBytes: 16 * 1024
|
|
404
|
-
}).catch(() => undefined);
|
|
405
|
-
}
|
|
406
|
-
return status;
|
|
407
375
|
}
|
|
408
376
|
export async function bootstrapExistingDesktopBridgeService(options = {}) {
|
|
409
377
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
410
378
|
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
411
|
-
if ((options.platform || process.platform) !== 'darwin')
|
|
379
|
+
if ((options.platform || process.platform) !== 'darwin' || !(await exists(paths.launch_agent_path)) || !(await exists(paths.settings_path)))
|
|
412
380
|
return desktopBridgeServiceStatus({ ...options, home });
|
|
413
|
-
}
|
|
414
|
-
if (!(await exists(paths.launch_agent_path)) || !(await exists(paths.settings_path))) {
|
|
415
|
-
return desktopBridgeServiceStatus({ ...options, home });
|
|
416
|
-
}
|
|
417
381
|
const run = options.run || runProcess;
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
await run(
|
|
422
|
-
|
|
423
|
-
maxOutputBytes:
|
|
424
|
-
|
|
425
|
-
const bootstrap = await run(launchctl, ['bootstrap', domain, paths.launch_agent_path], {
|
|
426
|
-
timeoutMs: 10_000,
|
|
427
|
-
maxOutputBytes: 32 * 1024
|
|
428
|
-
}).catch((error) => failedProcess(error));
|
|
429
|
-
if (bootstrap.code === 0 || bootstrap.timedOut) {
|
|
430
|
-
await run(launchctl, ['kickstart', '-k', service], {
|
|
431
|
-
timeoutMs: 10_000,
|
|
432
|
-
maxOutputBytes: 32 * 1024
|
|
433
|
-
}).catch(() => undefined);
|
|
434
|
-
}
|
|
435
|
-
return waitForDesktopBridge({ ...options, home });
|
|
382
|
+
const ctl = options.launchctl || '/bin/launchctl';
|
|
383
|
+
const service = launchService(options.uid);
|
|
384
|
+
await run(ctl, ['bootout', service], { timeoutMs: 5_000, maxOutputBytes: 16 * 1024 }).catch(() => undefined);
|
|
385
|
+
const result = await run(ctl, ['bootstrap', launchDomain(options.uid), paths.launch_agent_path], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch((error) => failedProcess(error));
|
|
386
|
+
if (result.code === 0 || result.timedOut)
|
|
387
|
+
await run(ctl, ['kickstart', '-k', service], { timeoutMs: 10_000, maxOutputBytes: 32 * 1024 }).catch(() => undefined);
|
|
388
|
+
return waitForBridge({ ...options, home });
|
|
436
389
|
}
|
|
437
390
|
export async function stopDesktopBridgeService(options = {}) {
|
|
438
391
|
const home = options.home || options.env?.HOME || process.env.HOME || os.homedir();
|
|
439
|
-
|
|
440
|
-
if ((options.platform || process.platform) !== 'darwin') {
|
|
392
|
+
if ((options.platform || process.platform) !== 'darwin')
|
|
441
393
|
return desktopBridgeServiceStatus({ ...options, home });
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const launchctl = options.launchctl || '/bin/launchctl';
|
|
445
|
-
const service = desktopBridgeLaunchService(options.uid);
|
|
446
|
-
await run(launchctl, ['bootout', service], {
|
|
447
|
-
timeoutMs: 8_000,
|
|
448
|
-
maxOutputBytes: 32 * 1024
|
|
449
|
-
}).catch(() => undefined);
|
|
394
|
+
const paths = overridePaths(desktopBridgeServicePaths(home), options);
|
|
395
|
+
await (options.run || runProcess)(options.launchctl || '/bin/launchctl', ['bootout', launchService(options.uid)], { timeoutMs: 8_000, maxOutputBytes: 32 * 1024 }).catch(() => undefined);
|
|
450
396
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
451
397
|
const state = await readDesktopBridgeState(paths.state_path).catch(() => null);
|
|
452
398
|
if (!state || !(options.processExists || desktopBridgeProcessExists)(state.pid))
|
|
453
399
|
break;
|
|
454
400
|
await delay(100);
|
|
455
401
|
}
|
|
456
|
-
await
|
|
457
|
-
if (options.removePlist
|
|
402
|
+
await removeStaleState(paths.state_path, options.processExists);
|
|
403
|
+
if (options.removePlist)
|
|
458
404
|
await fsp.unlink(paths.launch_agent_path).catch(() => undefined);
|
|
459
|
-
if (options.removeSettings
|
|
405
|
+
if (options.removeSettings) {
|
|
460
406
|
await fsp.unlink(paths.settings_path).catch(() => undefined);
|
|
407
|
+
await fsp.unlink(paths.client_capability_path).catch(() => undefined);
|
|
408
|
+
}
|
|
461
409
|
const status = await desktopBridgeServiceStatus({ ...options, home });
|
|
462
|
-
return {
|
|
463
|
-
...status,
|
|
464
|
-
ok: !status.running,
|
|
465
|
-
blockers: status.running ? ['desktop_bridge_process_still_running'] : []
|
|
466
|
-
};
|
|
410
|
+
return { ...status, ok: !status.running, blockers: status.running ? ['desktop_bridge_process_still_running'] : [] };
|
|
467
411
|
}
|
|
468
412
|
export async function serveDesktopBridge(options = {}) {
|
|
469
413
|
let handle = null;
|
|
470
414
|
try {
|
|
471
415
|
const runtime = await resolveDesktopBridgeRuntimeConfig(options);
|
|
472
|
-
|
|
473
|
-
handle
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
event: 'sks.codex_lb.bridge.started',
|
|
477
|
-
pid: handle.state.pid,
|
|
478
|
-
listen_origin: handle.state.listen_origin,
|
|
479
|
-
remote_origin_sha256: handle.state.remote_origin_sha256,
|
|
480
|
-
gateway_auth_transport: handle.state.gateway_auth_transport,
|
|
481
|
-
secret_fields_redacted: true
|
|
482
|
-
})}\n`);
|
|
483
|
-
await waitForShutdownSignal(handle);
|
|
484
|
-
return {
|
|
485
|
-
schema: 'sks.codex-lb-desktop-bridge-serve.v1',
|
|
486
|
-
ok: true,
|
|
487
|
-
status: 'stopped',
|
|
488
|
-
state: handle.state
|
|
489
|
-
};
|
|
416
|
+
handle = await startPreparedDesktopBridge(await preflightDesktopBridge(runtime.config), { statePath: runtime.paths.state_path });
|
|
417
|
+
process.stdout.write(`${JSON.stringify({ schema: 'sks.desktop-bridge-log.v2', event: 'sks.desktop_bridge.started', pid: handle.state.pid, process_generation: handle.state.schema === DESKTOP_BRIDGE_STATE_SCHEMA ? handle.state.process_generation : null, provider_registry_generation: runtime.config.providerRegistry?.generation, route_policy_generation: runtime.config.routePolicy?.policy_generation, secret_fields_redacted: true })}\n`);
|
|
418
|
+
await waitForShutdown(handle);
|
|
419
|
+
return { schema: 'sks.desktop-bridge-serve.v1', ok: true, status: 'stopped', state: handle.state };
|
|
490
420
|
}
|
|
491
421
|
catch (error) {
|
|
492
|
-
return {
|
|
493
|
-
schema: 'sks.codex-lb-desktop-bridge-serve.v1',
|
|
494
|
-
ok: false,
|
|
495
|
-
status: 'failed',
|
|
496
|
-
state: handle?.state || null,
|
|
497
|
-
blocker: safeServiceError(error)
|
|
498
|
-
};
|
|
422
|
+
return { schema: 'sks.desktop-bridge-serve.v1', ok: false, status: 'failed', state: handle?.state || null, blocker: safeServiceError(error) };
|
|
499
423
|
}
|
|
500
424
|
finally {
|
|
501
425
|
if (handle)
|
|
502
426
|
await handle.stop().catch(() => undefined);
|
|
503
427
|
}
|
|
504
428
|
}
|
|
505
|
-
async function resolveDesktopBridgeCredentials(settings, options) {
|
|
506
|
-
if (settings.provider_mode === 'openrouter') {
|
|
507
|
-
const resolved = await resolveOpenRouterApiKey({ env: options.env || process.env });
|
|
508
|
-
if (!resolved.key)
|
|
509
|
-
throw new Error('openrouter_key_missing');
|
|
510
|
-
return {
|
|
511
|
-
remote_base_url: OPENROUTER_RESPONSES_BASE_URL,
|
|
512
|
-
key: resolved.key,
|
|
513
|
-
auth_transport: 'authorization-bearer-compat',
|
|
514
|
-
source: resolved.source === 'env' ? 'openrouter-env' : 'openrouter-user-secret-store',
|
|
515
|
-
loaded_env: null
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
const loaded = await loadCodexLbEnv({
|
|
519
|
-
home: options.home,
|
|
520
|
-
...(options.envPath ? { envPath: options.envPath } : {}),
|
|
521
|
-
...(options.metadataPath ? { metadataPath: options.metadataPath } : {}),
|
|
522
|
-
...(options.env ? { processEnv: options.env } : {})
|
|
523
|
-
});
|
|
524
|
-
if (!loaded.base_url || !loaded.secret_api_key || !loaded.configured) {
|
|
525
|
-
throw new Error(loaded.credential_binding.blockers[0]
|
|
526
|
-
|| loaded.missing[0]
|
|
527
|
-
|| 'codex_lb_credentials_unavailable');
|
|
528
|
-
}
|
|
529
|
-
return {
|
|
530
|
-
remote_base_url: loaded.base_url,
|
|
531
|
-
key: loaded.secret_api_key,
|
|
532
|
-
auth_transport: settings.gateway_auth_transport,
|
|
533
|
-
source: loaded.source,
|
|
534
|
-
loaded_env: loaded
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
429
|
function validateDesktopBridgeServiceSettings(value) {
|
|
538
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
430
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
539
431
|
throw new Error('desktop_bridge_settings_invalid');
|
|
540
|
-
}
|
|
541
432
|
const row = value;
|
|
542
|
-
|
|
433
|
+
const input = row;
|
|
434
|
+
if (input.schema !== DESKTOP_BRIDGE_SETTINGS_SCHEMA)
|
|
543
435
|
throw new Error('desktop_bridge_settings_schema_invalid');
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
: [];
|
|
555
|
-
const connectTimeoutMs = Number(row.connect_timeout_ms);
|
|
556
|
-
const idleTimeoutMs = Number(row.idle_timeout_ms);
|
|
557
|
-
const catalogVersion = row.catalog_version === undefined ? undefined : String(row.catalog_version || '').trim();
|
|
558
|
-
const registeredChildModels = row.registered_child_models === undefined
|
|
559
|
-
? undefined
|
|
560
|
-
: Array.isArray(row.registered_child_models)
|
|
561
|
-
? [...new Set(row.registered_child_models.map((entry) => String(entry || '').trim()).filter(Boolean))]
|
|
562
|
-
: null;
|
|
563
|
-
const sessionPins = row.session_pins === undefined
|
|
564
|
-
? undefined
|
|
565
|
-
: Array.isArray(row.session_pins) ? row.session_pins : null;
|
|
566
|
-
const requireSessionPin = row.require_session_pin === true;
|
|
567
|
-
if (listenHost !== '127.0.0.1' && listenHost !== '::1') {
|
|
436
|
+
const allowedKeys = new Set(['schema', 'listen_host', 'listen_port', 'provider_registry', 'route_policy', 'provider_session_pins', 'client_capability_sha256', 'allowed_origins', 'connect_timeout_ms', 'idle_timeout_ms']);
|
|
437
|
+
if (Object.keys(input).some((key) => !allowedKeys.has(key)))
|
|
438
|
+
throw new Error('desktop_bridge_settings_unknown_field');
|
|
439
|
+
const serialized = JSON.stringify(input);
|
|
440
|
+
if (/"(?:api_?key|secret|authorization|cookie|access_token|refresh_token|gatewayKey)"\s*:/i.test(serialized) || /Bearer\s+[A-Za-z0-9._~-]{8,}/i.test(serialized)) {
|
|
441
|
+
throw new Error('desktop_bridge_settings_secret_forbidden');
|
|
442
|
+
}
|
|
443
|
+
const host = input.listen_host;
|
|
444
|
+
const port = Number(input.listen_port);
|
|
445
|
+
if (host !== '127.0.0.1' && host !== '::1')
|
|
568
446
|
throw new Error('desktop_bridge_settings_listen_host_invalid');
|
|
569
|
-
|
|
570
|
-
if (!Number.isInteger(listenPort) || listenPort < 49_152 || listenPort > 65_535) {
|
|
447
|
+
if (!Number.isInteger(port) || port < 49_152 || port > 65_535)
|
|
571
448
|
throw new Error('desktop_bridge_settings_listen_port_invalid');
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
throw new Error('
|
|
578
|
-
|
|
449
|
+
const registry = normalizeProviderRegistrySnapshot(input.provider_registry);
|
|
450
|
+
const policy = input.route_policy;
|
|
451
|
+
if (!registry || registry.schema !== 'sks.desktop-bridge-provider-registry.v1' || !registry.providers?.['codex-lb'] || !registry.providers.openrouter)
|
|
452
|
+
throw new Error('desktop_bridge_settings_provider_registry_invalid');
|
|
453
|
+
if (!policy || policy.schema !== 'sks.bridge-routing-policy.v1' || policy.fallback !== 'none')
|
|
454
|
+
throw new Error('desktop_bridge_settings_route_policy_invalid');
|
|
455
|
+
const pins = validateProviderSessionPins(input.provider_session_pins);
|
|
456
|
+
const clientCapabilitySha256 = typeof input.client_capability_sha256 === 'string' ? input.client_capability_sha256 : '0'.repeat(64);
|
|
457
|
+
if (!/^[a-f0-9]{64}$/.test(clientCapabilitySha256))
|
|
458
|
+
throw new Error('desktop_bridge_settings_client_capability_invalid');
|
|
459
|
+
const origins = Array.isArray(input.allowed_origins) ? input.allowed_origins.map(String).filter(Boolean) : [];
|
|
460
|
+
if (!origins.length)
|
|
579
461
|
throw new Error('desktop_bridge_settings_allowed_origins_empty');
|
|
580
|
-
|
|
462
|
+
const connect = Number(input.connect_timeout_ms);
|
|
463
|
+
const idle = Number(input.idle_timeout_ms);
|
|
464
|
+
if (!Number.isFinite(connect) || connect < 100 || connect > 120_000)
|
|
581
465
|
throw new Error('desktop_bridge_settings_connect_timeout_invalid');
|
|
582
|
-
|
|
583
|
-
if (!Number.isFinite(idleTimeoutMs) || idleTimeoutMs < 1_000 || idleTimeoutMs > 86_400_000) {
|
|
466
|
+
if (!Number.isFinite(idle) || idle < 1_000 || idle > 86_400_000)
|
|
584
467
|
throw new Error('desktop_bridge_settings_idle_timeout_invalid');
|
|
585
|
-
}
|
|
586
|
-
if (catalogVersion !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(catalogVersion)) {
|
|
587
|
-
throw new Error('desktop_bridge_settings_catalog_version_invalid');
|
|
588
|
-
}
|
|
589
|
-
if (registeredChildModels === null || (registeredChildModels?.length ?? 0) > 512) {
|
|
590
|
-
throw new Error('desktop_bridge_settings_child_models_invalid');
|
|
591
|
-
}
|
|
592
|
-
if (sessionPins === null || (sessionPins?.length ?? 0) > 10_000) {
|
|
593
|
-
throw new Error('desktop_bridge_settings_session_pins_invalid');
|
|
594
|
-
}
|
|
595
|
-
return {
|
|
596
|
-
schema: CODEX_LB_DESKTOP_BRIDGE_SETTINGS_SCHEMA,
|
|
597
|
-
listen_host: listenHost,
|
|
598
|
-
listen_port: listenPort,
|
|
599
|
-
provider_mode: providerMode,
|
|
600
|
-
allowed_models: allowedModels,
|
|
601
|
-
gateway_auth_transport: gatewayAuthTransport,
|
|
602
|
-
allowed_origins: [...new Set(allowedOrigins)],
|
|
603
|
-
connect_timeout_ms: connectTimeoutMs,
|
|
604
|
-
idle_timeout_ms: idleTimeoutMs,
|
|
605
|
-
...(catalogVersion === undefined ? {} : { catalog_version: catalogVersion }),
|
|
606
|
-
...(registeredChildModels === undefined ? {} : { registered_child_models: registeredChildModels }),
|
|
607
|
-
...(sessionPins === undefined ? {} : { session_pins: sessionPins }),
|
|
608
|
-
require_session_pin: requireSessionPin
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
function desktopBridgeConfigArchitectureFields(settings) {
|
|
612
|
-
const architecture = desktopBridgeArchitecturePolicy(settings);
|
|
613
|
-
return {
|
|
614
|
-
providerPolicy: architecture.policy,
|
|
615
|
-
credentialReadiness: architecture.credential,
|
|
616
|
-
childPolicy: architecture.child,
|
|
617
|
-
sessionPins: architecture.sessionPins,
|
|
618
|
-
requireSessionPin: architecture.requireSessionPin,
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
function overridePaths(defaults, options) {
|
|
622
|
-
return {
|
|
623
|
-
settings_path: options.settingsPath || defaults.settings_path,
|
|
624
|
-
state_path: options.statePath || defaults.state_path,
|
|
625
|
-
launch_agent_path: options.launchAgentPath || defaults.launch_agent_path,
|
|
626
|
-
stdout_log_path: options.stdoutLogPath || defaults.stdout_log_path,
|
|
627
|
-
stderr_log_path: options.stderrLogPath || defaults.stderr_log_path
|
|
628
|
-
};
|
|
468
|
+
return { schema: DESKTOP_BRIDGE_SETTINGS_SCHEMA, listen_host: host, listen_port: port, provider_registry: registry, route_policy: policy, provider_session_pins: pins, client_capability_sha256: clientCapabilitySha256, allowed_origins: [...new Set(origins)], connect_timeout_ms: connect, idle_timeout_ms: idle };
|
|
629
469
|
}
|
|
630
|
-
function
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
470
|
+
function validateProviderSessionPins(value) {
|
|
471
|
+
const pins = Array.isArray(value) ? value : [];
|
|
472
|
+
if (pins.length > 10_000)
|
|
473
|
+
throw new Error('desktop_bridge_settings_session_pins_invalid');
|
|
474
|
+
const seen = new Set();
|
|
475
|
+
return pins.map((value) => {
|
|
476
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
477
|
+
throw new Error('desktop_bridge_settings_session_pins_invalid');
|
|
478
|
+
const pin = value;
|
|
479
|
+
const allowed = new Set([
|
|
480
|
+
'thread_id', 'provider_id', 'public_model', 'upstream_model',
|
|
481
|
+
'catalog_generation', 'route_policy_generation', 'created_at'
|
|
482
|
+
]);
|
|
483
|
+
const threadId = typeof pin.thread_id === 'string' ? pin.thread_id.trim() : '';
|
|
484
|
+
if (Object.keys(pin).some((key) => !allowed.has(key))
|
|
485
|
+
|| !threadId
|
|
486
|
+
|| threadId !== pin.thread_id
|
|
487
|
+
|| threadId.length > 256
|
|
488
|
+
|| !/^[A-Za-z0-9._:/-]+$/.test(threadId)
|
|
489
|
+
|| seen.has(threadId)
|
|
490
|
+
|| (pin.provider_id !== 'codex-lb' && pin.provider_id !== 'openrouter')
|
|
491
|
+
|| canonicalizeBridgeModelId(pin.public_model) !== pin.public_model
|
|
492
|
+
|| normalizeBridgeUpstreamModelId(pin.upstream_model) !== pin.upstream_model
|
|
493
|
+
|| typeof pin.catalog_generation !== 'string'
|
|
494
|
+
|| !pin.catalog_generation.trim()
|
|
495
|
+
|| typeof pin.route_policy_generation !== 'string'
|
|
496
|
+
|| !pin.route_policy_generation.trim()
|
|
497
|
+
|| typeof pin.created_at !== 'string'
|
|
498
|
+
|| !Number.isFinite(Date.parse(pin.created_at))) {
|
|
499
|
+
throw new Error('desktop_bridge_settings_session_pins_invalid');
|
|
500
|
+
}
|
|
501
|
+
seen.add(threadId);
|
|
648
502
|
return {
|
|
649
|
-
|
|
650
|
-
|
|
503
|
+
thread_id: threadId,
|
|
504
|
+
provider_id: pin.provider_id,
|
|
505
|
+
public_model: pin.public_model,
|
|
506
|
+
upstream_model: pin.upstream_model,
|
|
507
|
+
catalog_generation: pin.catalog_generation,
|
|
508
|
+
route_policy_generation: pin.route_policy_generation,
|
|
509
|
+
created_at: pin.created_at
|
|
651
510
|
};
|
|
652
|
-
}
|
|
653
|
-
const sks = await which('sks').catch(() => null);
|
|
654
|
-
if (sks)
|
|
655
|
-
return { executable: path.resolve(sks), arguments: [] };
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
async function inspectLaunchdService(options, service) {
|
|
659
|
-
if ((options.platform || process.platform) !== 'darwin')
|
|
660
|
-
return { loaded: false, running: false };
|
|
661
|
-
const run = options.run || runProcess;
|
|
662
|
-
const launchctl = options.launchctl || '/bin/launchctl';
|
|
663
|
-
const result = await run(launchctl, ['print', service], {
|
|
664
|
-
timeoutMs: 3_000,
|
|
665
|
-
maxOutputBytes: 32 * 1024
|
|
666
|
-
}).catch(() => null);
|
|
667
|
-
if (!result || result.code !== 0)
|
|
668
|
-
return { loaded: false, running: false };
|
|
669
|
-
const text = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
670
|
-
const state = text.match(/^[ \t]*state = ([^\n]+)/m)?.[1]?.trim() || null;
|
|
671
|
-
const pid = Number(text.match(/^[ \t]*pid = (\d+)/m)?.[1] || 0);
|
|
672
|
-
return { loaded: true, running: state === 'running' && pid > 0 };
|
|
673
|
-
}
|
|
674
|
-
async function waitForDesktopBridge(options) {
|
|
675
|
-
let status = await desktopBridgeServiceStatus(options);
|
|
676
|
-
for (let attempt = 0; attempt < 40 && !status.ok; attempt += 1) {
|
|
677
|
-
await delay(100);
|
|
678
|
-
status = await desktopBridgeServiceStatus(options);
|
|
679
|
-
}
|
|
680
|
-
return status;
|
|
681
|
-
}
|
|
682
|
-
async function removeStaleBridgeState(statePath, processExists) {
|
|
683
|
-
const state = await readDesktopBridgeState(statePath).catch(() => null);
|
|
684
|
-
if (!state || !(processExists || desktopBridgeProcessExists)(state.pid)) {
|
|
685
|
-
await fsp.unlink(statePath).catch(() => undefined);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
async function waitForShutdownSignal(handle) {
|
|
689
|
-
await new Promise((resolve) => {
|
|
690
|
-
let settled = false;
|
|
691
|
-
const finish = () => {
|
|
692
|
-
if (settled)
|
|
693
|
-
return;
|
|
694
|
-
settled = true;
|
|
695
|
-
process.off('SIGINT', finish);
|
|
696
|
-
process.off('SIGTERM', finish);
|
|
697
|
-
resolve();
|
|
698
|
-
};
|
|
699
|
-
process.once('SIGINT', finish);
|
|
700
|
-
process.once('SIGTERM', finish);
|
|
701
|
-
handle.server.once('close', finish);
|
|
702
511
|
});
|
|
703
512
|
}
|
|
704
|
-
function
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
paths: input.paths,
|
|
715
|
-
state: null,
|
|
716
|
-
settings: input.settings,
|
|
717
|
-
expected_config_generation: input.expectedConfigGeneration,
|
|
718
|
-
credential_source: input.credentialSource,
|
|
719
|
-
blockers: [input.blocker]
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
function bridgeStatusBlocker(status) {
|
|
723
|
-
if (status.status === 'missing')
|
|
724
|
-
return 'desktop_bridge_state_missing';
|
|
725
|
-
if (status.status === 'running')
|
|
726
|
-
return '';
|
|
727
|
-
return status.blocker;
|
|
728
|
-
}
|
|
729
|
-
function safeServiceError(error) {
|
|
730
|
-
const bridge = safeBridgeErrorCode(error);
|
|
731
|
-
if (bridge !== 'bridge_upstream_unavailable')
|
|
732
|
-
return bridge;
|
|
733
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
734
|
-
return /^[a-z0-9_:-]+$/i.test(message) ? message : 'desktop_bridge_service_failed';
|
|
513
|
+
async function prepareDesktopBridgeServicePaths(paths) {
|
|
514
|
+
await Promise.all([
|
|
515
|
+
ensurePrivateDirectory(path.dirname(paths.settings_path)),
|
|
516
|
+
ensurePrivateDirectory(path.dirname(paths.stdout_log_path)),
|
|
517
|
+
ensureDir(path.dirname(paths.launch_agent_path))
|
|
518
|
+
]);
|
|
519
|
+
await Promise.all([
|
|
520
|
+
ensurePrivateLogFile(paths.stdout_log_path),
|
|
521
|
+
ensurePrivateLogFile(paths.stderr_log_path)
|
|
522
|
+
]);
|
|
735
523
|
}
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
524
|
+
async function ensurePrivateDirectory(directory) {
|
|
525
|
+
await fsp.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
526
|
+
const stat = await fsp.lstat(directory);
|
|
527
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
528
|
+
throw new Error('desktop_bridge_private_directory_invalid');
|
|
529
|
+
const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
530
|
+
if (expectedUid !== null && stat.uid !== expectedUid)
|
|
531
|
+
throw new Error('desktop_bridge_private_directory_owner_mismatch');
|
|
532
|
+
if (process.platform !== 'win32')
|
|
533
|
+
await fsp.chmod(directory, 0o700);
|
|
746
534
|
}
|
|
747
|
-
function
|
|
748
|
-
|
|
535
|
+
async function ensurePrivateLogFile(file) {
|
|
536
|
+
const flags = fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | (fsConstants.O_NOFOLLOW || 0);
|
|
537
|
+
const handle = await fsp.open(file, flags, 0o600);
|
|
538
|
+
try {
|
|
539
|
+
const stat = await handle.stat();
|
|
540
|
+
if (!stat.isFile())
|
|
541
|
+
throw new Error('desktop_bridge_log_not_regular_file');
|
|
542
|
+
const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
543
|
+
if (expectedUid !== null && stat.uid !== expectedUid)
|
|
544
|
+
throw new Error('desktop_bridge_log_owner_mismatch');
|
|
545
|
+
if (process.platform !== 'win32')
|
|
546
|
+
await handle.chmod(0o600);
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
await handle.close().catch(() => undefined);
|
|
550
|
+
}
|
|
749
551
|
}
|
|
750
|
-
function
|
|
751
|
-
|
|
552
|
+
function normalizeProviderRegistrySnapshot(value) {
|
|
553
|
+
const registry = value;
|
|
554
|
+
if (!registry || registry.schema !== 'sks.desktop-bridge-provider-registry.v1'
|
|
555
|
+
|| !registry.providers?.['codex-lb'] || !registry.providers.openrouter) {
|
|
556
|
+
throw new Error('desktop_bridge_settings_provider_registry_invalid');
|
|
557
|
+
}
|
|
558
|
+
const providers = Object.fromEntries(['codex-lb', 'openrouter'].map((providerId) => {
|
|
559
|
+
const provider = registry.providers[providerId];
|
|
560
|
+
const sourceCatalogGeneration = provider.source_catalog_generation ?? provider.catalog_generation ?? null;
|
|
561
|
+
const { catalog_generation: _legacyCatalogGeneration, ...rest } = provider;
|
|
562
|
+
return [providerId, { ...rest, source_catalog_generation: sourceCatalogGeneration }];
|
|
563
|
+
}));
|
|
564
|
+
return { ...registry, providers };
|
|
752
565
|
}
|
|
566
|
+
function overridePaths(base, options) { return { settings_path: options.settingsPath || base.settings_path, state_path: options.statePath || base.state_path, client_capability_path: options.clientCapabilityPath || base.client_capability_path, launch_agent_path: options.launchAgentPath || base.launch_agent_path, stdout_log_path: options.stdoutLogPath || base.stdout_log_path, stderr_log_path: options.stderrLogPath || base.stderr_log_path }; }
|
|
567
|
+
function launchDomain(uid = typeof process.getuid === 'function' ? process.getuid() : 0) { return `gui/${uid}`; }
|
|
568
|
+
function launchService(uid) { return `${launchDomain(uid)}/${DESKTOP_BRIDGE_LAUNCHD_LABEL}`; }
|
|
569
|
+
async function resolveLaunchCommand(options) { if (options.executablePath)
|
|
570
|
+
return await exists(path.resolve(options.executablePath)) ? { executable: path.resolve(options.executablePath), arguments: [...(options.executableArguments || [])] } : null; const entry = String(process.argv[1] || ''); if (entry && ['sks', 'sneakoscope'].includes(path.basename(entry).replace(/\.js$/i, '')) && await exists(entry))
|
|
571
|
+
return { executable: path.resolve(process.execPath), arguments: [path.resolve(entry)] }; const sks = await which('sks').catch(() => null); return sks ? { executable: path.resolve(sks), arguments: [] } : null; }
|
|
572
|
+
async function inspectLaunchd(options, service) { if ((options.platform || process.platform) !== 'darwin')
|
|
573
|
+
return { loaded: false, running: false }; const result = await (options.run || runProcess)(options.launchctl || '/bin/launchctl', ['print', service], { timeoutMs: 3_000, maxOutputBytes: 32 * 1024 }).catch(() => null); if (!result || result.code !== 0)
|
|
574
|
+
return { loaded: false, running: false }; const text = `${result.stdout}\n${result.stderr}`; return { loaded: true, running: /state = running/.test(text) && /pid = \d+/.test(text) }; }
|
|
575
|
+
async function waitForBridge(options) { let status = await desktopBridgeServiceStatus(options); for (let i = 0; i < 40 && !status.ok; i += 1) {
|
|
576
|
+
await delay(100);
|
|
577
|
+
status = await desktopBridgeServiceStatus(options);
|
|
578
|
+
} return status; }
|
|
579
|
+
async function removeStaleState(file, probe) { const state = await readDesktopBridgeState(file).catch(() => null); if (!state || !(probe || desktopBridgeProcessExists)(state.pid))
|
|
580
|
+
await fsp.unlink(file).catch(() => undefined); }
|
|
581
|
+
async function waitForShutdown(handle) { await new Promise((resolve) => { const done = () => { process.off('SIGINT', done); process.off('SIGTERM', done); resolve(); }; process.once('SIGINT', done); process.once('SIGTERM', done); handle.server.once('close', done); }); }
|
|
582
|
+
function failedStatus(paths, settings, service, status, blocker) { return { schema: DESKTOP_BRIDGE_SERVICE_SCHEMA, ok: false, supported: true, installed: false, loaded: false, running: false, status, service, paths, state: null, settings, expected_config_generation: null, credential_source: null, blockers: [blocker] }; }
|
|
583
|
+
function bridgeStatusBlocker(status) { return status.status === 'missing' ? 'desktop_bridge_state_missing' : status.status === 'running' ? '' : status.blocker; }
|
|
584
|
+
function safeServiceError(error) { const bridge = safeBridgeErrorCode(error); if (bridge !== 'bridge_upstream_unavailable')
|
|
585
|
+
return bridge; const message = error instanceof Error ? error.message : String(error); return /^[a-z0-9_:-]+$/i.test(message) ? message : 'desktop_bridge_service_failed'; }
|
|
586
|
+
function failedProcess(error) { return { code: 1, stdout: '', stderr: safeServiceError(error), stdoutBytes: 0, stderrBytes: 0, truncated: false, timedOut: false }; }
|
|
587
|
+
function delay(ms) { return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms))); }
|