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,57 +1,79 @@
1
1
  import http, {} from 'node:http';
2
2
  import https from 'node:https';
3
3
  import { pipeline } from 'node:stream/promises';
4
- import { buildUpstreamHeaders, rewriteResponseHeaders } from './header-policy.js';
5
- import { assertDesktopBridgeRequestPolicy, resolveDesktopBridgeTarget, safeBridgeErrorCode, } from './security.js';
4
+ import { buildProviderUpstreamHeaders, rewriteResponseHeaders } from './header-policy.js';
5
+ import { resolveAndBindDesktopBridgeRouteContext, resolveCodexSessionIdentity, resolveDesktopBridgeTarget, safeBridgeErrorCode, singleBridgeHeader } from './security.js';
6
6
  import { desktopBridgeListenOrigin } from './state.js';
7
7
  import { DesktopBridgeError } from './types.js';
8
- const MAX_MODEL_VALIDATION_BODY_BYTES = 16 * 1024 * 1024;
9
- function isResponsesPath(rawUrl) {
8
+ const MAX_UPSTREAM_ERROR_BODY_BYTES = 1024 * 1024;
9
+ function bodyCarriesModel(rawUrl) {
10
10
  const pathname = new URL(String(rawUrl || '/'), 'http://bridge.invalid').pathname;
11
- return pathname === '/backend-api/codex/responses'
12
- || pathname === '/api/v1/responses'
13
- || pathname === '/v1/responses';
11
+ return pathname === '/backend-api/codex/responses' || pathname === '/api/v1/responses' || pathname === '/v1/responses';
14
12
  }
15
- export async function prepareDesktopBridgeRequestBody(req, config) {
16
- if (!config.providerMode || !isResponsesPath(req.url))
17
- return null;
13
+ async function readBoundedBody(req, maximum) {
18
14
  const declared = Number(req.headers['content-length'] || 0);
19
- if (Number.isFinite(declared) && declared > MAX_MODEL_VALIDATION_BODY_BYTES) {
15
+ if (Number.isFinite(declared) && declared > maximum)
20
16
  throw new DesktopBridgeError('bridge_request_body_too_large');
21
- }
22
17
  const chunks = [];
23
18
  let total = 0;
24
19
  for await (const raw of req) {
25
20
  const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
26
21
  total += chunk.length;
27
- if (total > MAX_MODEL_VALIDATION_BODY_BYTES)
22
+ if (total > maximum)
28
23
  throw new DesktopBridgeError('bridge_request_body_too_large');
29
24
  chunks.push(chunk);
30
25
  }
31
- const body = Buffer.concat(chunks);
32
- let payload;
33
- try {
34
- payload = JSON.parse(body.toString('utf8'));
26
+ return Buffer.concat(chunks);
27
+ }
28
+ async function resolveCredential(config, route) {
29
+ const provider = config.providers[route.provider_id];
30
+ if (!provider)
31
+ throw new DesktopBridgeError('bridge_provider_route_unavailable');
32
+ const credential = await config.resolveProviderCredential(route.provider_id, provider.credential_generation);
33
+ if (credential.provider_id !== route.provider_id || credential.generation !== provider.credential_generation
34
+ || (provider.credential_fingerprint && credential.fingerprint !== provider.credential_fingerprint)) {
35
+ throw new DesktopBridgeError('bridge_provider_credential_generation_mismatch');
35
36
  }
36
- catch {
37
- throw new DesktopBridgeError('bridge_responses_body_invalid_json');
37
+ return credential;
38
+ }
39
+ export async function prepareDesktopBridgeRequest(req, config) {
40
+ const pathname = new URL(String(req.url || '/'), 'http://bridge.invalid').pathname;
41
+ let body = null;
42
+ let payload = null;
43
+ if (bodyCarriesModel(req.url)) {
44
+ body = await readBoundedBody(req, config.maxRequestBodyBytes ?? 16 * 1024 * 1024);
45
+ try {
46
+ const parsed = JSON.parse(body.toString('utf8'));
47
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
48
+ throw new Error();
49
+ payload = parsed;
50
+ }
51
+ catch {
52
+ throw new DesktopBridgeError('bridge_responses_body_invalid_json');
53
+ }
38
54
  }
39
- const model = payload && typeof payload === 'object' && !Array.isArray(payload)
40
- ? payload.model
41
- : null;
42
- assertDesktopBridgeRequestPolicy({ headers: req.headers, config, model });
43
- return body;
55
+ const headerModel = singleBridgeHeader(req.headers, 'x-sks-model');
56
+ const model = typeof payload?.model === 'string' ? payload.model : headerModel;
57
+ const sessionIdentity = resolveCodexSessionIdentity(req.headers, payload);
58
+ const route = await resolveAndBindDesktopBridgeRouteContext({
59
+ public_model: String(model || ''), session_id: sessionIdentity.thread_id,
60
+ pathname, transport: 'http', headers: req.headers,
61
+ }, config);
62
+ if (payload && payload.model !== route.upstream_model) {
63
+ payload.model = route.upstream_model;
64
+ body = Buffer.from(JSON.stringify(payload));
65
+ }
66
+ const credential = await resolveCredential(config, route);
67
+ return { body, route, credential };
44
68
  }
45
69
  function connectTimeout(request, config) {
46
70
  request.once('socket', (socket) => {
47
71
  if (!socket.connecting)
48
72
  return;
49
- const timer = setTimeout(() => {
50
- request.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout'));
51
- }, config.connectTimeoutMs);
73
+ const timer = setTimeout(() => request.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout')), config.connectTimeoutMs);
52
74
  timer.unref();
53
- const event = config.remote.secure ? 'secureConnect' : 'connect';
54
- socket.once(event, () => clearTimeout(timer));
75
+ socket.once('connect', () => clearTimeout(timer));
76
+ socket.once('secureConnect', () => clearTimeout(timer));
55
77
  socket.once('close', () => clearTimeout(timer));
56
78
  });
57
79
  }
@@ -61,80 +83,90 @@ function writeHttpBridgeError(res, error) {
61
83
  res.destroy(error instanceof Error ? error : undefined);
62
84
  return;
63
85
  }
64
- res.writeHead(502, {
65
- 'content-type': 'application/json',
66
- 'cache-control': 'no-store',
67
- connection: 'close',
86
+ res.writeHead(code.startsWith('catalog_') || code.startsWith('session_') || code.includes('route_') ? 409 : 502, {
87
+ 'content-type': 'application/json', 'cache-control': 'no-store', connection: 'close',
68
88
  });
69
- res.end(JSON.stringify({
89
+ res.end(JSON.stringify({ error: { type: 'sks_bridge_error', code, message: code } }));
90
+ }
91
+ async function readRedactedUpstreamError(response) {
92
+ let total = 0;
93
+ for await (const raw of response) {
94
+ const chunk = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
95
+ total += chunk.length;
96
+ if (total > MAX_UPSTREAM_ERROR_BODY_BYTES) {
97
+ response.destroy();
98
+ break;
99
+ }
100
+ }
101
+ return Buffer.from(JSON.stringify({
70
102
  error: {
71
- type: 'sks_bridge_error',
72
- code,
73
- message: code,
74
- },
103
+ type: 'upstream_error',
104
+ code: 'bridge_upstream_request_failed',
105
+ message: 'Upstream request failed'
106
+ }
75
107
  }));
76
108
  }
77
- export async function forwardHttp(req, res, config, bufferedBody = null) {
78
- const target = resolveDesktopBridgeTarget(req.url, config.remote);
79
- const transport = config.remote.secure ? https : http;
80
- const headers = buildUpstreamHeaders(req.headers, config, target.host);
81
- const localOrigin = desktopBridgeListenOrigin(config);
109
+ export async function forwardHttp(req, res, config, prepared, authenticatedLocalBaseUrl = desktopBridgeListenOrigin(config)) {
82
110
  try {
111
+ const request = prepared || await prepareDesktopBridgeRequest(req, config);
112
+ const provider = config.providers[request.route.provider_id];
113
+ if (!provider)
114
+ throw new DesktopBridgeError('bridge_provider_route_unavailable');
115
+ const target = resolveDesktopBridgeTarget(req.url, provider.remote);
116
+ const transport = provider.remote.secure ? https : http;
117
+ const headers = buildProviderUpstreamHeaders(req.headers, {
118
+ providerId: provider.provider_id, authTransport: provider.auth_transport, credential: request.credential,
119
+ }, target.host);
120
+ if (request.body)
121
+ headers['content-length'] = String(request.body.length);
122
+ else
123
+ delete headers['content-length'];
83
124
  await new Promise((resolve, reject) => {
84
125
  let responseStarted = false;
85
126
  let settled = false;
86
- const finish = (error) => {
87
- if (settled)
88
- return;
89
- settled = true;
90
- if (error)
91
- reject(error);
92
- else
93
- resolve();
94
- };
127
+ const finish = (error) => { if (settled)
128
+ return; settled = true; error ? reject(error) : resolve(); };
95
129
  const upstream = transport.request({
96
- protocol: target.protocol,
97
- hostname: config.remote.address,
98
- family: config.remote.family,
99
- port: config.remote.port,
100
- method: req.method,
101
- path: `${target.pathname}${target.search}`,
102
- headers,
103
- ...(config.remote.tlsServername ? { servername: config.remote.tlsServername } : {}),
130
+ protocol: target.protocol, hostname: provider.remote.address, family: provider.remote.family,
131
+ port: provider.remote.port, method: req.method, path: `${target.pathname}${target.search}`, headers,
132
+ ...(provider.remote.tlsServername ? { servername: provider.remote.tlsServername } : {}),
104
133
  });
105
134
  connectTimeout(upstream, config);
106
- upstream.setTimeout(config.idleTimeoutMs, () => {
107
- upstream.destroy(new DesktopBridgeError('bridge_upstream_idle_timeout'));
108
- });
109
- const abortUpstream = () => {
110
- if (!res.writableEnded)
111
- upstream.destroy(new DesktopBridgeError('bridge_client_disconnected'));
112
- };
113
- req.once('aborted', abortUpstream);
114
- res.once('close', abortUpstream);
135
+ upstream.setTimeout(config.idleTimeoutMs, () => upstream.destroy(new DesktopBridgeError('bridge_upstream_idle_timeout')));
136
+ const abort = () => { if (!res.writableEnded)
137
+ upstream.destroy(new DesktopBridgeError('bridge_client_disconnected')); };
138
+ req.once('aborted', abort);
139
+ res.once('close', abort);
115
140
  upstream.once('error', finish);
116
- upstream.once('response', (upstreamResponse) => {
141
+ upstream.once('response', (response) => {
142
+ const statusCode = response.statusCode || 502;
143
+ if (statusCode >= 400) {
144
+ void readRedactedUpstreamError(response).then((body) => {
145
+ responseStarted = true;
146
+ const responseHeaders = rewriteResponseHeaders(response.headers, provider.base_url, authenticatedLocalBaseUrl);
147
+ responseHeaders['content-length'] = String(body.length);
148
+ delete responseHeaders['transfer-encoding'];
149
+ res.writeHead(statusCode, responseHeaders);
150
+ res.end(body, () => finish());
151
+ }).catch(finish);
152
+ return;
153
+ }
117
154
  responseStarted = true;
118
155
  try {
119
- const responseHeaders = rewriteResponseHeaders(upstreamResponse.headers, config, localOrigin);
120
- res.writeHead(upstreamResponse.statusCode || 502, responseHeaders);
156
+ res.writeHead(statusCode, rewriteResponseHeaders(response.headers, provider.base_url, authenticatedLocalBaseUrl));
121
157
  }
122
158
  catch (error) {
123
- upstreamResponse.destroy(error instanceof Error ? error : undefined);
159
+ response.destroy(error instanceof Error ? error : undefined);
124
160
  finish(error);
125
161
  return;
126
162
  }
127
- void pipeline(upstreamResponse, res).then(() => finish(), finish);
163
+ void pipeline(response, res).then(() => finish(), finish);
128
164
  });
129
- if (bufferedBody) {
130
- upstream.end(bufferedBody);
131
- }
132
- else {
133
- void pipeline(req, upstream).catch((error) => {
134
- if (!responseStarted)
135
- finish(error);
136
- });
137
- }
165
+ if (request.body)
166
+ upstream.end(request.body);
167
+ else
168
+ void pipeline(req, upstream).catch((error) => { if (!responseStarted)
169
+ finish(error); });
138
170
  });
139
171
  }
140
172
  catch (error) {
@@ -3,7 +3,8 @@ import fsp from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { DesktopBridgeError } from './types.js';
6
- export const DESKTOP_BRIDGE_LAUNCHD_LABEL = 'com.sneakoscope.codex-lb-desktop-bridge';
6
+ import { withFileLock } from '../../locks/file-lock.js';
7
+ export const DESKTOP_BRIDGE_LAUNCHD_LABEL = 'com.sneakoscope.desktop-bridge';
7
8
  export function desktopBridgeLaunchdPlistPath(home = os.homedir()) {
8
9
  return path.join(home, 'Library', 'LaunchAgents', `${DESKTOP_BRIDGE_LAUNCHD_LABEL}.plist`);
9
10
  }
@@ -66,16 +67,22 @@ ${programArguments}
66
67
  `;
67
68
  }
68
69
  export async function writeDesktopBridgeLaunchdPlist(file, options) {
69
- const contents = renderDesktopBridgeLaunchdPlist(options);
70
- const directory = path.dirname(file);
71
- await fsp.mkdir(directory, { recursive: true, mode: 0o700 });
72
- const temp = path.join(directory, `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
73
- try {
74
- await fsp.writeFile(temp, contents, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
75
- await fsp.rename(temp, file);
76
- await fsp.chmod(file, 0o600);
77
- }
78
- finally {
79
- await fsp.rm(temp, { force: true }).catch(() => undefined);
80
- }
70
+ await withFileLock({
71
+ lockPath: `${path.resolve(file)}.lock`,
72
+ timeoutMs: 10_000,
73
+ staleMs: 60_000
74
+ }, async () => {
75
+ const contents = renderDesktopBridgeLaunchdPlist(options);
76
+ const directory = path.dirname(file);
77
+ await fsp.mkdir(directory, { recursive: true, mode: 0o700 });
78
+ const temp = path.join(directory, `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
79
+ try {
80
+ await fsp.writeFile(temp, contents, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
81
+ await fsp.rename(temp, file);
82
+ await fsp.chmod(file, 0o600);
83
+ }
84
+ finally {
85
+ await fsp.rm(temp, { force: true }).catch(() => undefined);
86
+ }
87
+ });
81
88
  }
@@ -13,7 +13,10 @@ export function rewriteLocationHeader(value, remoteBaseUrl, localOrigin) {
13
13
  throw new DesktopBridgeError('bridge_location_origin_forbidden');
14
14
  }
15
15
  const local = new URL(localOrigin);
16
- local.pathname = location.pathname;
16
+ const authenticatedBasePath = local.pathname === '/'
17
+ ? ''
18
+ : local.pathname.replace(/\/+$/, '');
19
+ local.pathname = `${authenticatedBasePath}${location.pathname}`;
17
20
  local.search = location.search;
18
21
  local.hash = '';
19
22
  if (location.protocol === 'wss:' || location.protocol === 'ws:')