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,151 +1,326 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
1
2
  import net from 'node:net';
2
3
  import tls from 'node:tls';
3
- import { buildWebSocketHeaders } from './header-policy.js';
4
+ import { buildProviderWebSocketHeaders } from './header-policy.js';
4
5
  import { rewriteLocationHeader } from './location-rewrite.js';
5
- import { resolveDesktopBridgeTarget } from './security.js';
6
+ import { resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, singleBridgeHeader } from './security.js';
6
7
  import { desktopBridgeListenOrigin } from './state.js';
7
- import { DesktopBridgeError } from './types.js';
8
- const MAX_UPGRADE_RESPONSE_HEAD_BYTES = 64 * 1024;
9
- const REDACTED_UPGRADE_RESPONSE_HEADERS = new Set([
10
- 'authorization',
11
- 'cookie',
12
- 'proxy-authenticate',
13
- 'proxy-authorization',
14
- 'set-cookie',
15
- 'x-codex-lb-api-key',
16
- ]);
8
+ import { DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL, DesktopBridgeError, } from './types.js';
9
+ const MAX_HEAD = 64 * 1024;
10
+ const REDACTED_RESPONSE_HEADERS = new Set(['authorization', 'cookie', 'proxy-authenticate', 'proxy-authorization', 'set-cookie', 'x-api-key', 'x-codex-lb-api-key']);
11
+ const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
17
12
  function serializeHeaders(headers) {
18
- return Object.entries(headers).flatMap(([name, value]) => {
19
- if (value === undefined)
20
- return [];
21
- if (Array.isArray(value))
22
- return value.map((entry) => `${name}: ${String(entry)}`);
23
- return [`${name}: ${String(value)}`];
24
- });
13
+ return Object.entries(headers).flatMap(([name, value]) => value === undefined ? [] : Array.isArray(value) ? value.map((v) => `${name}: ${v}`) : [`${name}: ${value}`]);
25
14
  }
