cartethyia 2.5.9 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/.env.example +15 -42
  2. package/README.md +11 -10
  3. package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  27. package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
  28. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
  38. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  39. package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
  40. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  41. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  42. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  43. package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
  44. package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  46. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  48. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
  50. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
  52. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
  54. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
  60. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
  62. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  66. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  68. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
  72. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
  74. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
  76. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  77. package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
  78. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
  80. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  82. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  86. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  92. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
  94. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  96. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
  98. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
  100. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
  102. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  105. package/dist/dashboard/index.html +1 -1
  106. package/dist/dashboard/index.html.gz +0 -0
  107. package/dist/dashboard/precompressed-assets.json +51 -51
  108. package/migrations/README.md +52 -52
  109. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  110. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  111. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  112. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  113. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  114. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  115. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  116. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  117. package/package.json +96 -97
  118. package/scripts/cli/admin.ts +126 -125
  119. package/scripts/cli/autostart.ts +237 -237
  120. package/scripts/cli/background.ts +120 -110
  121. package/scripts/cli/bin/carte.cjs +8 -8
  122. package/scripts/cli/bin/cartethyia.cjs +167 -167
  123. package/scripts/cli/console-menu.ts +308 -275
  124. package/scripts/cli/controller.ts +145 -0
  125. package/scripts/cli/doctor.ts +1 -1
  126. package/scripts/cli/env.ts +99 -99
  127. package/scripts/cli/gateway.ts +264 -261
  128. package/scripts/{health.ts → cli/health.ts} +4 -4
  129. package/scripts/cli/instance.ts +234 -224
  130. package/scripts/cli/main.ts +188 -164
  131. package/scripts/cli/platform.ts +64 -64
  132. package/scripts/cli/prompt.ts +275 -107
  133. package/scripts/cli/services.ts +88 -88
  134. package/scripts/cli/setup.ts +445 -445
  135. package/scripts/cli/start.ts +85 -82
  136. package/scripts/cli/startup-signal.ts +23 -23
  137. package/scripts/cli/telemetry.ts +70 -0
  138. package/scripts/cli/tray/index.ts +263 -263
  139. package/scripts/cli/tray/windows.ps1 +107 -107
  140. package/scripts/cli/tray.ts +192 -208
  141. package/scripts/cli/update.ts +39 -39
  142. package/scripts/cli/version.ts +67 -67
  143. package/scripts/cli/wordmark.ts +58 -58
  144. package/src/boot/app.ts +16 -1
  145. package/src/boot/dependencies.ts +128 -25
  146. package/src/config/package-paths.ts +24 -0
  147. package/src/config/resources.ts +137 -0
  148. package/src/config/runtime.ts +133 -76
  149. package/src/config/spec.ts +30 -29
  150. package/src/config/telemetry.ts +0 -25
  151. package/src/console/api-keys/bucket-spend.ts +14 -17
  152. package/src/console/api-keys/contracts.ts +410 -410
  153. package/src/console/api-keys/routes.ts +12 -0
  154. package/src/console/audit/store.ts +2 -3
  155. package/src/console/auth/setup-service.ts +8 -1
  156. package/src/console/backup/routes.ts +7 -0
  157. package/src/console/backup/service.ts +463 -463
  158. package/src/console/backup/store.ts +16 -11
  159. package/src/console/backup/validate.ts +1 -4
  160. package/src/console/cli-tools/fs-ops.ts +18 -2
  161. package/src/console/data-management/contracts.ts +1 -8
  162. package/src/console/data-management/operations.ts +61 -48
  163. package/src/console/data-management/routes.ts +4 -2
  164. package/src/console/domain-registration.ts +25 -3
  165. package/src/console/inferhub/client.ts +139 -0
  166. package/src/console/inferhub/key-store.ts +129 -0
  167. package/src/console/inferhub/normalize.ts +347 -0
  168. package/src/console/inferhub/routes.ts +411 -0
  169. package/src/console/inferhub/store.ts +109 -0
  170. package/src/console/inferhub/types.ts +183 -0
  171. package/src/console/observability/contracts.ts +2 -41
  172. package/src/console/observability/store.ts +176 -371
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/store.ts +23 -36
  175. package/src/console/quota/account-quota-shared.ts +52 -52
  176. package/src/console/quota/account-quota-tenant.ts +16 -24
  177. package/src/console/quota/refresh.ts +441 -441
  178. package/src/console/remote-support/service.ts +270 -114
  179. package/src/console/routing/pools/import-parse.ts +206 -206
  180. package/src/console/routing/pools/operations.ts +543 -543
  181. package/src/console/routing/pools/probe-verdict.ts +132 -132
  182. package/src/console/routing/pools/routes.ts +181 -181
  183. package/src/console/routing/pools/store.ts +1 -1
  184. package/src/console/settings/contracts.ts +42 -40
  185. package/src/console/settings/store.ts +25 -25
  186. package/src/console/share/share-stats.ts +22 -4
  187. package/src/console/share/share-usage.ts +30 -4
  188. package/src/console/shared/cursor.ts +15 -0
  189. package/src/console/updates/repository-updates.ts +40 -39
  190. package/src/console/updates/routes.ts +12 -0
  191. package/src/main.ts +13 -5
  192. package/src/network/pool/resolver.ts +1104 -1104
  193. package/src/observability/client-name.ts +22 -0
  194. package/src/observability/event-log.ts +5 -29
  195. package/src/observability/event-query.ts +690 -0
  196. package/src/observability/log-ring.ts +26 -2
  197. package/src/observability/log-sanitize.ts +118 -0
  198. package/src/observability/logger.ts +17 -13
  199. package/src/observability/metrics.ts +35 -0
  200. package/src/observability/payload-capture.ts +114 -41
  201. package/src/observability/payload-store.ts +249 -24
  202. package/src/observability/raw-event-writer.ts +118 -0
  203. package/src/observability/runtime-metrics.ts +71 -1
  204. package/src/observability/telemetry-buffer.ts +100 -17
  205. package/src/observability/telemetry-reconciliation.ts +417 -0
  206. package/src/persistence/boot.ts +20 -9
  207. package/src/persistence/db-pg.ts +13 -0
  208. package/src/persistence/db-sqlite.ts +569 -497
  209. package/src/persistence/dialect.ts +3 -2
  210. package/src/persistence/migrate.ts +181 -181
  211. package/src/persistence/schema.ts +1496 -1406
  212. package/src/persistence/shared-cache.ts +5 -1
  213. package/src/persistence/stores/api-key-store.ts +581 -581
  214. package/src/persistence/stores/telemetry-store.ts +124 -63
  215. package/src/protocol/reasoning-text.ts +30 -0
  216. package/src/protocol/registry.ts +2 -0
  217. package/src/protocol/response/chat.ts +33 -7
  218. package/src/protocol/response/codex.ts +14 -2
  219. package/src/protocol/response/messages.ts +5 -2
  220. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  221. package/src/providers/bundled-discovery-providers.ts +122 -122
  222. package/src/providers/default-registry.ts +14 -0
  223. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  224. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  225. package/src/providers/discovery/probe-phases.ts +43 -7
  226. package/src/providers/discovery/probing-service.ts +70 -32
  227. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  228. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  231. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  232. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  233. package/src/providers/integrations/buddy/capabilities.ts +4 -0
  234. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  235. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  236. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  237. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  238. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  239. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  240. package/src/providers/integrations/claude/claude.ts +3 -4
  241. package/src/providers/integrations/cline/cline.ts +94 -8
  242. package/src/providers/integrations/codex/codex.ts +61 -37
  243. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  244. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  245. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  246. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  247. package/src/providers/integrations/cursor/cursor.ts +29 -17
  248. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  249. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  250. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  251. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  252. package/src/providers/integrations/github/github.ts +3 -1
  253. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  254. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  255. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  256. package/src/providers/integrations/hermes.ts +7 -1
  257. package/src/providers/integrations/inferhub.ts +201 -73
  258. package/src/providers/integrations/kilo/kilo.ts +152 -152
  259. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  260. package/src/providers/integrations/opencode/provider.ts +2 -0
  261. package/src/providers/integrations/openrouter/provider.ts +129 -129
  262. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  263. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  264. package/src/providers/integrations/trae/trae-headers.ts +17 -48
  265. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  266. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  267. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  268. package/src/providers/integrations/trae/trae.ts +22 -24
  269. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  270. package/src/providers/integrations/zcode/oauth.ts +1 -1
  271. package/src/providers/operations/account-health-service.ts +605 -279
  272. package/src/providers/provider-metadata.ts +12 -1
  273. package/src/providers/provider-model-definition.ts +373 -373
  274. package/src/providers/provider-probe.ts +3 -0
  275. package/src/providers/quota/quota-contracts.ts +3 -7
  276. package/src/providers/search/search-provider.ts +2 -6
  277. package/src/security/admission/contracts.ts +10 -0
  278. package/src/security/admission/in-memory-store.ts +89 -14
  279. package/src/security/admission/lua.ts +191 -182
  280. package/src/security/admission/redis-store.ts +30 -0
  281. package/src/security/admission/service.ts +18 -0
  282. package/src/security/api-key-auth-cache.ts +96 -0
  283. package/src/security/api-key-auth.ts +11 -100
  284. package/src/security/client-router-fingerprint.ts +3 -7
  285. package/src/security/ip-boundary.ts +7 -3
  286. package/src/shared/coerce.ts +67 -0
  287. package/src/shared/hash.ts +6 -0
  288. package/src/shared/time.ts +7 -0
  289. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  290. package/src/transport/dispatch/attempt-loop.ts +103 -81
  291. package/src/transport/dispatch/capture-policy.ts +78 -0
  292. package/src/transport/dispatch/proxy-request.ts +4 -2
  293. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  294. package/src/transport/dispatch/streaming/stream.ts +19 -2
  295. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  296. package/src/transport/failure-policy.ts +27 -0
  297. package/src/transport/guards/abuse-guard.ts +2 -2
  298. package/src/transport/guards/auth-guard.ts +17 -0
  299. package/src/transport/guards/body-policy.ts +3 -2
  300. package/src/transport/guards/concurrency-guard.ts +89 -13
  301. package/src/transport/guards/early-rejection-capture.ts +43 -3
  302. package/src/transport/guards/error-lifecycle.ts +10 -2
  303. package/src/transport/guards/guard-errors.ts +23 -0
  304. package/src/transport/guards/headroom-guard.ts +41 -7
  305. package/src/transport/guards/pipeline.ts +24 -0
  306. package/src/transport/guards/request-context.ts +2 -1
  307. package/src/transport/routing/admission.ts +51 -9
  308. package/src/transport/routing/bounded-lru.ts +49 -0
  309. package/src/transport/routing/candidate-index.ts +88 -0
  310. package/src/transport/routing/reservations.ts +121 -7
  311. package/src/transport/routing/round-robin.ts +10 -22
  312. package/src/transport/routing/route-catalog.ts +26 -13
  313. package/src/transport/routing/route-model.ts +22 -2
  314. package/src/transport/routing/router.ts +53 -31
  315. package/src/transport/surface/chat/encode.ts +2 -3
  316. package/src/transport/surface/chat/parse.ts +2 -1
  317. package/src/transport/surface/dialects.ts +11 -3
  318. package/src/transport/surface/messages/encode.ts +2 -1
  319. package/src/transport/surface/messages/parse.ts +3 -1
  320. package/src/transport/surface/messages/stream.ts +2 -6
  321. package/src/workers/maintenance-lease.ts +200 -0
  322. package/src/workers/scheduler.ts +101 -1
  323. package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
  324. package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
  325. package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
  326. package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
  327. package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
  328. package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
  329. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
  330. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
  331. package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
  332. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  333. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
  334. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
  335. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
  336. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
  337. package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
  338. package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
  339. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
  340. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
  341. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
  342. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
  343. package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
  344. package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
  345. package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
  346. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  347. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  348. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  349. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  350. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  351. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  352. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  353. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  354. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  355. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  356. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  357. package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
  358. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
  360. package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
  361. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  363. package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  365. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  367. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  368. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  369. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  370. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  371. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  372. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  373. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  374. package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
  375. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  376. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  377. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  378. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  379. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  380. package/scripts/build/aot.ts +0 -143
  381. package/scripts/build/build.ts +0 -24
  382. package/scripts/build/compile.ts +0 -88
  383. package/scripts/build/restart.ts +0 -120
  384. package/scripts/build/start.ts +0 -25
  385. package/src/observability/event-compaction.ts +0 -317
  386. package/src/observability/event-day-index.ts +0 -440
