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,51 +1,75 @@
1
1
  import path from 'node:path';
2
- import os from 'node:os';
3
- import { constants as fsConstants } from 'node:fs';
4
- import fsp from 'node:fs/promises';
5
- import { randomBytes } from 'node:crypto';
6
- import { assertTestHomeWriteAllowed, ensureDir, readText, writeBinaryAtomic } from '../core/fsx.js';
7
- import { GLM_CODEX_CONFIG_PROVIDER_ID, GLM_52_OPENROUTER_MODEL, OPENROUTER_AUTH_COMMAND, OPENROUTER_AUTH_REFRESH_INTERVAL_MS, OPENROUTER_AUTH_TIMEOUT_MS, openRouterAuthCommandArgs, OPENROUTER_DEFAULT_PROFILE_ID, RETIRED_GLM_DESKTOP_CONFIG_PROFILE_IDS } from '../core/codex-app/openrouter-provider.js';
8
- import { openRouterSecretPaths, resolveOpenRouterApiKey } from '../core/providers/openrouter/openrouter-secret-store.js';
9
- import { reconcileRetiredSksConfigText } from '../core/auto-review.js';
10
- import { ensureTrailingNewline, removeTopLevelTomlKeyIfValue, safeWriteCodexConfigToml, upsertTopLevelTomlString, upsertTomlTable } from '../core/codex-runtime/codex-desktop-config-policy.js';
11
- import { codexLbConfigPath, normalizeCodexLbBaseUrl } from './install-helpers-codex-lb-shared.js';
2
+ import { ensureTrailingNewline, upsertTopLevelTomlString } from '../core/codex-runtime/codex-desktop-config-policy.js';
12
3
  import { escapeRegExp } from '../core/text/regex.js';