26
- function rewriteUpgradeResponseHead(head, config) {
27
- const text = head.toString('latin1');
28
- const lines = text.split('\r\n');
29
- const statusLine = lines.shift();
30
- if (!statusLine || !/^HTTP\/1\.[01] \d{3}(?: |$)/.test(statusLine)) {
31
- throw new DesktopBridgeError('bridge_websocket_response_invalid');
15
+ function expectedAccept(key) { return createHash('sha1').update(`${key}${WS_MAGIC}`).digest('base64'); }
16
+ function parseResponseHead(head) {
17
+ const lines = head.toString('latin1').split('\r\n');
18
+ const match = lines.shift()?.match(/^HTTP\/1\.[01] (\d{3})(?: |$)/);
19
+ if (!match)
20
+ throw new DesktopBridgeError('desktop_bridge_websocket_upgrade_no_response');
21
+ const headers = new Map();
22
+ for (const line of lines) {
23
+ if (!line)
24
+ continue;
25
+ const colon = line.indexOf(':');
26
+ if (colon <= 0 || /^[ \t]/.test(line))
27
+ throw new DesktopBridgeError('desktop_bridge_websocket_upgrade_no_response');
28
+ const name = line.slice(0, colon).toLowerCase();
29
+ if (headers.has(name))
30
+ headers.set(name, `${headers.get(name)}, ${line.slice(colon + 1).trim()}`);
31
+ else
32
+ headers.set(name, line.slice(colon + 1).trim());
32
33
  }
33
- const output = [statusLine];
34
+ return { status: Number(match[1]), headers };
35
+ }
36
+ function validateUpgrade(head, key, requestedProtocol) {
37
+ const parsed = parseResponseHead(head);
38
+ if (parsed.status !== 101)
39
+ throw new DesktopBridgeError('desktop_bridge_websocket_upgrade_failed');
40
+ if (parsed.headers.get('sec-websocket-accept') !== expectedAccept(key))
41
+ throw new DesktopBridgeError('desktop_bridge_websocket_accept_invalid');
42
+ const protocol = parsed.headers.get('sec-websocket-protocol') || null;
43
+ if (requestedProtocol && protocol !== requestedProtocol)
44
+ throw new DesktopBridgeError('desktop_bridge_websocket_protocol_mismatch');
45
+ return { status: parsed.status, protocol };
46
+ }
47
+ function rewriteUpgradeResponseHead(head, providerBaseUrl, localOrigin) {
48
+ const lines = head.toString('latin1').split('\r\n');
49
+ const status = lines.shift();
50
+ if (!status)
51
+ throw new DesktopBridgeError('bridge_websocket_response_invalid');
52
+ const output = [status];
34
53
  for (const line of lines) {
35
54
  if (!line)
36
55
  continue;
37
- if (/^[ \t]/.test(line))
38
- throw new DesktopBridgeError('bridge_websocket_response_invalid');
39
56
  const colon = line.indexOf(':');
40
57
  if (colon <= 0)
41
58
  throw new DesktopBridgeError('bridge_websocket_response_invalid');
42
- const rawName = line.slice(0, colon);
43
- const name = rawName.toLowerCase();
59
+ const name = line.slice(0, colon).toLowerCase();
44
60
  const value = line.slice(colon + 1).trim();
45
- if (REDACTED_UPGRADE_RESPONSE_HEADERS.has(name) || name.startsWith('access-control-'))
61
+ if (REDACTED_RESPONSE_HEADERS.has(name) || name.startsWith('access-control-'))
46
62
  continue;
47
- if (name === 'location') {
48
- output.push(`${rawName}: ${rewriteLocationHeader(value, config.remoteBaseUrl, desktopBridgeListenOrigin(config))}`);
49
- continue;
50
- }
51
- output.push(line);
63
+ output.push(name === 'location' ? `${line.slice(0, colon)}: ${rewriteLocationHeader(value, providerBaseUrl, localOrigin)}` : line);
52
64
  }
53
65
  return Buffer.from(`${output.join('\r\n')}\r\n\r\n`, 'latin1');
54
66
  }
55
- function writeUpgradeFailure(clientSocket) {
56
- if (clientSocket.destroyed)
57
- return;
58
- clientSocket.end('HTTP/1.1 502 Bad Gateway\r\n'
59
- + 'Content-Type: application/json\r\n'
60
- + 'Cache-Control: no-store\r\n'
61
- + 'Connection: close\r\n'
62
- + '\r\n'
63
- + '{"error":{"type":"sks_bridge_error","code":"bridge_websocket_upstream_unavailable"}}');
67
+ export async function prepareDesktopBridgeWebSocketRequest(req, config) {
68
+ const sessionIdentity = resolveCodexSessionIdentity(req.headers);
69
+ const route = await resolveAndBindDesktopBridgeRouteContext({
70
+ public_model: singleBridgeHeader(req.headers, 'x-sks-model') || '',
71
+ session_id: sessionIdentity.thread_id,
72
+ pathname: new URL(req.url || '/', 'http://bridge.invalid').pathname,
73
+ transport: 'websocket', headers: req.headers,
74
+ }, config);
75
+ const provider = config.providers[route.provider_id];
76
+ if (!provider)
77
+ throw new DesktopBridgeError('bridge_provider_route_unavailable');
78
+ const credential = await config.resolveProviderCredential(route.provider_id, provider.credential_generation);
79
+ if (credential.provider_id !== route.provider_id || credential.generation !== provider.credential_generation
80
+ || (provider.credential_fingerprint && credential.fingerprint !== provider.credential_fingerprint)) {
81
+ throw new DesktopBridgeError('bridge_provider_credential_generation_mismatch');
82
+ }
83
+ return { route, provider, credential };
84
+ }
85
+ function writeUpgradeFailure(client) {
86
+ if (!client.destroyed)
87
+ client.end('HTTP/1.1 502 Bad Gateway\r\nContent-Type: application/json\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n{"error":{"type":"sks_bridge_error","code":"bridge_websocket_upstream_unavailable"}}');
64
88
  }
65
- export function forwardWebSocket(req, clientSocket, head, config) {
66
- const target = resolveDesktopBridgeTarget(req.url, config.remote);
67
- const upstream = config.remote.secure
68
- ? tls.connect({
69
- host: config.remote.address,
70
- port: config.remote.port,
71
- ...(config.remote.tlsServername ? { servername: config.remote.tlsServername } : {}),
72
- })
73
- : net.connect({
74
- host: config.remote.address,
75
- port: config.remote.port,
76
- family: config.remote.family,
77
- });
89
+ export async function forwardWebSocket(req, client, head, config, authenticatedLocalBaseUrl = desktopBridgeListenOrigin(config)) {
90
+ let prepared;
91
+ try {
92
+ prepared = await prepareDesktopBridgeWebSocketRequest(req, config);
93
+ }
94
+ catch (error) {
95
+ writeUpgradeFailure(client);
96
+ throw error;
97
+ }
98
+ const { provider, credential } = prepared;
99
+ const target = resolveDesktopBridgeTarget(req.url, provider.remote);
100
+ const upstream = provider.remote.secure
101
+ ? tls.connect({ host: provider.remote.address, port: provider.remote.port, ...(provider.remote.tlsServername ? { servername: provider.remote.tlsServername } : {}) })
102
+ : net.connect({ host: provider.remote.address, port: provider.remote.port, family: provider.remote.family });
103
+ const key = String(req.headers['sec-websocket-key'] || '');
104
+ const requestedProtocol = String(req.headers['sec-websocket-protocol'] || '').split(',')[0]?.trim() || null;
78
105
  let connected = false;
79
- let responseHead = Buffer.alloc(0);
80
- const connectTimer = setTimeout(() => {
81
- upstream.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout'));
82
- }, config.connectTimeoutMs);
83
- connectTimer.unref();
84
- const fail = () => {
85
- clearTimeout(connectTimer);
86
- if (!connected)
87
- writeUpgradeFailure(clientSocket);
88
- else
89
- clientSocket.destroy();
90
- };
106
+ let response = Buffer.alloc(0);
107
+ const timer = setTimeout(() => upstream.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout')), config.connectTimeoutMs);
108
+ timer.unref();
109
+ const fail = () => { clearTimeout(timer); if (!connected)
110
+ writeUpgradeFailure(client);
111
+ else
112
+ client.destroy(); };
91
113
  const onConnected = () => {
92
114
  connected = true;
93
- clearTimeout(connectTimer);
115
+ clearTimeout(timer);
94
116
  upstream.setNoDelay(true);
95
- upstream.setTimeout(config.idleTimeoutMs, () => {
96
- upstream.destroy(new DesktopBridgeError('bridge_upstream_idle_timeout'));
97
- });
98
- if (clientSocket instanceof net.Socket) {
99
- clientSocket.setNoDelay(true);
100
- clientSocket.setTimeout(config.idleTimeoutMs, () => clientSocket.destroy());
101
- }
102
- const headers = buildWebSocketHeaders(req.headers, config, target.host);
103
- const requestHead = [
104
- `${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`,
105
- ...serializeHeaders(headers),
106
- '',
107
- '',
108
- ].join('\r\n');
109
- upstream.write(requestHead);
117
+ upstream.setTimeout(config.idleTimeoutMs, () => upstream.destroy());
118
+ const headers = buildProviderWebSocketHeaders(req.headers, { providerId: provider.provider_id, authTransport: provider.auth_transport, credential }, target.host);
119
+ upstream.write([`${req.method || 'GET'} ${target.pathname}${target.search} HTTP/1.1`, ...serializeHeaders(headers), '', ''].join('\r\n'));
110
120
  if (head.length)
111
121
  upstream.write(head);
112
- clientSocket.pipe(upstream);
113
- const onHandshakeData = (chunk) => {
114
- responseHead = Buffer.concat([responseHead, chunk]);
115
- if (responseHead.length > MAX_UPGRADE_RESPONSE_HEAD_BYTES) {
122
+ const onData = (chunk) => {
123
+ response = Buffer.concat([response, chunk]);
124
+ if (response.length > MAX_HEAD) {
116
125
  upstream.destroy(new DesktopBridgeError('bridge_websocket_response_headers_too_large'));
117
126
  return;
118
127
  }
119
- const boundary = responseHead.indexOf('\r\n\r\n');
128
+ const boundary = response.indexOf('\r\n\r\n');
120
129
  if (boundary < 0)
121
130
  return;
122
- upstream.off('data', onHandshakeData);
123
- const rawHead = responseHead.subarray(0, boundary);
124
- const remaining = responseHead.subarray(boundary + 4);
131
+ upstream.off('data', onData);
132
+ const raw = response.subarray(0, boundary);
133
+ const remaining = response.subarray(boundary + 4);
125
134
  try {
126
- clientSocket.write(rewriteUpgradeResponseHead(rawHead, config));
135
+ validateUpgrade(raw, key, requestedProtocol);
136
+ client.write(rewriteUpgradeResponseHead(raw, provider.base_url, authenticatedLocalBaseUrl));
127
137
  if (remaining.length)
128
- clientSocket.write(remaining);
138
+ client.write(remaining);
129
139
  }
130
140
  catch (error) {
131
141
  upstream.destroy(error instanceof Error ? error : undefined);
132
142
  return;
133
143
  }
134
- upstream.pipe(clientSocket);
135
- responseHead = Buffer.alloc(0);
144
+ client.pipe(upstream);
145
+ upstream.pipe(client);
136
146
  };
137
- upstream.on('data', onHandshakeData);
147
+ upstream.on('data', onData);
138
148
  };
139
- if (config.remote.secure)
149
+ if (provider.remote.secure)
140
150
  upstream.once('secureConnect', onConnected);
141
151
  else
142
152
  upstream.once('connect', onConnected);
143
153
  upstream.once('error', fail);
144
- upstream.once('close', () => {
145
- clearTimeout(connectTimer);
146
- if (!clientSocket.destroyed)
147
- clientSocket.end();
154
+ upstream.once('close', () => { clearTimeout(timer); if (!client.destroyed)
155
+ client.end(); });
156
+ client.once('error', () => upstream.destroy());
157
+ client.once('close', () => upstream.destroy());
158
+ }
159
+ function encodeClientFrame(opcode, payload) {
160
+ if (payload.length > 125)
161
+ throw new DesktopBridgeError('desktop_bridge_websocket_frame_send_failed');
162
+ const mask = randomBytes(4);
163
+ const encoded = Buffer.alloc(payload.length);
164
+ for (let i = 0; i < payload.length; i += 1)
165
+ encoded[i] = (payload[i] || 0) ^ (mask[i % 4] || 0);
166
+ return Buffer.concat([Buffer.from([0x80 | opcode, 0x80 | payload.length]), mask, encoded]);
167
+ }
168
+ function decodeServerFrame(buffer) {
169
+ if (buffer.length < 2)
170
+ return null;
171
+ const length = buffer[1] & 0x7f;
172
+ if ((buffer[1] & 0x80) !== 0 || length === 126 || length === 127)
173
+ throw new DesktopBridgeError('desktop_bridge_websocket_frame_invalid');
174
+ if (buffer.length < 2 + length)
175
+ return null;
176
+ return { opcode: buffer[0] & 0x0f, payload: buffer.subarray(2, 2 + length), consumed: 2 + length };
177
+ }
178
+ function probeResult(input) {
179
+ return {
180
+ schema: 'sks.desktop-bridge-websocket-probe.v2', state: input.state, terminal_stage: input.terminal_stage,
181
+ root_cause: input.root_cause, status_code: input.status_code ?? null, negotiated_protocol: input.negotiated_protocol ?? null,
182
+ upgrade_verified: input.upgrade_verified === true, protocol_verified: input.protocol_verified === true,
183
+ frame_round_trip_verified: input.frame_round_trip_verified === true, clean_close_verified: input.clean_close_verified === true,
184
+ latency_ms: input.latency_ms ?? null, blockers: input.root_cause ? [input.root_cause] : [], warnings: input.warnings || [],
185
+ };
186
+ }
187
+ async function probeOnce(options) {
188
+ const started = Date.now();
189
+ let url;
190
+ try {
191
+ url = new URL(options.url);
192
+ }
193
+ catch {
194
+ return probeResult({ state: 'blocked', terminal_stage: 'tcp_connect', root_cause: 'desktop_bridge_websocket_target_invalid' });
195
+ }
196
+ if (url.protocol !== 'ws:' && url.protocol !== 'wss:')
197
+ return probeResult({ state: 'blocked', terminal_stage: 'tcp_connect', root_cause: 'desktop_bridge_websocket_target_invalid' });
198
+ const key = randomBytes(16).toString('base64');
199
+ return new Promise((resolve) => {
200
+ let settled = false;
201
+ let connected = false;
202
+ let upgraded = false;
203
+ let protocolVerified = false;
204
+ let frameVerified = false;
205
+ let buffer = Buffer.alloc(0);
206
+ const socket = url.protocol === 'wss:' ? tls.connect({ host: url.hostname, port: Number(url.port || 443), servername: url.hostname }) : net.connect({ host: url.hostname, port: Number(url.port || 80) });
207
+ let stageTimer;
208
+ const timeoutResult = () => probeResult({
209
+ state: 'failed',
210
+ terminal_stage: frameVerified ? 'clean_close' : upgraded ? 'frame_round_trip' : connected ? 'websocket_upgrade' : 'tcp_connect',
211
+ root_cause: frameVerified ? 'desktop_bridge_websocket_close_failed' : upgraded ? 'desktop_bridge_websocket_frame_receive_failed' : connected ? 'desktop_bridge_websocket_upgrade_no_response' : 'desktop_bridge_tcp_connect_timeout',
212
+ upgrade_verified: upgraded, protocol_verified: protocolVerified, frame_round_trip_verified: frameVerified,
213
+ });
214
+ const finish = (result) => { if (settled)
215
+ return; settled = true; clearTimeout(stageTimer); socket.destroy(); resolve({ ...result, latency_ms: Date.now() - started }); };
216
+ stageTimer = setTimeout(() => finish(timeoutResult()), Math.min(options.connectTimeoutMs, options.stageTimeoutMs));
217
+ stageTimer.unref();
218
+ socket.once('error', () => finish(probeResult({ state: 'failed', terminal_stage: upgraded ? 'frame_round_trip' : 'tcp_connect', root_cause: upgraded ? 'desktop_bridge_websocket_frame_receive_failed' : 'desktop_bridge_tcp_connect_failed', upgrade_verified: upgraded, protocol_verified: protocolVerified })));
219
+ const onConnected = () => {
220
+ connected = true;
221
+ clearTimeout(stageTimer);
222
+ stageTimer = setTimeout(() => finish(timeoutResult()), options.stageTimeoutMs);
223
+ stageTimer.unref();
224
+ const headers = [`GET ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`, 'Connection: Upgrade', 'Upgrade: websocket', 'Sec-WebSocket-Version: 13', `Sec-WebSocket-Key: ${key}`, `Sec-WebSocket-Protocol: ${options.protocol}`];
225
+ if (options.origin)
226
+ headers.push(`Origin: ${options.origin}`);
227
+ socket.write(`${headers.join('\r\n')}\r\n\r\n`);
228
+ };
229
+ if (url.protocol === 'wss:')
230
+ socket.once('secureConnect', onConnected);
231
+ else
232
+ socket.once('connect', onConnected);
233
+ socket.once('close', () => {
234
+ if (settled)
235
+ return;
236
+ finish(probeResult({
237
+ state: 'failed', terminal_stage: frameVerified ? 'clean_close' : upgraded ? 'frame_round_trip' : connected ? 'websocket_upgrade' : 'tcp_connect',
238
+ root_cause: frameVerified ? 'desktop_bridge_websocket_close_failed' : upgraded ? 'desktop_bridge_websocket_frame_receive_failed' : connected ? 'desktop_bridge_websocket_upgrade_no_response' : 'desktop_bridge_tcp_connect_failed',
239
+ upgrade_verified: upgraded, protocol_verified: protocolVerified, frame_round_trip_verified: frameVerified,
240
+ }));
241
+ });
242
+ socket.on('data', (chunk) => {
243
+ buffer = Buffer.concat([buffer, chunk]);
244
+ if (!upgraded) {
245
+ if (buffer.length > MAX_HEAD)
246
+ return finish(probeResult({ state: 'failed', terminal_stage: 'websocket_upgrade', root_cause: 'desktop_bridge_websocket_upgrade_no_response' }));
247
+ const boundary = buffer.indexOf('\r\n\r\n');
248
+ if (boundary < 0)
249
+ return;
250
+ try {
251
+ const parsed = validateUpgrade(buffer.subarray(0, boundary), key, options.protocol);
252
+ upgraded = true;
253
+ protocolVerified = parsed.protocol === options.protocol;
254
+ buffer = buffer.subarray(boundary + 4);
255
+ }
256
+ catch (error) {
257
+ const code = error instanceof DesktopBridgeError ? error.code : 'desktop_bridge_websocket_upgrade_no_response';
258
+ const stage = code.includes('protocol') ? 'websocket_protocol' : 'websocket_upgrade';
259
+ return finish(probeResult({ state: 'failed', terminal_stage: stage, root_cause: code }));
260
+ }
261
+ if (options.handshakeOnly)
262
+ return finish(probeResult({ state: options.requestedLevel === 'shallow' ? 'not_attempted' : 'degraded', terminal_stage: 'websocket_protocol', root_cause: null, status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified, frame_round_trip_verified: false, clean_close_verified: false, warnings: ['websocket_handshake_only_frame_not_attempted'] }));
263
+ try {
264
+ socket.write(encodeClientFrame(1, options.framePayload));
265
+ }
266
+ catch {
267
+ return finish(probeResult({ state: 'failed', terminal_stage: 'frame_round_trip', root_cause: 'desktop_bridge_websocket_frame_send_failed', status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified }));
268
+ }
269
+ }
270
+ while (upgraded && !settled) {
271
+ let frame;
272
+ try {
273
+ frame = decodeServerFrame(buffer);
274
+ }
275
+ catch {
276
+ return finish(probeResult({ state: 'failed', terminal_stage: 'frame_round_trip', root_cause: 'desktop_bridge_websocket_frame_invalid', status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified }));
277
+ }
278
+ if (!frame)
279
+ return;
280
+ buffer = buffer.subarray(frame.consumed);
281
+ if (frame.opcode === 1 || frame.opcode === 2) {
282
+ if (!frame.payload.equals(options.framePayload))
283
+ return finish(probeResult({ state: 'failed', terminal_stage: 'frame_round_trip', root_cause: 'desktop_bridge_websocket_frame_invalid', status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified }));
284
+ frameVerified = true;
285
+ socket.write(encodeClientFrame(8, Buffer.from([0x03, 0xe8])));
286
+ }
287
+ else if (frame.opcode === 8 && frameVerified) {
288
+ return finish(probeResult({ state: 'verified', terminal_stage: 'complete', root_cause: null, status_code: 101, negotiated_protocol: options.protocol, upgrade_verified: true, protocol_verified: protocolVerified, frame_round_trip_verified: true, clean_close_verified: true }));
289
+ }
290
+ }
291
+ });
148
292
  });
149
- clientSocket.once('error', () => upstream.destroy());
150
- clientSocket.once('close', () => upstream.destroy());
293
+ }
294
+ export async function probeDesktopBridgeWebSocket(input) {
295
+ const maxRetries = Math.min(2, Math.max(0, input.maxRetries ?? 2));
296
+ const total = Math.min(30_000, Math.max(500, input.totalTimeoutMs ?? 10_000));
297
+ const started = Date.now();
298
+ const warnings = [];
299
+ const options = {
300
+ url: input.url, origin: input.origin || null, protocol: input.protocol || DESKTOP_BRIDGE_DIAGNOSTIC_PROTOCOL,
301
+ framePayload: Buffer.isBuffer(input.framePayload) ? input.framePayload : Buffer.from(input.framePayload || 'sks-desktop-bridge-probe'),
302
+ handshakeOnly: input.handshakeOnly === true, requestedLevel: input.requestedLevel || 'transport', connectTimeoutMs: Math.max(100, input.connectTimeoutMs ?? 2_000),
303
+ stageTimeoutMs: Math.max(100, input.stageTimeoutMs ?? 2_000), totalTimeoutMs: total, maxRetries, jitter: input.jitter || Math.random,
304
+ };
305
+ let result = probeResult({ state: 'failed', terminal_stage: 'tcp_connect', root_cause: 'desktop_bridge_tcp_connect_failed' });
306
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
307
+ const remaining = total - (Date.now() - started);
308
+ if (remaining <= 0)
309
+ break;
310
+ result = await probeOnce({
311
+ ...options,
312
+ connectTimeoutMs: Math.min(options.connectTimeoutMs, remaining),
313
+ stageTimeoutMs: Math.min(options.stageTimeoutMs, remaining),
314
+ });
315
+ if (result.root_cause === null)
316
+ return { ...result, warnings: [...warnings, ...result.warnings] };
317
+ if (attempt < maxRetries) {
318
+ warnings.push(`desktop_bridge_websocket_retry:${attempt + 1}:${result.root_cause || 'unknown'}`);
319
+ const delay = Math.min(500, 50 * (2 ** attempt) + Math.floor(Math.max(0, Math.min(1, options.jitter())) * 25));
320
+ if (Date.now() - started + delay >= total)
321
+ break;
322
+ await new Promise((resolve) => setTimeout(resolve, delay));
323
+ }
324
+ }
325
+ return { ...result, blockers: result.root_cause ? [result.root_cause] : [], warnings: [...warnings, ...result.warnings] };
151
326
  }