@@ -1,263 +1,263 @@
1
- import { spawn, type ChildProcess } from "node:child_process";
2
- import { createInterface } from "node:readline";
3
- import SysTray, { type MenuItem } from "systray2";
4
- import { resolvePackagePath } from "../../../src/config/package-paths";
5
-
6
- /**
7
- * A row as systray2 holds it. The helper numbers the rows itself and reports
8
- * that number back on a click and on an update, but it is absent from the
9
- * package's typings.
10
- */
11
- type IdentifiedItem = MenuItem & { readonly __id?: number };
12
-
13
- /**
14
- * One tray row. `action` rows report their id on click; separators are drawn
15
- * but never fire.
16
- */
17
- export type TrayItem =
18
- | { readonly kind: "action"; readonly id: string; readonly title: string; readonly enabled?: boolean }
19
- | { readonly kind: "separator" };
20
-
21
- export interface TrayOptions {
22
- readonly iconPath: string;
23
- readonly tooltip: string;
24
- readonly items: readonly TrayItem[];
25
- readonly onSelect: (id: string) => void;
26
- readonly onExit: () => void;
27
- }
28
-
29
- export interface Tray {
30
- /** Resolves once the icon is on screen and clicks can arrive. */
31
- ready(): Promise<void>;
32
- /** Replaces the title and enabled state of an existing row. */
33
- update(id: string, title: string, enabled?: boolean): void;
34
- setTooltip(text: string): void;
35
- /** Removes the icon. Idempotent. */
36
- close(): Promise<void>;
37
- }
38
-
39
- /**
40
- * Windows tray: a PowerShell NotifyIcon driven over JSON lines.
41
- *
42
- * `systray2` on Windows leaves the spawning process attached to a console, so
43
- * Windows Terminal keeps an empty tab open for the tray's whole lifetime — the
44
- * window cannot be hidden from inside the process because it is owned by the
45
- * terminal, and `MainWindowHandle` reads 0 so it cannot be found and closed
46
- * either. A PowerShell NotifyIcon started with `-WindowStyle Hidden` owns no
47
- * console at all, which is the only arrangement that shows an icon and no
48
- * window.
49
- */
50
- class WindowsTray implements Tray {
51
- readonly #process: ChildProcess;
52
- readonly #onSelect: (id: string) => void;
53
- readonly #onExit: () => void;
54
- readonly #index = new Map<string, number>();
55
- #ready: Promise<void>;
56
- #closed = false;
57
-
58
- constructor(options: TrayOptions) {
59
- this.#onSelect = options.onSelect;
60
- this.#onExit = options.onExit;
61
-
62
- this.#process = spawn(
63
- "powershell.exe",
64
- [
65
- "-NoProfile",
66
- "-NonInteractive",
67
- "-ExecutionPolicy",
68
- "Bypass",
69
- "-WindowStyle",
70
- "Hidden",
71
- "-InputFormat",
72
- "Text",
73
- "-OutputFormat",
74
- "Text",
75
- "-File",
76
- resolvePackagePath("scripts", "cli", "tray", "windows.ps1"),
77
- "-IconPath",
78
- options.iconPath,
79
- "-Tooltip",
80
- options.tooltip,
81
- ],
82
- { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] },
83
- );
84
-
85
- const { promise, resolve, reject } = Promise.withResolvers<void>();
86
- this.#ready = promise;
87
-
88
- const lines = createInterface({ input: this.#process.stdout! });
89
- lines.on("line", (line) => {
90
- let event: unknown;
91
- try {
92
- event = JSON.parse(line);
93
- } catch {
94
- return;
95
- }
96
- if (event === null || typeof event !== "object" || !("type" in event)) return;
97
- if (event.type === "ready") {
98
- resolve();
99
- return;
100
- }
101
- if (event.type === "click" && "index" in event && typeof event.index === "number") {
102
- for (const [id, index] of this.#index) {
103
- if (index === event.index) {
104
- this.#onSelect(id);
105
- return;
106
- }
107
- }
108
- }
109
- });
110
-
111
- this.#process.once("error", reject);
112
- this.#process.once("exit", () => {
113
- if (!this.#closed) this.#onExit();
114
- reject(new Error("the tray process exited before it was ready"));
115
- });
116
-
117
- // Rows are added after "started": the script creates its NotifyIcon first
118
- // and would drop commands that arrive before it is listening.
119
- let index = 0;
120
- for (const item of options.items) {
121
- if (item.kind === "separator") {
122
- this.#send({ action: "add-separator", index });
123
- index += 1;
124
- continue;
125
- }
126
- this.#index.set(item.id, index);
127
- this.#send({ action: "add-item", index, title: item.title, enabled: item.enabled ?? true });
128
- index += 1;
129
- }
130
- this.#send({ action: "ready" });
131
- }
132
-
133
- #send(command: Record<string, unknown>): void {
134
- if (this.#closed || !this.#process.stdin?.writable) return;
135
- this.#process.stdin.write(`${JSON.stringify(command)}\n`, "utf8");
136
- }
137
-
138
- ready(): Promise<void> {
139
- return this.#ready;
140
- }
141
-
142
- update(id: string, title: string, enabled = true): void {
143
- const index = this.#index.get(id);
144
- if (index === undefined) return;
145
- this.#send({ action: "update-item", index, title, enabled });
146
- }
147
-
148
- setTooltip(text: string): void {
149
- this.#send({ action: "set-tooltip", text });
150
- }
151
-
152
- async close(): Promise<void> {
153
- if (this.#closed) return;
154
- this.#closed = true;
155
- this.#send({ action: "kill" });
156
- await new Promise<void>((resolve) => {
157
- const timer = setTimeout(() => {
158
- this.#process.kill();
159
- resolve();
160
- }, 1_500);
161
- this.#process.once("exit", () => {
162
- clearTimeout(timer);
163
- resolve();
164
- });
165
- });
166
- }
167
- }
168
-
169
- /**
170
- * macOS and Linux tray: `systray2` runs its own helper, and neither platform
171
- * attaches a console window to it, so the bundled tray is used as-is.
172
- *
173
- * Clicks arrive only through `onClick`; a menu built without a listener shows
174
- * every row and reports none of them, which is why the callbacks are wired
175
- * here rather than left to the default.
176
- */
177
- class UnixTray implements Tray {
178
- readonly #tray: SysTray;
179
- readonly #onSelect: (id: string) => void;
180
- readonly #onExit: () => void;
181
- /** Row objects kept by reference: a click hands back the object, not the id. */
182
- readonly #items = new Map<string, IdentifiedItem>();
183
- #closed = false;
184
-
185
- constructor(options: TrayOptions) {
186
- this.#onSelect = options.onSelect;
187
- this.#onExit = options.onExit;
188
- const items = options.items.map((item) => {
189
- if (item.kind === "separator") return SysTray.separator;
190
- const menuItem: IdentifiedItem = {
191
- title: item.title,
192
- tooltip: item.title,
193
- enabled: item.enabled ?? true,
194
- };
195
- this.#items.set(item.id, menuItem);
196
- return menuItem;
197
- });
198
- this.#tray = new SysTray({
199
- menu: {
200
- icon: options.iconPath,
201
- title: "Cartethyia",
202
- tooltip: options.tooltip,
203
- items,
204
- },
205
- copyDir: true,
206
- });
207
- }
208
-
209
- async ready(): Promise<void> {
210
- await this.#tray.ready();
211
- // Registered after `ready()`: the helper process does not exist before
212
- // that, and `onExit` attaches to it directly.
213
- this.#tray.onExit(() => {
214
- if (!this.#closed) this.#onExit();
215
- });
216
- await this.#tray.onClick((action) => {
217
- for (const [id, item] of this.#items) {
218
- // Identity first: `onClick` merges the clicked row into the object this
219
- // class built, so a match needs no assumptions about the numbering.
220
- if (item === action.item || item.__id === action.__id) {
221
- this.#onSelect(id);
222
- return;
223
- }
224
- }
225
- });
226
- }
227
-
228
- update(id: string, title: string, enabled = true): void {
229
- const item = this.#items.get(id);
230
- // An update sent before the helper reports ready carries no row number, and
231
- // systray2 then rewrites the first row instead of this one.
232
- if (item?.__id === undefined) return;
233
- const next: IdentifiedItem = { title, tooltip: title, enabled, __id: item.__id };
234
- void this.#tray.sendAction({ type: "update-item", item: next });
235
- }
236
-
237
- setTooltip(text: string): void {
238
- // systray2 has no tooltip-only action; the tooltip travels with the title,
239
- // which `update()` already sends.
240
- void text;
241
- }
242
-
243
- async close(): Promise<void> {
244
- if (this.#closed) return;
245
- this.#closed = true;
246
- const helper = this.#tray.process;
247
- // `kill()` settles on the helper's `exit` event, so a helper that already
248
- // exited would leave it pending forever and hang the shutdown after it.
249
- if (helper.exitCode !== null || helper.signalCode !== null) return;
250
- try {
251
- // `false`: systray2 otherwise calls `process.exit(0)` when the helper
252
- // exits, which discards the exit code this close was given.
253
- await this.#tray.kill(false);
254
- } catch {
255
- // The helper may already be gone.
256
- }
257
- }
258
- }
259
-
260
- /** Creates the tray for this platform. */
261
- export function createTray(options: TrayOptions): Tray {
262
- return process.platform === "win32" ? new WindowsTray(options) : new UnixTray(options);
263
- }
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+ import SysTray, { type MenuItem } from "systray2";
4
+ import { resolvePackagePath } from "../../../src/config/package-paths";
5
+
6
+ /**
7
+ * A row as systray2 holds it. The helper numbers the rows itself and reports
8
+ * that number back on a click and on an update, but it is absent from the
9
+ * package's typings.
10
+ */
11
+ type IdentifiedItem = MenuItem & { readonly __id?: number };
12
+
13
+ /**
14
+ * One tray row. `action` rows report their id on click; separators are drawn
15
+ * but never fire.
16
+ */
17
+ export type TrayItem =
18
+ | { readonly kind: "action"; readonly id: string; readonly title: string; readonly enabled?: boolean }
19
+ | { readonly kind: "separator" };
20
+
21
+ export interface TrayOptions {
22
+ readonly iconPath: string;
23
+ readonly tooltip: string;
24
+ readonly items: readonly TrayItem[];
25
+ readonly onSelect: (id: string) => void;
26
+ readonly onExit: () => void;
27
+ }
28
+
29
+ export interface Tray {
30
+ /** Resolves once the icon is on screen and clicks can arrive. */
31
+ ready(): Promise<void>;
32
+ /** Replaces the title and enabled state of an existing row. */
33
+ update(id: string, title: string, enabled?: boolean): void;
34
+ setTooltip(text: string): void;
35
+ /** Removes the icon. Idempotent. */
36
+ close(): Promise<void>;
37
+ }
38
+
39
+ /**
40
+ * Windows tray: a PowerShell NotifyIcon driven over JSON lines.
41
+ *
42
+ * `systray2` on Windows leaves the spawning process attached to a console, so
43
+ * Windows Terminal keeps an empty tab open for the tray's whole lifetime — the
44
+ * window cannot be hidden from inside the process because it is owned by the
45
+ * terminal, and `MainWindowHandle` reads 0 so it cannot be found and closed
46
+ * either. A PowerShell NotifyIcon started with `-WindowStyle Hidden` owns no
47
+ * console at all, which is the only arrangement that shows an icon and no
48
+ * window.
49
+ */
50
+ class WindowsTray implements Tray {
51
+ readonly #process: ChildProcess;
52
+ readonly #onSelect: (id: string) => void;
53
+ readonly #onExit: () => void;
54
+ readonly #index = new Map<string, number>();
55
+ #ready: Promise<void>;
56
+ #closed = false;
57
+
58
+ constructor(options: TrayOptions) {
59
+ this.#onSelect = options.onSelect;
60
+ this.#onExit = options.onExit;
61
+
62
+ this.#process = spawn(
63
+ "powershell.exe",
64
+ [
65
+ "-NoProfile",
66
+ "-NonInteractive",
67
+ "-ExecutionPolicy",
68
+ "Bypass",
69
+ "-WindowStyle",
70
+ "Hidden",
71
+ "-InputFormat",
72
+ "Text",
73
+ "-OutputFormat",
74
+ "Text",
75
+ "-File",
76
+ resolvePackagePath("scripts", "cli", "tray", "windows.ps1"),
77
+ "-IconPath",
78
+ options.iconPath,
79
+ "-Tooltip",
80
+ options.tooltip,
81
+ ],
82
+ { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] },
83
+ );
84
+
85
+ const { promise, resolve, reject } = Promise.withResolvers<void>();
86
+ this.#ready = promise;
87
+
88
+ const lines = createInterface({ input: this.#process.stdout! });
89
+ lines.on("line", (line) => {
90
+ let event: unknown;
91
+ try {
92
+ event = JSON.parse(line);
93
+ } catch {
94
+ return;
95
+ }
96
+ if (event === null || typeof event !== "object" || !("type" in event)) return;
97
+ if (event.type === "ready") {
98
+ resolve();
99
+ return;
100
+ }
101
+ if (event.type === "click" && "index" in event && typeof event.index === "number") {
102
+ for (const [id, index] of this.#index) {
103
+ if (index === event.index) {
104
+ this.#onSelect(id);
105
+ return;
106
+ }
107
+ }
108
+ }
109
+ });
110
+
111
+ this.#process.once("error", reject);
112
+ this.#process.once("exit", () => {
113
+ if (!this.#closed) this.#onExit();
114
+ reject(new Error("the tray process exited before it was ready"));
115
+ });
116
+
117
+ // Rows are added after "started": the script creates its NotifyIcon first
118
+ // and would drop commands that arrive before it is listening.
119
+ let index = 0;
120
+ for (const item of options.items) {
121
+ if (item.kind === "separator") {
122
+ this.#send({ action: "add-separator", index });
123
+ index += 1;
124
+ continue;
125
+ }
126
+ this.#index.set(item.id, index);
127
+ this.#send({ action: "add-item", index, title: item.title, enabled: item.enabled ?? true });
128
+ index += 1;
129
+ }
130
+ this.#send({ action: "ready" });
131
+ }
132
+
133
+ #send(command: Record<string, unknown>): void {
134
+ if (this.#closed || !this.#process.stdin?.writable) return;
135
+ this.#process.stdin.write(`${JSON.stringify(command)}\n`, "utf8");
136
+ }
137
+
138
+ ready(): Promise<void> {
139
+ return this.#ready;
140
+ }
141
+
142
+ update(id: string, title: string, enabled = true): void {
143
+ const index = this.#index.get(id);
144
+ if (index === undefined) return;
145
+ this.#send({ action: "update-item", index, title, enabled });
146
+ }
147
+
148
+ setTooltip(text: string): void {
149
+ this.#send({ action: "set-tooltip", text });
150
+ }
151
+
152
+ async close(): Promise<void> {
153
+ if (this.#closed) return;
154
+ this.#closed = true;
155
+ this.#send({ action: "kill" });
156
+ await new Promise<void>((resolve) => {
157
+ const timer = setTimeout(() => {
158
+ this.#process.kill();
159
+ resolve();
160
+ }, 1_500);
161
+ this.#process.once("exit", () => {
162
+ clearTimeout(timer);
163
+ resolve();
164
+ });
165
+ });
166
+ }
167
+ }
168
+
169
+ /**
170
+ * macOS and Linux tray: `systray2` runs its own helper, and neither platform
171
+ * attaches a console window to it, so the bundled tray is used as-is.
172
+ *
173
+ * Clicks arrive only through `onClick`; a menu built without a listener shows
174
+ * every row and reports none of them, which is why the callbacks are wired
175
+ * here rather than left to the default.
176
+ */
177
+ class UnixTray implements Tray {
178
+ readonly #tray: SysTray;
179
+ readonly #onSelect: (id: string) => void;
180
+ readonly #onExit: () => void;
181
+ /** Row objects kept by reference: a click hands back the object, not the id. */
182
+ readonly #items = new Map<string, IdentifiedItem>();
183
+ #closed = false;
184
+
185
+ constructor(options: TrayOptions) {
186
+ this.#onSelect = options.onSelect;
187
+ this.#onExit = options.onExit;
188
+ const items = options.items.map((item) => {
189
+ if (item.kind === "separator") return SysTray.separator;
190
+ const menuItem: IdentifiedItem = {
191
+ title: item.title,
192
+ tooltip: item.title,
193
+ enabled: item.enabled ?? true,
194
+ };
195
+ this.#items.set(item.id, menuItem);
196
+ return menuItem;
197
+ });
198
+ this.#tray = new SysTray({
199
+ menu: {
200
+ icon: options.iconPath,
201
+ title: "Cartethyia",
202
+ tooltip: options.tooltip,
203
+ items,
204
+ },
205
+ copyDir: true,
206
+ });
207
+ }
208
+
209
+ async ready(): Promise<void> {
210
+ await this.#tray.ready();
211
+ // Registered after `ready()`: the helper process does not exist before
212
+ // that, and `onExit` attaches to it directly.
213
+ this.#tray.onExit(() => {
214
+ if (!this.#closed) this.#onExit();
215
+ });
216
+ await this.#tray.onClick((action) => {
217
+ for (const [id, item] of this.#items) {
218
+ // Identity first: `onClick` merges the clicked row into the object this
219
+ // class built, so a match needs no assumptions about the numbering.
220
+ if (item === action.item || item.__id === action.__id) {
221
+ this.#onSelect(id);
222
+ return;
223
+ }
224
+ }
225
+ });
226
+ }
227
+
228
+ update(id: string, title: string, enabled = true): void {
229
+ const item = this.#items.get(id);
230
+ // An update sent before the helper reports ready carries no row number, and
231
+ // systray2 then rewrites the first row instead of this one.
232
+ if (item?.__id === undefined) return;
233
+ const next: IdentifiedItem = { title, tooltip: title, enabled, __id: item.__id };
234
+ void this.#tray.sendAction({ type: "update-item", item: next });
235
+ }
236
+
237
+ setTooltip(text: string): void {
238
+ // systray2 has no tooltip-only action; the tooltip travels with the title,
239
+ // which `update()` already sends.
240
+ void text;
241
+ }
242
+
243
+ async close(): Promise<void> {
244
+ if (this.#closed) return;
245
+ this.#closed = true;
246
+ const helper = this.#tray.process;
247
+ // `kill()` settles on the helper's `exit` event, so a helper that already
248
+ // exited would leave it pending forever and hang the shutdown after it.
249
+ if (helper.exitCode !== null || helper.signalCode !== null) return;
250
+ try {
251
+ // `false`: systray2 otherwise calls `process.exit(0)` when the helper
252
+ // exits, which discards the exit code this close was given.
253
+ await this.#tray.kill(false);
254
+ } catch {
255
+ // The helper may already be gone.
256
+ }
257
+ }
258
+ }
259
+
260
+ /** Creates the tray for this platform. */
261
+ export function createTray(options: TrayOptions): Tray {
262
+ return process.platform === "win32" ? new WindowsTray(options) : new UnixTray(options);
263
+ }