13
- import { upsertExplicitCodexProviderMode } from '../core/codex-app/provider-mode.js';
14
- export const CODEX_LB_DESKTOP_BRIDGE_MARKER = '# sks-codex-lb-managed-desktop-bridge';
15
- export const CODEX_LB_DESKTOP_COMPAT_MARKER = '# sks-codex-lb-managed-desktop-compat';
16
- export const CODEX_LB_MODEL_CATALOG_MARKER = '# sks-codex-lb-managed-model-catalog';
17
- export const CODEX_LB_PROVIDER_SELECTION_MARKER = '# sks-codex-lb-managed-provider-selection';
18
- export const CODEX_LB_OAUTH_SELECTION_MARKER = '# sks-codex-lb-managed-oauth-selection';
19
- export const LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER = '# sks-codex-lb-managed-openai-base-url';
20
- const SKS_SWITCHABLE_THIRD_PARTY_PROVIDER_IDS = ['openrouter', 'sks-router'];
21
- const SKS_THIRD_PARTY_CATALOG_BASENAMES = ['sks-openrouter-catalog.json', 'opencodex-catalog.json'];
22
- export function releaseSksManagedThirdPartySelection(text) {
23
- let next = String(text || '');
24
- const selected = topLevelTomlString(next, 'model_provider');
25
- if (!selected || !SKS_SWITCHABLE_THIRD_PARTY_PROVIDER_IDS.includes(selected)) {
26
- return next;
4
+ export const DESKTOP_BRIDGE_MANAGED_MARKER = '# sks-desktop-bridge-managed';
5
+ export const DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER = '# sks-desktop-bridge-managed-base-url';
6
+ export const DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER = '# sks-desktop-bridge-managed-model-catalog';
7
+ export function upsertDesktopBridgeManagedConfig(text, input) {
8
+ const bridgeBaseUrl = normalizeManagedBridgeBaseUrl(input.bridgeBaseUrl);
9
+ const combinedCatalogPath = String(input.combinedCatalogPath || '').trim();
10
+ if (!combinedCatalogPath || !path.isAbsolute(combinedCatalogPath)) {
11
+ throw new Error('desktop_bridge_combined_catalog_path_invalid');
12
+ }
13
+ if (path.basename(combinedCatalogPath) !== 'sks-bridge-catalog.json') {
14
+ throw new Error('desktop_bridge_combined_catalog_path_invalid');
15
+ }
16
+ const orphanCleanup = removeDesktopBridgeOrphanManagedMarkers(String(text || ''));
17
+ let next = orphanCleanup.text;
18
+ const selectedProvider = topLevelTomlString(next, 'model_provider');
19
+ if (selectedProvider && (selectedProvider !== 'openai'
20
+ || !topLevelHasLine(next, DESKTOP_BRIDGE_MANAGED_MARKER))) {
21
+ throw new Error('historical_user_owned_config_conflict:model_provider');
22
+ }
23
+ const existingBaseUrl = topLevelTomlString(next, 'openai_base_url');
24
+ const baseUrlOwned = topLevelHasLine(next, DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER);
25
+ if (existingBaseUrl && !baseUrlOwned) {
26
+ throw new Error('historical_user_owned_config_conflict:openai_base_url');
27
27
  }
28
- if (!new RegExp(`(^|\\n)\\[model_providers\\.${escapeRegExp(selected)}\\]`).test(next))
29
- return next;
30
- next = removeTopLevelTomlKeyIfValue(next, 'model_provider', selected);
28
+ const existingCatalog = topLevelTomlString(next, 'model_catalog_json');
29
+ const catalogOwned = topLevelHasLine(next, DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER);
30
+ if (existingCatalog && !catalogOwned) {
31
+ throw new Error('historical_user_owned_config_conflict:model_catalog_json');
32
+ }
33
+ next = removeManagedBridgeTopLevelBindings(next);
34
+ next = upsertTopLevelTomlString(next, 'model_provider', 'openai');
35
+ next = addTopLevelMarkerBeforeKey(next, 'model_provider', DESKTOP_BRIDGE_MANAGED_MARKER);
36
+ next = upsertTopLevelTomlString(next, 'openai_base_url', bridgeBaseUrl);
37
+ next = addTopLevelMarkerBeforeKey(next, 'openai_base_url', DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER);
38
+ next = upsertTopLevelTomlString(next, 'model_catalog_json', combinedCatalogPath);
39
+ next = addTopLevelMarkerBeforeKey(next, 'model_catalog_json', DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER);
40
+ return ensureTrailingNewline(next);
41
+ }
42
+ export function removeDesktopBridgeManagedConfig(text) {
43
+ let next = String(text || '');
44
+ const selectedProvider = topLevelTomlString(next, 'model_provider');
45
+ const baseUrl = topLevelTomlString(next, 'openai_base_url');
31
46
  const catalog = topLevelTomlString(next, 'model_catalog_json');
32
- if (catalog
33
- && !topLevelHasLine(next, CODEX_LB_MODEL_CATALOG_MARKER)
34
- && SKS_THIRD_PARTY_CATALOG_BASENAMES.some((name) => catalog.endsWith(`/${name}`) || catalog === name)) {
35
- next = removeTopLevelTomlKey(next, 'model_catalog_json');
47
+ const owned = [
48
+ [DESKTOP_BRIDGE_MANAGED_MARKER, 'model_provider', selectedProvider === 'openai'],
49
+ [DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, 'openai_base_url', Boolean(baseUrl)],
50
+ [DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER, 'model_catalog_json', Boolean(catalog)]
51
+ ];
52
+ for (const [marker, key, valid] of owned) {
53
+ if (!topLevelHasLine(next, marker)) {
54
+ const value = key === 'model_provider' ? selectedProvider : key === 'openai_base_url' ? baseUrl : catalog;
55
+ if (value)
56
+ throw new Error(`desktop_bridge_unmanage_ownership_missing:${key}`);
57
+ continue;
58
+ }
59
+ if (!valid)
60
+ throw new Error(`desktop_bridge_unmanage_owned_value_invalid:${key}`);
61
+ next = removeTopLevelTomlKey(next, key);
62
+ next = removeTopLevelLine(next, marker);
36
63
  }
37
- return next;
64
+ return ensureTrailingNewline(next);
38
65
  }
39
- export function removeCodexLbOrphanManagedMarkers(text) {
66
+ export function removeDesktopBridgeOrphanManagedMarkers(text) {
40
67
  const source = String(text || '');
41
68
  const selectedProvider = topLevelTomlString(source, 'model_provider');
42
69
  const markerTargets = [
43
- [CODEX_LB_DESKTOP_BRIDGE_MARKER, Boolean(topLevelTomlString(source, 'openai_base_url'))],
44
- [LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER, Boolean(topLevelTomlString(source, 'openai_base_url'))],
45
- [CODEX_LB_DESKTOP_COMPAT_MARKER, selectedProvider === 'codex-lb'],
46
- [CODEX_LB_MODEL_CATALOG_MARKER, Boolean(topLevelTomlString(source, 'model_catalog_json'))],
47
- [CODEX_LB_PROVIDER_SELECTION_MARKER, selectedProvider === 'codex-lb'],
48
- [CODEX_LB_OAUTH_SELECTION_MARKER, selectedProvider === 'openai']
70
+ [DESKTOP_BRIDGE_MANAGED_MARKER, selectedProvider === 'openai'],
71
+ [DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, Boolean(topLevelTomlString(source, 'openai_base_url'))],
72
+ [DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER, Boolean(topLevelTomlString(source, 'model_catalog_json'))]
49
73
  ];
50
74
  const orphanMarkers = markerTargets
51
75
  .filter(([marker, hasManagedContent]) => topLevelHasLine(source, marker) && !hasManagedContent)
@@ -54,891 +78,21 @@ export function removeCodexLbOrphanManagedMarkers(text) {
54
78
  for (const marker of orphanMarkers)
55
79
  next = removeTopLevelLine(next, marker);
56
80
  return {
57
- schema: 'sks.codex-lb-orphan-managed-marker-cleanup.v1',
81
+ schema: 'sks.desktop-bridge-orphan-managed-marker-cleanup.v1',
58
82
  changed: orphanMarkers.length > 0,
59
83
  orphan_markers: orphanMarkers,
60
84
  text: next
61
85
  };
62
86
  }
63
- export function upsertCodexLbNativeDesktopConfig(text, input) {
64
- let next = releaseSksManagedThirdPartySelection(removeCodexLbOrphanManagedMarkers(String(text || '')).text);
65
- const selectedProvider = topLevelTomlString(next, 'model_provider');
66
- if (selectedProvider && selectedProvider !== 'openai' && selectedProvider !== 'codex-lb') {
67
- throw new Error('codex_lb_user_owned_model_provider_conflict');
68
- }
69
- if (selectedProvider === 'codex-lb'
70
- && !topLevelHasLine(next, CODEX_LB_DESKTOP_COMPAT_MARKER)
71
- && !topLevelHasLine(next, CODEX_LB_PROVIDER_SELECTION_MARKER)) {
72
- throw new Error('codex_lb_legacy_desktop_config_requires_migration');
73
- }
74
- if (topLevelHasLine(next, LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER)
75
- && !topLevelHasLine(next, CODEX_LB_DESKTOP_BRIDGE_MARKER)) {
76
- throw new Error('codex_lb_legacy_desktop_config_requires_migration');
77
- }
78
- const modelCatalog = topLevelTomlString(next, 'model_catalog_json');
79
- if (modelCatalog && !topLevelHasLine(next, CODEX_LB_MODEL_CATALOG_MARKER)) {
80
- throw new Error('codex_lb_user_owned_model_catalog_json_conflict');
81
- }
82
- next = removeManagedCodexLbSelection(next);
83
- next = removeTopLevelLine(next, CODEX_LB_DESKTOP_COMPAT_MARKER);
84
- next = removeManagedModelCatalogJson(next);
85
- next = upsertManagedTopLevelTomlString(next, 'openai_base_url', input.bridgeBaseUrl, CODEX_LB_DESKTOP_BRIDGE_MARKER, [LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER]);
86
- next = upsertTopLevelTomlString(next, 'model_provider', 'openai');
87
- next = upsertTomlTable(next, 'model_providers.codex-lb', cliProviderBlock(input.remoteBaseUrl));
88
- return ensureTrailingNewline(upsertExplicitCodexProviderMode(next, 'codex-lb'));
89
- }
90
- export function upsertCodexLbCompatDesktopConfig(text, input) {
91
- let next = removeManagedDesktopBridgeRouting(String(text || ''));
92
- next = removeManagedModelCatalogJson(next);
93
- next = upsertManagedTopLevelTomlString(next, 'model_provider', 'codex-lb', CODEX_LB_DESKTOP_COMPAT_MARKER);
94
- const block = [
95
- '[model_providers.codex-lb]',
96
- 'name = "OpenAI"',
97
- `base_url = ${JSON.stringify(input.remoteBaseUrl)}`,
98
- 'wire_api = "responses"',
99
- 'requires_openai_auth = true',
100
- 'supports_websockets = true',
101
- 'env_http_headers = { "X-Codex-LB-API-Key" = "CODEX_LB_API_KEY" }'
102
- ].join('\n');
103
- next = upsertTomlTable(next, 'model_providers.codex-lb', block);
104
- return ensureTrailingNewline(next);
105
- }
106
- export function upsertCodexLbCliProviderConfig(text, input) {
107
- const cleaned = removeCodexLbOrphanManagedMarkers(String(text || '')).text;
108
- if (topLevelHasLine(cleaned, LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER)) {
109
- throw new Error('codex_lb_legacy_desktop_config_requires_migration');
110
- }
111
- const cliBase = input.selectGlobally === true
112
- ? releaseSksManagedThirdPartySelection(removeCodexLbManagedDesktopConfig(cleaned))
113
- : removeCodexLbManagedDesktopConfig(cleaned);
114
- const claimedSelection = input.selectGlobally === true
115
- && topLevelTomlString(cliBase, 'model_provider') === 'codex-lb'
116
- && !topLevelHasLine(cliBase, CODEX_LB_PROVIDER_SELECTION_MARKER)
117
- ? addTopLevelMarkerBeforeKey(cliBase, 'model_provider', CODEX_LB_PROVIDER_SELECTION_MARKER)
118
- : cliBase;
119
- let next = input.selectGlobally === true
120
- ? upsertManagedTopLevelTomlString(removeTopLevelTomlKeyIfValue(removeManagedOAuthSelection(claimedSelection), 'model_provider', 'openai'), 'model_provider', 'codex-lb', CODEX_LB_PROVIDER_SELECTION_MARKER)
121
- : removeManagedCodexLbSelection(cliBase);
122
- next = upsertTomlTable(next, 'model_providers.codex-lb', cliProviderBlock(input.remoteBaseUrl));
123
- return ensureTrailingNewline(next);
124
- }
125
- export function restoreCodexLbOAuthSelectionConfig(text) {
126
- let next = removeManagedCodexLbSelection(String(text || ''));
127
- next = upsertManagedTopLevelTomlString(next, 'model_provider', 'openai', CODEX_LB_OAUTH_SELECTION_MARKER);
128
- return ensureTrailingNewline(upsertExplicitCodexProviderMode(next, 'chatgpt-oauth'));
129
- }
130
- export function removeManagedCodexLbSelection(text) {
131
- let next = removeTopLevelTomlKeyIfValue(String(text || ''), 'model_provider', 'codex-lb');
132
- next = removeTopLevelLine(next, CODEX_LB_PROVIDER_SELECTION_MARKER);
133
- return next;
134
- }
135
- function removeManagedOAuthSelection(text) {
136
- let next = String(text || '');
137
- if (topLevelHasLine(next, CODEX_LB_OAUTH_SELECTION_MARKER)) {
138
- next = removeTopLevelTomlKeyIfValue(next, 'model_provider', 'openai');
139
- next = removeTopLevelLine(next, CODEX_LB_OAUTH_SELECTION_MARKER);
140
- }
141
- return next;
142
- }
143
- export function removeCodexLbManagedDesktopConfig(text) {
144
- let next = removeManagedDesktopBridgeRouting(removeCodexLbOrphanManagedMarkers(String(text || '')).text);
145
- if (topLevelHasLine(next, CODEX_LB_DESKTOP_COMPAT_MARKER)) {
146
- next = removeTopLevelTomlKeyIfValue(next, 'model_provider', 'codex-lb');
147
- next = removeTopLevelLine(next, CODEX_LB_DESKTOP_COMPAT_MARKER);
148
- }
149
- next = removeManagedModelCatalogJson(next);
150
- if (topLevelTomlString(next, 'model_provider') === 'codex-lb') {
151
- next = restoreCodexLbOAuthSelectionConfig(next);
152
- }
153
- return ensureTrailingNewline(next);
154
- }
155
- export function removeManagedModelCatalogJson(text) {
156
- if (!topLevelHasLine(text, CODEX_LB_MODEL_CATALOG_MARKER))
157
- return text;
158
- const withoutKey = removeTopLevelTomlKey(text, 'model_catalog_json');
159
- return removeTopLevelLine(withoutKey, CODEX_LB_MODEL_CATALOG_MARKER);
160
- }
161
- export function removeManagedDesktopBridgeRouting(text) {
162
- const markers = [
163
- CODEX_LB_DESKTOP_BRIDGE_MARKER,
164
- LEGACY_CODEX_LB_OPENAI_ROUTING_MARKER
165
- ].filter((marker) => topLevelHasLine(text, marker));
166
- if (markers.length === 0)
167
- return text;
168
- let next = removeTopLevelTomlKey(text, 'openai_base_url');
169
- for (const marker of markers)
170
- next = removeTopLevelLine(next, marker);
171
- return next;
172
- }
173
- export function upsertCodexLbConfig(text = '', baseUrl, selectDefault = true) {
174
- return upsertCodexLbCliProviderConfig(text, {
175
- remoteBaseUrl: String(baseUrl || ''),
176
- selectGlobally: selectDefault === true
177
- });
178
- }
179
- function cliProviderBlock(remoteBaseUrl) {
180
- return [
181
- '[model_providers.codex-lb]',
182
- 'name = "codex-lb"',
183
- `base_url = ${JSON.stringify(remoteBaseUrl)}`,
184
- 'wire_api = "responses"',
185
- 'env_key = "CODEX_LB_API_KEY"',
186
- 'supports_websockets = true',
187
- 'requires_openai_auth = false'
188
- ].join('\n');
189
- }
190
- function upsertManagedTopLevelTomlString(text, key, value, marker, legacyMarkers = []) {
191
- const acceptedMarkers = [marker, ...legacyMarkers];
192
- const existing = topLevelTomlString(text, key);
193
- const ownedMarker = acceptedMarkers.find((candidate) => topLevelHasLine(text, candidate));
194
- if (existing && !ownedMarker) {
195
- if (existing === value)
196
- return text;
197
- throw new Error(`codex_lb_user_owned_${key}_conflict`);
198
- }
199
- let next = text;
200
- for (const candidate of acceptedMarkers)
201
- next = removeTopLevelLine(next, candidate);
202
- next = upsertTopLevelTomlString(next, key, value);
203
- return addTopLevelMarkerBeforeKey(next, key, marker);
204
- }
205
- function removeTopLevelTomlKey(text, key) {
206
- const lines = String(text || '').split('\n');
207
- const firstTable = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
208
- const end = firstTable === -1 ? lines.length : firstTable;
209
- const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
210
- return lines
211
- .filter((line, index) => index >= end || !keyPattern.test(line))
212
- .join('\n')
213
- .replace(/^\n+/, '')
214
- .replace(/\n{3,}/g, '\n\n');
215
- }
216
- const CODEX_LB_SHARED_OPENAI_ROUTING_MARKER = '# sks-codex-lb-managed-openai-base-url';
217
- export function codexLbSharedOpenAiRoutingState(text = '', baseUrl = '') {
218
- const expectedBaseUrl = normalizeCodexLbBaseUrl(baseUrl);
219
- const configuredBaseUrl = topLevelTomlString(text, 'openai_base_url');
220
- return {
221
- status: !configuredBaseUrl ? 'missing' : configuredBaseUrl === expectedBaseUrl ? 'matched' : 'conflict',
222
- expected_base_url: expectedBaseUrl,
223
- configured_base_url: configuredBaseUrl || null,
224
- managed: topLevelHasLine(text, CODEX_LB_SHARED_OPENAI_ROUTING_MARKER)
225
- };
226
- }
227
- export function upsertCodexLbSharedOpenAiRouting(text = '', baseUrl = '') {
228
- const state = codexLbSharedOpenAiRoutingState(text, baseUrl);
229
- if (!state.expected_base_url)
230
- return { ...state, routing_status: state.status, ok: false, status: 'missing_base_url', text: String(text || '') };
231
- if (state.status === 'conflict')
232
- return { ...state, routing_status: state.status, ok: false, status: 'conflicting_user_openai_base_url', text: String(text || '') };
233
- if (state.status === 'matched' && state.managed) {
234
- return { ...state, routing_status: state.status, ok: true, status: 'present', text: String(text || '') };
235
- }
236
- const withValue = state.status === 'matched'
237
- ? String(text || '')
238
- : upsertTopLevelTomlString(text, 'openai_base_url', state.expected_base_url);
239
- const next = addTopLevelMarkerBeforeKey(withValue, 'openai_base_url', CODEX_LB_SHARED_OPENAI_ROUTING_MARKER);
240
- return { ...state, routing_status: state.status, ok: true, status: 'added', text: `${next.trim()}\n`, managed: true };
241
- }
242
- export function removeCodexLbSharedOpenAiRouting(text = '', baseUrl = '') {
243
- const state = codexLbSharedOpenAiRoutingState(text, baseUrl);
244
- if (state.status === 'missing' && state.managed) {
245
- return {
246
- ...state,
247
- changed: true,
248
- text: removeTopLevelLine(String(text || ''), CODEX_LB_SHARED_OPENAI_ROUTING_MARKER)
249
- };
250
- }
251
- if (state.status !== 'matched' || !state.managed)
252
- return { ...state, changed: false, text: String(text || '') };
253
- const withoutValue = removeTopLevelTomlKeyIfValue(text, 'openai_base_url', state.expected_base_url);
254
- return {
255
- ...state,
256
- changed: true,
257
- text: removeTopLevelLine(withoutValue, CODEX_LB_SHARED_OPENAI_ROUTING_MARKER)
258
- };
259
- }
260
- export function upsertCodexAppGlmConfig(text = '', input = {}) {
261
- let next = String(text || '');
262
- const authEnv = { ...(input.env || process.env), ...(input.home ? { HOME: input.home } : {}) };
263
- const authArgs = openRouterAuthCommandArgs(openRouterSecretPaths(authEnv).keyPath);
264
- const providerBlock = [
265
- `[model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}]`,
266
- 'name = "OpenRouter"',
267
- 'base_url = "https://openrouter.ai/api/v1"',
268
- 'wire_api = "responses"',
269
- 'requires_openai_auth = false'
270
- ].join('\n');
271
- next = upsertTomlTable(next, `model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}`, providerBlock);
272
- const authBlock = [
273
- `[model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}.auth]`,
274
- `command = ${JSON.stringify(OPENROUTER_AUTH_COMMAND)}`,
275
- `args = [${authArgs.map((value) => JSON.stringify(value)).join(', ')}]`,
276
- `timeout_ms = ${OPENROUTER_AUTH_TIMEOUT_MS}`,
277
- `refresh_interval_ms = ${OPENROUTER_AUTH_REFRESH_INTERVAL_MS}`
278
- ].join('\n');
279
- next = upsertTomlTable(next, `model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}.auth`, authBlock);
280
- next = next.replace(new RegExp(`\\n+\\[model_providers\\.${GLM_CODEX_CONFIG_PROVIDER_ID}\\.auth\\]`), `\n\n[model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}.auth]`);
281
- next = reconcileRetiredSksConfigText(next).text;
282
- return `${next.trim()}\n`;
283
- }
284
- export async function ensureGlobalCodexAppGlmProfile(opts = {}) {
285
- const env = opts.env || process.env;
286
- if (env.SKS_SKIP_CODEX_GLM_PROFILE_REPAIR === '1' && opts.force !== true) {
287
- return { ok: true, status: 'skipped', reason: 'SKS_SKIP_CODEX_GLM_PROFILE_REPAIR=1' };
288
- }
289
- const home = opts.home || env.HOME || os.homedir();
290
- const configPath = opts.configPath || codexLbConfigPath(home);
291
- try {
292
- await ensureDir(path.dirname(configPath));
293
- const current = await readText(configPath, '');
294
- const next = upsertCodexAppGlmConfig(current, { home, env: opts.env });
295
- const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'openrouter-provider');
296
- return {
297
- ...safeWrite,
298
- status: safeWrite.status === 'written' ? 'updated' : safeWrite.status,
299
- provider: GLM_CODEX_CONFIG_PROVIDER_ID,
300
- model: GLM_52_OPENROUTER_MODEL,
301
- codex_config_profile: OPENROUTER_DEFAULT_PROFILE_ID,
302
- reasoning_profiles: [],
303
- retired_glm_profiles: [...RETIRED_GLM_DESKTOP_CONFIG_PROFILE_IDS]
304
- };
305
- }
306
- catch (err) {
307
- return {
308
- ok: false,
309
- status: 'failed',
310
- config_path: configPath,
311
- error: err.message,
312
- provider: GLM_CODEX_CONFIG_PROVIDER_ID,
313
- model: GLM_52_OPENROUTER_MODEL,
314
- codex_config_profile: OPENROUTER_DEFAULT_PROFILE_ID,
315
- reasoning_profiles: [],
316
- retired_glm_profiles: [...RETIRED_GLM_DESKTOP_CONFIG_PROFILE_IDS]
317
- };
318
- }
319
- }
320
- export async function ensureStoredOpenRouterProviderDuringInstall(opts = {}) {
321
- const home = opts.home || opts.env?.HOME || process.env.HOME || os.homedir();
322
- const env = { ...(opts.env || process.env), HOME: home };
323
- const key = await resolveOpenRouterApiKey({ env });
324
- if (!key.key) {
325
- return {
326
- schema: 'sks.openrouter-provider-upgrade-repair.v1',
327
- ok: true,
328
- status: 'skipped',
329
- reason: 'openrouter_key_missing',
330
- key_present: false,
331
- key_source: null,
332
- blockers: [],
333
- warnings: key.warnings
334
- };
335
- }
336
- const repair = await ensureGlobalCodexAppGlmProfile({
337
- ...opts,
338
- home,
339
- env
340
- });
341
- const configPath = repair?.config_path || opts.configPath || codexLbConfigPath(home);
342
- const { ensureOpenRouterModelCatalog } = await import('../core/codex-app/openrouter-model-catalog.js');
343
- const catalogRepair = await ensureOpenRouterModelCatalog({ configPath, home, env })
344
- .catch((err) => ({ ok: false, status: 'failed', error: err?.message || String(err) }));
345
- const ok = repair?.ok !== false && catalogRepair?.ok !== false;
346
- return {
347
- schema: 'sks.openrouter-provider-upgrade-repair.v1',
348
- ok,
349
- status: ok ? repair?.status || 'present' : 'failed',
350
- reason: ok ? 'stored_openrouter_key_provider_reconciled' : 'stored_openrouter_key_provider_repair_failed',
351
- key_present: true,
352
- key_source: key.source,
353
- config_path: configPath,
354
- provider: GLM_CODEX_CONFIG_PROVIDER_ID,
355
- blockers: ok ? [] : ['openrouter_provider_repair_failed'],
356
- warnings: key.warnings,
357
- repair,
358
- model_catalog_repair: catalogRepair
359
- };
360
- }
361
- export function detectCodexLbSetupDrift(state = {}) {
362
- const drift = [];
363
- if (state.useDefaultProvider && state.selected !== true)
364
- drift.push('default_provider_not_selected');
365
- if (!state.useDefaultProvider && state.selected === true)
366
- drift.push('default_provider_selected_despite_no_default_provider');
367
- if (state.writeEnvFile && state.envFile !== true)
368
- drift.push('env_file_not_written');
369
- if (!state.writeEnvFile && state.beforeState && state.afterState && state.beforeState.envHash !== state.afterState.envHash)
370
- drift.push('env_file_changed_despite_no_env_file');
371
- if (!state.writeEnvFile && !state.beforeState && state.envFile === true)
372
- drift.push('env_file_written_despite_no_env_file');
373
- if (!state.storeKeychain && state.keychain?.status && state.keychain.status !== 'skipped')
374
- drift.push('keychain_touched_despite_no_keychain');
375
- if (!state.syncLaunchctl && state.codexEnvironment?.launch_environment?.status === 'synced')
376
- drift.push('launchctl_base_url_synced_despite_no_launchctl');
377
- if (state.codexEnvironment?.launch_environment?.secret_env_cleanup?.status === 'partial')
378
- drift.push('launchctl_secret_env_cleanup_incomplete');
379
- if (state.shellProfile === 'skip' && state.shellProfileResult?.status === 'installed')
380
- drift.push('shell_profile_written_despite_skip');
381
- if (state.shellProfile === 'skip' && state.beforeState && state.afterState && state.beforeState.profileHash !== state.afterState.profileHash)
382
- drift.push('shell_profile_changed_despite_skip');
383
- return drift;
384
- }
385
- export async function captureCodexLbSetupWriteState({ home, configPath, envPath, metadataPath, shellProfile } = {}) {
386
- const profileFiles = profileFilesForDrift(home, shellProfile);
387
- const paths = [configPath, envPath, metadataPath, ...profileFiles].filter(Boolean);
388
- const files = await Promise.all(paths.map(captureSetupFile));
389
- const hashesByPath = new Map(await Promise.all(files.map(async (file) => [file.path, await capturedSetupFileHash(file)])));
390
- const hashForPath = (file) => file ? hashesByPath.get(file) || 'missing' : 'missing';
391
- return {
392
- configHash: hashForPath(configPath),
393
- envHash: hashForPath(envPath),
394
- metadataHash: hashForPath(metadataPath),
395
- profileHash: profileFiles.map(hashForPath).join('|'),
396
- files,
397
- stateHash: await sha256Text(JSON.stringify(files))
398
- };
399
- }
400
- export async function writeCodexLbSetupFileIfUnchanged(input) {
401
- const file = path.resolve(input.file);
402
- if (path.resolve(String(input.expected.path || '')) !== file) {
403
- return {
404
- ok: false,
405
- status: 'write_failed',
406
- installed: false,
407
- expected_after: {
408
- path: file,
409
- existed: true,
410
- kind: 'regular',
411
- bytes_base64: Buffer.from(input.text).toString('base64'),
412
- mode: Number(input.mode) & 0o777
413
- },
414
- error: 'setup_snapshot_path_mismatch'
415
- };
416
- }
417
- const expected = { ...input.expected, path: file };
418
- const mode = Number(input.mode) & 0o777;
419
- const expectedAfter = {
420
- path: file,
421
- existed: true,
422
- kind: 'regular',
423
- bytes_base64: Buffer.from(input.text).toString('base64'),
424
- mode
425
- };
426
- if (expected.existed === true && expected.kind !== 'regular') {
427
- return {
428
- ok: false,
429
- status: 'unsafe_setup_write_target',
430
- installed: false,
431
- expected_after: expectedAfter
432
- };
433
- }
434
- assertTestHomeWriteAllowed(file);
435
- await ensureDir(path.dirname(file));
436
- const token = `${Date.now().toString(36)}-${process.pid}-${randomBytes(6).toString('hex')}`;
437
- const claimedPath = `${file}.sks-setup-claimed-${token}`;
438
- const candidatePath = `${file}.sks-setup-candidate-${token}`;
439
- let claimed = false;
440
- let installed = false;
441
- try {
442
- await writeBinaryAtomic(candidatePath, Buffer.from(input.text), { mode });
443
- const observed = await captureSetupFile(file);
444
- if (!setupFileStatesEqual(observed, expected)) {
445
- return {
446
- ok: false,
447
- status: 'concurrent_change_detected',
448
- installed: false,
449
- expected_after: expectedAfter
450
- };
451
- }
452
- if (setupFileStatesEqual(expected, expectedAfter)) {
453
- return {
454
- ok: true,
455
- status: 'present',
456
- installed: false,
457
- expected_after: expectedAfter
458
- };
459
- }
460
- if (expected.existed === true) {
461
- try {
462
- await fsp.rename(file, claimedPath);
463
- claimed = true;
464
- }
465
- catch {
466
- return {
467
- ok: false,
468
- status: 'concurrent_change_detected',
469
- installed: false,
470
- expected_after: expectedAfter
471
- };
472
- }
473
- const claimedState = await captureSetupFile(claimedPath);
474
- if (!setupFileStatesEqual(claimedState, { ...expected, path: claimedPath })) {
475
- const recovered = await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState);
476
- claimed = !recovered;
477
- const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
478
- return {
479
- ok: false,
480
- status: hardened ? 'concurrent_change_detected' : 'write_failed',
481
- installed: false,
482
- expected_after: expectedAfter,
483
- ...(claimed ? { recovery_path: claimedPath } : {}),
484
- ...(hardened ? {} : { error: 'setup_recovery_mode_hardening_failed' })
485
- };
486
- }
487
- }
488
- await input.beforeReplacement?.({ path: file });
489
- try {
490
- await fsp.link(candidatePath, file);
491
- installed = true;
492
- }
493
- catch {
494
- if (claimed) {
495
- const claimedState = await captureSetupFile(claimedPath).catch(() => null);
496
- const recovered = claimedState
497
- ? await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState)
498
- : false;
499
- claimed = !recovered;
500
- }
501
- const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
502
- return {
503
- ok: false,
504
- status: hardened ? 'concurrent_change_detected' : 'write_failed',
505
- installed: false,
506
- expected_after: expectedAfter,
507
- ...(claimed ? { recovery_path: claimedPath } : {}),
508
- ...(hardened ? {} : { error: 'setup_recovery_mode_hardening_failed' })
509
- };
510
- }
511
- const committed = await captureSetupFile(file);
512
- if (!setupFileStatesEqual(committed, expectedAfter)) {
513
- const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
514
- return {
515
- ok: false,
516
- status: hardened ? 'concurrent_change_detected' : 'write_failed',
517
- installed: true,
518
- expected_after: expectedAfter,
519
- ...(claimed ? { recovery_path: claimedPath } : {}),
520
- ...(hardened ? {} : { error: 'setup_recovery_mode_hardening_failed' })
521
- };
522
- }
523
- if (claimed && !await hardenCodexLbSetupRecoveryPath(claimedPath)) {
524
- return {
525
- ok: false,
526
- status: 'write_failed',
527
- installed: true,
528
- expected_after: expectedAfter,
529
- recovery_path: claimedPath,
530
- error: 'setup_recovery_mode_hardening_failed'
531
- };
532
- }
533
- return {
534
- ok: true,
535
- status: 'written',
536
- installed: true,
537
- expected_after: expectedAfter,
538
- ...(claimed ? { recovery_path: claimedPath } : {})
539
- };
540
- }
541
- catch (error) {
542
- if (claimed && !installed) {
543
- const claimedState = await captureSetupFile(claimedPath).catch(() => null);
544
- const recovered = claimedState
545
- ? await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState)
546
- : false;
547
- claimed = !recovered;
548
- }
549
- const hardened = !claimed || await hardenCodexLbSetupRecoveryPath(claimedPath);
550
- return {
551
- ok: false,
552
- status: 'write_failed',
553
- installed,
554
- expected_after: expectedAfter,
555
- ...(claimed ? { recovery_path: claimedPath } : {}),
556
- error: [
557
- error instanceof Error ? error.message : String(error),
558
- ...(hardened ? [] : ['setup_recovery_mode_hardening_failed'])
559
- ].join(':')
560
- };
561
- }
562
- finally {
563
- await fsp.rm(candidatePath, { force: true }).catch(() => undefined);
564
- }
565
- }
566
- export async function hardenCodexLbSetupRecoveryPath(file) {
567
- let handle = null;
568
- try {
569
- handle = await fsp.open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
570
- const before = await handle.stat();
571
- if (!before.isFile())
572
- return false;
573
- await handle.chmod(0o600);
574
- const hardened = await handle.stat();
575
- const pathStat = await fsp.lstat(file);
576
- return hardened.isFile()
577
- && (hardened.mode & 0o777) === 0o600
578
- && pathStat.isFile()
579
- && !pathStat.isSymbolicLink()
580
- && pathStat.dev === hardened.dev
581
- && pathStat.ino === hardened.ino;
582
- }
583
- catch {
584
- return false;
585
- }
586
- finally {
587
- await handle?.close().catch(() => undefined);
588
- }
589
- }
590
- export async function removeCodexLbSetupRecoveryPath(file) {
591
- if (!path.basename(file).includes('.sks-setup-claimed-'))
592
- return false;
593
- let handle = null;
594
- try {
595
- handle = await fsp.open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
596
- const opened = await handle.stat();
597
- const pathStat = await fsp.lstat(file);
598
- if (!opened.isFile()
599
- || !pathStat.isFile()
600
- || pathStat.isSymbolicLink()
601
- || pathStat.dev !== opened.dev
602
- || pathStat.ino !== opened.ino)
603
- return false;
604
- await fsp.unlink(file);
605
- return await fsp.lstat(file).then(() => false, (error) => error.code === 'ENOENT');
606
- }
607
- catch {
608
- return false;
609
- }
610
- finally {
611
- await handle?.close().catch(() => undefined);
612
- }
613
- }
614
- export async function restoreCodexLbSetupWriteState(state, expectedCurrentState, opts = {}) {
615
- const restored = [];
616
- const removed = [];
617
- const blockers = [];
618
- const recovery = [];
619
- const expectedByPath = new Map((Array.isArray(expectedCurrentState?.files) ? expectedCurrentState.files : [])
620
- .map((entry) => [String(entry?.path || ''), entry]));
621
- for (const entry of Array.isArray(state?.files) ? [...state.files].reverse() : []) {
622
- const file = String(entry?.path || '');
623
- if (!file)
624
- continue;
625
- try {
626
- const expected = expectedByPath.get(file);
627
- if (!expected) {
628
- blockers.push(`setup_rollback_missing_expected_state:${file}`);
629
- continue;
630
- }
631
- if (expected && setupFileStatesEqual(entry, expected))
632
- continue;
633
- const commit = await commitSetupRollbackIfUnchanged({
634
- file,
635
- before: entry,
636
- expected,
637
- ...(opts.beforeReplacement ? { beforeReplacement: opts.beforeReplacement } : {})
638
- });
639
- if (commit.recoveryPath)
640
- recovery.push(commit.recoveryPath);
641
- if (!commit.ok) {
642
- blockers.push(`${commit.failed ? 'setup_rollback_failed' : 'setup_rollback_conflict'}:${file}`);
643
- continue;
644
- }
645
- if (commit.restored)
646
- restored.push(file);
647
- if (commit.removed)
648
- removed.push(file);
649
- }
650
- catch {
651
- blockers.push(`setup_rollback_failed:${file}`);
652
- }
653
- }
654
- return { ok: blockers.length === 0, restored, removed, blockers, recovery };
655
- }
656
- function setupFileStatesEqual(left, right) {
657
- return left?.existed === right?.existed
658
- && String(left?.kind || (left?.existed ? 'regular' : 'missing')) === String(right?.kind || (right?.existed ? 'regular' : 'missing'))
659
- && String(left?.bytes_base64 || '') === String(right?.bytes_base64 || '')
660
- && (left?.existed !== true || Number(left?.mode) === Number(right?.mode));
661
- }
662
- async function captureSetupFile(file) {
663
- let pathStat;
664
- try {
665
- pathStat = await fsp.lstat(file);
666
- }
667
- catch (error) {
668
- if (error?.code === 'ENOENT') {
669
- return { path: file, existed: false, kind: 'missing', bytes_base64: '', mode: null };
670
- }
671
- throw error;
672
- }
673
- if (pathStat.isSymbolicLink()) {
674
- return { path: file, existed: true, kind: 'symlink', bytes_base64: '', mode: pathStat.mode & 0o777 };
675
- }
676
- if (!pathStat.isFile()) {
677
- return { path: file, existed: true, kind: 'non_regular', bytes_base64: '', mode: pathStat.mode & 0o777 };
678
- }
679
- let handle = null;
680
- try {
681
- handle = await fsp.open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
682
- const stat = await handle.stat();
683
- if (!stat.isFile()) {
684
- return { path: file, existed: true, kind: 'non_regular', bytes_base64: '', mode: stat.mode & 0o777 };
685
- }
686
- const bytes = await handle.readFile();
687
- return {
688
- path: file,
689
- existed: true,
690
- kind: 'regular',
691
- bytes_base64: bytes.toString('base64'),
692
- mode: stat.mode & 0o777
693
- };
694
- }
695
- catch (error) {
696
- if (error?.code === 'ENOENT') {
697
- return { path: file, existed: false, kind: 'missing', bytes_base64: '', mode: null };
698
- }
699
- if (error?.code === 'ELOOP') {
700
- const stat = await fsp.lstat(file).catch(() => null);
701
- return {
702
- path: file,
703
- existed: true,
704
- kind: 'symlink',
705
- bytes_base64: '',
706
- mode: stat ? stat.mode & 0o777 : null
707
- };
708
- }
709
- throw error;
710
- }
711
- finally {
712
- await handle?.close().catch(() => undefined);
713
- }
714
- }
715
- async function commitSetupRollbackIfUnchanged(input) {
716
- const { file, before, expected } = input;
717
- await ensureDir(path.dirname(file));
718
- const token = `${Date.now().toString(36)}-${process.pid}-${randomBytes(6).toString('hex')}`;
719
- const claimedPath = `${file}.sks-rollback-claimed-${token}`;
720
- const candidatePath = `${file}.sks-rollback-candidate-${token}`;
721
- let claimed = false;
722
- try {
723
- if (expected.existed === true) {
724
- const currentStat = await fsp.lstat(file).catch((error) => {
725
- if (error.code === 'ENOENT')
726
- return null;
727
- throw error;
728
- });
729
- if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isFile()) {
730
- return { ok: false, restored: false, removed: false };
731
- }
732
- try {
733
- await fsp.rename(file, claimedPath);
734
- claimed = true;
735
- }
736
- catch {
737
- return { ok: false, restored: false, removed: false };
738
- }
739
- const claimedState = await captureSetupFile(claimedPath);
740
- if (!setupFileStatesEqual(claimedState, { ...expected, path: claimedPath })) {
741
- const recovered = await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState);
742
- claimed = !recovered;
743
- return {
744
- ok: false,
745
- restored: false,
746
- removed: false,
747
- ...(claimed ? { recoveryPath: claimedPath } : {})
748
- };
749
- }
750
- }
751
- else {
752
- const current = await captureSetupFile(file);
753
- if (!setupFileStatesEqual(current, expected)) {
754
- return { ok: false, restored: false, removed: false };
755
- }
756
- }
757
- await input.beforeReplacement?.({ path: file });
758
- if (before.existed === true) {
759
- if (String(before.kind || 'regular') !== 'regular') {
760
- if (claimed) {
761
- const claimedState = await captureSetupFile(claimedPath);
762
- const recovered = await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState);
763
- claimed = !recovered;
764
- }
765
- return {
766
- ok: false,
767
- restored: false,
768
- removed: false,
769
- failed: true,
770
- ...(claimed ? { recoveryPath: claimedPath } : {})
771
- };
772
- }
773
- const bytes = Buffer.from(String(before.bytes_base64 || ''), 'base64');
774
- await writeBinaryAtomic(candidatePath, bytes, { mode: Number(before.mode || 0o600) });
775
- try {
776
- await fsp.link(candidatePath, file);
777
- }
778
- catch {
779
- return {
780
- ok: false,
781
- restored: false,
782
- removed: false,
783
- ...(claimed ? { recoveryPath: claimedPath } : {})
784
- };
785
- }
786
- const committed = await captureSetupFile(file);
787
- if (!setupFileStatesEqual(committed, before)) {
788
- return {
789
- ok: false,
790
- restored: false,
791
- removed: false,
792
- ...(claimed ? { recoveryPath: claimedPath } : {})
793
- };
794
- }
795
- return {
796
- ok: true,
797
- restored: true,
798
- removed: false,
799
- ...(claimed ? { recoveryPath: claimedPath } : {})
800
- };
801
- }
802
- const committed = await captureSetupFile(file);
803
- if (!setupFileStatesEqual(committed, before)) {
804
- return {
805
- ok: false,
806
- restored: false,
807
- removed: false,
808
- ...(claimed ? { recoveryPath: claimedPath } : {})
809
- };
810
- }
811
- return {
812
- ok: true,
813
- restored: false,
814
- removed: true,
815
- ...(claimed ? { recoveryPath: claimedPath } : {})
816
- };
817
- }
818
- catch {
819
- if (claimed) {
820
- const claimedState = await captureSetupFile(claimedPath).catch(() => null);
821
- const recovered = claimedState
822
- ? await restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState)
823
- : false;
824
- claimed = !recovered;
825
- }
826
- return {
827
- ok: false,
828
- restored: false,
829
- removed: false,
830
- failed: true,
831
- ...(claimed ? { recoveryPath: claimedPath } : {})
832
- };
833
- }
834
- finally {
835
- await fsp.rm(candidatePath, { force: true }).catch(() => undefined);
836
- }
837
- }
838
- async function restoreClaimedSetupPathIfAbsent(claimedPath, file, claimedState) {
839
- try {
840
- if (claimedState?.kind === 'regular') {
841
- await fsp.link(claimedPath, file);
842
- }
843
- else if (claimedState?.kind === 'symlink') {
844
- await fsp.symlink(await fsp.readlink(claimedPath), file);
845
- }
846
- else {
847
- return false;
848
- }
849
- await fsp.unlink(claimedPath);
850
- return true;
851
- }
852
- catch {
853
- return false;
854
- }
855
- }
856
- async function capturedSetupFileHash(file) {
857
- if (file.kind !== 'regular')
858
- return 'missing';
859
- return await sha256Text(Buffer.from(file.bytes_base64, 'base64').toString('utf8'));
860
- }
861
- function profileFilesForDrift(home, shellProfile) {
862
- const targets = {
863
- zsh: path.join(home, '.zshrc'),
864
- bash: path.join(home, '.bashrc'),
865
- fish: path.join(home, '.config', 'fish', 'config.fish')
866
- };
867
- if (shellProfile === 'zsh')
868
- return [targets.zsh];
869
- if (shellProfile === 'bash')
870
- return [targets.bash];
871
- if (shellProfile === 'fish')
872
- return [targets.fish];
873
- if (shellProfile === 'all')
874
- return [targets.zsh, targets.bash, targets.fish];
875
- return [targets.zsh, targets.bash, targets.fish];
876
- }
877
- export function appliedCodexLbPersistenceModes(state = {}) {
878
- const modes = [];
879
- if (state.writeEnvFile && state.envFile === true)
880
- modes.push('durable_env_file');
881
- if (state.storeKeychain && state.keychain?.ok === true)
882
- modes.push('durable_keychain');
883
- if (state.syncLaunchctl && state.codexEnvironment?.launch_environment?.status === 'synced')
884
- modes.push('process_only_ephemeral');
885
- if (state.shellProfile !== 'skip' && state.shellProfileResult?.status === 'installed')
886
- modes.push('shell_profile');
887
- if (!modes.length && state.apiKeySource === 'process.env')
888
- modes.push('process_only_ephemeral');
889
- if (!modes.length)
890
- modes.push('none');
891
- return modes;
892
- }
893
- export function shellSingleQuote(value) {
894
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
895
- }
896
- export function parseCodexLbEnvBaseUrl(text = '') {
897
- const value = parseShellEnvValue(text, 'CODEX_LB_BASE_URL');
898
- return value ? normalizeCodexLbBaseUrl(value) : '';
899
- }
900
- export function parseCodexSharedLoginApiKey(text = '') {
901
- try {
902
- const parsed = JSON.parse(String(text || ''));
903
- const authMode = String(parsed?.auth_mode || parsed?.authMode || parsed?.mode || '').toLowerCase();
904
- const key = parsed?.key || parsed?.api_key || parsed?.apiKey || parsed?.openai_api_key || parsed?.OPENAI_API_KEY;
905
- if (!key || typeof key !== 'string')
906
- return '';
907
- if (authMode && !/api[-_]?key|apikey/.test(authMode))
908
- return '';
909
- return key.trim();
910
- }
911
- catch {
912
- return '';
913
- }
914
- }
915
- function parseShellEnvValue(text = '', key = '') {
916
- const re = new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=\\s*(.+?)\\s*$`, 'm');
917
- const envMatch = String(text || '').match(re);
918
- const raw = envMatch?.[1]?.trim() || '';
919
- if (!raw)
920
- return '';
921
- if (raw.startsWith("'"))
922
- return raw.endsWith("'") && raw.length > 1 ? raw.slice(1, -1).replace(/'\\''/g, "'") : '';
923
- if (raw.startsWith('"'))
924
- return raw.endsWith('"') && raw.length > 1 ? raw.slice(1, -1).replace(/\\"/g, '"') : '';
925
- if (raw.includes("'") || raw.includes('"') || /\s/.test(raw))
926
- return '';
927
- return raw;
928
- }
929
- export async function sha256Text(value = '') {
930
- const { createHash } = await import('node:crypto');
931
- return createHash('sha256').update(String(value || '')).digest('hex');
932
- }
933
- function topLevelTomlString(text = '', key) {
87
+ function topLevelTomlString(text, key) {
934
88
  const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
935
89
  return topLevel.match(new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]+)"\\s*(?:#.*)?(?=\\n|$)`))?.[2] || '';
