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
@@ -0,0 +1,207 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { BRIDGE_ACTIVE_GENERATION_FILENAME, BRIDGE_ACTIVE_GENERATION_SCHEMA } from './contracts.js';
5
+ import { safeErrorCode, unique } from './shared.js';
6
+ import { assertPrivateDirectory, catalogObservationGeneration, emptyCatalog, emptyRouteIndex, fsyncDirectory, generationBundleLayout, isPrivateRegular, readVerifiedPair, restoreSnapshot, snapshot, stagePrivateFile, verifyStagedPair } from './storage.js';
7
+ import { writeTextAtomic } from '../../fsx.js';
8
+ import { withFileLock } from '../../locks/file-lock.js';
9
+ export async function stageCombinedBridgeCatalog(input) {
10
+ const previous = await readActiveCombinedBridgeCatalog(input.catalogPath, input.routeIndexPath);
11
+ const layout = generationBundleLayout(input.catalogPath, input.routeIndexPath, input.build);
12
+ if (!layout.ok) {
13
+ return stagingFailure(layout.pointerPath, previous, layout.blockers);
14
+ }
15
+ if (!input.build.ok) {
16
+ return stagingFailure(layout.pointerPath, previous, unique(input.build.blockers.length > 0
17
+ ? input.build.blockers
18
+ : ['combined_catalog_build_not_verified']));
19
+ }
20
+ const catalogText = `${JSON.stringify(input.build.catalog, null, 2)}\n`;
21
+ const routeText = `${JSON.stringify(input.build.route_index, null, 2)}\n`;
22
+ const tempBundle = `${layout.bundleDirectory}.${process.pid}.${crypto.randomBytes(6).toString('hex')}.tmp`;
23
+ const tempCatalog = path.join(tempBundle, layout.catalogFilename);
24
+ const tempRouteIndex = path.join(tempBundle, layout.routeIndexFilename);
25
+ try {
26
+ await fs.mkdir(layout.generationsRoot, { recursive: true, mode: 0o700 });
27
+ await assertPrivateDirectory(layout.generationsRoot);
28
+ await fs.mkdir(tempBundle, { recursive: false, mode: 0o700 });
29
+ await Promise.all([
30
+ stagePrivateFile(tempCatalog, catalogText),
31
+ stagePrivateFile(tempRouteIndex, routeText)
32
+ ]);
33
+ await verifyStagedPair(tempCatalog, tempRouteIndex, input.build.catalog.generation, input.build.route_index.generation);
34
+ await fsyncDirectory(tempBundle);
35
+ const existingBundle = await fs.lstat(layout.bundleDirectory).catch(() => null);
36
+ if (existingBundle) {
37
+ if (!existingBundle.isDirectory() || existingBundle.isSymbolicLink()) {
38
+ throw new Error('combined_catalog_generation_bundle_invalid');
39
+ }
40
+ await assertPrivateDirectory(layout.bundleDirectory);
41
+ const existing = await readVerifiedPair(layout.catalogPath, layout.routeIndexPath);
42
+ if (existing.catalog.generation !== input.build.catalog.generation
43
+ || existing.route_index.generation !== input.build.route_index.generation) {
44
+ throw new Error('combined_catalog_generation_bundle_mismatch');
45
+ }
46
+ }
47
+ else {
48
+ await fs.rename(tempBundle, layout.bundleDirectory);
49
+ await fsyncDirectory(layout.generationsRoot);
50
+ }
51
+ const pointer = {
52
+ schema: BRIDGE_ACTIVE_GENERATION_SCHEMA,
53
+ catalog_generation: input.build.catalog.generation,
54
+ route_index_generation: input.build.route_index.generation,
55
+ observation_generation: catalogObservationGeneration(input.build.catalog),
56
+ bundle_directory: path.relative(layout.parentDirectory, layout.bundleDirectory),
57
+ catalog_filename: layout.catalogFilename,
58
+ route_index_filename: layout.routeIndexFilename
59
+ };
60
+ return {
61
+ schema: 'sks.bridge-combined-catalog-staging.v1',
62
+ staged: true,
63
+ generation: input.build.catalog.generation,
64
+ previous_generation: previous.ok ? previous.catalog.generation : null,
65
+ catalog_path: layout.catalogPath,
66
+ route_index_path: layout.routeIndexPath,
67
+ pointer_path: layout.pointerPath,
68
+ pointer_text: `${JSON.stringify(pointer, null, 2)}\n`,
69
+ blockers: []
70
+ };
71
+ }
72
+ catch (error) {
73
+ return stagingFailure(layout.pointerPath, previous, [safeErrorCode(error, 'combined_catalog_staging_failed')]);
74
+ }
75
+ finally {
76
+ await fs.rm(tempBundle, { recursive: true, force: true }).catch(() => undefined);
77
+ }
78
+ }
79
+ export async function activateCombinedBridgeCatalog(input) {
80
+ const staged = await stageCombinedBridgeCatalog(input);
81
+ if (!staged.staged || !staged.pointer_text) {
82
+ return activationResult(staged, false, null, staged.catalog_path, staged.route_index_path, staged.blockers);
83
+ }
84
+ const pointerText = staged.pointer_text;
85
+ return withFileLock({
86
+ lockPath: `${path.resolve(staged.pointer_path)}.lock`,
87
+ timeoutMs: 10_000,
88
+ staleMs: 60_000
89
+ }, async () => {
90
+ const priorPointer = await snapshot(staged.pointer_path);
91
+ try {
92
+ await input.testHooks?.afterCatalogRename?.();
93
+ await input.testHooks?.beforeRouteIndexRename?.();
94
+ await writeTextAtomic(staged.pointer_path, pointerText, { mode: 0o600 });
95
+ await fsyncDirectory(path.dirname(staged.pointer_path));
96
+ const activated = await readActiveCombinedBridgeCatalog(input.catalogPath, input.routeIndexPath);
97
+ if (!activated.ok
98
+ || activated.catalog.generation !== input.build.catalog.generation
99
+ || activated.route_index.generation !== input.build.route_index.generation) {
100
+ throw new Error('combined_catalog_activation_verification_failed');
101
+ }
102
+ return activationResult(staged, true, staged.generation, staged.catalog_path, staged.route_index_path, []);
103
+ }
104
+ catch (error) {
105
+ const rollbackBlockers = [];
106
+ try {
107
+ await restoreSnapshot(staged.pointer_path, priorPointer);
108
+ }
109
+ catch {
110
+ rollbackBlockers.push('combined_catalog_previous_generation_restore_failed');
111
+ }
112
+ const restored = rollbackBlockers.length === 0
113
+ ? await readActiveCombinedBridgeCatalog(input.catalogPath, input.routeIndexPath)
114
+ : null;
115
+ return activationResult(staged, false, null, restored?.ok ? restored.catalog_path : null, restored?.ok ? restored.route_index_path : null, unique([safeErrorCode(error, 'combined_catalog_activation_failed'), ...rollbackBlockers]));
116
+ }
117
+ });
118
+ }
119
+ export async function readActiveCombinedBridgeCatalog(catalogPath, routeIndexPath) {
120
+ const parentDirectory = path.dirname(path.resolve(catalogPath));
121
+ const pointerPath = path.join(parentDirectory, BRIDGE_ACTIVE_GENERATION_FILENAME);
122
+ try {
123
+ if (path.dirname(path.resolve(routeIndexPath)) !== parentDirectory) {
124
+ throw new Error('combined_catalog_paths_must_share_directory');
125
+ }
126
+ const pointerStat = await fs.lstat(pointerPath).catch(() => null);
127
+ if (!pointerStat) {
128
+ const direct = await readVerifiedPair(path.resolve(catalogPath), path.resolve(routeIndexPath));
129
+ return {
130
+ ...direct,
131
+ catalog_path: path.resolve(catalogPath),
132
+ route_index_path: path.resolve(routeIndexPath),
133
+ pointer_path: pointerPath
134
+ };
135
+ }
136
+ if (!isPrivateRegular(pointerStat))
137
+ throw new Error('combined_catalog_active_pointer_insecure');
138
+ const pointer = JSON.parse(await fs.readFile(pointerPath, 'utf8'));
139
+ if (pointer.schema !== BRIDGE_ACTIVE_GENERATION_SCHEMA) {
140
+ throw new Error('combined_catalog_active_pointer_schema_invalid');
141
+ }
142
+ const generationsRoot = path.join(parentDirectory, '.sks-bridge-generations');
143
+ const bundleDirectory = path.resolve(parentDirectory, pointer.bundle_directory);
144
+ const expectedBundleDirectory = path.join(generationsRoot, pointer.observation_generation
145
+ ? `${pointer.catalog_generation}.${pointer.route_index_generation}.${pointer.observation_generation}`
146
+ : `${pointer.catalog_generation}.${pointer.route_index_generation}`);
147
+ if (bundleDirectory !== expectedBundleDirectory
148
+ || !bundleDirectory.startsWith(`${generationsRoot}${path.sep}`)
149
+ || pointer.catalog_filename !== path.basename(catalogPath)
150
+ || pointer.route_index_filename !== path.basename(routeIndexPath)
151
+ || path.dirname(path.resolve(bundleDirectory, pointer.catalog_filename)) !== bundleDirectory
152
+ || path.dirname(path.resolve(bundleDirectory, pointer.route_index_filename)) !== bundleDirectory) {
153
+ throw new Error('combined_catalog_active_pointer_path_invalid');
154
+ }
155
+ await assertPrivateDirectory(generationsRoot);
156
+ await assertPrivateDirectory(bundleDirectory);
157
+ const activeCatalogPath = path.join(bundleDirectory, pointer.catalog_filename);
158
+ const activeRouteIndexPath = path.join(bundleDirectory, pointer.route_index_filename);
159
+ const pair = await readVerifiedPair(activeCatalogPath, activeRouteIndexPath);
160
+ if (pair.catalog.generation !== pointer.catalog_generation
161
+ || pair.route_index.generation !== pointer.route_index_generation) {
162
+ throw new Error('combined_catalog_active_pointer_generation_mismatch');
163
+ }
164
+ return {
165
+ ...pair,
166
+ catalog_path: activeCatalogPath,
167
+ route_index_path: activeRouteIndexPath,
168
+ pointer_path: pointerPath
169
+ };
170
+ }
171
+ catch (error) {
172
+ return {
173
+ ok: false,
174
+ catalog: emptyCatalog(),
175
+ route_index: emptyRouteIndex(),
176
+ catalog_path: null,
177
+ route_index_path: null,
178
+ pointer_path: pointerPath,
179
+ blockers: [safeErrorCode(error, 'combined_catalog_active_generation_missing')]
180
+ };
181
+ }
182
+ }
183
+ function stagingFailure(pointerPath, previous, blockers) {
184
+ return {
185
+ schema: 'sks.bridge-combined-catalog-staging.v1',
186
+ staged: false,
187
+ generation: null,
188
+ previous_generation: previous.ok ? previous.catalog.generation : null,
189
+ catalog_path: previous.ok ? previous.catalog_path : null,
190
+ route_index_path: previous.ok ? previous.route_index_path : null,
191
+ pointer_path: pointerPath,
192
+ pointer_text: null,
193
+ blockers
194
+ };
195
+ }
196
+ function activationResult(staged, activated, generation, catalogPath, routeIndexPath, blockers) {
197
+ return {
198
+ schema: 'sks.bridge-combined-catalog-activation.v1',
199
+ activated,
200
+ generation,
201
+ previous_generation: staged.previous_generation,
202
+ catalog_path: catalogPath,
203
+ route_index_path: routeIndexPath,
204
+ pointer_path: staged.pointer_path,
205
+ blockers
206
+ };
207
+ }
@@ -0,0 +1,12 @@
1
+ export function safeErrorCode(error, fallback) {
2
+ const message = error instanceof Error ? error.message : String(error || '');
3
+ return /^[a-z0-9_:-]{1,160}$/i.test(message) ? message : fallback;
4
+ }
5
+ export function compareModels(left, right) {
6
+ return left.public_id.localeCompare(right.public_id)
7
+ || left.provider_id.localeCompare(right.provider_id)
8
+ || left.upstream_model.localeCompare(right.upstream_model);
9
+ }
10
+ export function unique(values) {
11
+ return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
12
+ }
@@ -0,0 +1,171 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { buildBridgeRouteIndex, routeIndexMatchesGeneration, sha256Stable } from '../route-index.js';
4
+ import { writeTextAtomic } from '../../fsx.js';
5
+ import { BRIDGE_ACTIVE_GENERATION_FILENAME, COMBINED_BRIDGE_CATALOG_SCHEMA } from './contracts.js';
6
+ import { unique } from './shared.js';
7
+ export function generationBundleLayout(catalogPath, routeIndexPath, build) {
8
+ const resolvedCatalog = path.resolve(catalogPath);
9
+ const resolvedRoute = path.resolve(routeIndexPath);
10
+ const parentDirectory = path.dirname(resolvedCatalog);
11
+ const generationsRoot = path.join(parentDirectory, '.sks-bridge-generations');
12
+ const generationName = `${build.catalog.generation}.${build.route_index.generation}.${catalogObservationGeneration(build.catalog)}`;
13
+ const bundleDirectory = path.join(generationsRoot, generationName);
14
+ const catalogFilename = path.basename(resolvedCatalog);
15
+ const routeIndexFilename = path.basename(resolvedRoute);
16
+ const blockers = unique([
17
+ ...(path.dirname(resolvedRoute) === parentDirectory ? [] : ['combined_catalog_paths_must_share_directory']),
18
+ ...(catalogFilename !== routeIndexFilename ? [] : ['combined_catalog_paths_must_be_distinct']),
19
+ ...(/^[a-f0-9]{64}\.[a-f0-9]{64}\.[a-f0-9]{64}$/.test(generationName) ? [] : ['combined_catalog_generation_invalid'])
20
+ ]);
21
+ return {
22
+ ok: blockers.length === 0,
23
+ parentDirectory,
24
+ generationsRoot,
25
+ bundleDirectory,
26
+ catalogFilename,
27
+ routeIndexFilename,
28
+ catalogPath: path.join(bundleDirectory, catalogFilename),
29
+ routeIndexPath: path.join(bundleDirectory, routeIndexFilename),
30
+ pointerPath: path.join(parentDirectory, BRIDGE_ACTIVE_GENERATION_FILENAME),
31
+ blockers
32
+ };
33
+ }
34
+ export async function readVerifiedPair(catalogPath, routeIndexPath) {
35
+ const [catalogStat, routeStat] = await Promise.all([fs.lstat(catalogPath), fs.lstat(routeIndexPath)]);
36
+ if (!isPrivateRegular(catalogStat) || !isPrivateRegular(routeStat)) {
37
+ throw new Error('combined_catalog_active_file_insecure');
38
+ }
39
+ const [catalog, routeIndex] = await Promise.all([
40
+ fs.readFile(catalogPath, 'utf8').then((text) => JSON.parse(text)),
41
+ fs.readFile(routeIndexPath, 'utf8').then((text) => JSON.parse(text))
42
+ ]);
43
+ const validCatalog = catalog.schema === COMBINED_BRIDGE_CATALOG_SCHEMA
44
+ && catalog.digest === catalog.generation
45
+ && sha256Stable({ models: catalog.models }) === catalog.digest
46
+ && validProviderStatuses(catalog.provider_statuses);
47
+ const validRoute = routeIndexMatchesGeneration(routeIndex, routeIndex.generation);
48
+ const providerBindingsValid = validCatalog && ['codex-lb', 'openrouter'].every((providerId) => catalog.provider_statuses[providerId].generation === routeIndex.providers[providerId].catalog_generation);
49
+ if (!validCatalog || !validRoute || !providerBindingsValid) {
50
+ throw new Error('combined_catalog_active_generation_invalid');
51
+ }
52
+ return { ok: true, catalog, route_index: routeIndex, blockers: [] };
53
+ }
54
+ export async function stagePrivateFile(file, text) {
55
+ await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
56
+ const handle = await fs.open(file, 'wx', 0o600);
57
+ try {
58
+ await handle.writeFile(text, 'utf8');
59
+ await handle.sync();
60
+ }
61
+ finally {
62
+ await handle.close();
63
+ }
64
+ }
65
+ export async function verifyStagedPair(catalogPath, routePath, catalogGeneration, routeGeneration) {
66
+ const [catalog, route] = await Promise.all([
67
+ fs.readFile(catalogPath, 'utf8').then((text) => JSON.parse(text)),
68
+ fs.readFile(routePath, 'utf8').then((text) => JSON.parse(text))
69
+ ]);
70
+ if (catalog.generation !== catalogGeneration || route.generation !== routeGeneration) {
71
+ throw new Error('combined_catalog_staged_generation_mismatch');
72
+ }
73
+ if (sha256Stable({ models: catalog.models }) !== catalog.digest
74
+ || !routeIndexMatchesGeneration(route, routeGeneration)) {
75
+ throw new Error('combined_catalog_staged_digest_mismatch');
76
+ }
77
+ }
78
+ export async function snapshot(file) {
79
+ try {
80
+ return { exists: true, text: await fs.readFile(file, 'utf8') };
81
+ }
82
+ catch {
83
+ return { exists: false, text: '' };
84
+ }
85
+ }
86
+ export async function restoreSnapshot(file, prior) {
87
+ if (prior.exists)
88
+ await writeTextAtomic(file, prior.text, { mode: 0o600 });
89
+ else
90
+ await fs.rm(file, { force: true });
91
+ }
92
+ export async function fsyncDirectory(directory) {
93
+ const handle = await fs.open(directory, 'r');
94
+ try {
95
+ await handle.sync();
96
+ }
97
+ finally {
98
+ await handle.close();
99
+ }
100
+ }
101
+ export async function assertPrivateDirectory(directory) {
102
+ const stat = await fs.lstat(directory);
103
+ const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
104
+ if (!stat.isDirectory()
105
+ || stat.isSymbolicLink()
106
+ || (expectedUid !== null && Number(stat.uid) !== expectedUid)
107
+ || (process.platform !== 'win32' && (Number(stat.mode) & 0o777) !== 0o700)) {
108
+ throw new Error('combined_catalog_generation_directory_insecure');
109
+ }
110
+ }
111
+ export function isPrivateRegular(stat) {
112
+ return stat.isFile()
113
+ && !stat.isSymbolicLink()
114
+ && (typeof process.getuid !== 'function' || Number(stat.uid) === process.getuid())
115
+ && (process.platform === 'win32' || (Number(stat.mode) & 0o777) === 0o600);
116
+ }
117
+ export function emptyCatalog() {
118
+ const digest = sha256Stable({ models: [] });
119
+ return {
120
+ schema: COMBINED_BRIDGE_CATALOG_SCHEMA,
121
+ generation: digest,
122
+ created_at: new Date(0).toISOString(),
123
+ digest,
124
+ models: [],
125
+ provider_statuses: {
126
+ 'codex-lb': emptyProviderStatus('codex-lb'),
127
+ openrouter: emptyProviderStatus('openrouter')
128
+ }
129
+ };
130
+ }
131
+ export function emptyRouteIndex() {
132
+ return buildBridgeRouteIndex({
133
+ models: [],
134
+ providers: {
135
+ 'codex-lb': { catalog_generation: null, credential_fingerprint: null, state: 'unknown' },
136
+ openrouter: { catalog_generation: null, credential_fingerprint: null, state: 'unknown' }
137
+ },
138
+ created_at: new Date(0).toISOString()
139
+ }).route_index;
140
+ }
141
+ export function catalogObservationGeneration(catalog) {
142
+ return sha256Stable(catalog.provider_statuses);
143
+ }
144
+ function validProviderStatuses(value) {
145
+ if (!value || typeof value !== 'object' || Array.isArray(value))
146
+ return false;
147
+ const rows = value;
148
+ return ['codex-lb', 'openrouter'].every((providerId) => {
149
+ const row = rows[providerId];
150
+ return Boolean(row
151
+ && row.schema === 'sks.catalog-sync-state.v2'
152
+ && row.provider_id === providerId
153
+ && (row.expires_at === null || Number.isFinite(Date.parse(row.expires_at))));
154
+ });
155
+ }
156
+ function emptyProviderStatus(providerId) {
157
+ return {
158
+ schema: 'sks.catalog-sync-state.v2',
159
+ provider_id: providerId,
160
+ state: 'not_started',
161
+ source: providerId === 'codex-lb' ? 'gateway' : 'openrouter',
162
+ generation: null,
163
+ digest: null,
164
+ model_count: 0,
165
+ checked_at: null,
166
+ expires_at: null,
167
+ blockers: [],
168
+ warnings: [],
169
+ recovery_action: null
170
+ };
171
+ }
@@ -0,0 +1,12 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ export { BRIDGE_ACTIVE_GENERATION_FILENAME, BRIDGE_ACTIVE_GENERATION_SCHEMA, BRIDGE_ROUTE_INDEX_FILENAME, COMBINED_BRIDGE_CATALOG_FILENAME, COMBINED_BRIDGE_CATALOG_SCHEMA } from './combined-catalog/contracts.js';
4
+ export { buildCombinedBridgeCatalog } from './combined-catalog/builder.js';
5
+ export { activateCombinedBridgeCatalog, readActiveCombinedBridgeCatalog, stageCombinedBridgeCatalog } from './combined-catalog/persistence.js';
6
+ import { BRIDGE_ROUTE_INDEX_FILENAME, COMBINED_BRIDGE_CATALOG_FILENAME } from './combined-catalog/contracts.js';
7
+ export function combinedBridgeCatalogPath(codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex')) {
8
+ return path.join(path.resolve(codexHome), 'sks', COMBINED_BRIDGE_CATALOG_FILENAME);
9
+ }
10
+ export function bridgeRouteIndexPath(codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex')) {
11
+ return path.join(path.resolve(codexHome), 'sks', BRIDGE_ROUTE_INDEX_FILENAME);
12
+ }
@@ -1,4 +1,4 @@
1
- import { readDesktopBridgeState } from './state.js';
1
+ import { isDesktopBridgeStateFresh, readDesktopBridgeState } from './state.js';
2
2
  import { safeBridgeErrorCode } from './security.js';
