sneakoscope 7.3.0 → 7.6.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 (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -11,58 +11,18 @@ const CODEX_LB_TOOL_CATALOG_MAX_METADATA_BYTES = 16 * 1024;
11
11
  const DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
12
12
  const GPT56_MODEL_RE = /^gpt-5\.6-(?:sol|terra|luna)$/;
13
13
  const ensureInflight = new Map();
14
- const REQUIRED_CODEX_0144_MODEL_FIELDS = {
14
+ const REQUIRED_MODEL_FIELDS = {
15
15
  slug: ['string'],
16
16
  display_name: ['string'],
17
17
  supported_reasoning_levels: ['array'],
18
- shell_type: ['string'],
19
- visibility: ['string'],
20
- supported_in_api: ['boolean'],
21
- priority: ['number'],
22
- base_instructions: ['string'],
23
- support_verbosity: ['boolean'],
24
- truncation_policy: ['object'],
25
- supports_parallel_tool_calls: ['boolean'],
26
- experimental_supported_tools: ['array']
27
- };
28
- const REASONING_SUMMARY_FIELD_NAMES = ['supports_reasoning_summaries', 'supports_reasoning_summary_parameter'];
29
- const CODEX_0144_MODEL_FIELD_TYPES = {
30
- ...REQUIRED_CODEX_0144_MODEL_FIELDS,
31
- supports_reasoning_summaries: ['boolean'],
32
- supports_reasoning_summary_parameter: ['boolean'],
33
- description: ['string'],
34
- default_reasoning_level: ['string'],
35
- additional_speed_tiers: ['array'],
36
- service_tiers: ['array'],
37
- availability_nux: ['object', 'null'],
38
- upgrade: ['object', 'null'],
39
- model_messages: ['object'],
40
- include_skills_usage_instructions: ['boolean'],
41
- default_reasoning_summary: ['string'],
42
- default_verbosity: ['string'],
43
- apply_patch_tool_type: ['string'],
44
- web_search_tool_type: ['string'],
45
- supports_image_detail_original: ['boolean'],
46
- context_window: ['number'],
47
- max_context_window: ['number'],
48
- comp_hash: ['string'],
49
- effective_context_window_percent: ['number'],
50
- input_modalities: ['array'],
51
- supports_search_tool: ['boolean'],
52
- use_responses_lite: ['boolean'],
53
- tool_mode: ['string', 'null'],
54
- multi_agent_version: ['string', 'null'],
55
- minimal_client_version: ['string'],
56
- available_in_plans: ['array'],
57
- prefer_websockets: ['boolean'],
58
- auto_review_model_override: ['string', 'null'],
59
- auto_compact_token_limit: ['number', 'null'],
60
- reasoning_summary_format: ['string'],
61
- default_service_tier: ['string', 'null']
18
+ truncation_policy: ['object']
62
19
  };
63
20
  export function isCodexLbGpt56Model(model) {
64
21
  return GPT56_MODEL_RE.test(String(model || '').trim());
65
22
  }
23
+ export function shouldBindLocalModelCatalog(mode) {
24
+ return mode === 'desktop-dual-auth-compat' || mode === 'cli-provider';
25
+ }
66
26
  export function codexLbToolCatalogPath(codexHome) {
67
27
  return path.join(path.resolve(codexHome), CODEX_LB_TOOL_CATALOG_FILENAME);
68
28
  }
@@ -85,30 +45,34 @@ export function normalizeCodexLbToolCatalog(payload, opts = {}) {
85
45
  validationIssues.push(`codex_lb_model_catalog_row_invalid:${index}:object`);
86
46
  return {};
87
47
  }
88
- const model = String(row.slug || row.id || row.model || row.name || '').trim();
48
+ let normalized;
49
+ try {
50
+ normalized = normalizeCodexLbModelRow(row, index);
51
+ }
52
+ catch (error) {
53
+ validationIssues.push(error instanceof Error ? error.message : `codex_lb_model_catalog_row_invalid:${index}`);
54
+ return {};
55
+ }
56
+ const model = String(normalized.slug || '').trim();
89
57
  if (isCodexLbGpt56Model(model))
90
58
  gpt56Models.push(model);
91
- const sanitized = sanitizeCodex0144Model(row);
92
- validationIssues.push(...validateCodex0144Model(sanitized, index));
93
- if (!isCodexLbGpt56Model(model))
94
- return sanitized;
95
59
  if (row.use_responses_lite !== false)
96
60
  patchedModels.push(model);
97
- return { ...sanitized, use_responses_lite: false };
61
+ validationIssues.push(...validateKnownModelFields(normalized, index));
62
+ return normalized;
98
63
  });