936
90
  }
937
- function topLevelHasLine(text = '', line) {
91
+ function topLevelHasLine(text, line) {
938
92
  const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
939
93
  return topLevel.split(/\r?\n/).some((candidate) => candidate.trim() === line);
940
94
  }
941
- function addTopLevelMarkerBeforeKey(text = '', key, marker) {
95
+ function addTopLevelMarkerBeforeKey(text, key, marker) {
942
96
  const lines = String(text || '').split('\n');
943
97
  const firstTable = lines.findIndex((line) => /^\s*\[/.test(line));
944
98
  const end = firstTable === -1 ? lines.length : firstTable;
@@ -947,7 +101,7 @@ function addTopLevelMarkerBeforeKey(text = '', key, marker) {
947
101
  lines.splice(keyIndex, 0, marker);
948
102
  return lines.join('\n');
949
103
  }
950
- function removeTopLevelLine(text = '', target) {
104
+ function removeTopLevelLine(text, target) {
951
105
  const lines = String(text || '').split('\n');
952
106
  const firstTable = lines.findIndex((line) => /^\s*\[/.test(line));
953
107
  const end = firstTable === -1 ? lines.length : firstTable;
@@ -957,3 +111,47 @@ function removeTopLevelLine(text = '', target) {
957
111
  }
958
112
  return lines.join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
959
113
  }
114
+ function removeTopLevelTomlKey(text, key) {
115
+ const lines = String(text || '').split('\n');
116
+ const firstTable = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
117
+ const end = firstTable === -1 ? lines.length : firstTable;
118
+ const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
119
+ return lines
120
+ .filter((line, index) => index >= end || !keyPattern.test(line))
121
+ .join('\n')
122
+ .replace(/^\n+/, '')
123
+ .replace(/\n{3,}/g, '\n\n');
124
+ }
125
+ function removeManagedBridgeTopLevelBindings(text) {
126
+ let next = String(text || '');
127
+ next = removeTopLevelTomlKey(next, 'model_provider');
128
+ next = removeTopLevelTomlKey(next, 'openai_base_url');
129
+ next = removeTopLevelTomlKey(next, 'model_catalog_json');
130
+ for (const marker of [
131
+ DESKTOP_BRIDGE_MANAGED_MARKER,
132
+ DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER,
133
+ DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER
134
+ ])
135
+ next = removeTopLevelLine(next, marker);
136
+ return next;
137
+ }
138
+ function normalizeManagedBridgeBaseUrl(value) {
139
+ const normalized = String(value || '').trim().replace(/\/+$/, '');
140
+ let parsed;
141
+ try {
142
+ parsed = new URL(normalized);
143
+ }
144
+ catch {
145
+ throw new Error('desktop_bridge_loopback_base_url_required');
146
+ }
147
+ const hostname = parsed.hostname.replace(/^\[|\]$/g, '').toLowerCase();
148
+ if (parsed.protocol !== 'http:'
149
+ || !['127.0.0.1', 'localhost', '::1'].includes(hostname)
150
+ || parsed.username
151
+ || parsed.password
152
+ || parsed.search
153
+ || parsed.hash
154
+ || !/^\/__sks\/client\/[A-Za-z0-9_-]{43}\/backend-api\/codex$/.test(parsed.pathname.replace(/\/+$/, '')))
155
+ throw new Error('desktop_bridge_loopback_base_url_required');
156
+ return normalized;
157
+ }