3
3
  export const desktopBridgeProcessExists = (pid) => {
4
4
  try {
@@ -30,5 +30,8 @@ export async function getDesktopBridgeStatus(input) {
30
30
  if (!processExists(state.pid)) {
31
31
  return { status: 'stale', state, blocker: 'bridge_process_not_running' };
32
32
  }
33
+ if (!isDesktopBridgeStateFresh(state)) {
34
+ return { status: 'stale', state, blocker: 'bridge_state_stale' };
35
+ }
33
36
  return { status: 'running', state };
34
37
  }
@@ -1,106 +1,87 @@
1
1
  import { rewriteLocationHeader } from './location-rewrite.js';
2
2
  import { DesktopBridgeError } from './types.js';
3
- const HOP_BY_HOP = new Set([
4
- 'connection',
5
- 'keep-alive',
6
- 'proxy-authenticate',
7
- 'proxy-authorization',
8
- 'te',
9
- 'trailer',
10
- 'transfer-encoding',
11
- 'upgrade',
3
+ const HOP_BY_HOP = new Set(['connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'upgrade']);
4
+ const INTERNAL_PREFIX = 'x-sks-';
5
+ const REQUEST_HEADER_ALLOWLIST = new Set([
6
+ 'accept', 'accept-encoding', 'cache-control', 'content-encoding', 'content-length', 'content-type',
7
+ ]);
8
+ const CODEX_LOCAL_HEADERS = new Set([
9
+ 'thread-id', 'session-id', 'x-client-request-id', 'x-codex-installation-id',
10
+ 'x-codex-turn-metadata', 'x-codex-window-id', 'originator',
12
11
  ]);
13
12
  const NEVER_FORWARD_FROM_CLIENT = new Set([
14
- 'authorization',
15
- 'cookie',
16
- 'forwarded',
17
- 'proxy-authorization',
18
- 'x-codex-lb-api-key',
19
- 'x-forwarded-for',
20
- 'x-forwarded-host',
21
- 'x-forwarded-port',
22
- 'x-forwarded-proto',
23
- 'x-real-ip',
24
- 'x-sks-child-model',
25
- 'x-sks-child-policy-hash',
26
- 'x-sks-child-request',
27
- 'x-sks-parent-snapshot-hash',
28
- 'x-sks-provider-mode',
29
- 'x-sks-session-id',
13
+ 'authorization', 'cookie', 'forwarded', 'proxy-authorization', 'x-api-key', 'x-codex-lb-api-key',
14
+ 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-proto', 'x-real-ip',
30
15
  ]);
31
16
  const NEVER_FORWARD_TO_CLIENT = new Set([
32
- 'authorization',
33
- 'cookie',
34
- 'proxy-authenticate',
35
- 'proxy-authorization',
36
- 'set-cookie',
37
- 'x-codex-lb-api-key',
17
+ 'authorization', 'cookie', 'proxy-authenticate', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-codex-lb-api-key',
18
+ ]);
19
+ const OPENROUTER_CLIENT_HEADERS = new Set(['http-referer', 'x-title']);
20
+ const WEBSOCKET_HEADER_ALLOWLIST = new Set([
21
+ 'sec-websocket-key', 'sec-websocket-version', 'sec-websocket-protocol', 'sec-websocket-extensions',
38
22
  ]);
39
23
  function connectionTokens(inbound) {
40
- const raw = inbound.connection;
41
- const values = Array.isArray(raw) ? raw : raw ? [raw] : [];
42
- return new Set(values.flatMap((value) => value.split(',')).map((value) => value.trim().toLowerCase()).filter(Boolean));
24
+ const values = Array.isArray(inbound.connection) ? inbound.connection : inbound.connection ? [inbound.connection] : [];
25
+ return new Set(values.flatMap((v) => v.split(',')).map((v) => v.trim().toLowerCase()).filter(Boolean));
43
26
  }
44
- function injectGatewayCredential(result, gatewayKey, transport) {
45
- if (transport === 'x-codex-lb-api-key') {
46
- result['x-codex-lb-api-key'] = gatewayKey;
47
- return;
27
+ function injectCredential(result, providerId, transport, credential) {
28
+ if (credential.provider_id !== providerId || !credential.value || /[\r\n\0]/.test(credential.value)) {
29
+ throw new DesktopBridgeError('bridge_provider_credential_invalid');
48
30
  }
49
- result.authorization = `Bearer ${gatewayKey}`;
31
+ if (providerId === 'openrouter' && transport !== 'openrouter-bearer')
32
+ throw new DesktopBridgeError('bridge_provider_auth_transport_mismatch');
33
+ if (providerId === 'codex-lb' && transport === 'openrouter-bearer')
34
+ throw new DesktopBridgeError('bridge_provider_auth_transport_mismatch');
35
+ if (transport === 'x-codex-lb-api-key')
36
+ result['x-codex-lb-api-key'] = credential.value;
37
+ else
38
+ result.authorization = `Bearer ${credential.value}`;
50
39
  }
51
- export function buildUpstreamHeaders(inbound, config, upstreamHost) {
40
+ export function buildProviderUpstreamHeaders(inbound, context, upstreamHost) {
52
41
  const result = {};
53
- const dynamicHopByHop = connectionTokens(inbound);
54
42
  for (const [rawName, rawValue] of Object.entries(inbound)) {
55
43
  if (rawValue === undefined)
56
44
  continue;
57
45
  const name = rawName.toLowerCase();
58
- if (HOP_BY_HOP.has(name) || dynamicHopByHop.has(name))
59
- continue;
60
- if (NEVER_FORWARD_FROM_CLIENT.has(name) || name === 'host')
46
+ const providerAllowed = context.providerId === 'openrouter' && OPENROUTER_CLIENT_HEADERS.has(name);
47
+ if (!REQUEST_HEADER_ALLOWLIST.has(name) && !providerAllowed)
61
48
  continue;
62
49
  result[name] = rawValue;
63
50
  }
64
51
  result.host = upstreamHost;
65
- injectGatewayCredential(result, config.gatewayKey, config.gatewayAuthTransport);
52
+ injectCredential(result, context.providerId, context.authTransport, context.credential);
66
53
  return result;
67
54
  }
68
- export function buildWebSocketHeaders(inbound, config, upstreamHost) {
69
- const result = buildUpstreamHeaders(inbound, config, upstreamHost);
55
+ export function buildProviderWebSocketHeaders(inbound, context, upstreamHost) {
56
+ const result = buildProviderUpstreamHeaders(inbound, context, upstreamHost);
70
57
  for (const [rawName, rawValue] of Object.entries(inbound)) {
71
58
  if (rawValue === undefined)
72
59
  continue;
73
60
  const name = rawName.toLowerCase();
74
- if (name === 'connection' || name === 'upgrade' || name.startsWith('sec-websocket-')) {
61
+ if (WEBSOCKET_HEADER_ALLOWLIST.has(name))
75
62
  result[name] = rawValue;
76
- }
77
63
  }
78
64
  result.connection = 'Upgrade';
79
65
  result.upgrade = 'websocket';
80
66
  return result;
81
67
  }
82
- export function rewriteResponseHeaders(inbound, config, localOrigin) {
68
+ export function rewriteResponseHeaders(inbound, providerBaseUrl, localOrigin) {
83
69
  const result = {};
84
- const dynamicHopByHop = connectionTokens(inbound);
70
+ const dynamic = connectionTokens(inbound);
85
71
  for (const [rawName, rawValue] of Object.entries(inbound)) {
86
72
  if (rawValue === undefined)
87
73
  continue;
88
74
  const name = rawName.toLowerCase();
89
- if (HOP_BY_HOP.has(name) || dynamicHopByHop.has(name) || NEVER_FORWARD_TO_CLIENT.has(name))
90
- continue;
91
- if (name.startsWith('access-control-'))
75
+ if (HOP_BY_HOP.has(name) || dynamic.has(name) || NEVER_FORWARD_TO_CLIENT.has(name) || name.startsWith('access-control-'))
92
76
  continue;
93
77
  if (name === 'location') {
94
- if (Array.isArray(rawValue) && rawValue.length !== 1) {
78
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
79
+ if (values.length !== 1 || !values[0])
95
80
  throw new DesktopBridgeError('bridge_location_header_invalid');
96
- }
97
- const location = Array.isArray(rawValue) ? rawValue[0] : rawValue;
98
- if (!location)
99
- throw new DesktopBridgeError('bridge_location_header_invalid');
100
- result.location = rewriteLocationHeader(location, config.remoteBaseUrl, localOrigin);
101
- continue;
81
+ result.location = rewriteLocationHeader(values[0], providerBaseUrl, localOrigin);
102
82
  }
103
- result[name] = rawValue;
83
+ else
84
+ result[name] = rawValue;
104
85
  }
105
86
  return result;
106
87
  }
@@ -108,5 +89,9 @@ export function isRedactedHeaderName(name) {
108
89
  const normalized = name.toLowerCase();
109
90
  return NEVER_FORWARD_FROM_CLIENT.has(normalized)
110
91
  || NEVER_FORWARD_TO_CLIENT.has(normalized)
111
- || normalized === 'set-cookie';
92
+ || CODEX_LOCAL_HEADERS.has(normalized)
93
+ || normalized.startsWith(INTERNAL_PREFIX);
94
+ }
95
+ export function redactHeaderValue(name, value) {
96
+ return isRedactedHeaderName(name) ? '[REDACTED]' : String(value ?? '');
112
97
  }