99
- const compatibleRows = models.filter((row) => isCodexLbGpt56Model(row.slug));
100
- const compatible = gpt56Models.length > 0
101
- && compatibleRows.length === gpt56Models.length
102
- && compatibleRows.every((row) => row.use_responses_lite === false);
64
+ const compatible = models.length > 0
65
+ && validationIssues.length === 0
66
+ && models.every((row) => row.use_responses_lite === false);
103
67
  const blockers = uniqueBounded([
104
68
  ...(rows.length ? [] : ['codex_lb_model_catalog_empty']),
105
69
  ...(rows.length > maxModels ? [`codex_lb_model_catalog_model_limit_exceeded:${rows.length}:${maxModels}`] : []),
106
70
  ...validationIssues,
107
- ...(gpt56Models.length ? [] : ['codex_lb_gpt56_models_missing']),
108
- ...(compatible ? [] : ['codex_lb_gpt56_tools_transport_incompatible'])
71
+ ...(compatible ? [] : ['codex_lb_model_catalog_tools_transport_incompatible'])
109
72
  ]);
110
73
  return {
111
74
  schema: CODEX_LB_TOOL_CATALOG_SCHEMA,
75
+ contract: 'codex-model-catalog-pass-through.v2',
112
76
  ok: blockers.length === 0,
113
77
  catalog: { models },
114
78
  model_count: models.length,
@@ -230,7 +194,11 @@ async function ensureValidatedCodexLbToolCatalog(input, outputPath, baseUrl, api
230
194
  const { catalog: _catalog, ...status } = normalized;
231
195
  return { ...status, generated_at: nowIso(), status: 'blocked', path: outputPath, exists: await exists(outputPath), fetched: true, required: true };
232
196
  }
233
- await writeSecureCatalog(outputPath, normalized.catalog, identity);
197
+ await writeSecureCatalog(outputPath, normalized.catalog, identity, {
198
+ upstream_etag: response.headers.get('etag'),
199
+ upstream_last_modified: response.headers.get('last-modified'),
200
+ client_version: String(input.clientVersion || 'runtime-discovered')
201
+ });
234
202
  const verified = await inspectCodexLbToolCatalog(outputPath, inspectOpts);
235
203
  return {
236
204
  ...verified,
@@ -305,7 +273,7 @@ function parseCatalogJson(text) {
305
273
  throw new CatalogFetchError('codex_lb_tool_catalog_response_json_invalid');
306
274
  }
307
275
  }
308
- async function writeSecureCatalog(file, catalog, identity) {
276
+ async function writeSecureCatalog(file, catalog, identity, upstream) {
309
277
  const text = `${JSON.stringify(catalog, null, 2)}\n`;
310
278
  const metadataPath = codexLbToolCatalogMetadataPath(file);
311
279
  const metadata = {
@@ -314,7 +282,12 @@ async function writeSecureCatalog(file, catalog, identity) {
314
282
  catalog_schema: CODEX_LB_TOOL_CATALOG_SCHEMA,
315
283
  catalog_sha256: sha256(text),
316
284
  identity,
317
- model_count: Array.isArray(catalog?.models) ? catalog.models.length : 0
285
+ model_count: Array.isArray(catalog?.models) ? catalog.models.length : 0,
286
+ upstream_etag: upstream.upstream_etag,
287
+ upstream_last_modified: upstream.upstream_last_modified,
288
+ client_version: upstream.client_version,
289
+ unknown_fields_preserved: true,
290
+ normalizations: ['desktop_full_responses_required']
318
291
  };
319
292
  await ensureDir(path.dirname(file));
320
293
  await writeTextAtomic(file, text, { mode: 0o600 });
@@ -344,14 +317,26 @@ async function inspectSecureRegularFile(file, maxBytes, prefix) {
344
317
  blockers.push(`${prefix}_mode_insecure:${mode.toString(8)}`);
345
318
  return { ok: blockers.length === 0, exists: true, updated_at_ms: stat.mtimeMs, size_bytes: stat.size, mode: mode.toString(8).padStart(3, '0'), owner_uid: ownerUid, blockers };
346
319
  }
347
- function sanitizeCodex0144Model(row) {
348
- return Object.fromEntries(Object.keys(CODEX_0144_MODEL_FIELD_TYPES)
349
- .filter((field) => Object.prototype.hasOwnProperty.call(row, field))
350
- .map((field) => [field, row[field]]));
320
+ export function normalizeCodexLbModelRow(row, index = 0) {
321
+ const cloned = structuredClone(row);
322
+ for (const key of Object.keys(cloned)) {
323
+ if (isSensitiveCatalogField(key))
324
+ delete cloned[key];
325
+ }
326
+ const slug = String(cloned.slug ?? cloned.id ?? cloned.model ?? cloned.name ?? '').trim();
327
+ if (!slug)
328
+ throw new Error(`codex_lb_model_catalog_slug_missing:${index}`);
329
+ if (!Object.prototype.hasOwnProperty.call(cloned, 'slug'))
330
+ cloned.slug = slug;
331
+ cloned.use_responses_lite = false;
332
+ return cloned;
333
+ }
334
+ function isSensitiveCatalogField(field) {
335
+ return /(?:^|[_-])(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|cookie|set[_-]?cookie)(?:$|[_-])/i.test(field);
351
336
  }
352
- function validateCodex0144Model(row, index) {
337
+ function validateKnownModelFields(row, index) {
353
338
  const issues = [];
354
- for (const [field, allowedTypes] of Object.entries(REQUIRED_CODEX_0144_MODEL_FIELDS)) {
339
+ for (const [field, allowedTypes] of Object.entries(REQUIRED_MODEL_FIELDS)) {
355
340
  if (!Object.prototype.hasOwnProperty.call(row, field)) {
356
341
  issues.push(`codex_lb_model_catalog_required_field_missing:${index}:${field}`);
357
342
  continue;
@@ -359,13 +344,6 @@ function validateCodex0144Model(row, index) {
359
344
  if (!allowedTypes.includes(valueType(row[field])))
360
345
  issues.push(`codex_lb_model_catalog_field_type_invalid:${index}:${field}`);
361
346
  }
362
- if (!REASONING_SUMMARY_FIELD_NAMES.some((field) => Object.prototype.hasOwnProperty.call(row, field))) {
363
- issues.push(`codex_lb_model_catalog_required_field_missing:${index}:supports_reasoning_summaries`);
364
- }
365
- for (const [field, value] of Object.entries(row)) {
366
- if (!(CODEX_0144_MODEL_FIELD_TYPES[field] || []).includes(valueType(value)))
367
- issues.push(`codex_lb_model_catalog_field_type_invalid:${index}:${field}`);
368
- }
369
347
  if (typeof row.slug === 'string' && !row.slug.trim())
370
348
  issues.push(`codex_lb_model_catalog_field_empty:${index}:slug`);
371
349
  if (typeof row.display_name === 'string' && !row.display_name.trim())
@@ -394,7 +372,7 @@ function catalogIdentity(baseUrl, apiKey) {
394
372
  origin: new URL(baseUrl).origin,
395
373
  base_url_sha256: sha256(baseUrl),
396
374
  api_key_sha256: sha256(apiKey),
397
- contract: 'codex-cli-0.144.5-model-catalog'
375
+ contract: 'codex-model-catalog-pass-through.v2'
398
376
  };
399
377
  }
400
378
  function sameCatalogIdentity(actual, expected) {
@@ -5,7 +5,7 @@ export const CODEX_LB_TOOL_OUTPUT_RECOVERY_MIN_VERSION = '1.21.0-beta.3';
5
5
  export const CODEX_LB_TOOL_OUTPUT_RECOVERY_OVERRIDE_ENV = 'SKS_ALLOW_UNVERIFIED_CODEX_LB_RECOVERY';
6
6
  export const CODEX_LB_TOOL_OUTPUT_RECOVERY_OVERRIDE_FLAG = '--allow-unverified-codex-lb-recovery';
7
7
  const UPGRADE_ACTION = `Upgrade codex-lb to ${CODEX_LB_TOOL_OUTPUT_RECOVERY_MIN_VERSION} or later, then rerun \`sks codex-lb status\`.`;
8
- const OAUTH_ACTION = 'To avoid the proxy, run `sks codex-lb use-oauth`; SKS will not switch providers silently.';
8
+ const OAUTH_ACTION = 'To bypass codex-lb routing while preserving ChatGPT OAuth, run `sks codex-lb disable`; SKS will not change routing silently.';
9
9
  const FRESH_THREAD_ACTION = 'After upgrading or switching providers, open a fresh Codex thread and continue the persisted mission; do not replay the corrupted turn.';
10
10
  export async function probeCodexLbToolOutputRecovery(opts = {}) {
11
11
  const baseUrl = normalizeCodexLbBaseUrl(opts.baseUrl);
@@ -0,0 +1,185 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import fsp from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ export async function captureCodexAuthSnapshot(input = {}) {
6
+ const home = input.home || process.env.HOME || os.homedir();
7
+ const authPath = input.authPath || path.join(home, '.codex', 'auth.json');
8
+ let bytes;
9
+ try {
10
+ const stat = await fsp.lstat(authPath);
11
+ if (!stat.isFile() || stat.isSymbolicLink())
12
+ throw new Error('desktop_auth_not_regular_file');
13
+ bytes = await fsp.readFile(authPath);
14
+ }
15
+ catch (error) {
16
+ if (isMissingFileError(error)) {
17
+ return {
18
+ path: authPath,
19
+ exists: false,
20
+ sha256: null,
21
+ semantic_fingerprint: null,
22
+ mode: 'missing',
23
+ has_refresh_token: false,
24
+ has_access_token: false,
25
+ has_api_key: false
26
+ };
27
+ }
28
+ throw error;
29
+ }
30
+ const parsed = parseJsonObject(bytes.toString('utf8'));
31
+ const hasRefreshToken = containsNamedSecret(parsed, /^(?:refresh_token|refreshToken)$/);
32
+ const hasAccessToken = containsNamedSecret(parsed, /^(?:access_token|accessToken)$/);
33
+ const hasIdToken = containsNamedSecret(parsed, /^(?:id_token|idToken)$/);
34
+ const apiKey = findNamedSecret(parsed, /^(?:key|api_key|apiKey|openai_api_key|OPENAI_API_KEY)$/);
35
+ const hasApiKey = Boolean(apiKey);
36
+ const authMode = stringField(parsed, ['auth_mode', 'authMode', 'mode']).toLowerCase();
37
+ const oauth = hasRefreshToken || hasAccessToken || hasIdToken || /chatgpt|oauth|browser/.test(authMode);
38
+ const mode = oauth && hasApiKey
39
+ ? 'mixed'
40
+ : oauth
41
+ ? 'chatgpt_oauth'
42
+ : hasApiKey
43
+ ? 'openai_api_key'
44
+ : 'unknown';
45
+ return {
46
+ path: authPath,
47
+ exists: true,
48
+ sha256: sha256(bytes),
49
+ semantic_fingerprint: oauth ? oauthSemanticFingerprint(parsed) : null,
50
+ mode,
51
+ has_refresh_token: hasRefreshToken,
52
+ has_access_token: hasAccessToken,
53
+ has_api_key: hasApiKey
54
+ };
55
+ }
56
+ export async function codexAuthApiKeyMatches(input) {
57
+ const expectedApiKey = String(input.expectedApiKey || '');
58
+ if (!expectedApiKey)
59
+ return false;
60
+ const home = input.home || process.env.HOME || os.homedir();
61
+ const authPath = input.authPath || path.join(home, '.codex', 'auth.json');
62
+ try {
63
+ const stat = await fsp.lstat(authPath);
64
+ if (!stat.isFile() || stat.isSymbolicLink())
65
+ return false;
66
+ const parsed = parseJsonObject(await fsp.readFile(authPath, 'utf8'));
67
+ const apiKey = findNamedSecret(parsed, /^(?:key|api_key|apiKey|openai_api_key|OPENAI_API_KEY)$/);
68
+ if (!apiKey)
69
+ return false;
70
+ const actualDigest = Buffer.from(sha256(apiKey), 'hex');
71
+ const expectedDigest = Buffer.from(sha256(expectedApiKey), 'hex');
72
+ return timingSafeEqual(actualDigest, expectedDigest);
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ }
78
+ export async function assertDesktopAuthUnchangedBySks(before, afterConfigCommitBeforeRestart) {
79
+ if (before.path !== afterConfigCommitBeforeRestart.path
80
+ || before.exists !== afterConfigCommitBeforeRestart.exists
81
+ || before.sha256 !== afterConfigCommitBeforeRestart.sha256) {
82
+ throw new Error('desktop_auth_mutated_by_sks');
83
+ }
84
+ }
85
+ export function assertDesktopOAuthSemanticIdentity(before, afterAppRestart) {
86
+ if (before.mode !== 'chatgpt_oauth' && before.mode !== 'mixed') {
87
+ throw new Error(`desktop_oauth_missing_before_restart:${before.mode}`);
88
+ }
89
+ if (afterAppRestart.mode !== 'chatgpt_oauth' && afterAppRestart.mode !== 'mixed') {
90
+ throw new Error(`desktop_oauth_missing_after_restart:${afterAppRestart.mode}`);
91
+ }
92
+ if (before.semantic_fingerprint === null || afterAppRestart.semantic_fingerprint === null) {
93
+ throw new Error('desktop_oauth_identity_unverifiable');
94
+ }
95
+ if (before.semantic_fingerprint !== afterAppRestart.semantic_fingerprint) {
96
+ throw new Error('desktop_oauth_identity_changed');
97
+ }
98
+ }
99
+ function parseJsonObject(text) {
100
+ try {
101
+ const parsed = JSON.parse(text);
102
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
103
+ ? parsed
104
+ : null;
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ function containsNamedSecret(value, keyPattern) {
111
+ return Boolean(findNamedSecret(value, keyPattern));
112
+ }
113
+ function findNamedSecret(value, keyPattern) {
114
+ if (!value || typeof value !== 'object')
115
+ return null;
116
+ for (const [key, entry] of Object.entries(value)) {
117
+ if (keyPattern.test(key) && typeof entry === 'string' && entry.trim())
118
+ return entry.trim();
119
+ const nested = findNamedSecret(entry, keyPattern);
120
+ if (nested)
121
+ return nested;
122
+ }
123
+ return null;
124
+ }
125
+ function stringField(value, keys) {
126
+ if (!value)
127
+ return '';
128
+ for (const key of keys) {
129
+ const entry = value[key];
130
+ if (typeof entry === 'string')
131
+ return entry;
132
+ }
133
+ return '';
134
+ }
135
+ function oauthSemanticFingerprint(parsed) {
136
+ const identity = {};
137
+ collectIdentityFields(parsed, identity);
138
+ for (const token of collectTokenValues(parsed, /^(?:id_token|idToken|access_token|accessToken)$/)) {
139
+ collectIdentityFields(decodeJwtClaims(token), identity);
140
+ }
141
+ if (Object.keys(identity).length === 0)
142
+ return null;
143
+ const sortedIdentity = Object.fromEntries(Object.entries(identity).sort(([left], [right]) => left.localeCompare(right)));
144
+ return sha256(JSON.stringify(sortedIdentity));
145
+ }
146
+ function collectIdentityFields(value, output) {
147
+ if (!value || typeof value !== 'object')
148
+ return;
149
+ for (const [key, entry] of Object.entries(value)) {
150
+ if (/^(?:sub|email|account_id|accountId|chatgpt_account_id|chatgptAccountId|user_id|userId|organization_id|organizationId)$/i.test(key)
151
+ && (typeof entry === 'string' || typeof entry === 'number')) {
152
+ output[key.toLowerCase()] = String(entry);
153
+ }
154
+ if (typeof entry === 'object')
155
+ collectIdentityFields(entry, output);
156
+ }
157
+ }
158
+ function collectTokenValues(value, keyPattern, output = []) {
159
+ if (!value || typeof value !== 'object')
160
+ return output;
161
+ for (const [key, entry] of Object.entries(value)) {
162
+ if (keyPattern.test(key) && typeof entry === 'string' && entry.trim())
163
+ output.push(entry);
164
+ else if (typeof entry === 'object')
165
+ collectTokenValues(entry, keyPattern, output);
166
+ }
167
+ return output;
168
+ }
169
+ function decodeJwtClaims(token) {
170
+ const payload = token.split('.')[1];
171
+ if (!payload)
172
+ return null;
173
+ try {
174
+ return parseJsonObject(Buffer.from(payload, 'base64url').toString('utf8'));
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ }
180
+ function sha256(value) {
181
+ return createHash('sha256').update(value).digest('hex');
182
+ }
183
+ function isMissingFileError(error) {
184
+ return Boolean(error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT');
185
+ }
@@ -0,0 +1,34 @@
1
+ import { readDesktopBridgeState } from './state.js';
2
+ import { safeBridgeErrorCode } from './security.js';
3
+ export const desktopBridgeProcessExists = (pid) => {
4
+ try {
5
+ process.kill(pid, 0);
6
+ return true;
7
+ }
8
+ catch (error) {
9
+ return error.code === 'EPERM';
10
+ }
11
+ };
12
+ export async function getDesktopBridgeStatus(input) {
13
+ let state;
14
+ try {
15
+ state = await readDesktopBridgeState(input.statePath);
16
+ }
17
+ catch (error) {
18
+ return { status: 'invalid', state: null, blocker: safeBridgeErrorCode(error) };
19
+ }
20
+ if (!state)
21
+ return { status: 'missing', state: null };
22
+ if (input.expectedConfigGeneration && state.config_generation !== input.expectedConfigGeneration) {
23
+ return {
24
+ status: 'configuration_mismatch',
25
+ state,
26
+ blocker: 'bridge_config_generation_mismatch',
27
+ };
28
+ }
29
+ const processExists = input.processExists || desktopBridgeProcessExists;
30
+ if (!processExists(state.pid)) {
31
+ return { status: 'stale', state, blocker: 'bridge_process_not_running' };
32
+ }
33
+ return { status: 'running', state };
34
+ }
@@ -0,0 +1,106 @@
1
+ import { rewriteLocationHeader } from './location-rewrite.js';
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',
12
+ ]);
13
+ 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
+ ]);
25
+ const NEVER_FORWARD_TO_CLIENT = new Set([
26
+ 'authorization',
27
+ 'cookie',
28
+ 'proxy-authenticate',
29
+ 'proxy-authorization',
30
+ 'set-cookie',
31
+ 'x-codex-lb-api-key',
32
+ ]);
33
+ function connectionTokens(inbound) {
34
+ const raw = inbound.connection;
35
+ const values = Array.isArray(raw) ? raw : raw ? [raw] : [];
36
+ return new Set(values.flatMap((value) => value.split(',')).map((value) => value.trim().toLowerCase()).filter(Boolean));
37
+ }
38
+ function injectGatewayCredential(result, gatewayKey, transport) {
39
+ if (transport === 'x-codex-lb-api-key') {
40
+ result['x-codex-lb-api-key'] = gatewayKey;
41
+ return;
42
+ }
43
+ result.authorization = `Bearer ${gatewayKey}`;
44
+ }
45
+ export function buildUpstreamHeaders(inbound, config, upstreamHost) {
46
+ const result = {};
47
+ const dynamicHopByHop = connectionTokens(inbound);
48
+ for (const [rawName, rawValue] of Object.entries(inbound)) {
49
+ if (rawValue === undefined)
50
+ continue;
51
+ const name = rawName.toLowerCase();
52
+ if (HOP_BY_HOP.has(name) || dynamicHopByHop.has(name))
53
+ continue;
54
+ if (NEVER_FORWARD_FROM_CLIENT.has(name) || name === 'host')
55
+ continue;
56
+ result[name] = rawValue;
57
+ }
58
+ result.host = upstreamHost;
59
+ injectGatewayCredential(result, config.gatewayKey, config.gatewayAuthTransport);
60
+ return result;
61
+ }
62
+ export function buildWebSocketHeaders(inbound, config, upstreamHost) {
63
+ const result = buildUpstreamHeaders(inbound, config, upstreamHost);
64
+ for (const [rawName, rawValue] of Object.entries(inbound)) {
65
+ if (rawValue === undefined)
66
+ continue;
67
+ const name = rawName.toLowerCase();
68
+ if (name === 'connection' || name === 'upgrade' || name.startsWith('sec-websocket-')) {
69
+ result[name] = rawValue;
70
+ }
71
+ }
72
+ result.connection = 'Upgrade';
73
+ result.upgrade = 'websocket';
74
+ return result;
75
+ }
76
+ export function rewriteResponseHeaders(inbound, config, localOrigin) {
77
+ const result = {};
78
+ const dynamicHopByHop = connectionTokens(inbound);
79
+ for (const [rawName, rawValue] of Object.entries(inbound)) {
80
+ if (rawValue === undefined)
81
+ continue;
82
+ const name = rawName.toLowerCase();
83
+ if (HOP_BY_HOP.has(name) || dynamicHopByHop.has(name) || NEVER_FORWARD_TO_CLIENT.has(name))
84
+ continue;
85
+ if (name.startsWith('access-control-'))
86
+ continue;
87
+ if (name === 'location') {
88
+ if (Array.isArray(rawValue) && rawValue.length !== 1) {
89
+ throw new DesktopBridgeError('bridge_location_header_invalid');
90
+ }
91
+ const location = Array.isArray(rawValue) ? rawValue[0] : rawValue;
92
+ if (!location)
93
+ throw new DesktopBridgeError('bridge_location_header_invalid');
94
+ result.location = rewriteLocationHeader(location, config.remoteBaseUrl, localOrigin);
95
+ continue;
96
+ }
97
+ result[name] = rawValue;
98
+ }
99
+ return result;
100
+ }
101
+ export function isRedactedHeaderName(name) {
102
+ const normalized = name.toLowerCase();
103
+ return NEVER_FORWARD_FROM_CLIENT.has(normalized)
104
+ || NEVER_FORWARD_TO_CLIENT.has(normalized)
105
+ || normalized === 'set-cookie';
106
+ }
@@ -0,0 +1,101 @@
1
+ import http, {} from 'node:http';
2
+ import https from 'node:https';
3
+ import { pipeline } from 'node:stream/promises';
4
+ import { buildUpstreamHeaders, rewriteResponseHeaders } from './header-policy.js';
5
+ import { resolveDesktopBridgeTarget, safeBridgeErrorCode } from './security.js';
6
+ import { desktopBridgeListenOrigin } from './state.js';
7
+ import { DesktopBridgeError } from './types.js';
8
+ function connectTimeout(request, config) {
9
+ request.once('socket', (socket) => {
10
+ if (!socket.connecting)
11
+ return;
12
+ const timer = setTimeout(() => {
13
+ request.destroy(new DesktopBridgeError('bridge_upstream_connect_timeout'));
14
+ }, config.connectTimeoutMs);
15
+ timer.unref();
16
+ const event = config.remote.secure ? 'secureConnect' : 'connect';
17
+ socket.once(event, () => clearTimeout(timer));
18
+ socket.once('close', () => clearTimeout(timer));
19
+ });
20
+ }
21
+ function writeHttpBridgeError(res, error) {
22
+ const code = safeBridgeErrorCode(error);
23
+ if (res.headersSent) {
24
+ res.destroy(error instanceof Error ? error : undefined);
25
+ return;
26
+ }
27
+ res.writeHead(502, {
28
+ 'content-type': 'application/json',
29
+ 'cache-control': 'no-store',
30
+ connection: 'close',
31
+ });
32
+ res.end(JSON.stringify({
33
+ error: {
34
+ type: 'sks_bridge_error',
35
+ code,
36
+ message: code,
37
+ },
38
+ }));
39
+ }
40
+ export async function forwardHttp(req, res, config) {
41
+ const target = resolveDesktopBridgeTarget(req.url, config.remote);
42
+ const transport = config.remote.secure ? https : http;
43
+ const headers = buildUpstreamHeaders(req.headers, config, target.host);
44
+ const localOrigin = desktopBridgeListenOrigin(config);
45
+ try {
46
+ await new Promise((resolve, reject) => {
47
+ let responseStarted = false;
48
+ let settled = false;
49
+ const finish = (error) => {
50
+ if (settled)
51
+ return;
52
+ settled = true;
53
+ if (error)
54
+ reject(error);
55
+ else
56
+ resolve();
57
+ };
58
+ const upstream = transport.request({
59
+ protocol: target.protocol,
60
+ hostname: config.remote.address,
61
+ family: config.remote.family,
62
+ port: config.remote.port,
63
+ method: req.method,
64
+ path: `${target.pathname}${target.search}`,
65
+ headers,
66
+ ...(config.remote.tlsServername ? { servername: config.remote.tlsServername } : {}),
67
+ });
68
+ connectTimeout(upstream, config);
69
+ upstream.setTimeout(config.idleTimeoutMs, () => {
70
+ upstream.destroy(new DesktopBridgeError('bridge_upstream_idle_timeout'));
71
+ });
72
+ const abortUpstream = () => {
73
+ if (!res.writableEnded)
74
+ upstream.destroy(new DesktopBridgeError('bridge_client_disconnected'));
75
+ };
76
+ req.once('aborted', abortUpstream);
77
+ res.once('close', abortUpstream);
78
+ upstream.once('error', finish);
79
+ upstream.once('response', (upstreamResponse) => {
80
+ responseStarted = true;
81
+ try {
82
+ const responseHeaders = rewriteResponseHeaders(upstreamResponse.headers, config, localOrigin);
83
+ res.writeHead(upstreamResponse.statusCode || 502, responseHeaders);
84
+ }
85
+ catch (error) {
86
+ upstreamResponse.destroy(error instanceof Error ? error : undefined);
87
+ finish(error);
88
+ return;
89
+ }
90
+ void pipeline(upstreamResponse, res).then(() => finish(), finish);
91
+ });
92
+ void pipeline(req, upstream).catch((error) => {
93
+ if (!responseStarted)
94
+ finish(error);
95
+ });
96
+ });
97
+ }
98
+ catch (error) {
99
+ writeHttpBridgeError(res, error);
100
+ }
101
+ }
@@ -0,0 +1,10 @@
1
+ export * from './types.js';
2
+ export * from './security.js';
3
+ export * from './header-policy.js';
4
+ export * from './location-rewrite.js';
5
+ export * from './state.js';
6
+ export * from './http-forward.js';
7
+ export * from './websocket-forward.js';
8
+ export * from './server.js';
9
+ export * from './client.js';
10
+ export * from './launchd.js';