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.
Files changed (261) hide show
  1. package/README.md +96 -58
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/bin/fast-inline.js +5 -51
  5. package/dist/cli/command-manifest-lite.js +2 -1
  6. package/dist/cli/command-registry.js +2 -1
  7. package/dist/cli/global-mode-router.js +1 -1
  8. package/dist/cli/install-helpers-codex-lb-config.js +112 -914
  9. package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
  10. package/dist/cli/install-helpers-install-support.js +4 -20
  11. package/dist/cli/install-helpers.js +36 -2716
  12. package/dist/cli/router.js +9 -9
  13. package/dist/commands/bridge-command-output.js +111 -0
  14. package/dist/commands/bridge.js +320 -0
  15. package/dist/commands/codex-app.js +39 -123
  16. package/dist/commands/doctor-helpers.js +380 -0
  17. package/dist/commands/doctor-profile.js +3 -0
  18. package/dist/commands/doctor.js +114 -873
  19. package/dist/commands/mad-sks.js +0 -4
  20. package/dist/config/skills-manifest.json +1 -1
  21. package/dist/core/agents/agent-effort-policy.js +11 -21
  22. package/dist/core/agents/agent-recursion-guard.js +26 -0
  23. package/dist/core/agents/agent-runner-codex-exec.js +7 -9
  24. package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
  25. package/dist/core/architecture-hardening/state/state-service.js +49 -13
  26. package/dist/core/auto-review.js +15 -9
  27. package/dist/core/codex/codex-config-guard.js +8 -0
  28. package/dist/core/codex-adapter.js +8 -10
  29. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
  30. package/dist/core/codex-app/codex-app-launcher.js +10 -12
  31. package/dist/core/codex-app/codex-app-restart.js +10 -12
  32. package/dist/core/codex-app/menubar/constants.js +2 -5
  33. package/dist/core/codex-app/menubar/global-install.js +3 -2
  34. package/dist/core/codex-app/menubar/installer/result.js +23 -0
  35. package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
  36. package/dist/core/codex-app/menubar/installer.js +5 -96
  37. package/dist/core/codex-app/menubar/migration.js +1 -42
  38. package/dist/core/codex-app/openrouter-provider.js +0 -78
  39. package/dist/core/codex-app.js +97 -360
  40. package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
  41. package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
  42. package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
  43. package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
  44. package/dist/core/codex-control/codex-task-runner.js +16 -60
  45. package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
  46. package/dist/core/codex-exec-output-schema.js +11 -13
  47. package/dist/core/codex-lb/bridge-contracts.js +2 -0
  48. package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
  49. package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
  50. package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
  51. package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
  52. package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
  53. package/dist/core/codex-lb/capability-runner.js +505 -201
  54. package/dist/core/codex-lb/codex-lb-env.js +179 -12
  55. package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
  56. package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
  57. package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
  58. package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
  59. package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
  60. package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
  61. package/dist/core/codex-lb/combined-catalog.js +12 -0
  62. package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
  63. package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
  64. package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
  65. package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
  66. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
  67. package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
  68. package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
  69. package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
  70. package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
  71. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
  72. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
  73. package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
  74. package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
  75. package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
  76. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
  77. package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
  78. package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
  79. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
  80. package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
  81. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
  82. package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
  83. package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
  84. package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
  85. package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
  86. package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
  87. package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
  88. package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
  89. package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
  90. package/dist/core/codex-lb/desktop-controller.js +2 -1730
  91. package/dist/core/codex-lb/desktop-service.js +462 -627
  92. package/dist/core/codex-lb/migration-receipt.js +560 -118
  93. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
  94. package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
  95. package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
  96. package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
  97. package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
  98. package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
  99. package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
  100. package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
  101. package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
  102. package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
  103. package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
  104. package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
  105. package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
  106. package/dist/core/codex-lb/provider-credentials/types.js +1 -0
  107. package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
  108. package/dist/core/codex-lb/provider-credentials.js +5 -0
  109. package/dist/core/codex-lb/provider-registry.js +323 -0
  110. package/dist/core/codex-lb/provider-route-policy.js +118 -0
  111. package/dist/core/codex-lb/request-route-resolver.js +172 -0
  112. package/dist/core/codex-lb/route-index.js +148 -0
  113. package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
  114. package/dist/core/commands/basic-cli.js +1 -1
  115. package/dist/core/commands/check-command.js +3 -10
  116. package/dist/core/commands/gate-result-contract.js +10 -14
  117. package/dist/core/commands/intent-normalization/normalizer.js +1 -1
  118. package/dist/core/commands/mad-sks-command.js +2 -6
  119. package/dist/core/commands/release-command.js +11 -1
  120. package/dist/core/commands/telegram-command.js +46 -6
  121. package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
  122. package/dist/core/doctor/skill-legacy-surface.js +5 -5
  123. package/dist/core/evidence/v2/evidence-key.js +30 -3
  124. package/dist/core/feature-fixtures.js +8 -1
  125. package/dist/core/feature-registry.js +3 -2
  126. package/dist/core/hooks-runtime/stop-finalization.js +11 -1
  127. package/dist/core/hooks-runtime/subagent-context.js +15 -1
  128. package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
  129. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  130. package/dist/core/hooks-runtime.js +73 -87
  131. package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
  132. package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
  133. package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
  134. package/dist/core/imagegen/imagegen-capability.js +43 -151
  135. package/dist/core/init/skills/filesystem.js +89 -0
  136. package/dist/core/init/skills/inventory.js +97 -0
  137. package/dist/core/init/skills/manifest.js +151 -0
  138. package/dist/core/init/skills.js +5 -325
  139. package/dist/core/init.js +0 -63
  140. package/dist/core/install/installed-package-smoke.js +1 -1
  141. package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
  142. package/dist/core/policy/latest-version-guidance.js +15 -2
  143. package/dist/core/preflight/parallel-preflight-engine.js +27 -51
  144. package/dist/core/provider/provider-badge.js +9 -4
  145. package/dist/core/provider/provider-context.js +57 -107
  146. package/dist/core/providers/openrouter/openrouter-account.js +3 -1
  147. package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
  148. package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
  149. package/dist/core/release/gate-manifest.js +0 -1
  150. package/dist/core/release/main-push-guard.js +13 -0
  151. package/dist/core/release/main-push-receipt.js +11 -0
  152. package/dist/core/release/physical-release-gates.js +664 -61
  153. package/dist/core/release/publish-preflight.js +3 -3
  154. package/dist/core/release/release-gate-batch-runner.js +19 -6
  155. package/dist/core/release/release-gate-dag.js +33 -6
  156. package/dist/core/release/release-gate-node.js +1 -3
  157. package/dist/core/release/release-gate-output-contract.js +55 -0
  158. package/dist/core/release/release-pack-contract.js +1 -1
  159. package/dist/core/release/release-pack-receipt.js +209 -8
  160. package/dist/core/release/release-real-contract.js +1 -1
  161. package/dist/core/release/stage-publish.js +106 -28
  162. package/dist/core/retention/temp-lease.js +204 -0
  163. package/dist/core/retention.js +18 -195
  164. package/dist/core/routes.js +1 -2
  165. package/dist/core/safety/intent-contract/intent-contract.js +3 -3
  166. package/dist/core/subagents/naruto-host-credentials.js +6 -1
  167. package/dist/core/subagents/official-subagent-config.js +27 -9
  168. package/dist/core/subagents/official-subagent-runner.js +14 -31
  169. package/dist/core/subagents/wave-lifecycle.js +36 -8
  170. package/dist/core/subagents/wave-parent-guidance.js +2 -1
  171. package/dist/core/telegram/doctor.js +2 -2
  172. package/dist/core/telegram/liveness.js +10 -4
  173. package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
  174. package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
  175. package/dist/core/update/update-migration-state/session-state-split.js +247 -0
  176. package/dist/core/update/update-migration-state/simple-stages.js +46 -0
  177. package/dist/core/update/update-migration-state.js +4 -481
  178. package/dist/core/version.js +1 -1
  179. package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
  180. package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
  181. package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
  182. package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
  183. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
  184. package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
  185. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
  186. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
  187. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
  188. package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
  189. package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
  190. package/dist/scripts/canonical-test-runner.js +7 -1
  191. package/dist/scripts/check-runtime-schemas.js +7 -2
  192. package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
  193. package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
  194. package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
  195. package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
  196. package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
  197. package/dist/scripts/desktop-bridge-transport-client.js +116 -0
  198. package/dist/scripts/desktop-bridge-unification-check.js +276 -0
  199. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
  200. package/dist/scripts/gate-policy-audit-check.js +52 -2
  201. package/dist/scripts/provider-badge-context-check.js +55 -12
  202. package/dist/scripts/publish-preflight.js +1 -2
  203. package/dist/scripts/release-affected-selector-check.js +1 -2
  204. package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
  205. package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
  206. package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
  207. package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
  208. package/dist/scripts/release-gate-batch-runner-check.js +9 -0
  209. package/dist/scripts/release-gate-dag-runner-check.js +6 -0
  210. package/dist/scripts/release-gate-dag-runner.js +4 -2
  211. package/dist/scripts/release-metadata-check.js +16 -3
  212. package/dist/scripts/release-pack-receipt.js +29 -6
  213. package/dist/scripts/release-physical-gates-check.js +39 -0
  214. package/dist/scripts/release-real-check.js +7 -5
  215. package/package.json +6 -8
  216. package/release-gates.v2.json +156 -157
  217. package/runtime-required-scripts.json +11 -11
  218. package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
  219. package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
  220. package/schemas/release/release-gate-node.schema.json +2 -3
  221. package/dist/cli/codex-lb-setup-warning-output.js +0 -17
  222. package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
  223. package/dist/commands/codex-lb.js +0 -1232
  224. package/dist/core/architecture-hardening/migration/migration.js +0 -111
  225. package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
  226. package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
  227. package/dist/core/codex-app/child-policy/child-policy.js +0 -35
  228. package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
  229. package/dist/core/codex-app/glm-model-profile.js +0 -1
  230. package/dist/core/codex-app/glm-profile-installer.js +0 -191
  231. package/dist/core/codex-app/glm-profile-schema.js +0 -42
  232. package/dist/core/codex-app/multi-provider-router.js +0 -391
  233. package/dist/core/codex-app/openrouter-activate.js +0 -405
  234. package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
  235. package/dist/core/codex-app/provider-child-policy.js +0 -64
  236. package/dist/core/codex-app/provider-mode.js +0 -91
  237. package/dist/core/codex-app/provider-session-policy.js +0 -85
  238. package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
  239. package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
  240. package/dist/core/codex-lb/codex-lb-setup.js +0 -293
  241. package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
  242. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
  243. package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
  244. package/dist/core/codex-lb/desktop-mode.js +0 -32
  245. package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
  246. package/dist/core/codex-lb/legacy-migration.js +0 -576
  247. package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
  248. package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
  249. package/dist/core/codex-lb/routing-truth.js +0 -327
  250. package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
  251. package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
  252. package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
  253. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
  254. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
  255. package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
  256. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
  257. package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
  258. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
  259. package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
  260. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
  261. 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 { loadCodexLbEnv, readCodexLbModelCatalog } from './codex-lb-env.js';
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 { ARCHITECTURE_HARDENING_CONTRACT_VERSION, credentialClassForMode, stableArchitectureHash, } from '../architecture-hardening/contracts/contracts.js';
8
- import { createChildPolicySnapshot } from '../codex-app/child-policy/child-policy.js';
9
- import { DESKTOP_BRIDGE_ALLOWED_PATH_PREFIXES, DESKTOP_BRIDGE_LAUNCHD_LABEL, desktopBridgeConfigGeneration, desktopBridgeLaunchdPlistPath, desktopBridgeProcessExists, desktopBridgeStatePath, getDesktopBridgeStatus, preflightDesktopBridge, readDesktopBridgeState, renderDesktopBridgeLaunchdPlist, safeBridgeErrorCode, selectAvailableDesktopBridgePort, startPreparedDesktopBridge } from './desktop-bridge/index.js';
10
- import { DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT, parseCodexLbGatewayAuthTransport } from './desktop-mode.js';
11
- export const DEFAULT_CODEX_LB_DESKTOP_BRIDGE_HOST = '127.0.0.1';
12
- export const DEFAULT_CODEX_LB_DESKTOP_BRIDGE_PORT = 49_152;
13
- export const CODEX_LB_DESKTOP_BRIDGE_SETTINGS_SCHEMA = 'sks.codex-lb-desktop-bridge-settings.v1';
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 DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
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 resolvedHome = path.resolve(home);
21
- const runtimeDir = path.join(resolvedHome, '.codex', 'sks');
22
- const logDir = path.join(runtimeDir, 'logs');
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
- export function defaultDesktopBridgeServiceSettings(input = {}) {
32
- return validateDesktopBridgeServiceSettings({
33
- schema: CODEX_LB_DESKTOP_BRIDGE_SETTINGS_SCHEMA,
34
- listen_host: input.listen_host || DEFAULT_CODEX_LB_DESKTOP_BRIDGE_HOST,
35
- listen_port: input.listen_port ?? DEFAULT_CODEX_LB_DESKTOP_BRIDGE_PORT,
36
- provider_mode: input.provider_mode || 'codex-lb',
37
- allowed_models: [...(input.allowed_models || [])],
38
- gateway_auth_transport: parseCodexLbGatewayAuthTransport(input.gateway_auth_transport || DEFAULT_CODEX_LB_GATEWAY_AUTH_TRANSPORT),
39
- allowed_origins: [...(input.allowed_origins || DEFAULT_ALLOWED_ORIGINS)],
40
- connect_timeout_ms: input.connect_timeout_ms ?? DEFAULT_CONNECT_TIMEOUT_MS,
41
- idle_timeout_ms: input.idle_timeout_ms ?? DEFAULT_IDLE_TIMEOUT_MS,
42
- catalog_version: input.catalog_version,
43
- registered_child_models: input.registered_child_models,
44
- session_pins: input.session_pins,
45
- require_session_pin: input.require_session_pin
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
- export function desktopBridgeArchitecturePolicy(settings) {
49
- const mode = settings.provider_mode;
50
- const allowedModels = [...settings.allowed_models];
51
- const catalogVersion = settings.catalog_version
52
- || `catalog-${stableArchitectureHash({ mode, allowed_models: [...allowedModels].sort() }).slice(0, 24)}`;
53
- const seed = {
54
- schema: 'sks.provider-policy-snapshot.v1',
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
- policy,
69
- credential: { status: 'ready', reason_code: null },
70
- child,
71
- sessionPins: [...(settings.session_pins || [])],
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 listenHost = options.settings?.listen_host
80
- || persisted?.listen_host
81
- || DEFAULT_CODEX_LB_DESKTOP_BRIDGE_HOST;
82
- const listenPort = options.settings?.listen_port
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 (errorCode(error) === 'ENOENT')
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) > 64 * 1024)
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
- await writeTextAtomic(file, `${JSON.stringify(validated, null, 2)}\n`, { mode: 0o600 });
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 defaults = desktopBridgeServicePaths(home);
127
- const paths = overridePaths(defaults, options);
128
- const persisted = await readDesktopBridgeServiceSettings(paths.settings_path);
129
- const settings = defaultDesktopBridgeServiceSettings({
130
- ...(persisted || {}),
131
- ...(options.settings || {})
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
- if (!settings.allowed_models.length)
134
- throw new Error('desktop_bridge_provider_model_allowlist_empty');
135
- const credentials = await resolveDesktopBridgeCredentials(settings, { ...options, home });
136
- const architecture = desktopBridgeArchitecturePolicy(settings);
273
+ }
274
+ function settingsLockOptions(file) {
137
275
  return {
138
- config: {
139
- listenHost: settings.listen_host,
140
- listenPort: settings.listen_port,
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 = desktopBridgeLaunchService(options.uid);
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
- let expectedConfigGeneration = null;
187
- let credentialSource = null;
188
- const credentialBlockers = [];
189
- if (settings) {
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
- expectedConfigGeneration = desktopBridgeConfigGeneration(runtime.config);
193
- credentialSource = runtime.credential_source;
297
+ expected = desktopBridgeConfigGeneration(runtime.config);
298
+ source = runtime.credential_source;
299
+ sources = runtime.credential_sources;
194
300
  }
195
301
  catch (error) {
196
- credentialBlockers.push(safeServiceError(error));
302
+ blockers.push(safeServiceError(error));
197
303
  }
198
- }
199
- const bridgeStatus = await getDesktopBridgeStatus({
200
- statePath: paths.state_path,
201
- ...(expectedConfigGeneration ? { expectedConfigGeneration } : {}),
202
- ...(options.processExists ? { processExists: options.processExists } : {})
203
- });
204
- const launchd = await inspectLaunchdService(options, service);
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 paths = overridePaths(desktopBridgeServicePaths(home), options);
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
- let settings = await resolveDesktopBridgeActivationSettings({
241
- ...options,
242
- home,
243
- settingsPath: paths.settings_path
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 credentials;
337
+ let runtime;
246
338
  try {
247
- credentials = await resolveDesktopBridgeCredentials(settings, { ...options, home });
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
- schema: CODEX_LB_DESKTOP_BRIDGE_SERVICE_SCHEMA,
252
- ok: false,
253
- supported: true,
254
- installed: await exists(paths.launch_agent_path),
255
- loaded: false,
256
- running: false,
257
- status: 'credentials_unavailable',
258
- service: desktopBridgeLaunchService(options.uid),
259
- paths,
260
- state: await readDesktopBridgeState(paths.state_path).catch(() => null),
261
- settings,
262
- expected_config_generation: null,
263
- credential_source: null,
264
- blockers: [safeServiceError(error)]
265
- };
266
- }
267
- if (!settings.allowed_models.length && settings.provider_mode === 'codex-lb' && credentials.loaded_env) {
268
- const catalog = await readCodexLbModelCatalog({
269
- loadedEnv: credentials.loaded_env,
270
- gatewayAuthTransport: credentials.auth_transport
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 preflightDesktopBridge(config);
367
+ await cleanupRetiredDesktopBridgeRuntime(retired);
368
+ return status;
312
369
  }
313
370
  catch (error) {
314
- return failedServiceStatus({
315
- paths,
316
- settings,
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 launchctl = options.launchctl || '/bin/launchctl';
419
- const domain = desktopBridgeLaunchDomain(options.uid);
420
- const service = desktopBridgeLaunchService(options.uid);
421
- await run(launchctl, ['bootout', service], {
422
- timeoutMs: 5_000,
423
- maxOutputBytes: 16 * 1024
424
- }).catch(() => undefined);
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
- const paths = overridePaths(desktopBridgeServicePaths(home), options);
440
- if ((options.platform || process.platform) !== 'darwin') {
392
+ if ((options.platform || process.platform) !== 'darwin')
441
393
  return desktopBridgeServiceStatus({ ...options, home });
442
- }
443
- const run = options.run || runProcess;
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 removeStaleBridgeState(paths.state_path, options.processExists);
457
- if (options.removePlist === true)
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 === true)
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
- const prepared = await preflightDesktopBridge(runtime.config);
473
- handle = await startPreparedDesktopBridge(prepared, { statePath: runtime.paths.state_path });
474
- process.stdout.write(`${JSON.stringify({
475
- schema: 'sks.codex-lb-desktop-bridge-log.v1',
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
- if (row.schema !== CODEX_LB_DESKTOP_BRIDGE_SETTINGS_SCHEMA) {
433
+ const input = row;
434
+ if (input.schema !== DESKTOP_BRIDGE_SETTINGS_SCHEMA)
543
435
  throw new Error('desktop_bridge_settings_schema_invalid');
544
- }
545
- const listenHost = row.listen_host;
546
- const listenPort = Number(row.listen_port);
547
- const providerMode = row.provider_mode === undefined ? 'codex-lb' : row.provider_mode;
548
- const allowedModels = Array.isArray(row.allowed_models)
549
- ? [...new Set(row.allowed_models.map((entry) => String(entry || '').trim()).filter(Boolean))]
550
- : [];
551
- const gatewayAuthTransport = parseCodexLbGatewayAuthTransport(row.gateway_auth_transport);
552
- const allowedOrigins = Array.isArray(row.allowed_origins)
553
- ? row.allowed_origins.map((entry) => String(entry || '').trim()).filter(Boolean)
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
- if (providerMode !== 'codex-lb' && providerMode !== 'openrouter') {
574
- throw new Error('desktop_bridge_settings_provider_mode_invalid');
575
- }
576
- if (allowedModels.length > 512)
577
- throw new Error('desktop_bridge_settings_allowed_models_too_many');
578
- if (!allowedOrigins.length)
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
- if (!Number.isFinite(connectTimeoutMs) || connectTimeoutMs < 100 || connectTimeoutMs > 120_000) {
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 desktopBridgeLaunchDomain(uid = typeof process.getuid === 'function' ? process.getuid() : 0) {
631
- return `gui/${uid}`;
632
- }
633
- function desktopBridgeLaunchService(uid) {
634
- return `${desktopBridgeLaunchDomain(uid)}/${DESKTOP_BRIDGE_LAUNCHD_LABEL}`;
635
- }
636
- async function resolveDesktopBridgeLaunchCommand(options) {
637
- if (options.executablePath) {
638
- const executable = path.resolve(options.executablePath);
639
- return await exists(executable)
640
- ? { executable, arguments: [...(options.executableArguments || [])] }
641
- : null;
642
- }
643
- const entry = String(process.argv[1] || '').trim();
644
- const entryName = path.basename(entry).replace(/\.js$/i, '');
645
- if (entry
646
- && (entryName === 'sks' || entryName === 'sneakoscope')
647
- && await exists(entry)) {
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
- executable: path.resolve(process.execPath),
650
- arguments: [path.resolve(entry)]
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 failedServiceStatus(input) {
705
- return {
706
- schema: CODEX_LB_DESKTOP_BRIDGE_SERVICE_SCHEMA,
707
- ok: false,
708
- supported: true,
709
- installed: false,
710
- loaded: false,
711
- running: false,
712
- status: input.status,
713
- service: input.service,
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 failedProcess(error) {
737
- return {
738
- code: 1,
739
- stdout: '',
740
- stderr: safeServiceError(error),
741
- stdoutBytes: 0,
742
- stderrBytes: 0,
743
- truncated: false,
744
- timedOut: false
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 errorCode(error) {
748
- return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
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 delay(ms) {
751
- return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
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))); }