cartethyia 2.5.4 → 2.5.7

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 (395) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-DE2607Tb.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-DE2607Tb.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-C2PNYWzk.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-C2PNYWzk.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-TXP9OfAM.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-TXP9OfAM.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-B-9xnAQN.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-B-9xnAQN.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-xeax9nJt.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-xeax9nJt.js.gz +0 -0
  15. package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-Df4jjaHJ.js} +1 -1
  16. package/dist/dashboard/assets/HealthEventsModal-Df4jjaHJ.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-Dyojsm84.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-Dyojsm84.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-DeWSOEUO.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-DeWSOEUO.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-CkpWln2S.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-CkpWln2S.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-C_klIZom.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-C_klIZom.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-BYph-InK.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-BYph-InK.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-CvyQCYWJ.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-CvyQCYWJ.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-Cm1JYilI.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-Cm1JYilI.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-DlFnc3_S.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-DlFnc3_S.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-Chi7pCnt.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-D4pQapKj.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-DH6cg-_B.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-fTgljTGA.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-D5Ys_sM0.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  60. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  61. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  62. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
  64. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-Bog0p925.js +4 -0
  66. package/dist/dashboard/assets/entry-Bog0p925.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-js6TJvdr.js → index-CZQIrdoX.js} +2 -2
  72. package/dist/dashboard/assets/index-CZQIrdoX.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
  74. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
  76. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  77. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  78. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
  80. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
  82. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
  86. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  87. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  88. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
  92. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-WUCXorXw.js → select-BPpSsBgL.js} +1 -1
  94. package/dist/dashboard/assets/select-BPpSsBgL.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
  96. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
  98. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
  100. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
  102. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  103. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  104. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  105. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  106. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  107. package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
  108. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  109. package/dist/dashboard/index.html +1 -1
  110. package/dist/dashboard/index.html.gz +0 -0
  111. package/dist/dashboard/precompressed-assets.json +54 -54
  112. package/migrations/README.md +52 -52
  113. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  114. package/package.json +95 -95
  115. package/scripts/cli/admin.ts +125 -0
  116. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  117. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
  118. package/scripts/cli/doctor.ts +37 -0
  119. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  120. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  121. package/scripts/cli/instance.ts +224 -0
  122. package/scripts/cli/main.ts +61 -18
  123. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  124. package/scripts/cli/port.ts +100 -0
  125. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  126. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  127. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  128. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  129. package/scripts/cli/tray/index.ts +62 -12
  130. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  131. package/scripts/cli/{command/update.ts → update.ts} +39 -39
  132. package/scripts/cli/version.ts +67 -0
  133. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  134. package/src/boot/app.ts +697 -693
  135. package/src/boot/dependencies.ts +647 -646
  136. package/src/boot/timeout.ts +2 -13
  137. package/src/config/cooldowns.ts +186 -138
  138. package/src/config/network.ts +8 -14
  139. package/src/config/providers.ts +7 -10
  140. package/src/config/runtime.ts +76 -76
  141. package/src/config/security.ts +15 -0
  142. package/src/config/spec.ts +20 -30
  143. package/src/config/telemetry.ts +0 -20
  144. package/src/config/timeouts.ts +0 -6
  145. package/src/console/admin/model-abuse/contracts.ts +28 -6
  146. package/src/console/admin/model-abuse/routes.ts +56 -3
  147. package/src/console/api-keys/contracts.ts +410 -414
  148. package/src/console/api-keys/routes.ts +881 -880
  149. package/src/console/auth/credentials.ts +3 -3
  150. package/src/console/auth/setup-service.ts +2 -2
  151. package/src/console/backup/export-endpoint.ts +6 -2
  152. package/src/console/backup/nine-router.ts +2 -5
  153. package/src/console/backup/service.ts +4 -0
  154. package/src/console/backup/store.ts +192 -29
  155. package/src/console/backup/validate.ts +3 -45
  156. package/src/console/cli-tools/fs-ops.ts +487 -487
  157. package/src/console/console-router-guards.ts +1 -1
  158. package/src/console/data-management/contracts.ts +13 -6
  159. package/src/console/data-management/operations.ts +23 -18
  160. package/src/console/data-management/routes.ts +0 -1
  161. package/src/console/domain-registration.ts +10 -5
  162. package/src/console/observability/live.ts +22 -0
  163. package/src/console/observability/store.ts +39 -16
  164. package/src/console/observability/usage-dimensions.ts +4 -4
  165. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  166. package/src/console/providers/catalog/model-operations.ts +5 -2
  167. package/src/console/providers/catalog/provider-operations.ts +4 -4
  168. package/src/console/providers/catalog/public-model-store.ts +8 -11
  169. package/src/console/providers/catalog/store.ts +1553 -1553
  170. package/src/console/providers/oauth/routes.ts +8 -12
  171. package/src/console/quota/account-quota-tenant.ts +6 -6
  172. package/src/console/quota/quota-view.ts +1 -1
  173. package/src/console/remote-support/contracts.ts +61 -0
  174. package/src/console/remote-support/routes.ts +65 -0
  175. package/src/console/remote-support/service.ts +207 -0
  176. package/src/console/response-compression.ts +0 -3
  177. package/src/console/routing/pools/probe-result.ts +3 -2
  178. package/src/console/routing/pools/probe-verdict.ts +132 -132
  179. package/src/console/routing/pools/store.ts +8 -8
  180. package/src/console/settings/contracts.ts +58 -2
  181. package/src/console/settings/store.ts +30 -3
  182. package/src/console/shared/cursor.ts +8 -8
  183. package/src/console/shared/errors.ts +1 -3
  184. package/src/network/http2-bidi.ts +2 -4
  185. package/src/network/pool/agent.ts +478 -478
  186. package/src/network/pool/selector.ts +5 -4
  187. package/src/network/retry.ts +1 -2
  188. package/src/network/ssrf.ts +5 -4
  189. package/src/observability/event-compaction.ts +80 -78
  190. package/src/observability/event-day-index.ts +13 -15
  191. package/src/observability/event-log.ts +4 -4
  192. package/src/observability/logger.ts +6 -6
  193. package/src/observability/payload-capture.ts +151 -33
  194. package/src/observability/payload-store.ts +397 -440
  195. package/src/observability/redaction.ts +5 -6
  196. package/src/observability/request-notify.ts +43 -0
  197. package/src/observability/telemetry-status.ts +8 -9
  198. package/src/observability/token-speed.ts +5 -4
  199. package/src/persistence/db-mode.ts +7 -7
  200. package/src/persistence/readiness.ts +1 -1
  201. package/src/persistence/schema.ts +1406 -1402
  202. package/src/persistence/shared-cache.ts +1 -2
  203. package/src/persistence/stores/api-key-store.ts +18 -6
  204. package/src/persistence/stores/share-store.ts +4 -3
  205. package/src/persistence/stores/telemetry-store.ts +236 -237
  206. package/src/protocol/canonical-model.ts +11 -3
  207. package/src/protocol/codecs.ts +138 -4
  208. package/src/protocol/gateway-error.ts +6 -4
  209. package/src/protocol/request/chat.ts +21 -1
  210. package/src/protocol/request/codex.ts +10 -1
  211. package/src/protocol/request/messages.ts +4 -5
  212. package/src/protocol/request/responses.ts +9 -1
  213. package/src/protocol/response/chat.ts +34 -4
  214. package/src/protocol/response/codex.ts +7 -4
  215. package/src/protocol/response/dsml.ts +140 -28
  216. package/src/protocol/response/responses.ts +11 -5
  217. package/src/protocol/stream-error-frames.ts +4 -5
  218. package/src/providers/authentication/jwt-validator.ts +9 -10
  219. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  220. package/src/providers/configured-openai-providers.ts +134 -136
  221. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  222. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  223. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  224. package/src/providers/discovery/probe-phases.ts +4 -4
  225. package/src/providers/discovery/probe-wire.ts +6 -13
  226. package/src/providers/discovery/probing-service.ts +1117 -1045
  227. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  228. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  231. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  232. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  233. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  234. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  235. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  236. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  237. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  238. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  239. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  240. package/src/providers/integrations/claude/claude.ts +608 -608
  241. package/src/providers/integrations/cline/cline-quota.ts +132 -132
  242. package/src/providers/integrations/cline/cline.ts +537 -539
  243. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  244. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  245. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  246. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  247. package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
  248. package/src/providers/integrations/cursor/cursor.ts +11 -163
  249. package/src/providers/integrations/factory/factory-models.ts +3 -5
  250. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  251. package/src/providers/integrations/grok/grok.ts +6 -7
  252. package/src/providers/integrations/hermes.ts +14 -18
  253. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  254. package/src/providers/integrations/kiro/kiro.ts +1 -1
  255. package/src/providers/integrations/mistral.ts +6 -6
  256. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  257. package/src/providers/integrations/opencode/provider.ts +8 -8
  258. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  259. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  260. package/src/providers/integrations/qoder/qoder.ts +884 -865
  261. package/src/providers/integrations/shared/connect.ts +6 -6
  262. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  263. package/src/providers/integrations/trae/trae.ts +2 -2
  264. package/src/providers/integrations/zcode/oauth.ts +6 -1
  265. package/src/providers/operations/account-health-service.ts +1528 -1523
  266. package/src/providers/operations/account-reset-service.ts +1 -1
  267. package/src/providers/operations/client-versions.ts +6 -6
  268. package/src/providers/operations/custom-cli-headers.ts +3 -4
  269. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  270. package/src/providers/operations/provider-catalog-service.ts +3 -4
  271. package/src/providers/provider-metadata.ts +4 -8
  272. package/src/providers/provider-registry.ts +6 -7
  273. package/src/providers/provider-usage.ts +1 -1
  274. package/src/security/abuse.ts +15 -10
  275. package/src/security/access-control.ts +144 -146
  276. package/src/security/admission/contracts.ts +11 -0
  277. package/src/security/admission/in-memory-store.ts +4 -3
  278. package/src/security/admission/lease-sweep.ts +6 -6
  279. package/src/security/admission/reasons.ts +5 -1
  280. package/src/security/admission/redis-store.ts +211 -210
  281. package/src/security/admission/service.ts +4 -0
  282. package/src/security/api-key-auth.ts +504 -504
  283. package/src/security/ip-boundary.ts +155 -15
  284. package/src/security/model-abuse.ts +366 -94
  285. package/src/security/outbound-headers.ts +3 -4
  286. package/src/shared/sleep.ts +4 -0
  287. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  288. package/src/transport/dispatch/attempt-loop.ts +531 -530
  289. package/src/transport/dispatch/stream-bridge.ts +1 -3
  290. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  291. package/src/transport/dispatch/streaming/stream.ts +5 -6
  292. package/src/transport/failure-policy.ts +162 -8
  293. package/src/transport/guards/auth-guard.ts +7 -4
  294. package/src/transport/guards/concurrency-guard.ts +0 -5
  295. package/src/transport/guards/error-lifecycle.ts +13 -6
  296. package/src/transport/guards/pipeline.ts +15 -3
  297. package/src/transport/guards/request-context.ts +8 -2
  298. package/src/transport/request/inflight.ts +5 -6
  299. package/src/transport/request/preparer.ts +2 -2
  300. package/src/transport/request/state.ts +12 -8
  301. package/src/transport/routing/admission.ts +4 -0
  302. package/src/transport/routing/route-catalog.ts +8 -1
  303. package/src/transport/routing/route-model.ts +46 -14
  304. package/src/transport/routing/router.ts +21 -14
  305. package/src/transport/surface/completion.ts +2 -3
  306. package/src/transport/surface/dialects.ts +5 -6
  307. package/src/transport/surface/responses/encode.ts +38 -0
  308. package/src/transport/surface/responses/parse.ts +4 -0
  309. package/src/transport/translation/capabilities.ts +2 -3
  310. package/src/transport/translation/quirks.ts +3 -3
  311. package/src/workers/buddy-growth-tasks.ts +5 -10
  312. package/src/workers/daily-checkin.ts +17 -23
  313. package/src/workers/quota-refresh-sweep.ts +26 -26
  314. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  315. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
  316. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  317. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  318. package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
  319. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  320. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  321. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  322. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  323. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  324. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
  325. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  326. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
  327. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  328. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
  329. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  330. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  331. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
  332. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  333. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
  334. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  335. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
  336. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  337. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  338. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  339. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  340. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
  341. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  342. package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
  343. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  344. package/dist/dashboard/assets/app-COlK95oi.css +0 -2
  345. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  346. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  347. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  348. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  349. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  350. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  351. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  352. package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
  353. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  354. package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
  355. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  356. package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
  357. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  358. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
  360. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  361. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  363. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  365. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
  367. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  368. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  369. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  370. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  371. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  372. package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
  373. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  374. package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
  375. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  376. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  377. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  378. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  379. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  380. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  381. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  382. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  383. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  384. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  385. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  386. package/scripts/cli/command/admin.ts +0 -65
  387. package/scripts/cli/command/doctor.ts +0 -257
  388. package/scripts/cli/gateway/drain-token.ts +0 -78
  389. package/scripts/cli/gateway/instance-lock.ts +0 -105
  390. package/scripts/cli/gateway/stop.ts +0 -51
  391. package/scripts/cli/support/bun.ts +0 -27
  392. package/scripts/cli/support/version.ts +0 -37
  393. /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
  394. /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
  395. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -102,8 +102,7 @@ export class DrizzleOAuthAccountStore implements OAuthAccountStore {
102
102
  lastErrorAt: null,
103
103
  lastRecoveredAt: null,
104
104
  // A fresh login supplies a refresh token, so the account returns
105
- // to normal OAuth refresh handling — a static-token flag set on
106
- // the previous (refresh-less) credential no longer applies.
105
+ // to normal OAuth refresh handling and is not a static token.
107
106
  staticToken: false,
108
107
  // Only what this login reported: a flow that learned no profile or
109
108
  // region leaves the stored configuration alone rather than blanking it.
@@ -164,16 +163,13 @@ export class DrizzleOAuthAccountStore implements OAuthAccountStore {
164
163
  * and device-code start/poll — driven by whichever `OAuthLoginClient` is
165
164
  * registered for a given provider id.
166
165
  *
167
- * > **Koreksi.** An earlier comment here read "Cartethyia hosts the callback
168
- * > itself (it's a server process, not a local CLI), so there is no
169
- * > loopback-listener/port-selection problem." That was wrong, and it is the
170
- * > premise that produced the bug. The redirect URIs these clients advertise
171
- * > are loopback (`localhost:1455`, `127.0.0.1:54549`, …), which name the
172
- * > machine the *operator's browser* is on — not this process. Advertising one
173
- * > without binding the port leaves the code in the browser's address bar, so
174
- * > every browser login needed the redirect pasted back by hand. The gateway
175
- * > is a server process, but it is not *the* server the redirect arrives at;
176
- * > `callback-listener.ts` is what makes those the same endpoint.
166
+ * The redirect URIs these clients advertise are loopback (`localhost:1455`,
167
+ * `127.0.0.1:54549`, …), which name the machine the *operator's browser* is on
168
+ * — not this process. Advertising one without binding the port leaves the code
169
+ * in the browser's address bar, so every browser login needs the redirect
170
+ * pasted back by hand. The gateway is a server process, but it is not *the*
171
+ * server the redirect arrives at; `callback-listener.ts` is what makes those
172
+ * the same endpoint.
177
173
  */
178
174
  export interface PersistedOAuthAccount {
179
175
  readonly accountId: string;
@@ -354,12 +354,12 @@ export function registerAccountQuotaTenantRoutes(
354
354
  const accounts = rows.map((row) => {
355
355
  const entry =
356
356
  row.tenantId === null ? globalCache.get(row.id) : ownedCache.get(row.id);
357
- // No background refresh is enqueued here. Opening the page used to
358
- // enqueue one for every account whose cached value had aged out, so a
359
- // single page open hit every provider's quota endpoint at once — and
360
- // because the page also polled, the operator saw that fan-out as "the
361
- // page refreshes everything when I open it". The periodic sweep owns
362
- // freshness; this route renders what is cached.
357
+ // No background refresh is enqueued here: enqueuing one for every
358
+ // account whose cached value had aged out means a single page open hits
359
+ // every provider's quota endpoint at once — and because the page also
360
+ // polls, the operator sees that fan-out as "the page refreshes
361
+ // everything when I open it". The periodic sweep owns freshness; this
362
+ // route renders what is cached.
363
363
  //
364
364
  // `refreshing` still counts accounts with no cached value, so the client
365
365
  // can show them as pending while the sweep fills them.
@@ -154,7 +154,7 @@ export function toQuotaAccountHealth(row: {
154
154
  cooldownUntil: row.cooldownUntil ? row.cooldownUntil.toISOString() : null,
155
155
  // Only deadlines still in force are returned: the sweep prunes the column
156
156
  // on a timer, so a raw read can carry an entry that has already elapsed and
157
- // would render as a cooldown that no longer applies.
157
+ // would render as a cooldown that is not in force.
158
158
  modelCooldowns: liveModelCooldowns(row.modelCooldowns),
159
159
  };
160
160
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Remote-support tunnel contracts.
3
+ *
4
+ * A tunnel publishes the loopback gateway to a public or tailnet address. This
5
+ * module owns the vocabulary (which tool, which states, what a status reports)
6
+ * and is deliberately free of Elysia and Node imports so the dashboard can
7
+ * import the same shape without dragging a server runtime into the browser
8
+ * bundle.
9
+ */
10
+
11
+ /** The two supported publishers. Both must already be installed. */
12
+ export const REMOTE_SUPPORT_TOOLS = ["cloudflared", "tailscale"] as const;
13
+
14
+ export type RemoteSupportTool = (typeof REMOTE_SUPPORT_TOOLS)[number];
15
+
16
+ /**
17
+ * Why a tunnel cannot start. `not_installed` is the common one: this product
18
+ * never installs third-party binaries, so a missing `cloudflared` or
19
+ * `tailscale` is reported for the operator to install, never downloaded.
20
+ */
21
+ export type RemoteSupportBlocker =
22
+ | "not_installed"
23
+ | "unsupported_platform"
24
+ | "launch_failed";
25
+
26
+ export interface RemoteSupportStatus {
27
+ /** Which publisher is up, or `null` when neither is. */
28
+ readonly active: RemoteSupportTool | null;
29
+ /** The published base URL, present once the publisher reported one. */
30
+ readonly url: string | null;
31
+ /** Set when a start was refused, with the operator-facing reason. */
32
+ readonly blocker: RemoteSupportBlocker | null;
33
+ /** Operator-facing detail for `blocker`, e.g. the install hint. */
34
+ readonly message: string | null;
35
+ /** Local port the tunnel forwards to. */
36
+ readonly port: number;
37
+ /** Which publishers are installed on this host. Cached so the UI doesn't poll. */
38
+ readonly installed: Readonly<Record<RemoteSupportTool, boolean>>;
39
+ }
40
+
41
+ export interface RemoteSupportStartRequest {
42
+ readonly tool: RemoteSupportTool;
43
+ /** Defaults to the gateway's own port when omitted. */
44
+ readonly port?: number;
45
+ }
46
+
47
+ export interface RemoteSupportStartResponse extends RemoteSupportStatus {
48
+ readonly started: boolean;
49
+ }
50
+
51
+ /** Human-readable install guidance, keyed by tool. Never an installer. */
52
+ export const REMOTE_SUPPORT_INSTALL_HINT: Readonly<Record<RemoteSupportTool, string>> = {
53
+ cloudflared:
54
+ "cloudflared is not installed. Install it yourself (winget install Cloudflare.cloudflared) and try again.",
55
+ tailscale:
56
+ "tailscale is not installed. Install it yourself (winget install Tailscale.Tailscale), run `tailscale up` once, then try again.",
57
+ };
58
+
59
+ export function isRemoteSupportTool(value: unknown): value is RemoteSupportTool {
60
+ return typeof value === "string" && (REMOTE_SUPPORT_TOOLS as readonly string[]).includes(value);
61
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Remote-support HTTP surface.
3
+ *
4
+ * Three routes over one external launcher script: read the current tunnel,
5
+ * start one, stop one. Every mutating route needs `dashboard:write` because it
6
+ * runs a process on the host running the gateway.
7
+ *
8
+ * The start response never contains a secret and never performs an install; it
9
+ * reports `not_installed` with guidance when the publisher is missing.
10
+ */
11
+ import { Elysia, t } from "elysia";
12
+ import type { ConsoleAccessResolver } from "../auth/access";
13
+ import { consoleErrorHandler, requireTenantScope } from "../shared/errors";
14
+ import { REMOTE_SUPPORT_TOOLS } from "./contracts";
15
+ import type { RemoteSupportService } from "./service";
16
+
17
+ export interface RemoteSupportRoutesConfig {
18
+ readonly accessResolver: ConsoleAccessResolver;
19
+ readonly service: RemoteSupportService;
20
+ }
21
+
22
+ const toolSchema = t.Union([
23
+ t.Literal(REMOTE_SUPPORT_TOOLS[0]),
24
+ t.Literal(REMOTE_SUPPORT_TOOLS[1]),
25
+ ]);
26
+
27
+ const startBody = t.Object({
28
+ tool: toolSchema,
29
+ port: t.Optional(t.Number()),
30
+ });
31
+
32
+ const stopBody = t.Object({
33
+ tool: t.Optional(toolSchema),
34
+ });
35
+
36
+ /**
37
+ * The return type is inferred rather than annotated as `Elysia`: `.error()`
38
+ * widens the instance type with its handler's response envelope, so the
39
+ * annotated base type no longer matches the chained result.
40
+ */
41
+ export function createRemoteSupportRoutes(config: RemoteSupportRoutesConfig) {
42
+ return new Elysia()
43
+ .error(consoleErrorHandler("Remote support failed"))
44
+ .get("/remote-support/status", async ({ request }) => {
45
+ requireTenantScope(config.accessResolver(request), "dashboard:read");
46
+ return await config.service.status();
47
+ })
48
+ .post(
49
+ "/remote-support/start",
50
+ { body: startBody },
51
+ async ({ request, body }) => {
52
+ requireTenantScope(config.accessResolver(request), "dashboard:write");
53
+ const status = await config.service.start(body.tool, body.port);
54
+ return { started: status.active !== null, ...status };
55
+ },
56
+ )
57
+ .post(
58
+ "/remote-support/stop",
59
+ { body: stopBody },
60
+ async ({ request, body }) => {
61
+ requireTenantScope(config.accessResolver(request), "dashboard:write");
62
+ return await config.service.stop(body.tool);
63
+ },
64
+ );
65
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Remote-support tunnel service.
3
+ *
4
+ * The tunnel is launched by an external script rather than by spawning
5
+ * `cloudflared`/`tailscale` directly, for one reason that matters: the script
6
+ * hands the publisher to the OS session, so the tunnel is not a child of this
7
+ * gateway. A gateway restart, a hot reload, or a Ctrl-C in the owning terminal
8
+ * therefore cannot kill an operator's live remote-support session.
9
+ *
10
+ * This service never installs anything. A missing binary is reported as a
11
+ * blocker with install guidance; downloading a third-party agent on the
12
+ * operator's behalf is out of scope.
13
+ */
14
+ import { spawn } from "node:child_process";
15
+ import { existsSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { PACKAGE_ROOT } from "../../config/package-paths";
18
+ import {
19
+ REMOTE_SUPPORT_INSTALL_HINT,
20
+ type RemoteSupportBlocker,
21
+ type RemoteSupportStatus,
22
+ type RemoteSupportTool,
23
+ } from "./contracts";
24
+
25
+ /** How long the launcher may take to report a URL before we stop waiting. */
26
+ const LAUNCH_TIMEOUT_MS = 40_000;
27
+
28
+ /** Cached install detection so we don't spawn `where` on every status poll. */
29
+ const installedCache = new Map<RemoteSupportTool, boolean>();
30
+
31
+ export interface RemoteSupportServiceConfig {
32
+ /** Defaults to the gateway's configured port. */
33
+ readonly port: number;
34
+ /** Overridable for tests; defaults to the package root. */
35
+ readonly scriptDir?: string;
36
+ }
37
+
38
+ interface CommandResult {
39
+ readonly status: number;
40
+ readonly stdout: string;
41
+ }
42
+
43
+ function scriptPath(config: RemoteSupportServiceConfig, name: string): string {
44
+ const dir = config.scriptDir ?? join(PACKAGE_ROOT, "scripts");
45
+ return join(dir, name);
46
+ }
47
+
48
+ function runScript(
49
+ config: RemoteSupportServiceConfig,
50
+ args: readonly string[],
51
+ ): Promise<CommandResult> {
52
+ const { promise, resolve, reject } = Promise.withResolvers<CommandResult>();
53
+ const isWindows = process.platform === "win32";
54
+ // Windows cannot spawn a `.cmd` directly — `spawn` rejects it with EINVAL
55
+ // because a batch file is not an executable image. It has to be handed to
56
+ // `cmd.exe /c`, which is also what lets the batch call `exit /b 3` and have
57
+ // that code reach us as the child's status.
58
+ const command = isWindows ? "cmd.exe" : "powershell";
59
+ const fileName = isWindows ? "remote-support.cmd" : "remote-support.ps1";
60
+ const file = scriptPath(config, fileName);
61
+ const spawnArgs = isWindows
62
+ ? ["/d", "/s", "/c", file, ...args]
63
+ : ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", file, ...args];
64
+ const child = spawn(command, spawnArgs, {
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ windowsHide: true,
67
+ });
68
+ let stdout = "";
69
+ let stderr = "";
70
+ child.stdout.setEncoding("utf8");
71
+ child.stderr.setEncoding("utf8");
72
+ child.stdout.on("data", (chunk: string) => {
73
+ stdout += chunk;
74
+ });
75
+ child.stderr.on("data", (chunk: string) => {
76
+ stderr += chunk;
77
+ });
78
+ const timer = setTimeout(() => child.kill(), LAUNCH_TIMEOUT_MS);
79
+ child.on("error", reject);
80
+ child.on("close", (status) => {
81
+ clearTimeout(timer);
82
+ resolve({ status: status ?? 1, stdout: `${stdout}${stderr}`.trim() });
83
+ });
84
+ return promise;
85
+ }
86
+
87
+ /**
88
+ * `where`/`which` lookup, mirroring what the script does itself so the route
89
+ * can answer `not_installed` with a clear message instead of a generic failure.
90
+ */
91
+ function toolAvailable(tool: RemoteSupportTool): Promise<boolean> {
92
+ const cached = installedCache.get(tool);
93
+ if (cached !== undefined) return Promise.resolve(cached);
94
+ const { promise, resolve } = Promise.withResolvers<boolean>();
95
+ const command = process.platform === "win32" ? "where" : "which";
96
+ const child = spawn(command, [tool], { stdio: "ignore", windowsHide: true });
97
+ child.on("error", () => {
98
+ installedCache.set(tool, false);
99
+ resolve(false);
100
+ });
101
+ child.on("close", (status) => {
102
+ const ok = status === 0;
103
+ installedCache.set(tool, ok);
104
+ resolve(ok);
105
+ });
106
+ return promise;
107
+ }
108
+
109
+ export interface RemoteSupportService {
110
+ status(): Promise<RemoteSupportStatus>;
111
+ start(tool: RemoteSupportTool, port?: number): Promise<RemoteSupportStatus>;
112
+ stop(tool?: RemoteSupportTool): Promise<RemoteSupportStatus>;
113
+ }
114
+
115
+ export function createRemoteSupportService(config: RemoteSupportServiceConfig): RemoteSupportService {
116
+ const port = config.port;
117
+
118
+ /**
119
+ * Install state for both publishers, cached per process.
120
+ *
121
+ * Every status carries it so the card can show "not installed" without the
122
+ * browser probing on its own — but the probe is `where`/`which`, which is a
123
+ * process spawn, so it is memoised here rather than run on each poll.
124
+ */
125
+ async function detectInstalled(): Promise<Readonly<Record<RemoteSupportTool, boolean>>> {
126
+ return {
127
+ cloudflared: await toolAvailable("cloudflared"),
128
+ tailscale: await toolAvailable("tailscale"),
129
+ } as const;
130
+ }
131
+
132
+ async function readStatus(blocker: RemoteSupportBlocker | null = null, message: string | null = null): Promise<RemoteSupportStatus> {
133
+ const result = await runScript(config, ["status"]);
134
+ const text = result.stdout;
135
+ const active: RemoteSupportTool | null = text.includes("cloudflared running")
136
+ ? "cloudflared"
137
+ : text.includes("tailscale running")
138
+ ? "tailscale"
139
+ : null;
140
+ let url: string | null = null;
141
+ if (active !== null) {
142
+ const urlResult = await runScript(config, ["url"]);
143
+ const candidate = urlResult.stdout.trim().split(/\s+/).find((line) => line.startsWith("https://"));
144
+ url = candidate ?? null;
145
+ }
146
+ const installed = await detectInstalled();
147
+ return { active, url, blocker, message, port, installed };
148
+ }
149
+
150
+ return {
151
+ async status() {
152
+ return await readStatus();
153
+ },
154
+
155
+ async start(tool, requestedPort) {
156
+ if (process.platform !== "win32") {
157
+ return {
158
+ active: null,
159
+ url: null,
160
+ blocker: "unsupported_platform",
161
+ message: "Remote support is currently scripted for Windows hosts only.",
162
+ port,
163
+ installed: { cloudflared: false, tailscale: false },
164
+ };
165
+ }
166
+ const launcher = scriptPath(config, "remote-support.cmd");
167
+ if (!existsSync(launcher)) {
168
+ return {
169
+ active: null,
170
+ url: null,
171
+ blocker: "launch_failed",
172
+ message: `Launcher not found: ${launcher}`,
173
+ port,
174
+ installed: await detectInstalled(),
175
+ };
176
+ }
177
+ if (!(await toolAvailable(tool))) {
178
+ return {
179
+ active: null,
180
+ url: null,
181
+ blocker: "not_installed",
182
+ message: REMOTE_SUPPORT_INSTALL_HINT[tool],
183
+ port,
184
+ installed: await detectInstalled(),
185
+ };
186
+ }
187
+ const target = requestedPort ?? port;
188
+ const result = await runScript(config, ["start", tool, String(target)]);
189
+ if (result.status !== 0 && !result.stdout.includes("started")) {
190
+ return {
191
+ active: null,
192
+ url: null,
193
+ blocker: "launch_failed",
194
+ message: result.stdout || `Failed to start ${tool}.`,
195
+ port,
196
+ installed: await detectInstalled(),
197
+ };
198
+ }
199
+ return await readStatus();
200
+ },
201
+
202
+ async stop(tool) {
203
+ await runScript(config, tool === undefined ? ["stop"] : ["stop", tool]);
204
+ return await readStatus();
205
+ },
206
+ };
207
+ }
@@ -18,9 +18,6 @@
18
18
  * only". A path test can, and it lives inside `gzipResponse` — the one place
19
19
  * that runs on every request.
20
20
  *
21
- * Consequence: `/v1/models` was previously compressed as a side effect of
22
- * that `hoc` propagation. It deliberately is not now.
23
- *
24
21
  * ## Why this is scoped to the console, not the proxy
25
22
  *
26
23
  * `/v1/*` is deliberately excluded, for two independent reasons:
@@ -51,8 +51,9 @@ export function classifyPoolConnectError(
51
51
  error: unknown,
52
52
  latencyMs: number,
53
53
  ): HealthCheckResult | undefined {
54
- // The agent throws a typed refusal carrying the status, so this no longer
55
- // re-parses the message text (which a wording change would silently break).
54
+ // The agent throws a typed refusal carrying the status, so this reads the
55
+ // status rather than re-parsing message text (which a wording change would
56
+ // silently break).
56
57
  if (!(error instanceof ProxyConnectRefusalError)) return undefined;
57
58
  if (error.statusCode !== 402 && error.statusCode !== 407) return undefined;
58
59
  return classifyPoolProbeResponse(poolId, { ok: false, status: error.statusCode }, latencyMs);