@xopcai/xopc 0.0.88 → 0.0.90

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 (275) hide show
  1. package/README.md +8 -1
  2. package/README.zh-CN.md +8 -1
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/telegram/xopc.extension.json +1 -1
  5. package/dist/gateway/static/root/assets/agents-cPvvYLXo.js +222 -0
  6. package/dist/gateway/static/root/assets/apps-page-Bk1_P5FJ.js +1 -0
  7. package/dist/gateway/static/root/assets/channels-settings-CZoeQwHz.js +1 -0
  8. package/dist/gateway/static/root/assets/{channels-status-swr-DIsl75Y3.js → channels-status-swr-BrtH2VzC.js} +1 -1
  9. package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +1 -0
  10. package/dist/gateway/static/root/assets/cron-api-CyqbgfHM.js +1 -0
  11. package/dist/gateway/static/root/assets/cron-dreaming-jobs-Ip703-qM.js +2 -0
  12. package/dist/gateway/static/root/assets/cron-page-BpLdiQN8.js +1 -0
  13. package/dist/gateway/static/root/assets/dist-BpAiK86n.js +1 -0
  14. package/dist/gateway/static/root/assets/{extension-debug-page-BVJohZoZ.js → extension-debug-page-D6Ak0STa.js} +1 -1
  15. package/dist/gateway/static/root/assets/{extension-page-BT2tmElC.js → extension-page-Q0P3d6DW.js} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-settings-page-BSS47c2j.js → extension-settings-page-CL55LwU_.js} +1 -1
  17. package/dist/gateway/static/root/assets/eye-DAfL1U7M.js +1 -0
  18. package/dist/gateway/static/root/assets/{fetch-BaFNUtkE.js → fetch-Dqa9iTWl.js} +1 -1
  19. package/dist/gateway/static/root/assets/{field-primitives-QwYEq6Hz.js → field-primitives-HUR6JElP.js} +1 -1
  20. package/dist/gateway/static/root/assets/{heartbeat-config-api-BVSidEDJ.js → heartbeat-config-api-DusckjUX.js} +1 -1
  21. package/dist/gateway/static/root/assets/{index-qNrVJp-y.js → index-BYcGfwcE.js} +97 -97
  22. package/dist/gateway/static/root/assets/index-V7MQ7834.css +1 -0
  23. package/dist/gateway/static/root/assets/{logs-page-DDonPVLn.js → logs-page-_HcZ2fgK.js} +1 -1
  24. package/dist/gateway/static/root/assets/sessions-page-iezSMjho.js +1 -0
  25. package/dist/gateway/static/root/assets/{settings-form-section-B8N3A3Zo.js → settings-form-section-a0qGVOlr.js} +1 -1
  26. package/dist/gateway/static/root/assets/settings-page-C9_nYQwM.js +3 -0
  27. package/dist/gateway/static/root/assets/{share-preview-page-Q7KqkO-u.js → share-preview-page-DExl7CJy.js} +1 -1
  28. package/dist/gateway/static/root/assets/skills-page-BlgGD93t.js +2 -0
  29. package/dist/gateway/static/root/assets/{theme-store-BbRc5ugR.js → theme-store-C0Ehmdo5.js} +1 -1
  30. package/dist/gateway/static/root/assets/url-fxyYANfA.js +3 -0
  31. package/dist/gateway/static/root/assets/{utils-CxDGduqK.js → utils-DRQryzdn.js} +1 -1
  32. package/dist/gateway/static/root/assets/voice-api-key-field-D0viACE2.js +1 -0
  33. package/dist/gateway/static/root/assets/workflow-page.utils-DnG8JBhV.js +1 -0
  34. package/dist/gateway/static/root/assets/workflows-page-BvMobnJP.js +27 -0
  35. package/dist/gateway/static/root/index.html +7 -6
  36. package/dist/package.js +1 -1
  37. package/dist/src/agent/agent-manager.d.ts +2 -0
  38. package/dist/src/agent/agent-manager.js +1 -0
  39. package/dist/src/agent/agent-manager.js.map +1 -1
  40. package/dist/src/agent/service.js +2 -1
  41. package/dist/src/agent/service.js.map +1 -1
  42. package/dist/src/agent/service.types.d.ts +3 -1
  43. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js +20 -18
  44. package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js.map +1 -1
  45. package/dist/src/agent/tools/cronjob-tool.d.ts +6 -0
  46. package/dist/src/agent/tools/cronjob-tool.js +76 -10
  47. package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
  48. package/dist/src/agent/tools/edit.d.ts +5 -1
  49. package/dist/src/agent/tools/edit.js +7 -5
  50. package/dist/src/agent/tools/edit.js.map +1 -1
  51. package/dist/src/agent/tools/factory.d.ts +3 -0
  52. package/dist/src/agent/tools/factory.js +4 -25
  53. package/dist/src/agent/tools/factory.js.map +1 -1
  54. package/dist/src/agent/tools/workflow-tool.d.ts +6 -28
  55. package/dist/src/agent/tools/workflow-tool.js +60 -260
  56. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  57. package/dist/src/agent/tools/write.d.ts +5 -1
  58. package/dist/src/agent/tools/write.js +7 -5
  59. package/dist/src/agent/tools/write.js.map +1 -1
  60. package/dist/src/agent/workflow/agent-progress.js +2 -0
  61. package/dist/src/agent/workflow/agent-progress.js.map +1 -1
  62. package/dist/src/agent/workflow/builtins/client-proposal.d.ts +12 -0
  63. package/dist/src/agent/workflow/builtins/client-proposal.js +155 -0
  64. package/dist/src/agent/workflow/builtins/client-proposal.js.map +1 -0
  65. package/dist/src/agent/workflow/builtins/competitor-scan.d.ts +12 -0
  66. package/dist/src/agent/workflow/builtins/competitor-scan.js +150 -0
  67. package/dist/src/agent/workflow/builtins/competitor-scan.js.map +1 -0
  68. package/dist/src/agent/workflow/builtins/content-draft.d.ts +13 -0
  69. package/dist/src/agent/workflow/builtins/content-draft.js +146 -0
  70. package/dist/src/agent/workflow/builtins/content-draft.js.map +1 -0
  71. package/dist/src/agent/workflow/builtins/content-repurpose.d.ts +11 -0
  72. package/dist/src/agent/workflow/builtins/content-repurpose.js +137 -0
  73. package/dist/src/agent/workflow/builtins/content-repurpose.js.map +1 -0
  74. package/dist/src/agent/workflow/builtins/decision-compare.d.ts +13 -0
  75. package/dist/src/agent/workflow/builtins/decision-compare.js +173 -0
  76. package/dist/src/agent/workflow/builtins/decision-compare.js.map +1 -0
  77. package/dist/src/agent/workflow/builtins/inbox-triage.d.ts +11 -0
  78. package/dist/src/agent/workflow/builtins/inbox-triage.js +148 -0
  79. package/dist/src/agent/workflow/builtins/inbox-triage.js.map +1 -0
  80. package/dist/src/agent/workflow/builtins/index.d.ts +10 -1
  81. package/dist/src/agent/workflow/builtins/index.js +46 -1
  82. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  83. package/dist/src/agent/workflow/builtins/meeting-prep.d.ts +12 -0
  84. package/dist/src/agent/workflow/builtins/meeting-prep.js +144 -0
  85. package/dist/src/agent/workflow/builtins/meeting-prep.js.map +1 -0
  86. package/dist/src/agent/workflow/builtins/offer-design.d.ts +12 -0
  87. package/dist/src/agent/workflow/builtins/offer-design.js +161 -0
  88. package/dist/src/agent/workflow/builtins/offer-design.js.map +1 -0
  89. package/dist/src/agent/workflow/builtins/weekly-review.d.ts +12 -0
  90. package/dist/src/agent/workflow/builtins/weekly-review.js +131 -0
  91. package/dist/src/agent/workflow/builtins/weekly-review.js.map +1 -0
  92. package/dist/src/agent/workflow/step-labels.js +2 -2
  93. package/dist/src/agent/workflow/step-labels.js.map +1 -1
  94. package/dist/src/agent/workflow/subagent-runner.js +3 -1
  95. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  96. package/dist/src/agent/workflow/types.d.ts +4 -0
  97. package/dist/src/agent/workflow/workflow-child-tools.d.ts +4 -0
  98. package/dist/src/agent/workflow/workflow-child-tools.js +21 -0
  99. package/dist/src/agent/workflow/workflow-child-tools.js.map +1 -0
  100. package/dist/src/auth/credentials.d.ts +14 -2
  101. package/dist/src/auth/credentials.js +38 -13
  102. package/dist/src/auth/credentials.js.map +1 -1
  103. package/dist/src/auth/oauth/types.d.ts +16 -0
  104. package/dist/src/chat-commands/agent-edit.d.ts +4 -0
  105. package/dist/src/chat-commands/agent-edit.js +136 -0
  106. package/dist/src/chat-commands/agent-edit.js.map +1 -0
  107. package/dist/src/chat-commands/index.d.ts +1 -0
  108. package/dist/src/chat-commands/index.js +3 -1
  109. package/dist/src/chat-commands/index.js.map +1 -1
  110. package/dist/src/cli/bin.js +2 -0
  111. package/dist/src/cli/bin.js.map +1 -1
  112. package/dist/src/cli/commands/auth.js +6 -0
  113. package/dist/src/cli/commands/auth.js.map +1 -1
  114. package/dist/src/cli/commands/cron.js +42 -3
  115. package/dist/src/cli/commands/cron.js.map +1 -1
  116. package/dist/src/cli/commands/doctor/checks/session-integrity.js +79 -56
  117. package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
  118. package/dist/src/cli/commands/onboard/model.js +6 -0
  119. package/dist/src/cli/commands/onboard/model.js.map +1 -1
  120. package/dist/src/cli/commands/update.js +86 -79
  121. package/dist/src/cli/commands/update.js.map +1 -1
  122. package/dist/src/commands/agents.config.d.ts +3 -2
  123. package/dist/src/commands/agents.config.js +5 -2
  124. package/dist/src/commands/agents.config.js.map +1 -1
  125. package/dist/src/config/agent-typed-models.d.ts +2 -7
  126. package/dist/src/config/agent-typed-models.js +3 -14
  127. package/dist/src/config/agent-typed-models.js.map +1 -1
  128. package/dist/src/config/localized-text.d.ts +6 -0
  129. package/dist/src/config/localized-text.js +42 -0
  130. package/dist/src/config/localized-text.js.map +1 -0
  131. package/dist/src/config/models-json.d.ts +6 -6
  132. package/dist/src/config/schema.d.ts +6 -21
  133. package/dist/src/config/schema.js +4 -4
  134. package/dist/src/config/schema.js.map +1 -1
  135. package/dist/src/cron/executor.d.ts +4 -0
  136. package/dist/src/cron/executor.js +169 -5
  137. package/dist/src/cron/executor.js.map +1 -1
  138. package/dist/src/cron/job-content.js +2 -1
  139. package/dist/src/cron/job-content.js.map +1 -1
  140. package/dist/src/cron/types.d.ts +28 -1
  141. package/dist/src/cron/validation.d.ts +80 -0
  142. package/dist/src/cron/validation.js +30 -4
  143. package/dist/src/cron/validation.js.map +1 -1
  144. package/dist/src/cron/workflow-run-completion.d.ts +23 -0
  145. package/dist/src/cron/workflow-run-completion.js +72 -0
  146. package/dist/src/cron/workflow-run-completion.js.map +1 -0
  147. package/dist/src/extensions/update.d.ts +51 -0
  148. package/dist/src/extensions/update.js +260 -0
  149. package/dist/src/extensions/update.js.map +1 -0
  150. package/dist/src/gateway/agents-admin.d.ts +15 -8
  151. package/dist/src/gateway/agents-admin.js +77 -28
  152. package/dist/src/gateway/agents-admin.js.map +1 -1
  153. package/dist/src/gateway/gateway-workflow-host.types.d.ts +17 -0
  154. package/dist/src/gateway/gateway-workflow-host.types.js +1 -0
  155. package/dist/src/gateway/heartbeat/service.js +1 -1
  156. package/dist/src/gateway/hono/lib/config-payload.d.ts +5 -0
  157. package/dist/src/gateway/hono/lib/config-payload.js +2 -1
  158. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  159. package/dist/src/gateway/hono/middleware/auth.d.ts +2 -0
  160. package/dist/src/gateway/hono/middleware/auth.js +12 -7
  161. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  162. package/dist/src/gateway/hono/oauth-async.js +40 -15
  163. package/dist/src/gateway/hono/oauth-async.js.map +1 -1
  164. package/dist/src/gateway/hono/oauth.js +31 -6
  165. package/dist/src/gateway/hono/oauth.js.map +1 -1
  166. package/dist/src/gateway/hono/routes/agents.js +55 -12
  167. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  168. package/dist/src/gateway/hono/routes/config-patch/agents.js +1 -1
  169. package/dist/src/gateway/hono/routes/models.js +11 -5
  170. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  171. package/dist/src/gateway/hono/routes/update.js +55 -107
  172. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  173. package/dist/src/gateway/hono/routes/workflows.js +72 -191
  174. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  175. package/dist/src/gateway/server.js +2 -0
  176. package/dist/src/gateway/server.js.map +1 -1
  177. package/dist/src/gateway/service.d.ts +5 -0
  178. package/dist/src/gateway/service.js +24 -3
  179. package/dist/src/gateway/service.js.map +1 -1
  180. package/dist/src/heartbeat/index.js +1 -1
  181. package/dist/src/infra/brew.d.ts +4 -0
  182. package/dist/src/infra/brew.js +20 -0
  183. package/dist/src/infra/brew.js.map +1 -0
  184. package/dist/src/infra/package-json.d.ts +2 -0
  185. package/dist/src/infra/package-json.js +23 -0
  186. package/dist/src/infra/package-json.js.map +1 -0
  187. package/dist/src/infra/package-update-steps.d.ts +35 -0
  188. package/dist/src/infra/package-update-steps.js +304 -0
  189. package/dist/src/infra/package-update-steps.js.map +1 -0
  190. package/dist/src/infra/path-env.d.ts +11 -0
  191. package/dist/src/infra/path-env.js +90 -0
  192. package/dist/src/infra/path-env.js.map +1 -0
  193. package/dist/src/infra/path-prepend.d.ts +7 -0
  194. package/dist/src/infra/path-prepend.js +44 -0
  195. package/dist/src/infra/path-prepend.js.map +1 -0
  196. package/dist/src/infra/stable-node-path.d.ts +2 -0
  197. package/dist/src/infra/stable-node-path.js +28 -0
  198. package/dist/src/infra/stable-node-path.js.map +1 -0
  199. package/dist/src/infra/update-global.d.ts +30 -23
  200. package/dist/src/infra/update-global.js +113 -64
  201. package/dist/src/infra/update-global.js.map +1 -1
  202. package/dist/src/infra/update-log.d.ts +1 -0
  203. package/dist/src/infra/update-log.js +12 -0
  204. package/dist/src/infra/update-log.js.map +1 -0
  205. package/dist/src/infra/update-restart.d.ts +20 -0
  206. package/dist/src/infra/update-restart.js +165 -0
  207. package/dist/src/infra/update-restart.js.map +1 -0
  208. package/dist/src/infra/update-runner.d.ts +89 -1
  209. package/dist/src/infra/update-runner.js +604 -173
  210. package/dist/src/infra/update-runner.js.map +1 -1
  211. package/dist/src/infra/update-startup.d.ts +3 -0
  212. package/dist/src/infra/update-startup.js +8 -4
  213. package/dist/src/infra/update-startup.js.map +1 -1
  214. package/dist/src/providers/index.d.ts +8 -0
  215. package/dist/src/providers/index.js +51 -12
  216. package/dist/src/providers/index.js.map +1 -1
  217. package/dist/src/routing/resolve-route.d.ts +3 -1
  218. package/dist/src/routing/resolve-route.js.map +1 -1
  219. package/dist/src/session/store.d.ts +5 -3
  220. package/dist/src/session/store.js +66 -20
  221. package/dist/src/session/store.js.map +1 -1
  222. package/dist/src/share/site-share-config.d.ts +3 -2
  223. package/dist/src/share/site-share-config.js.map +1 -1
  224. package/dist/src/utils/logger/stats.d.ts +1 -1
  225. package/dist/src/workflows/domain/command.d.ts +2 -1
  226. package/dist/src/workflows/domain/definition-utils.d.ts +14 -0
  227. package/dist/src/workflows/domain/definition-utils.js +50 -0
  228. package/dist/src/workflows/domain/definition-utils.js.map +1 -0
  229. package/dist/src/workflows/domain/event.d.ts +3 -0
  230. package/dist/src/workflows/domain/index.d.ts +2 -0
  231. package/dist/src/workflows/domain/index.js +3 -1
  232. package/dist/src/workflows/domain/run.d.ts +60 -0
  233. package/dist/src/workflows/domain/run.js.map +1 -1
  234. package/dist/src/workflows/domain/validation.d.ts +19 -0
  235. package/dist/src/workflows/domain/validation.js +66 -0
  236. package/dist/src/workflows/domain/validation.js.map +1 -0
  237. package/dist/src/workflows/engine/projector.js +17 -0
  238. package/dist/src/workflows/engine/projector.js.map +1 -1
  239. package/dist/src/workflows/engine/workflow-engine.d.ts +2 -1
  240. package/dist/src/workflows/engine/workflow-engine.js +128 -0
  241. package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
  242. package/dist/src/workflows/index.d.ts +4 -0
  243. package/dist/src/workflows/index.js +9 -2
  244. package/dist/src/workflows/service/run-view-to-snapshot.d.ts +4 -0
  245. package/dist/src/workflows/service/run-view-to-snapshot.js +63 -0
  246. package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -0
  247. package/dist/src/workflows/service/workflow-run-service.d.ts +37 -0
  248. package/dist/src/workflows/service/workflow-run-service.js +282 -0
  249. package/dist/src/workflows/service/workflow-run-service.js.map +1 -0
  250. package/dist/src/workflows/service/workflow-run-service.types.d.ts +47 -0
  251. package/dist/src/workflows/service/workflow-run-service.types.js +1 -0
  252. package/dist/src/workflows/service/workflow-session-bridge.d.ts +29 -0
  253. package/dist/src/workflows/service/workflow-session-bridge.js +177 -0
  254. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -0
  255. package/dist/src/workflows/service/workflow-session-key.d.ts +3 -0
  256. package/dist/src/workflows/service/workflow-session-key.js +21 -0
  257. package/dist/src/workflows/service/workflow-session-key.js.map +1 -0
  258. package/dist/src/workflows/store/run-store.js +1 -0
  259. package/dist/src/workflows/store/run-store.js.map +1 -1
  260. package/package.json +1 -1
  261. package/dist/gateway/static/root/assets/agents-CRxETUZx.js +0 -222
  262. package/dist/gateway/static/root/assets/apps-page-wKWf3l57.js +0 -1
  263. package/dist/gateway/static/root/assets/channels-settings-DDbqVNkx.js +0 -1
  264. package/dist/gateway/static/root/assets/copy-SxMW6Xpc.js +0 -1
  265. package/dist/gateway/static/root/assets/cron-api-N9hvuRrn.js +0 -1
  266. package/dist/gateway/static/root/assets/cron-dreaming-jobs-DueM3rBz.js +0 -2
  267. package/dist/gateway/static/root/assets/cron-page-tlNGNxhP.js +0 -1
  268. package/dist/gateway/static/root/assets/dist-CJwfHYvT.js +0 -1
  269. package/dist/gateway/static/root/assets/index-CqZzHNEg.css +0 -1
  270. package/dist/gateway/static/root/assets/sessions-page-DKt-Wmib.js +0 -1
  271. package/dist/gateway/static/root/assets/settings-page-DcJjvvw4.js +0 -3
  272. package/dist/gateway/static/root/assets/skills-page-DuJ4BTO3.js +0 -2
  273. package/dist/gateway/static/root/assets/url-D6jvVYIA.js +0 -7
  274. package/dist/gateway/static/root/assets/voice-api-key-field-CTyHz7L_.js +0 -1
  275. package/dist/gateway/static/root/assets/workflows-page-GacJ41Fv.js +0 -27
@@ -1 +1 @@
1
- {"version":3,"file":"update-runner.js","names":[],"sources":["../../../src/infra/update-runner.ts"],"sourcesContent":["// src/infra/update-runner.ts\n\nimport { spawn } from 'node:child_process';\nimport { access, readdir, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport type { UpdateChannel } from './update-channels.js';\n\nconst log = createLogger('UpdateRunner');\n\nconst AUTO_UPDATE_TIMEOUT_MS = 45 * 60 * 1000; // 45 minutes\n\nexport type AutoUpdateResult = {\n ok: boolean;\n exitCode: number | null;\n reason?: string;\n stdout?: string;\n stderr?: string;\n};\n\ntype SpawnUpdateParams = {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n};\n\nfunction createLineEmitter(\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>,\n) {\n let bufOut = '';\n let bufErr = '';\n const flush = (buf: string, source: 'stdout' | 'stderr'): string => {\n const parts = buf.split('\\n');\n const rest = parts.pop() ?? '';\n for (const line of parts) {\n if (line.length) void onProgress?.(line, source);\n }\n return rest;\n };\n return {\n pushStdout(chunk: string) {\n bufOut += chunk;\n bufOut = flush(bufOut, 'stdout');\n },\n pushStderr(chunk: string) {\n bufErr += chunk;\n bufErr = flush(bufErr, 'stderr');\n },\n flushEnd() {\n if (bufOut.length) void onProgress?.(bufOut, 'stdout');\n if (bufErr.length) void onProgress?.(bufErr, 'stderr');\n },\n };\n}\n\nasync function spawnUpdateCommand(params: SpawnUpdateParams): Promise<AutoUpdateResult> {\n const timeoutMs = params.timeoutMs ?? AUTO_UPDATE_TIMEOUT_MS;\n const baseArgs = ['update', '--yes', '--channel', params.channel, '--json'];\n const argv = await resolveUpdateCommandArgv(baseArgs, params.root ?? null);\n\n return new Promise<AutoUpdateResult>((resolve) => {\n const child = spawn(argv[0], argv.slice(1), {\n env: {\n ...process.env,\n XOPC_AUTO_UPDATE: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n });\n\n let stdout = '';\n let stderr = '';\n let stdoutTruncated = false;\n let stderrTruncated = false;\n\n const lineEmitter = createLineEmitter(params.onProgress);\n\n const timeoutId = setTimeout(() => {\n child.kill('SIGTERM');\n }, timeoutMs);\n\n const finish = (result: AutoUpdateResult) => {\n clearTimeout(timeoutId);\n lineEmitter.flushEnd();\n resolve(result);\n };\n\n child.stdout?.on('data', (chunk: Buffer) => {\n const text = chunk.toString();\n stdout += text;\n lineEmitter.pushStdout(text);\n if (stdout.length > 64_000) {\n if (!stdoutTruncated) {\n log.warn('Update command stdout exceeded 64KB; truncating');\n stdoutTruncated = true;\n }\n stdout = stdout.slice(-32_000);\n }\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n const text = chunk.toString();\n stderr += text;\n lineEmitter.pushStderr(text);\n if (stderr.length > 64_000) {\n if (!stderrTruncated) {\n log.warn('Update command stderr exceeded 64KB; truncating');\n stderrTruncated = true;\n }\n stderr = stderr.slice(-32_000);\n }\n });\n\n child.on('error', (err) => {\n log.error({ err }, `Update subprocess spawn error: ${err.message}`);\n finish({ ok: false, exitCode: null, reason: err.message, stdout, stderr });\n });\n\n child.on('exit', (code, signal) => {\n if (signal === 'SIGTERM' || code === 143) {\n log.warn({ code, signal }, 'Update subprocess timed out; attempting lock file cleanup');\n void cleanupNpmLockFiles(params.root).catch((cleanupErr) => {\n log.warn({ err: cleanupErr }, 'Failed to clean npm lock files after timeout');\n });\n finish({ ok: false, exitCode: code, reason: 'timeout', stdout, stderr });\n return;\n }\n finish({\n ok: code === 0,\n exitCode: code,\n reason: code === 0 ? undefined : 'non-zero-exit',\n stdout,\n stderr,\n });\n });\n });\n}\n\nexport async function runAutoUpdateCommand(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n}): Promise<AutoUpdateResult> {\n return spawnUpdateCommand(params);\n}\n\nexport async function runAutoUpdateCommandWithProgress(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n}): Promise<AutoUpdateResult> {\n return spawnUpdateCommand(params);\n}\n\nasync function cleanupNpmLockFiles(root: string | null | undefined): Promise<void> {\n if (!root) return;\n let entries: string[];\n try {\n entries = await readdir(root);\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.startsWith('.package-lock')) {\n try {\n await unlink(join(root, entry));\n } catch {\n // best-effort\n }\n }\n }\n}\n\n/**\n * Resolve the argv array for spawning the update command.\n *\n * Priority:\n * 1. process.execPath + process.argv[1] (current runtime + entry point)\n * 2. process.execPath + known dist entry points in root\n * 3. Fallback to bare `xopc` (assumes global install)\n */\nasync function resolveUpdateCommandArgv(\n baseArgs: string[],\n root: string | null,\n): Promise<string[]> {\n const execPath = process.execPath?.trim();\n const argv1 = process.argv[1]?.trim();\n\n if (execPath && argv1) {\n return [execPath, argv1, ...baseArgs];\n }\n\n if (execPath && root) {\n const candidates = [join(root, 'dist/src/cli/bin.js'), join(root, 'dist/index.js')];\n for (const candidate of candidates) {\n try {\n await access(candidate);\n return [execPath, candidate, ...baseArgs];\n } catch {\n // try next\n }\n }\n }\n\n log.warn('Falling back to bare `xopc` command — version mismatch possible');\n try {\n const { execSync } = await import('node:child_process');\n const cmd = process.platform === 'win32' ? 'where xopc' : 'which xopc';\n const whichResult = execSync(cmd, { encoding: 'utf-8', timeout: 3000 }).trim();\n if (whichResult) {\n log.info({ resolvedPath: whichResult.split(/\\r?\\n/)[0]?.trim() }, 'Resolved xopc via PATH');\n }\n } catch {\n log.warn('Could not resolve `xopc` in PATH; update command may fail');\n }\n\n return ['xopc', ...baseArgs];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"update-runner.js","names":[],"sources":["../../../src/infra/update-runner.ts"],"sourcesContent":["// src/infra/update-runner.ts — unified gateway/CLI update runner (OpenClaw-aligned)\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { readPackageName, readPackageVersion } from './package-json.js';\nimport { runGlobalPackageUpdateSteps, type PackageUpdateStepResult } from './package-update-steps.js';\nimport { createDefaultCommandRunner, runCommandWithTimeout } from './run-command.js';\nimport { resolveStableNodePath } from './stable-node-path.js';\nimport { trimLogTail } from './update-log.js';\nimport { DEFAULT_PACKAGE_CHANNEL, type UpdateChannel } from './update-channels.js';\nimport { compareSemver, resolveNpmChannelTag } from './update-check.js';\nimport { runPostUpdateExtensionSync, type ExtensionPostUpdateResult } from '../extensions/update.js';\nimport {\n cleanupGlobalRenameDirs,\n createGlobalInstallEnv,\n detectGlobalInstallManagerForRoot,\n resolveGlobalInstallSpec,\n resolveGlobalInstallTarget,\n XOPC_PACKAGE_NAME,\n type GlobalInstallManager,\n} from './update-global.js';\nimport {\n maybeRestartGatewayAfterUpdate,\n type InProcessRestartTrigger,\n type UpdateRestartResult,\n} from './update-restart.js';\n\nconst DEFAULT_TIMEOUT_MS = 45 * 60 * 1000;\nconst MAX_LOG_CHARS = 8000;\nconst CORE_PACKAGE_NAMES = new Set([XOPC_PACKAGE_NAME]);\n\nexport type UpdateStepResult = {\n name: string;\n command: string;\n cwd: string;\n durationMs: number;\n exitCode: number | null;\n stdoutTail?: string | null;\n stderrTail?: string | null;\n};\n\nexport type UpdatePostUpdateResult = {\n extensions?: ExtensionPostUpdateResult;\n restart?: UpdateRestartResult;\n};\n\nexport type UpdateRunResult = {\n status: 'ok' | 'error' | 'skipped';\n mode: 'git' | 'pnpm' | 'npm' | 'unknown';\n root?: string;\n reason?: string;\n before?: { sha?: string | null; version?: string | null };\n after?: { sha?: string | null; version?: string | null };\n steps: UpdateStepResult[];\n durationMs: number;\n postUpdate?: UpdatePostUpdateResult;\n};\n\nexport type UpdateStepInfo = {\n name: string;\n command: string;\n index: number;\n total: number;\n};\n\nexport type UpdateStepCompletion = UpdateStepInfo & {\n durationMs: number;\n exitCode: number | null;\n stderrTail?: string | null;\n};\n\nexport type UpdateStepProgress = {\n onStepStart?: (step: UpdateStepInfo) => void;\n onStepComplete?: (step: UpdateStepCompletion) => void;\n};\n\nexport type UpdateInstallSurface =\n | { kind: 'git'; mode: 'git'; root: string; packageRoot: string }\n | { kind: 'global'; mode: GlobalInstallManager; root: string; packageRoot: string }\n | { kind: 'package-root'; mode: 'unknown'; root: string; packageRoot: string }\n | { kind: 'missing'; mode: 'unknown'; root?: string; packageRoot?: undefined };\n\ntype UpdateRunnerOptions = {\n cwd?: string;\n argv1?: string;\n channel?: UpdateChannel;\n timeoutMs?: number;\n progress?: UpdateStepProgress;\n skipExtensionSync?: boolean;\n shouldRestart?: boolean;\n triggerInProcessRestart?: InProcessRestartTrigger;\n};\n\ntype CommandRunner = (\n argv: string[],\n options: { timeoutMs: number; cwd?: string; env?: NodeJS.ProcessEnv },\n) => Promise<{ stdout: string; stderr: string; code: number | null }>;\n\nfunction normalizeDir(value?: string | null): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed ? path.resolve(trimmed) : null;\n}\n\nfunction resolveNodeModulesBinPackageRoot(argv1: string): string | null {\n const normalized = path.resolve(argv1);\n const parts = normalized.split(path.sep);\n const binIndex = parts.lastIndexOf('.bin');\n if (binIndex <= 0 || parts[binIndex - 1] !== 'node_modules') return null;\n return path.join(parts.slice(0, binIndex).join(path.sep), path.basename(normalized));\n}\n\nfunction buildStartDirs(opts: UpdateRunnerOptions): string[] {\n const dirs: string[] = [];\n const cwd = normalizeDir(opts.cwd);\n if (cwd) dirs.push(cwd);\n const argv1 = normalizeDir(opts.argv1);\n if (argv1) {\n dirs.push(path.dirname(argv1));\n const packageRoot = resolveNodeModulesBinPackageRoot(argv1);\n if (packageRoot) dirs.push(packageRoot);\n }\n const proc = normalizeDir(process.cwd());\n if (proc) dirs.push(proc);\n return Array.from(new Set(dirs));\n}\n\nasync function resolveComparablePath(target: string): Promise<string> {\n return fs.realpath(target).catch(() => path.resolve(target));\n}\n\nasync function pathsReferToSameLocation(left: string, right: string): Promise<boolean> {\n return (await resolveComparablePath(left)) === (await resolveComparablePath(right));\n}\n\nasync function looksLikeGitCheckout(root: string): Promise<boolean> {\n try {\n await fs.access(path.join(root, '.git'));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function resolveGitRoot(\n runCommand: CommandRunner,\n candidates: string[],\n timeoutMs: number,\n): Promise<string | null> {\n for (const dir of candidates) {\n const res = await runCommand(['git', '-C', dir, 'rev-parse', '--show-toplevel'], {\n timeoutMs,\n }).catch(() => null);\n if (!res || res.code !== 0) continue;\n const root = res.stdout.trim();\n if (root) return root;\n }\n return null;\n}\n\nasync function findPackageRoot(candidates: string[]): Promise<string | null> {\n for (const dir of candidates) {\n let current = dir;\n for (let i = 0; i < 12; i += 1) {\n const pkgPath = path.join(current, 'package.json');\n try {\n const raw = await fs.readFile(pkgPath, 'utf-8');\n const parsed = JSON.parse(raw) as { name?: string };\n const name = parsed?.name?.trim();\n if (name && CORE_PACKAGE_NAMES.has(name)) return current;\n } catch {\n // ignore\n }\n const parent = path.dirname(current);\n if (parent === current) break;\n current = parent;\n }\n }\n return null;\n}\n\nfunction mergeCommandEnvironments(\n baseEnv: NodeJS.ProcessEnv | undefined,\n overrideEnv: NodeJS.ProcessEnv | undefined,\n): NodeJS.ProcessEnv | undefined {\n if (!baseEnv) return overrideEnv;\n if (!overrideEnv) return baseEnv;\n return { ...baseEnv, ...overrideEnv };\n}\n\nasync function buildUpdateCommandRunner(): Promise<{\n defaultCommandEnv: NodeJS.ProcessEnv | undefined;\n runCommand: CommandRunner;\n}> {\n const defaultCommandEnv = await createGlobalInstallEnv();\n return {\n defaultCommandEnv,\n runCommand: async (argv, options) => {\n const res = await runCommandWithTimeout(argv, {\n ...options,\n env: mergeCommandEnvironments(defaultCommandEnv, options.env),\n });\n return { stdout: res.stdout, stderr: res.stderr, code: res.code };\n },\n };\n}\n\ntype RunStepOptions = {\n runCommand: CommandRunner;\n name: string;\n argv: string[];\n cwd: string;\n timeoutMs: number;\n env?: NodeJS.ProcessEnv;\n progress?: UpdateStepProgress;\n stepIndex: number;\n totalSteps: number;\n};\n\nasync function runStep(opts: RunStepOptions): Promise<UpdateStepResult> {\n const { runCommand, name, argv, cwd, timeoutMs, env, progress, stepIndex, totalSteps } = opts;\n const command = argv.join(' ');\n const stepInfo: UpdateStepInfo = { name, command, index: stepIndex, total: totalSteps };\n progress?.onStepStart?.(stepInfo);\n const started = Date.now();\n const result = await runCommand(argv, { cwd, timeoutMs, env });\n const durationMs = Date.now() - started;\n const stderrTail = trimLogTail(result.stderr, MAX_LOG_CHARS);\n progress?.onStepComplete?.({ ...stepInfo, durationMs, exitCode: result.code, stderrTail });\n return {\n name,\n command,\n cwd,\n durationMs,\n exitCode: result.code,\n stdoutTail: trimLogTail(result.stdout, MAX_LOG_CHARS),\n stderrTail,\n };\n}\n\nfunction toUpdateStepResult(step: PackageUpdateStepResult): UpdateStepResult {\n return {\n name: step.name,\n command: step.command,\n cwd: step.cwd,\n durationMs: step.durationMs,\n exitCode: step.exitCode,\n stdoutTail: step.stdoutTail,\n stderrTail: step.stderrTail,\n };\n}\n\nexport async function resolveUpdateInstallSurface(\n opts: Pick<UpdateRunnerOptions, 'cwd' | 'argv1' | 'timeoutMs'> = {},\n): Promise<UpdateInstallSurface> {\n const { runCommand } = await buildUpdateCommandRunner();\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const candidates = buildStartDirs(opts);\n const pkgRoot = await findPackageRoot(candidates);\n\n let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs);\n if (gitRoot && pkgRoot && !(await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n gitRoot = null;\n }\n if (gitRoot && !pkgRoot) {\n return { kind: 'missing', mode: 'unknown', root: gitRoot };\n }\n if (gitRoot && pkgRoot && (await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n return { kind: 'git', mode: 'git', root: gitRoot, packageRoot: pkgRoot };\n }\n if (!pkgRoot) {\n return { kind: 'missing', mode: 'unknown' };\n }\n\n const globalManager = await detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs);\n if (globalManager) {\n return { kind: 'global', mode: globalManager, root: pkgRoot, packageRoot: pkgRoot };\n }\n\n return { kind: 'package-root', mode: 'unknown', root: pkgRoot, packageRoot: pkgRoot };\n}\n\nasync function runGitUpdate(params: {\n gitRoot: string;\n runCommand: CommandRunner;\n timeoutMs: number;\n progress?: UpdateStepProgress;\n defaultCommandEnv?: NodeJS.ProcessEnv;\n}): Promise<UpdateRunResult> {\n const startedAt = Date.now();\n const steps: UpdateStepResult[] = [];\n const { gitRoot, runCommand, timeoutMs, progress, defaultCommandEnv } = params;\n const totalSteps = 7;\n let stepIndex = 0;\n const step = (name: string, argv: string[], cwd: string, env?: NodeJS.ProcessEnv) =>\n runStep({\n runCommand,\n name,\n argv,\n cwd,\n timeoutMs,\n env,\n progress,\n stepIndex: stepIndex++,\n totalSteps,\n });\n\n const beforeShaResult = await runCommand(['git', '-C', gitRoot, 'rev-parse', 'HEAD'], {\n cwd: gitRoot,\n timeoutMs,\n });\n const beforeSha = beforeShaResult.stdout.trim() || null;\n const beforeVersion = await readPackageVersion(gitRoot);\n\n const statusCheck = await step(\n 'clean check',\n ['git', '-C', gitRoot, 'status', '--porcelain'],\n gitRoot,\n );\n steps.push(statusCheck);\n if (statusCheck.stdoutTail?.trim()) {\n return {\n status: 'skipped',\n mode: 'git',\n root: gitRoot,\n reason: 'dirty',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const fetchStep = await step(\n 'git fetch',\n ['git', '-C', gitRoot, 'fetch', '--all', '--prune', '--tags'],\n gitRoot,\n );\n steps.push(fetchStep);\n if (fetchStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'fetch-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const upstreamStep = await step(\n 'upstream check',\n ['git', '-C', gitRoot, 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'],\n gitRoot,\n );\n steps.push(upstreamStep);\n if (upstreamStep.exitCode !== 0) {\n return {\n status: 'skipped',\n mode: 'git',\n root: gitRoot,\n reason: 'no-upstream',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const rebaseStep = await step(\n 'git rebase',\n ['git', '-C', gitRoot, 'rebase', '@{upstream}'],\n gitRoot,\n );\n steps.push(rebaseStep);\n if (rebaseStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'rebase-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const depsStep = await step('deps install', ['pnpm', 'install'], gitRoot, defaultCommandEnv);\n steps.push(depsStep);\n if (depsStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'deps-install-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const buildStep = await step('build', ['pnpm', 'run', 'build'], gitRoot, defaultCommandEnv);\n steps.push(buildStep);\n if (buildStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'build-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const doctorEntry = path.join(gitRoot, 'dist/src/cli/bin.js');\n const doctorEntryExists = await fs.stat(doctorEntry).then(() => true).catch(() => false);\n if (doctorEntryExists) {\n const doctorNodePath = await resolveStableNodePath(process.execPath);\n const doctorStep = await step(\n 'xopc doctor',\n [doctorNodePath, doctorEntry, 'doctor', '--fix'],\n gitRoot,\n { ...defaultCommandEnv, XOPC_UPDATE_IN_PROGRESS: '1' },\n );\n steps.push(doctorStep);\n if (doctorStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'doctor-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n }\n\n const afterShaStep = await step(\n 'git rev-parse HEAD (after)',\n ['git', '-C', gitRoot, 'rev-parse', 'HEAD'],\n gitRoot,\n );\n steps.push(afterShaStep);\n const afterVersion = await readPackageVersion(gitRoot);\n\n return {\n status: 'ok',\n mode: 'git',\n root: gitRoot,\n before: { sha: beforeSha, version: beforeVersion },\n after: { sha: afterShaStep.stdoutTail?.trim() ?? null, version: afterVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n}\n\nasync function runGlobalUpdate(params: {\n pkgRoot: string;\n globalManager: GlobalInstallManager;\n channel: UpdateChannel;\n runCommand: CommandRunner;\n timeoutMs: number;\n defaultCommandEnv?: NodeJS.ProcessEnv;\n progress?: UpdateStepProgress;\n}): Promise<UpdateRunResult> {\n const startedAt = Date.now();\n const beforeVersion = await readPackageVersion(params.pkgRoot);\n const resolved = await resolveNpmChannelTag({ channel: params.channel, timeoutMs: 10_000 });\n if (!resolved.version) {\n return {\n status: 'error',\n mode: params.globalManager,\n root: params.pkgRoot,\n reason: 'registry-unreachable',\n before: { version: beforeVersion },\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n }\n\n const comparison = compareSemver(beforeVersion ?? '0.0.0', resolved.version);\n if (comparison !== null && comparison >= 0) {\n return {\n status: 'skipped',\n mode: params.globalManager,\n root: params.pkgRoot,\n reason: 'up-to-date',\n before: { version: beforeVersion },\n after: { version: beforeVersion },\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n }\n\n const installTarget = await resolveGlobalInstallTarget({\n manager: params.globalManager,\n runCommand: params.runCommand,\n timeoutMs: params.timeoutMs,\n pkgRoot: params.pkgRoot,\n });\n const packageName = (await readPackageName(params.pkgRoot)) ?? XOPC_PACKAGE_NAME;\n if (installTarget.globalRoot) {\n await cleanupGlobalRenameDirs({ globalRoot: installTarget.globalRoot, packageName });\n }\n\n const spec = resolveGlobalInstallSpec({\n version: resolved.version,\n env: params.defaultCommandEnv,\n });\n\n let stepIndex = 0;\n const packageUpdate = await runGlobalPackageUpdateSteps({\n installTarget,\n installSpec: spec,\n packageName,\n packageRoot: params.pkgRoot,\n runCommand: params.runCommand,\n timeoutMs: params.timeoutMs,\n ...(params.defaultCommandEnv === undefined ? {} : { env: params.defaultCommandEnv }),\n installCwd: params.pkgRoot,\n runStep: async (stepParams) => {\n const result = await runStep({\n runCommand: params.runCommand,\n name: stepParams.name,\n argv: stepParams.argv,\n cwd: stepParams.cwd ?? params.pkgRoot,\n timeoutMs: stepParams.timeoutMs,\n env: stepParams.env,\n progress: params.progress,\n stepIndex: stepIndex++,\n totalSteps: 3,\n });\n return {\n name: result.name,\n command: result.command,\n cwd: result.cwd,\n durationMs: result.durationMs,\n exitCode: result.exitCode,\n stdoutTail: result.stdoutTail,\n stderrTail: result.stderrTail,\n };\n },\n });\n\n const steps = packageUpdate.steps.map(toUpdateStepResult);\n return {\n status: packageUpdate.failedStep ? 'error' : 'ok',\n mode: params.globalManager,\n root: packageUpdate.verifiedPackageRoot ?? params.pkgRoot,\n reason: packageUpdate.failedStep ? 'global-install-failed' : undefined,\n before: { version: beforeVersion },\n after: { version: packageUpdate.afterVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n}\n\nexport async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<UpdateRunResult> {\n const startedAt = Date.now();\n const { defaultCommandEnv, runCommand } = await buildUpdateCommandRunner();\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const channel = opts.channel ?? DEFAULT_PACKAGE_CHANNEL;\n const candidates = buildStartDirs(opts);\n const pkgRoot = await findPackageRoot(candidates);\n\n let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs);\n if (!gitRoot && pkgRoot) {\n const cwdRoot = normalizeDir(opts.cwd);\n if (\n cwdRoot &&\n (await pathsReferToSameLocation(cwdRoot, pkgRoot)) &&\n (await looksLikeGitCheckout(cwdRoot))\n ) {\n gitRoot = await resolveComparablePath(cwdRoot);\n }\n }\n if (gitRoot && pkgRoot && !(await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n gitRoot = null;\n }\n\n if (gitRoot && pkgRoot && (await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n return runGitUpdate({\n gitRoot,\n runCommand,\n timeoutMs,\n progress: opts.progress,\n defaultCommandEnv,\n });\n }\n\n if (!pkgRoot) {\n return {\n status: 'error',\n mode: 'unknown',\n reason: 'not-xopc-root',\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n }\n\n const globalManager = await detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs);\n if (globalManager) {\n return runGlobalUpdate({\n pkgRoot,\n globalManager,\n channel,\n runCommand,\n timeoutMs,\n defaultCommandEnv,\n progress: opts.progress,\n });\n }\n\n return {\n status: 'skipped',\n mode: 'unknown',\n root: pkgRoot,\n reason: 'not-global-install',\n before: { version: await readPackageVersion(pkgRoot) },\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n}\n\nexport async function runGatewayUpdateWithPostSteps(\n opts: UpdateRunnerOptions = {},\n): Promise<UpdateRunResult> {\n const channel = opts.channel ?? DEFAULT_PACKAGE_CHANNEL;\n const result = await runGatewayUpdate(opts);\n\n if (result.status !== 'ok') {\n return result;\n }\n\n const postUpdate: UpdatePostUpdateResult = {};\n\n if (!opts.skipExtensionSync) {\n const extensions = await runPostUpdateExtensionSync({\n channel,\n timeoutMs: opts.timeoutMs,\n });\n postUpdate.extensions = extensions;\n if (extensions.status === 'error') {\n return {\n ...result,\n status: 'error',\n reason: 'post-update-extensions',\n postUpdate,\n };\n }\n }\n\n const restart = await maybeRestartGatewayAfterUpdate({\n shouldRestart: opts.shouldRestart,\n expectedVersion: result.after?.version ?? undefined,\n triggerInProcessRestart: opts.triggerInProcessRestart,\n });\n postUpdate.restart = restart;\n\n return { ...result, postUpdate };\n}\n\nexport function formatUpdateApiResult(result: UpdateRunResult, channel: UpdateChannel): Record<string, unknown> {\n if (result.status === 'skipped' && result.reason === 'up-to-date') {\n return {\n status: 'up-to-date',\n currentVersion: result.before?.version ?? null,\n latestVersion: result.before?.version ?? null,\n channel,\n mode: result.mode,\n };\n }\n if (result.status === 'ok') {\n return {\n status: 'ok',\n previousVersion: result.before?.version ?? null,\n installedVersion: result.after?.version ?? null,\n channel,\n mode: result.mode,\n steps: result.steps.length,\n postUpdate: result.postUpdate ?? undefined,\n };\n }\n const failed = result.steps.find((step) => step.exitCode !== 0);\n return {\n status: 'error',\n reason: result.reason ?? 'update-failed',\n mode: result.mode,\n message: failed?.stderrTail ?? failed?.stdoutTail ?? result.reason,\n stderrTail: failed?.stderrTail ?? undefined,\n };\n}\n\nexport type AutoUpdateResult = {\n ok: boolean;\n exitCode: number | null;\n reason?: string;\n stdout?: string;\n stderr?: string;\n result?: UpdateRunResult;\n};\n\nexport async function runAutoUpdateCommand(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n triggerInProcessRestart?: InProcessRestartTrigger;\n}): Promise<AutoUpdateResult> {\n const result = await runGatewayUpdateWithPostSteps({\n channel: params.channel,\n cwd: params.root ?? undefined,\n argv1: process.argv[1],\n timeoutMs: params.timeoutMs,\n triggerInProcessRestart: params.triggerInProcessRestart,\n });\n const payload = formatUpdateApiResult(result, params.channel);\n const stdout = JSON.stringify(payload);\n return {\n ok: result.status === 'ok' || (result.status === 'skipped' && result.reason === 'up-to-date'),\n exitCode: result.status === 'error' ? 1 : 0,\n reason: result.reason,\n stdout,\n stderr: result.steps.find((s) => s.exitCode !== 0)?.stderrTail ?? undefined,\n result,\n };\n}\n\nexport async function runAutoUpdateCommandWithProgress(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n triggerInProcessRestart?: InProcessRestartTrigger;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n}): Promise<AutoUpdateResult> {\n const result = await runGatewayUpdateWithPostSteps({\n channel: params.channel,\n cwd: params.root ?? undefined,\n argv1: process.argv[1],\n timeoutMs: params.timeoutMs,\n triggerInProcessRestart: params.triggerInProcessRestart,\n progress: {\n onStepStart: (step) => void params.onProgress?.(`[${step.index + 1}/${step.total}] ${step.name}`, 'stdout'),\n onStepComplete: (step) => {\n if (step.exitCode !== 0 && step.stderrTail) {\n void params.onProgress?.(step.stderrTail, 'stderr');\n }\n },\n },\n });\n const payload = formatUpdateApiResult(result, params.channel);\n return {\n ok: result.status === 'ok' || (result.status === 'skipped' && result.reason === 'up-to-date'),\n exitCode: result.status === 'error' ? 1 : 0,\n reason: result.reason,\n stdout: JSON.stringify(payload),\n stderr: result.steps.find((s) => s.exitCode !== 0)?.stderrTail ?? undefined,\n result,\n };\n}\n\n// Legacy export for tests that import createDefaultCommandRunner path\nexport { createDefaultCommandRunner };\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { Config } from '../config/schema.js';
2
2
  import { type UpdateAvailable } from './update-check.js';
3
+ import type { InProcessRestartTrigger } from './update-restart.js';
3
4
  /** Get the cached update-available state (populated after startup check). */
4
5
  export declare function getUpdateAvailable(): UpdateAvailable | null;
5
6
  /**
@@ -8,6 +9,7 @@ export declare function getUpdateAvailable(): UpdateAvailable | null;
8
9
  export declare function runGatewayUpdateCheck(params: {
9
10
  config: Config;
10
11
  onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;
12
+ triggerInProcessRestart?: InProcessRestartTrigger;
11
13
  /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */
12
14
  force?: boolean;
13
15
  }): Promise<void>;
@@ -17,4 +19,5 @@ export declare function runGatewayUpdateCheck(params: {
17
19
  export declare function scheduleGatewayUpdateCheck(params: {
18
20
  config: Config;
19
21
  onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;
22
+ triggerInProcessRestart?: InProcessRestartTrigger;
20
23
  }): () => void;
@@ -136,7 +136,8 @@ async function runGatewayUpdateCheck(params) {
136
136
  nextState,
137
137
  now,
138
138
  root,
139
- config
139
+ config,
140
+ triggerInProcessRestart: params.triggerInProcessRestart
140
141
  });
141
142
  } else {
142
143
  delete nextState.lastAvailableVersion;
@@ -203,16 +204,19 @@ async function handleAutoUpdate(params) {
203
204
  const { runAutoUpdateCommand } = await import("./update-runner.js");
204
205
  const result = await runAutoUpdateCommand({
205
206
  channel,
206
- root
207
+ root,
208
+ triggerInProcessRestart: params.triggerInProcessRestart
207
209
  });
208
210
  if (result.ok) {
209
211
  nextState.autoLastSuccessVersion = version;
210
212
  nextState.autoLastSuccessAt = new Date(now).toISOString();
213
+ const restartMode = result.result?.postUpdate?.restart?.mode;
211
214
  log.info({
212
215
  channel,
213
216
  version,
214
- tag
215
- }, "Auto-update applied successfully; restart gateway to refresh browser extension artifacts");
217
+ tag,
218
+ restartMode
219
+ }, restartMode === "in-process" ? "Auto-update applied; gateway restart scheduled" : "Auto-update applied successfully");
216
220
  } else log.warn({
217
221
  channel,
218
222
  version,
@@ -1 +1 @@
1
- {"version":3,"file":"update-startup.js","names":[],"sources":["../../../src/infra/update-startup.ts"],"sourcesContent":["// src/infra/update-startup.ts\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { writeTextAtomic } from './write-file-atomic.js';\nimport type { Config } from '../config/schema.js';\nimport { resolveUpdateCheckStatePath } from '../config/paths-state.js';\nimport { acquireUpdateLock } from './update-lock.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from './update-channels.js';\nimport {\n compareSemver,\n resolveNpmChannelTag,\n detectInstallKind,\n resolvePackageRoot,\n type InstallKind,\n type UpdateAvailable,\n} from './update-check.js';\n\nconst log = createLogger('UpdateCheck');\n\n// --- State persistence ---\n\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\ntype UpdateCheckState = {\n /** `package.json` version at the last successful registry check (used to bypass 24h throttle when you bump the local version). */\n lastCheckPackageVersion?: string;\n lastCheckedAt?: string;\n lastAvailableVersion?: string;\n lastAvailableTag?: string;\n lastNotifiedVersion?: string;\n lastNotifiedTag?: string;\n autoInstallId?: string;\n autoFirstSeenVersion?: string;\n autoFirstSeenTag?: string;\n autoFirstSeenAt?: string;\n autoLastAttemptVersion?: string;\n autoLastAttemptAt?: string;\n autoLastSuccessVersion?: string;\n autoLastSuccessAt?: string;\n};\n\n// --- In-memory cache ---\n\nlet updateAvailableCache: UpdateAvailable | null = null;\n\n/** Get the cached update-available state (populated after startup check). */\nexport function getUpdateAvailable(): UpdateAvailable | null {\n return updateAvailableCache;\n}\n\n// --- Core logic ---\n\nasync function readState(statePath: string): Promise<UpdateCheckState> {\n try {\n const raw = await readFile(statePath, 'utf-8');\n const parsed = JSON.parse(raw) as UpdateCheckState;\n if (!parsed || typeof parsed !== 'object') {\n log.warn({ statePath }, 'Update check state file contains non-object; resetting');\n return {};\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn({ err, statePath }, 'Failed to read update check state; resetting');\n }\n return {};\n }\n}\n\nasync function writeState(statePath: string, state: UpdateCheckState): Promise<void> {\n await writeTextAtomic(statePath, JSON.stringify(state, null, 2));\n}\n\nfunction resolveCheckIntervalMs(config: Config): number {\n const auto = config.update?.auto;\n if (!auto?.enabled) return CHECK_INTERVAL_MS;\n\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n if (channel === 'beta') {\n const hours = auto.betaCheckIntervalHours ?? 1;\n return Math.max(ONE_HOUR_MS / 4, Math.floor(hours * ONE_HOUR_MS));\n }\n return ONE_HOUR_MS;\n}\n\n/**\n * Compute a deterministic delay for stable auto-update rollout,\n * based on a per-installation hash to spread updates over time.\n */\nfunction resolveStableJitterMs(\n installId: string,\n version: string,\n tag: string,\n jitterWindowMs: number,\n): number {\n if (jitterWindowMs <= 0) return 0;\n const hash = createHash('sha256').update(`${installId}:${version}:${tag}`).digest();\n const bucket = hash.readUInt32BE(0);\n return bucket % (Math.floor(jitterWindowMs) + 1);\n}\n\n/**\n * Main startup update check. Called once when the gateway starts (or on demand).\n */\nexport async function runGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */\n force?: boolean;\n}): Promise<void> {\n const { config, force } = params;\n\n const autoEnabled = config.update?.auto?.enabled ?? false;\n const shouldCheckHints = config.update?.checkOnStart !== false;\n if (!force && !shouldCheckHints && !autoEnabled) return;\n\n const statePath = resolveUpdateCheckStatePath();\n const state = await readState(statePath);\n const now = Date.now();\n\n // Hydrate from persisted state if within throttle window\n const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;\n if (state.lastAvailableVersion && (shouldCheckHints || force)) {\n const comparison = compareSemver(PACKAGE_VERSION, state.lastAvailableVersion);\n if (comparison !== null && comparison < 0) {\n const cached: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: state.lastAvailableVersion,\n channel: state.lastAvailableTag ?? 'latest',\n };\n updateAvailableCache = cached;\n params.onUpdateAvailableChange?.(cached);\n }\n }\n\n const checkIntervalMs = resolveCheckIntervalMs(config);\n // Re-check npm when the local package version changed (e.g. after editing package.json) even within 24h.\n const shouldBypassThrottleForVersion =\n state.lastCheckPackageVersion === undefined || state.lastCheckPackageVersion !== PACKAGE_VERSION;\n if (\n !force &&\n !shouldBypassThrottleForVersion &&\n lastCheckedAt &&\n Number.isFinite(lastCheckedAt) &&\n now - lastCheckedAt < checkIntervalMs\n ) {\n return; // Within throttle window\n }\n\n // Install kind: auto-install only for npm global installs, but we still query npm in git\n // so the Web UI / CLI can show \"newer on registry\" and the top reminder bar.\n const root = await resolvePackageRoot();\n let installKind: InstallKind = 'unknown';\n if (root) {\n installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n log.info('Update check: git checkout (hint-only; use git pull to update, no auto npm install)');\n }\n }\n\n // Query npm registry\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 2500 });\n\n const nextState: UpdateCheckState = {\n ...state,\n lastCheckedAt: new Date(now).toISOString(),\n };\n\n if (!resolved.version) {\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n return;\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison !== null && comparison < 0) {\n // Update available\n const updateInfo: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n };\n\n if (shouldCheckHints || force) {\n updateAvailableCache = updateInfo;\n params.onUpdateAvailableChange?.(updateInfo);\n }\n\n nextState.lastAvailableVersion = resolved.version;\n nextState.lastAvailableTag = resolved.tag;\n\n // Log notification (once per version)\n const shouldNotify =\n state.lastNotifiedVersion !== resolved.version || state.lastNotifiedTag !== resolved.tag;\n if ((shouldCheckHints || force) && shouldNotify) {\n log.info(\n { currentVersion: PACKAGE_VERSION, latestVersion: resolved.version, tag: resolved.tag },\n `Update available (${resolved.tag}): v${resolved.version} (current v${PACKAGE_VERSION}). Run: xopc update`,\n );\n nextState.lastNotifiedVersion = resolved.version;\n nextState.lastNotifiedTag = resolved.tag;\n }\n\n // Auto-update logic (never from a git worktree)\n if (\n autoEnabled &&\n (channel === 'stable' || channel === 'beta') &&\n installKind !== 'git'\n ) {\n await handleAutoUpdate({\n channel,\n version: resolved.version,\n tag: resolved.tag,\n state,\n nextState,\n now,\n root,\n config,\n });\n }\n } else {\n // Current version is up to date or newer\n delete nextState.lastAvailableVersion;\n delete nextState.lastAvailableTag;\n updateAvailableCache = null;\n params.onUpdateAvailableChange?.(null);\n }\n\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n}\n\nasync function handleAutoUpdate(params: {\n channel: 'stable' | 'beta';\n version: string;\n tag: string;\n state: UpdateCheckState;\n nextState: UpdateCheckState;\n now: number;\n root: string | null;\n config: Config;\n}): Promise<void> {\n const { channel, version, tag, state, nextState, now, root, config } = params;\n const auto = config.update?.auto;\n if (!auto) return;\n\n const stableDelayHours = auto.stableDelayHours ?? 6;\n const stableJitterHours = auto.stableJitterHours ?? 12;\n const betaCheckIntervalHours = auto.betaCheckIntervalHours ?? 1;\n\n // Rate limit: don't re-attempt same version within interval\n const attemptIntervalMs =\n channel === 'beta'\n ? Math.max(ONE_HOUR_MS / 4, Math.floor(betaCheckIntervalHours * ONE_HOUR_MS))\n : ONE_HOUR_MS;\n const lastAttemptAt = state.autoLastAttemptAt ? Date.parse(state.autoLastAttemptAt) : null;\n const recentAttempt =\n state.autoLastAttemptVersion === version &&\n lastAttemptAt !== null &&\n Number.isFinite(lastAttemptAt) &&\n now - lastAttemptAt < attemptIntervalMs;\n\n if (recentAttempt) {\n log.info({ version, tag }, 'Auto-update deferred: recent attempt exists');\n return;\n }\n\n // Stable rollout delay + jitter\n if (channel === 'stable') {\n if (!nextState.autoInstallId) {\n nextState.autoInstallId = state.autoInstallId?.trim() || randomUUID();\n }\n // Track first-seen time for this version\n if (state.autoFirstSeenVersion !== version || state.autoFirstSeenTag !== tag) {\n nextState.autoFirstSeenVersion = version;\n nextState.autoFirstSeenTag = tag;\n nextState.autoFirstSeenAt = new Date(now).toISOString();\n } else {\n nextState.autoFirstSeenAt = state.autoFirstSeenAt;\n }\n\n const firstSeenMs = nextState.autoFirstSeenAt ? Date.parse(nextState.autoFirstSeenAt) : now;\n const baseDelayMs = Math.max(0, stableDelayHours) * ONE_HOUR_MS;\n const jitterWindowMs = Math.max(0, stableJitterHours) * ONE_HOUR_MS;\n const jitterMs = resolveStableJitterMs(nextState.autoInstallId, version, tag, jitterWindowMs);\n const applyAfterMs = firstSeenMs + baseDelayMs + jitterMs;\n\n if (now < applyAfterMs) {\n log.info(\n { version, tag, applyAfter: new Date(applyAfterMs).toISOString() },\n 'Auto-update deferred: stable rollout window not yet due',\n );\n return;\n }\n }\n\n // Execute auto-update\n nextState.autoLastAttemptVersion = version;\n nextState.autoLastAttemptAt = new Date(now).toISOString();\n\n log.info({ channel, version, tag }, 'Starting auto-update');\n\n const lock = await acquireUpdateLock('auto');\n if (!lock) {\n log.info({ version, tag }, 'Auto-update skipped: another update is in progress');\n return;\n }\n try {\n const { runAutoUpdateCommand } = await import('./update-runner.js');\n const result = await runAutoUpdateCommand({ channel, root });\n if (result.ok) {\n nextState.autoLastSuccessVersion = version;\n nextState.autoLastSuccessAt = new Date(now).toISOString();\n log.info({ channel, version, tag }, 'Auto-update applied successfully; restart gateway to refresh browser extension artifacts');\n } else {\n log.warn(\n { channel, version, tag, exitCode: result.exitCode, reason: result.reason },\n `Auto-update attempt failed: ${result.reason ?? `exit ${result.exitCode}`}`,\n );\n }\n } catch (err) {\n log.error({ err, channel, version }, 'Auto-update command threw');\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Schedule periodic update checks. Returns a cleanup function to stop the timer.\n */\nexport function scheduleGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n}): () => void {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const tick = async () => {\n if (stopped) return;\n try {\n await runGatewayUpdateCheck(params);\n } catch (err) {\n log.warn({ err }, 'Periodic update check failed');\n }\n if (!stopped) {\n const intervalMs = resolveCheckIntervalMs(params.config);\n timer = setTimeout(() => void tick(), intervalMs);\n }\n };\n\n // Initial check after a short delay (don't block startup)\n timer = setTimeout(() => void tick(), 5000);\n\n return () => {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"update-startup.js","names":[],"sources":["../../../src/infra/update-startup.ts"],"sourcesContent":["// src/infra/update-startup.ts\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { writeTextAtomic } from './write-file-atomic.js';\nimport type { Config } from '../config/schema.js';\nimport { resolveUpdateCheckStatePath } from '../config/paths-state.js';\nimport { acquireUpdateLock } from './update-lock.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from './update-channels.js';\nimport {\n compareSemver,\n resolveNpmChannelTag,\n detectInstallKind,\n resolvePackageRoot,\n type InstallKind,\n type UpdateAvailable,\n} from './update-check.js';\nimport type { InProcessRestartTrigger } from './update-restart.js';\n\nconst log = createLogger('UpdateCheck');\n\n// --- State persistence ---\n\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\ntype UpdateCheckState = {\n /** `package.json` version at the last successful registry check (used to bypass 24h throttle when you bump the local version). */\n lastCheckPackageVersion?: string;\n lastCheckedAt?: string;\n lastAvailableVersion?: string;\n lastAvailableTag?: string;\n lastNotifiedVersion?: string;\n lastNotifiedTag?: string;\n autoInstallId?: string;\n autoFirstSeenVersion?: string;\n autoFirstSeenTag?: string;\n autoFirstSeenAt?: string;\n autoLastAttemptVersion?: string;\n autoLastAttemptAt?: string;\n autoLastSuccessVersion?: string;\n autoLastSuccessAt?: string;\n};\n\n// --- In-memory cache ---\n\nlet updateAvailableCache: UpdateAvailable | null = null;\n\n/** Get the cached update-available state (populated after startup check). */\nexport function getUpdateAvailable(): UpdateAvailable | null {\n return updateAvailableCache;\n}\n\n// --- Core logic ---\n\nasync function readState(statePath: string): Promise<UpdateCheckState> {\n try {\n const raw = await readFile(statePath, 'utf-8');\n const parsed = JSON.parse(raw) as UpdateCheckState;\n if (!parsed || typeof parsed !== 'object') {\n log.warn({ statePath }, 'Update check state file contains non-object; resetting');\n return {};\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn({ err, statePath }, 'Failed to read update check state; resetting');\n }\n return {};\n }\n}\n\nasync function writeState(statePath: string, state: UpdateCheckState): Promise<void> {\n await writeTextAtomic(statePath, JSON.stringify(state, null, 2));\n}\n\nfunction resolveCheckIntervalMs(config: Config): number {\n const auto = config.update?.auto;\n if (!auto?.enabled) return CHECK_INTERVAL_MS;\n\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n if (channel === 'beta') {\n const hours = auto.betaCheckIntervalHours ?? 1;\n return Math.max(ONE_HOUR_MS / 4, Math.floor(hours * ONE_HOUR_MS));\n }\n return ONE_HOUR_MS;\n}\n\n/**\n * Compute a deterministic delay for stable auto-update rollout,\n * based on a per-installation hash to spread updates over time.\n */\nfunction resolveStableJitterMs(\n installId: string,\n version: string,\n tag: string,\n jitterWindowMs: number,\n): number {\n if (jitterWindowMs <= 0) return 0;\n const hash = createHash('sha256').update(`${installId}:${version}:${tag}`).digest();\n const bucket = hash.readUInt32BE(0);\n return bucket % (Math.floor(jitterWindowMs) + 1);\n}\n\n/**\n * Main startup update check. Called once when the gateway starts (or on demand).\n */\nexport async function runGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n triggerInProcessRestart?: InProcessRestartTrigger;\n /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */\n force?: boolean;\n}): Promise<void> {\n const { config, force } = params;\n\n const autoEnabled = config.update?.auto?.enabled ?? false;\n const shouldCheckHints = config.update?.checkOnStart !== false;\n if (!force && !shouldCheckHints && !autoEnabled) return;\n\n const statePath = resolveUpdateCheckStatePath();\n const state = await readState(statePath);\n const now = Date.now();\n\n // Hydrate from persisted state if within throttle window\n const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;\n if (state.lastAvailableVersion && (shouldCheckHints || force)) {\n const comparison = compareSemver(PACKAGE_VERSION, state.lastAvailableVersion);\n if (comparison !== null && comparison < 0) {\n const cached: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: state.lastAvailableVersion,\n channel: state.lastAvailableTag ?? 'latest',\n };\n updateAvailableCache = cached;\n params.onUpdateAvailableChange?.(cached);\n }\n }\n\n const checkIntervalMs = resolveCheckIntervalMs(config);\n // Re-check npm when the local package version changed (e.g. after editing package.json) even within 24h.\n const shouldBypassThrottleForVersion =\n state.lastCheckPackageVersion === undefined || state.lastCheckPackageVersion !== PACKAGE_VERSION;\n if (\n !force &&\n !shouldBypassThrottleForVersion &&\n lastCheckedAt &&\n Number.isFinite(lastCheckedAt) &&\n now - lastCheckedAt < checkIntervalMs\n ) {\n return; // Within throttle window\n }\n\n // Install kind: auto-install only for npm global installs, but we still query npm in git\n // so the Web UI / CLI can show \"newer on registry\" and the top reminder bar.\n const root = await resolvePackageRoot();\n let installKind: InstallKind = 'unknown';\n if (root) {\n installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n log.info('Update check: git checkout (hint-only; use git pull to update, no auto npm install)');\n }\n }\n\n // Query npm registry\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 2500 });\n\n const nextState: UpdateCheckState = {\n ...state,\n lastCheckedAt: new Date(now).toISOString(),\n };\n\n if (!resolved.version) {\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n return;\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison !== null && comparison < 0) {\n // Update available\n const updateInfo: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n };\n\n if (shouldCheckHints || force) {\n updateAvailableCache = updateInfo;\n params.onUpdateAvailableChange?.(updateInfo);\n }\n\n nextState.lastAvailableVersion = resolved.version;\n nextState.lastAvailableTag = resolved.tag;\n\n // Log notification (once per version)\n const shouldNotify =\n state.lastNotifiedVersion !== resolved.version || state.lastNotifiedTag !== resolved.tag;\n if ((shouldCheckHints || force) && shouldNotify) {\n log.info(\n { currentVersion: PACKAGE_VERSION, latestVersion: resolved.version, tag: resolved.tag },\n `Update available (${resolved.tag}): v${resolved.version} (current v${PACKAGE_VERSION}). Run: xopc update`,\n );\n nextState.lastNotifiedVersion = resolved.version;\n nextState.lastNotifiedTag = resolved.tag;\n }\n\n // Auto-update logic (never from a git worktree)\n if (\n autoEnabled &&\n (channel === 'stable' || channel === 'beta') &&\n installKind !== 'git'\n ) {\n await handleAutoUpdate({\n channel,\n version: resolved.version,\n tag: resolved.tag,\n state,\n nextState,\n now,\n root,\n config,\n triggerInProcessRestart: params.triggerInProcessRestart,\n });\n }\n } else {\n // Current version is up to date or newer\n delete nextState.lastAvailableVersion;\n delete nextState.lastAvailableTag;\n updateAvailableCache = null;\n params.onUpdateAvailableChange?.(null);\n }\n\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n}\n\nasync function handleAutoUpdate(params: {\n channel: 'stable' | 'beta';\n version: string;\n tag: string;\n state: UpdateCheckState;\n nextState: UpdateCheckState;\n now: number;\n root: string | null;\n config: Config;\n triggerInProcessRestart?: InProcessRestartTrigger;\n}): Promise<void> {\n const { channel, version, tag, state, nextState, now, root, config } = params;\n const auto = config.update?.auto;\n if (!auto) return;\n\n const stableDelayHours = auto.stableDelayHours ?? 6;\n const stableJitterHours = auto.stableJitterHours ?? 12;\n const betaCheckIntervalHours = auto.betaCheckIntervalHours ?? 1;\n\n // Rate limit: don't re-attempt same version within interval\n const attemptIntervalMs =\n channel === 'beta'\n ? Math.max(ONE_HOUR_MS / 4, Math.floor(betaCheckIntervalHours * ONE_HOUR_MS))\n : ONE_HOUR_MS;\n const lastAttemptAt = state.autoLastAttemptAt ? Date.parse(state.autoLastAttemptAt) : null;\n const recentAttempt =\n state.autoLastAttemptVersion === version &&\n lastAttemptAt !== null &&\n Number.isFinite(lastAttemptAt) &&\n now - lastAttemptAt < attemptIntervalMs;\n\n if (recentAttempt) {\n log.info({ version, tag }, 'Auto-update deferred: recent attempt exists');\n return;\n }\n\n // Stable rollout delay + jitter\n if (channel === 'stable') {\n if (!nextState.autoInstallId) {\n nextState.autoInstallId = state.autoInstallId?.trim() || randomUUID();\n }\n // Track first-seen time for this version\n if (state.autoFirstSeenVersion !== version || state.autoFirstSeenTag !== tag) {\n nextState.autoFirstSeenVersion = version;\n nextState.autoFirstSeenTag = tag;\n nextState.autoFirstSeenAt = new Date(now).toISOString();\n } else {\n nextState.autoFirstSeenAt = state.autoFirstSeenAt;\n }\n\n const firstSeenMs = nextState.autoFirstSeenAt ? Date.parse(nextState.autoFirstSeenAt) : now;\n const baseDelayMs = Math.max(0, stableDelayHours) * ONE_HOUR_MS;\n const jitterWindowMs = Math.max(0, stableJitterHours) * ONE_HOUR_MS;\n const jitterMs = resolveStableJitterMs(nextState.autoInstallId, version, tag, jitterWindowMs);\n const applyAfterMs = firstSeenMs + baseDelayMs + jitterMs;\n\n if (now < applyAfterMs) {\n log.info(\n { version, tag, applyAfter: new Date(applyAfterMs).toISOString() },\n 'Auto-update deferred: stable rollout window not yet due',\n );\n return;\n }\n }\n\n // Execute auto-update\n nextState.autoLastAttemptVersion = version;\n nextState.autoLastAttemptAt = new Date(now).toISOString();\n\n log.info({ channel, version, tag }, 'Starting auto-update');\n\n const lock = await acquireUpdateLock('auto');\n if (!lock) {\n log.info({ version, tag }, 'Auto-update skipped: another update is in progress');\n return;\n }\n try {\n const { runAutoUpdateCommand } = await import('./update-runner.js');\n const result = await runAutoUpdateCommand({\n channel,\n root,\n triggerInProcessRestart: params.triggerInProcessRestart,\n });\n if (result.ok) {\n nextState.autoLastSuccessVersion = version;\n nextState.autoLastSuccessAt = new Date(now).toISOString();\n const restartMode = result.result?.postUpdate?.restart?.mode;\n log.info(\n { channel, version, tag, restartMode },\n restartMode === 'in-process'\n ? 'Auto-update applied; gateway restart scheduled'\n : 'Auto-update applied successfully',\n );\n } else {\n log.warn(\n { channel, version, tag, exitCode: result.exitCode, reason: result.reason },\n `Auto-update attempt failed: ${result.reason ?? `exit ${result.exitCode}`}`,\n );\n }\n } catch (err) {\n log.error({ err, channel, version }, 'Auto-update command threw');\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Schedule periodic update checks. Returns a cleanup function to stop the timer.\n */\nexport function scheduleGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n triggerInProcessRestart?: InProcessRestartTrigger;\n}): () => void {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const tick = async () => {\n if (stopped) return;\n try {\n await runGatewayUpdateCheck(params);\n } catch (err) {\n log.warn({ err }, 'Periodic update check failed');\n }\n if (!stopped) {\n const intervalMs = resolveCheckIntervalMs(params.config);\n timer = setTimeout(() => void tick(), intervalMs);\n }\n };\n\n // Initial check after a short delay (don't block startup)\n timer = setTimeout(() => void tick(), 5000);\n\n return () => {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n}\n"],"mappings":"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"}
@@ -32,7 +32,15 @@ export declare function isProviderConfiguredSync(provider: string): boolean;
32
32
  export declare function isProviderConfigured(provider: string): Promise<boolean>;
33
33
  /** Where runtime {@link getApiKey} resolves the key from (no secret values). */
34
34
  export type ProviderActiveKeySource = 'none' | 'agent' | 'gateway' | 'oauth' | 'env' | 'models_json' | 'extension';
35
+ export type ProviderAuthMode = ProviderActiveKeySource;
36
+ export type ProviderAuthStatus = 'connected' | 'expired' | 'not_connected';
37
+ export interface ProviderAuthState {
38
+ authMode: ProviderAuthMode;
39
+ authStatus: ProviderAuthStatus;
40
+ expiresAt?: number;
41
+ }
35
42
  export declare function getProviderActiveKeySource(provider: string): Promise<ProviderActiveKeySource>;
43
+ export declare function getProviderAuthState(provider: string): Promise<ProviderAuthState>;
36
44
  export declare function getConfiguredProviders(): Promise<string[]>;
37
45
  export declare function getAllModels(): readonly Model<Api>[];
38
46
  export declare function getAvailableModels(): Promise<readonly Model<Api>[]>;
@@ -26,6 +26,7 @@ var providers_exports = /* @__PURE__ */ __exportAll({
26
26
  getModelRegistry: () => getModelRegistry,
27
27
  getModelsByProvider: () => getModelsByProvider,
28
28
  getProviderActiveKeySource: () => getProviderActiveKeySource,
29
+ getProviderAuthState: () => getProviderAuthState,
29
30
  getProviderDisplayName: () => getProviderDisplayName,
30
31
  getSortedProviders: () => getSortedProviders,
31
32
  isProviderConfigured: () => isProviderConfigured,
@@ -139,14 +140,49 @@ async function isProviderConfigured(provider) {
139
140
  return await hasCredentials(provider);
140
141
  }
141
142
  async function getProviderActiveKeySource(provider) {
142
- if (getProviderRegistry().has(provider)) return "extension";
143
- const fromCredentials = await new CredentialResolver().resolveApiKeySource(provider);
144
- if (fromCredentials === "agent") return "agent";
145
- if (fromCredentials === "global") return "gateway";
146
- if (fromCredentials === "oauth") return "oauth";
147
- if (fromCredentials === "env") return "env";
148
- if (getModelRegistry().getApiKey(provider)) return "models_json";
149
- return "none";
143
+ return (await getProviderAuthState(provider)).authMode;
144
+ }
145
+ async function getProviderAuthState(provider) {
146
+ if (getProviderRegistry().has(provider)) return {
147
+ authMode: "extension",
148
+ authStatus: "connected"
149
+ };
150
+ const resolver = new CredentialResolver();
151
+ const fromCredentials = await resolver.resolveApiKeySource(provider);
152
+ if (fromCredentials === "agent") return {
153
+ authMode: "agent",
154
+ authStatus: "connected"
155
+ };
156
+ if (fromCredentials === "global") return {
157
+ authMode: "gateway",
158
+ authStatus: "connected"
159
+ };
160
+ if (fromCredentials === "oauth") {
161
+ const token = await resolver.loadOAuthTokenRecord(provider);
162
+ return {
163
+ authMode: "oauth",
164
+ authStatus: Boolean(token?.expiresAt && token.expiresAt < Date.now()) ? "expired" : "connected",
165
+ ...token?.expiresAt ? { expiresAt: token.expiresAt } : {}
166
+ };
167
+ }
168
+ if (fromCredentials === "env") return {
169
+ authMode: "env",
170
+ authStatus: "connected"
171
+ };
172
+ const expiredOAuthToken = await resolver.loadOAuthTokenRecord(provider);
173
+ if (expiredOAuthToken?.expiresAt && expiredOAuthToken.expiresAt < Date.now()) return {
174
+ authMode: "oauth",
175
+ authStatus: "expired",
176
+ expiresAt: expiredOAuthToken.expiresAt
177
+ };
178
+ if (getModelRegistry().getApiKey(provider)) return {
179
+ authMode: "models_json",
180
+ authStatus: "connected"
181
+ };
182
+ return {
183
+ authMode: "none",
184
+ authStatus: "not_connected"
185
+ };
150
186
  }
151
187
  async function getConfiguredProviders() {
152
188
  const allProviders = getAllProviders();
@@ -412,7 +448,8 @@ var init_providers = __esmMin((() => {
412
448
  "github-copilot": {
413
449
  name: "GitHub Copilot (OAuth)",
414
450
  category: "oauth",
415
- supportsOAuth: true
451
+ supportsOAuth: true,
452
+ supportsApiKey: false
416
453
  },
417
454
  "openai-codex": {
418
455
  name: "OpenAI Codex (OAuth)",
@@ -423,18 +460,20 @@ var init_providers = __esmMin((() => {
423
460
  "google-gemini-cli": {
424
461
  name: "Google Gemini CLI (OAuth)",
425
462
  category: "oauth",
426
- supportsOAuth: true
463
+ supportsOAuth: true,
464
+ supportsApiKey: false
427
465
  },
428
466
  "google-antigravity": {
429
467
  name: "Google Antigravity (OAuth)",
430
468
  category: "oauth",
431
- supportsOAuth: true
469
+ supportsOAuth: true,
470
+ supportsApiKey: false
432
471
  }
433
472
  };
434
473
  DEFAULT_FALLBACK_MODEL = "deepseek/deepseek-v4-flash";
435
474
  }));
436
475
  //#endregion
437
476
  init_providers();
438
- export { EXTENSION_PROVIDER_BASE_URL, ModelRegistry, PROVIDER_ENV_MAP, PROVIDER_META, getAllModels, getAllProviders, getApiKey, getApiKeyFromEnv, getApiKeySync, getAvailableModels, getConfiguredProviders, getDefaultModel, getDefaultModelSync, getModelRegistry, getModelsByProvider, getProviderActiveKeySource, getProviderDisplayName, getSortedProviders, init_providers, isProviderConfigured, isProviderConfiguredSync, pluginModelToModel, prewarmModelRegistry, providerSupportsApiKey, providerSupportsOAuth, providers_exports, resetModelRegistry, resolveModel };
477
+ export { EXTENSION_PROVIDER_BASE_URL, ModelRegistry, PROVIDER_ENV_MAP, PROVIDER_META, getAllModels, getAllProviders, getApiKey, getApiKeyFromEnv, getApiKeySync, getAvailableModels, getConfiguredProviders, getDefaultModel, getDefaultModelSync, getModelRegistry, getModelsByProvider, getProviderActiveKeySource, getProviderAuthState, getProviderDisplayName, getSortedProviders, init_providers, isProviderConfigured, isProviderConfiguredSync, pluginModelToModel, prewarmModelRegistry, providerSupportsApiKey, providerSupportsOAuth, providers_exports, resetModelRegistry, resolveModel };
439
478
 
440
479
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["getPiAiModel","getPiAiProviders","getPiAiModels"],"sources":["../../../src/providers/index.ts"],"sourcesContent":["/**\n * Model provider module - integrates built-in models with custom models from models.json\n */\n\nimport {\n\tgetModel as getPiAiModel,\n\tgetModels as getPiAiModels,\n\tgetProviders as getPiAiProviders,\n\ttype Model,\n\ttype Api,\n} from '@earendil-works/pi-ai';\nimport type { Config } from '../config/schema.js';\nimport { getModelRegistry } from './model-registry.js';\nimport { CredentialResolver, resolveApiKey, hasCredentials } from '../auth/credentials.js';\nimport { hasProviderAuthOnDiskSync } from '../auth/sync-provider-auth.js';\nimport { getApiKeyFromEnv } from './env-keys.js';\nimport { getProviderRegistry } from './plugin-registry.js';\nimport type { ProviderModelDefinition } from '../extensions/types/providers.js';\n\nexport { getApiKeyFromEnv, PROVIDER_ENV_MAP } from './env-keys.js';\n\n/** Sentinel base URL: model is served by an extension {@link ProviderPluginRegistry} provider. */\nexport const EXTENSION_PROVIDER_BASE_URL = 'extension://provider-plugin';\n\n/** Map a plugin registry model to the pi-ai {@link Model} shape. */\nexport function pluginModelToModel(providerId: string, definition: ProviderModelDefinition): Model<Api> {\n\treturn {\n\t\tprovider: providerId,\n\t\tid: definition.id,\n\t\tname: definition.name,\n\t\tapi: 'openai-completions' as Api,\n\t\tbaseUrl: EXTENSION_PROVIDER_BASE_URL,\n\t\treasoning: false,\n\t\tinput: definition.supportsImages ? (['text', 'image'] as ('text' | 'image')[]) : (['text'] as ('text' | 'image')[]),\n\t\tcontextWindow: definition.contextWindow ?? 128000,\n\t\tmaxTokens: definition.maxOutputTokens ?? 4096,\n\t\tcost: {\n\t\t\tinput: definition.pricing?.input ?? 0,\n\t\t\toutput: definition.pricing?.output ?? 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t},\n\t} as Model<Api>;\n}\n\n/**\n * Get API key synchronously: checks registry (models.json) first, then environment variables.\n * Use this for Agent's getApiKey callback which must be synchronous.\n */\nexport function getApiKeySync(provider: string): string | undefined {\n const pluginRegistry = getProviderRegistry();\n if (pluginRegistry.has(provider)) return 'extension-managed';\n\n const registry = getModelRegistry();\n const registryKey = registry.getApiKey(provider);\n if (registryKey) {\n return registryKey;\n }\n return getApiKeyFromEnv(provider);\n}\n\n/**\n * Resolve model reference. Supports:\n * - \"provider/modelId\" format\n * - \"modelId\" auto-detection via pi-ai or custom models\n * @throws if model not found\n */\nexport function resolveModel(ref: string): Model<Api> {\n\t// First try ModelRegistry (includes custom models)\n\tconst registry = getModelRegistry();\n\tconst customModel = registry.resolve(ref);\n\tif (customModel) {\n\t\treturn customModel;\n\t}\n\n\tif (ref.includes('/')) {\n\t\tconst [provider, modelId] = ref.split('/');\n\t\tconst piAiModel = getPiAiModel(provider as any, modelId as any);\n\t\tif (piAiModel) return piAiModel as Model<Api>;\n\n\t\tconst pluginRegistry = getProviderRegistry();\n\t\tconst plugin = pluginRegistry.get(provider);\n\t\tif (plugin) {\n\t\t\tconst pluginModel = plugin.models.find(m => m.id === modelId);\n\t\t\tif (pluginModel) return pluginModelToModel(provider, pluginModel);\n\t\t}\n\t\tthrow new Error(`Model not found: ${ref}`);\n\t}\n\n\tfor (const provider of getPiAiProviders()) {\n\t\ttry {\n\t\t\tconst models = getPiAiModels(provider);\n\t\t\tconst found = models.find(m => m.id === ref);\n\t\t\tif (found) return found as Model<Api>;\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\tconst pluginRegistry = getProviderRegistry();\n\tfor (const plugin of pluginRegistry.listAll()) {\n\t\tconst found = plugin.models.find(m => m.id === ref);\n\t\tif (found) return pluginModelToModel(plugin.id, found);\n\t}\n\n\tthrow new Error(`Model not found: ${ref}. Use format: provider/model-id`);\n}\n\nexport function getModelsByProvider(provider: string): readonly Model<Api>[] {\n\tconst registry = getModelRegistry();\n\tconst fromRegistry = registry.getAll().filter(m => m.provider === provider);\n\tconst plugin = getProviderRegistry().get(provider);\n\tif (!plugin) return fromRegistry;\n\tconst pluginModels = plugin.models.map(m => pluginModelToModel(provider, m));\n\treturn [...fromRegistry, ...pluginModels];\n}\n\nexport function getAllProviders(): string[] {\n\tconst registry = getModelRegistry();\n\tconst providers = new Set<string>();\n\n\t// Add built-in providers\n\tfor (const p of getPiAiProviders()) {\n\t\tproviders.add(p);\n\t}\n\n\t// Add custom providers from registry\n\tfor (const m of registry.getAll()) {\n\t\tproviders.add(m.provider);\n\t}\n\n\tfor (const plugin of getProviderRegistry().listAll()) {\n\t\tproviders.add(plugin.id);\n\t}\n\n\treturn Array.from(providers);\n}\n\nexport async function getApiKey(provider: string): Promise<string | undefined> {\n\tif (getProviderRegistry().has(provider)) return 'extension-managed';\n\n\t// Use new credential resolver first (checks: agent private > global > oauth > env)\n\tconst credentialKey = await resolveApiKey(provider);\n\tif (credentialKey) {\n\t\treturn credentialKey;\n\t}\n\n\t// Check registry for custom providers (from models.json)\n\tconst registry = getModelRegistry();\n\tconst registryKey = registry.getApiKey(provider);\n\tif (registryKey) {\n\t\treturn registryKey;\n\t}\n\n\t// Fallback to environment variables\n\treturn getApiKeyFromEnv(provider);\n}\n\n/**\n * Synchronous version for use in non-async contexts\n * Only checks environment variables and registry, not credential system\n */\nexport function isProviderConfiguredSync(provider: string): boolean {\n\tif (getProviderRegistry().has(provider)) return true;\n\n\t// Check registry for custom providers\n\tconst registry = getModelRegistry();\n\tif (registry.getApiKey(provider)) {\n\t\treturn true;\n\t}\n\t// Check environment variables\n\tif (getApiKeyFromEnv(provider)) {\n\t\treturn true;\n\t}\n\t// Gateway UI / CLI store keys in auth-profiles.json (async CredentialResolver); sync path for fallback list\n\treturn hasProviderAuthOnDiskSync(provider);\n}\n\nexport async function isProviderConfigured(provider: string): Promise<boolean> {\n if (getProviderRegistry().has(provider)) return true;\n\n // Check registry first for custom providers (from models.json)\n const registry = getModelRegistry();\n if (registry.getApiKey(provider)) {\n return true;\n }\n return await hasCredentials(provider);\n}\n\n/** Where runtime {@link getApiKey} resolves the key from (no secret values). */\nexport type ProviderActiveKeySource = 'none' | 'agent' | 'gateway' | 'oauth' | 'env' | 'models_json' | 'extension';\n\nexport async function getProviderActiveKeySource(provider: string): Promise<ProviderActiveKeySource> {\n if (getProviderRegistry().has(provider)) return 'extension';\n\n const resolver = new CredentialResolver();\n const fromCredentials = await resolver.resolveApiKeySource(provider);\n if (fromCredentials === 'agent') return 'agent';\n if (fromCredentials === 'global') return 'gateway';\n if (fromCredentials === 'oauth') return 'oauth';\n if (fromCredentials === 'env') return 'env';\n\n const registry = getModelRegistry();\n if (registry.getApiKey(provider)) {\n return 'models_json';\n }\n\n return 'none';\n}\n\nexport async function getConfiguredProviders(): Promise<string[]> {\n\tconst allProviders = getAllProviders();\n\tconst configured: string[] = [];\n\tfor (const p of allProviders) {\n\t\tif (await isProviderConfigured(p)) {\n\t\t\tconfigured.push(p);\n\t\t}\n\t}\n\treturn configured;\n}\n\nexport function getAllModels(): readonly Model<Api>[] {\n\tconst registry = getModelRegistry();\n\tconst registryModels = registry.getAll();\n\tconst pluginProviders = getProviderRegistry().listAll();\n\tif (pluginProviders.length === 0) return registryModels;\n\n\tconst existingIds = new Set(registryModels.map(m => `${m.provider}/${m.id}`));\n\tconst merged: Model<Api>[] = [...registryModels];\n\tfor (const plugin of pluginProviders) {\n\t\tfor (const model of plugin.models) {\n\t\t\tconst compositeId = `${plugin.id}/${model.id}`;\n\t\t\tif (!existingIds.has(compositeId)) {\n\t\t\t\tmerged.push(pluginModelToModel(plugin.id, model));\n\t\t\t\texistingIds.add(compositeId);\n\t\t\t}\n\t\t}\n\t}\n\treturn merged;\n}\n\nexport async function getAvailableModels(): Promise<readonly Model<Api>[]> {\n\tconst allModels = getAllModels();\n\tconst pluginRegistry = getProviderRegistry();\n\tconst available: Model<Api>[] = [];\n\n\tfor (const model of allModels) {\n\t\tif (pluginRegistry.has(model.provider)) {\n\t\t\tavailable.push(model);\n\t\t} else if (await isProviderConfigured(model.provider)) {\n\t\t\tavailable.push(model);\n\t\t}\n\t}\n\treturn available;\n}\n\nexport type { Model, Api } from '@earendil-works/pi-ai';\n\nexport type ProviderCategory = 'common' | 'specialty' | 'oauth' | 'enterprise' | 'extension';\n\nexport interface ProviderMeta {\n name: string;\n category: ProviderCategory;\n supportsOAuth?: boolean;\n supportsApiKey?: boolean;\n}\n\nexport const PROVIDER_META: Record<string, ProviderMeta> = {\n 'openai': { name: 'OpenAI (GPT-4, o1, o3)', category: 'common', supportsApiKey: true },\n 'anthropic': { name: 'Anthropic Claude', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'deepseek': { name: 'DeepSeek', category: 'common', supportsApiKey: true },\n 'google': { name: 'Google Gemini', category: 'common', supportsApiKey: true },\n 'groq': { name: 'Groq (Fast Inference)', category: 'common', supportsApiKey: true },\n 'minimax': { name: 'MiniMax', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'minimax-cn': { name: 'MiniMax CN', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'kimi-coding': { name: 'Kimi For Coding', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'xai': { name: 'xAI (Grok)', category: 'specialty', supportsApiKey: true },\n 'mistral': { name: 'Mistral AI', category: 'specialty', supportsApiKey: true },\n 'cerebras': { name: 'Cerebras', category: 'specialty', supportsApiKey: true },\n 'openrouter': { name: 'OpenRouter (Multi-provider)', category: 'specialty', supportsApiKey: true },\n 'huggingface': { name: 'Hugging Face', category: 'specialty', supportsApiKey: true },\n moonshotai: { name: 'Moonshot AI (Kimi · International)', category: 'common', supportsApiKey: true },\n 'moonshotai-cn': { name: 'Moonshot AI (Kimi · China)', category: 'common', supportsApiKey: true },\n fireworks: { name: 'Fireworks AI', category: 'specialty', supportsApiKey: true },\n together: { name: 'Together AI', category: 'specialty', supportsApiKey: true },\n 'cloudflare-workers-ai': { name: 'Cloudflare Workers AI', category: 'enterprise', supportsApiKey: true },\n 'cloudflare-ai-gateway': { name: 'Cloudflare AI Gateway', category: 'enterprise', supportsApiKey: true },\n xiaomi: { name: 'Xiaomi Mimo', category: 'specialty', supportsApiKey: true },\n 'xiaomi-token-plan-cn': { name: 'Xiaomi Token Plan (CN)', category: 'specialty', supportsApiKey: true },\n 'xiaomi-token-plan-ams': { name: 'Xiaomi Token Plan (AMS)', category: 'specialty', supportsApiKey: true },\n 'xiaomi-token-plan-sgp': { name: 'Xiaomi Token Plan (SGP)', category: 'specialty', supportsApiKey: true },\n 'opencode': { name: 'OpenCode', category: 'specialty', supportsApiKey: true },\n 'opencode-go': { name: 'OpenCode Go', category: 'specialty', supportsApiKey: true },\n /** DashScope (Alibaba) — image, speech, STT; not an LLM KnownProvider. */\n 'dashscope': { name: 'DashScope (Alibaba)', category: 'specialty', supportsApiKey: true },\n /** International GLM (api.z.ai). Auth: API key (ZAI_API_KEY); no published OAuth for this HTTP API. */\n 'zai': { name: 'Zhipu GLM (International · z.ai)', category: 'common', supportsApiKey: true },\n 'amazon-bedrock': { name: 'Amazon Bedrock', category: 'enterprise', supportsApiKey: true },\n 'azure-openai-responses': { name: 'Azure OpenAI', category: 'enterprise', supportsApiKey: true },\n 'google-vertex': { name: 'Google Vertex AI', category: 'enterprise', supportsApiKey: true },\n 'vercel-ai-gateway': { name: 'Vercel AI Gateway', category: 'enterprise', supportsApiKey: true },\n 'github-copilot': { name: 'GitHub Copilot (OAuth)', category: 'oauth', supportsOAuth: true },\n 'openai-codex': { name: 'OpenAI Codex (OAuth)', category: 'oauth', supportsOAuth: true, supportsApiKey: false },\n 'google-gemini-cli': { name: 'Google Gemini CLI (OAuth)', category: 'oauth', supportsOAuth: true },\n 'google-antigravity': { name: 'Google Antigravity (OAuth)', category: 'oauth', supportsOAuth: true },\n};\n\nexport function getSortedProviders(): string[] {\n const all = getAllProviders();\n const catOrder: Record<ProviderCategory, number> = { common: 0, specialty: 1, enterprise: 2, oauth: 3, extension: 4 };\n const pluginRegistry = getProviderRegistry();\n\n return [...all].sort((a, b) => {\n const catA = pluginRegistry.has(a) ? 'extension' : (PROVIDER_META[a]?.category ?? 'specialty');\n const catB = pluginRegistry.has(b) ? 'extension' : (PROVIDER_META[b]?.category ?? 'specialty');\n if (catOrder[catA] !== catOrder[catB]) {\n return catOrder[catA] - catOrder[catB];\n }\n return a.localeCompare(b);\n });\n}\n\nexport function getProviderDisplayName(provider: string): string {\n const plugin = getProviderRegistry().get(provider);\n if (plugin) return plugin.name;\n return PROVIDER_META[provider]?.name || provider;\n}\n\nexport function providerSupportsOAuth(provider: string): boolean {\n return PROVIDER_META[provider]?.supportsOAuth ?? false;\n}\n\nexport function providerSupportsApiKey(provider: string): boolean {\n return PROVIDER_META[provider]?.supportsApiKey ?? true;\n}\n\n// ============================================\n// Dynamic Default Model Resolution\n// ============================================\n\n/** Preferred default model when no explicit model is configured. */\nconst DEFAULT_FALLBACK_MODEL = 'deepseek/deepseek-v4-flash';\n\n/**\n * Get a default model reference.\n * Priority:\n * 1. Explicitly configured model in agents.defaults.model (if set and available)\n * 2. Default fallback: deepseek/deepseek-v4-flash\n */\nexport async function getDefaultModel(config?: Config | null | undefined): Promise<string> {\n const defaultModel = config?.agents?.defaults?.model;\n if (defaultModel) {\n const modelRef = typeof defaultModel === 'string' ? defaultModel : defaultModel.primary;\n if (modelRef) {\n const availableModels = await getAvailableModels();\n const configured = availableModels.find(m => \n `${m.provider}/${m.id}` === modelRef ||\n m.id === modelRef\n );\n if (configured) {\n return `${configured.provider}/${configured.id}`;\n }\n return modelRef;\n }\n }\n\n return DEFAULT_FALLBACK_MODEL;\n}\n\n/**\n * Synchronous default model resolution for constructors and sync code paths.\n * Uses catalog/registry only (no async credential checks).\n *\n * When no model is explicitly configured, returns the preferred default\n * (`deepseek/deepseek-v4-flash`) rather than picking an arbitrary first\n * model from the full catalog.\n */\nexport function getDefaultModelSync(config?: Config | null | undefined): string {\n const defaultModel = config?.agents?.defaults?.model;\n if (defaultModel) {\n const modelRef = typeof defaultModel === 'string' ? defaultModel : defaultModel.primary;\n if (modelRef) {\n return modelRef;\n }\n }\n\n return DEFAULT_FALLBACK_MODEL;\n}\n\n// Re-export ModelRegistry for advanced use cases\nexport { ModelRegistry, getModelRegistry, resetModelRegistry, prewarmModelRegistry } from './model-registry.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,mBAAmB,YAAoB,YAAiD;AACvG,QAAO;EACN,UAAU;EACV,IAAI,WAAW;EACf,MAAM,WAAW;EACjB,KAAK;EACL,SAAS;EACT,WAAW;EACX,OAAO,WAAW,iBAAkB,CAAC,QAAQ,QAAQ,GAA6B,CAAC,OAAO;EAC1F,eAAe,WAAW,iBAAiB;EAC3C,WAAW,WAAW,mBAAmB;EACzC,MAAM;GACL,OAAO,WAAW,SAAS,SAAS;GACpC,QAAQ,WAAW,SAAS,UAAU;GACtC,WAAW;GACX,YAAY;GACZ;EACD;;;;;;AAOF,SAAgB,cAAc,UAAsC;AAElE,KADuB,qBACL,CAAC,IAAI,SAAS,CAAE,QAAO;CAGzC,MAAM,cADW,kBACW,CAAC,UAAU,SAAS;AAChD,KAAI,YACF,QAAO;AAET,QAAO,iBAAiB,SAAS;;;;;;;;AASnC,SAAgB,aAAa,KAAyB;CAGrD,MAAM,cADW,kBACW,CAAC,QAAQ,IAAI;AACzC,KAAI,YACH,QAAO;AAGR,KAAI,IAAI,SAAS,IAAI,EAAE;EACtB,MAAM,CAAC,UAAU,WAAW,IAAI,MAAM,IAAI;EAC1C,MAAM,YAAYA,SAAa,UAAiB,QAAe;AAC/D,MAAI,UAAW,QAAO;EAGtB,MAAM,SADiB,qBACM,CAAC,IAAI,SAAS;AAC3C,MAAI,QAAQ;GACX,MAAM,cAAc,OAAO,OAAO,MAAK,MAAK,EAAE,OAAO,QAAQ;AAC7D,OAAI,YAAa,QAAO,mBAAmB,UAAU,YAAY;;AAElE,QAAM,IAAI,MAAM,oBAAoB,MAAM;;AAG3C,MAAK,MAAM,YAAYC,cAAkB,CACxC,KAAI;EAEH,MAAM,QADSC,UAAc,SACT,CAAC,MAAK,MAAK,EAAE,OAAO,IAAI;AAC5C,MAAI,MAAO,QAAO;SACX;AACP;;CAIF,MAAM,iBAAiB,qBAAqB;AAC5C,MAAK,MAAM,UAAU,eAAe,SAAS,EAAE;EAC9C,MAAM,QAAQ,OAAO,OAAO,MAAK,MAAK,EAAE,OAAO,IAAI;AACnD,MAAI,MAAO,QAAO,mBAAmB,OAAO,IAAI,MAAM;;AAGvD,OAAM,IAAI,MAAM,oBAAoB,IAAI,iCAAiC;;AAG1E,SAAgB,oBAAoB,UAAyC;CAE5E,MAAM,eADW,kBACY,CAAC,QAAQ,CAAC,QAAO,MAAK,EAAE,aAAa,SAAS;CAC3E,MAAM,SAAS,qBAAqB,CAAC,IAAI,SAAS;AAClD,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,eAAe,OAAO,OAAO,KAAI,MAAK,mBAAmB,UAAU,EAAE,CAAC;AAC5E,QAAO,CAAC,GAAG,cAAc,GAAG,aAAa;;AAG1C,SAAgB,kBAA4B;CAC3C,MAAM,WAAW,kBAAkB;CACnC,MAAM,4BAAY,IAAI,KAAa;AAGnC,MAAK,MAAM,KAAKD,cAAkB,CACjC,WAAU,IAAI,EAAE;AAIjB,MAAK,MAAM,KAAK,SAAS,QAAQ,CAChC,WAAU,IAAI,EAAE,SAAS;AAG1B,MAAK,MAAM,UAAU,qBAAqB,CAAC,SAAS,CACnD,WAAU,IAAI,OAAO,GAAG;AAGzB,QAAO,MAAM,KAAK,UAAU;;AAG7B,eAAsB,UAAU,UAA+C;AAC9E,KAAI,qBAAqB,CAAC,IAAI,SAAS,CAAE,QAAO;CAGhD,MAAM,gBAAgB,MAAM,cAAc,SAAS;AACnD,KAAI,cACH,QAAO;CAKR,MAAM,cADW,kBACW,CAAC,UAAU,SAAS;AAChD,KAAI,YACH,QAAO;AAIR,QAAO,iBAAiB,SAAS;;;;;;AAOlC,SAAgB,yBAAyB,UAA2B;AACnE,KAAI,qBAAqB,CAAC,IAAI,SAAS,CAAE,QAAO;AAIhD,KADiB,kBACL,CAAC,UAAU,SAAS,CAC/B,QAAO;AAGR,KAAI,iBAAiB,SAAS,CAC7B,QAAO;AAGR,QAAO,0BAA0B,SAAS;;AAG3C,eAAsB,qBAAqB,UAAoC;AAC7E,KAAI,qBAAqB,CAAC,IAAI,SAAS,CAAE,QAAO;AAIhD,KADiB,kBACL,CAAC,UAAU,SAAS,CAC9B,QAAO;AAET,QAAO,MAAM,eAAe,SAAS;;AAMvC,eAAsB,2BAA2B,UAAoD;AACnG,KAAI,qBAAqB,CAAC,IAAI,SAAS,CAAE,QAAO;CAGhD,MAAM,kBAAkB,MAAM,IADT,oBACiB,CAAC,oBAAoB,SAAS;AACpE,KAAI,oBAAoB,QAAS,QAAO;AACxC,KAAI,oBAAoB,SAAU,QAAO;AACzC,KAAI,oBAAoB,QAAS,QAAO;AACxC,KAAI,oBAAoB,MAAO,QAAO;AAGtC,KADiB,kBACL,CAAC,UAAU,SAAS,CAC9B,QAAO;AAGT,QAAO;;AAGT,eAAsB,yBAA4C;CACjE,MAAM,eAAe,iBAAiB;CACtC,MAAM,aAAuB,EAAE;AAC/B,MAAK,MAAM,KAAK,aACf,KAAI,MAAM,qBAAqB,EAAE,CAChC,YAAW,KAAK,EAAE;AAGpB,QAAO;;AAGR,SAAgB,eAAsC;CAErD,MAAM,iBADW,kBACc,CAAC,QAAQ;CACxC,MAAM,kBAAkB,qBAAqB,CAAC,SAAS;AACvD,KAAI,gBAAgB,WAAW,EAAG,QAAO;CAEzC,MAAM,cAAc,IAAI,IAAI,eAAe,KAAI,MAAK,GAAG,EAAE,SAAS,GAAG,EAAE,KAAK,CAAC;CAC7E,MAAM,SAAuB,CAAC,GAAG,eAAe;AAChD,MAAK,MAAM,UAAU,gBACpB,MAAK,MAAM,SAAS,OAAO,QAAQ;EAClC,MAAM,cAAc,GAAG,OAAO,GAAG,GAAG,MAAM;AAC1C,MAAI,CAAC,YAAY,IAAI,YAAY,EAAE;AAClC,UAAO,KAAK,mBAAmB,OAAO,IAAI,MAAM,CAAC;AACjD,eAAY,IAAI,YAAY;;;AAI/B,QAAO;;AAGR,eAAsB,qBAAqD;CAC1E,MAAM,YAAY,cAAc;CAChC,MAAM,iBAAiB,qBAAqB;CAC5C,MAAM,YAA0B,EAAE;AAElC,MAAK,MAAM,SAAS,UACnB,KAAI,eAAe,IAAI,MAAM,SAAS,CACrC,WAAU,KAAK,MAAM;UACX,MAAM,qBAAqB,MAAM,SAAS,CACpD,WAAU,KAAK,MAAM;AAGvB,QAAO;;AAsDR,SAAgB,qBAA+B;CAC7C,MAAM,MAAM,iBAAiB;CAC7B,MAAM,WAA6C;EAAE,QAAQ;EAAG,WAAW;EAAG,YAAY;EAAG,OAAO;EAAG,WAAW;EAAG;CACrH,MAAM,iBAAiB,qBAAqB;AAE5C,QAAO,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM;EAC7B,MAAM,OAAO,eAAe,IAAI,EAAE,GAAG,cAAe,cAAc,IAAI,YAAY;EAClF,MAAM,OAAO,eAAe,IAAI,EAAE,GAAG,cAAe,cAAc,IAAI,YAAY;AAClF,MAAI,SAAS,UAAU,SAAS,MAC9B,QAAO,SAAS,QAAQ,SAAS;AAEnC,SAAO,EAAE,cAAc,EAAE;GACzB;;AAGJ,SAAgB,uBAAuB,UAA0B;CAC/D,MAAM,SAAS,qBAAqB,CAAC,IAAI,SAAS;AAClD,KAAI,OAAQ,QAAO,OAAO;AAC1B,QAAO,cAAc,WAAW,QAAQ;;AAG1C,SAAgB,sBAAsB,UAA2B;AAC/D,QAAO,cAAc,WAAW,iBAAiB;;AAGnD,SAAgB,uBAAuB,UAA2B;AAChE,QAAO,cAAc,WAAW,kBAAkB;;;;;;;;AAgBpD,eAAsB,gBAAgB,QAAqD;CACzF,MAAM,eAAe,QAAQ,QAAQ,UAAU;AAC/C,KAAI,cAAc;EAChB,MAAM,WAAW,OAAO,iBAAiB,WAAW,eAAe,aAAa;AAChF,MAAI,UAAU;GAEZ,MAAM,cAAa,MADW,oBAAoB,EACf,MAAK,MACtC,GAAG,EAAE,SAAS,GAAG,EAAE,SAAS,YAC5B,EAAE,OAAO,SACV;AACD,OAAI,WACF,QAAO,GAAG,WAAW,SAAS,GAAG,WAAW;AAE9C,UAAO;;;AAIX,QAAO;;;;;;;;;;AAWT,SAAgB,oBAAoB,QAA4C;CAC9E,MAAM,eAAe,QAAQ,QAAQ,UAAU;AAC/C,KAAI,cAAc;EAChB,MAAM,WAAW,OAAO,iBAAiB,WAAW,eAAe,aAAa;AAChF,MAAI,SACF,QAAO;;AAIX,QAAO;;;;sBAtX8C;mBACoC;0BACjB;gBACzB;uBACU;AAM9C,+BAA8B;AAqP9B,iBAA8C;EACzD,UAAU;GAAE,MAAM;GAA0B,UAAU;GAAU,gBAAgB;GAAM;EACtF,aAAa;GAAE,MAAM;GAAoB,UAAU;GAAU,gBAAgB;GAAM,eAAe;GAAM;EACxG,YAAY;GAAE,MAAM;GAAY,UAAU;GAAU,gBAAgB;GAAM;EAC1E,UAAU;GAAE,MAAM;GAAiB,UAAU;GAAU,gBAAgB;GAAM;EAC7E,QAAQ;GAAE,MAAM;GAAyB,UAAU;GAAU,gBAAgB;GAAM;EACnF,WAAW;GAAE,MAAM;GAAW,UAAU;GAAU,gBAAgB;GAAM,eAAe;GAAM;EAC7F,cAAc;GAAE,MAAM;GAAc,UAAU;GAAU,gBAAgB;GAAM,eAAe;GAAM;EACnG,eAAe;GAAE,MAAM;GAAmB,UAAU;GAAU,gBAAgB;GAAM,eAAe;GAAM;EACzG,OAAO;GAAE,MAAM;GAAc,UAAU;GAAa,gBAAgB;GAAM;EAC1E,WAAW;GAAE,MAAM;GAAc,UAAU;GAAa,gBAAgB;GAAM;EAC9E,YAAY;GAAE,MAAM;GAAY,UAAU;GAAa,gBAAgB;GAAM;EAC7E,cAAc;GAAE,MAAM;GAA+B,UAAU;GAAa,gBAAgB;GAAM;EAClG,eAAe;GAAE,MAAM;GAAgB,UAAU;GAAa,gBAAgB;GAAM;EACpF,YAAY;GAAE,MAAM;GAAsC,UAAU;GAAU,gBAAgB;GAAM;EACpG,iBAAiB;GAAE,MAAM;GAA8B,UAAU;GAAU,gBAAgB;GAAM;EACjG,WAAW;GAAE,MAAM;GAAgB,UAAU;GAAa,gBAAgB;GAAM;EAChF,UAAU;GAAE,MAAM;GAAe,UAAU;GAAa,gBAAgB;GAAM;EAC9E,yBAAyB;GAAE,MAAM;GAAyB,UAAU;GAAc,gBAAgB;GAAM;EACxG,yBAAyB;GAAE,MAAM;GAAyB,UAAU;GAAc,gBAAgB;GAAM;EACxG,QAAQ;GAAE,MAAM;GAAe,UAAU;GAAa,gBAAgB;GAAM;EAC5E,wBAAwB;GAAE,MAAM;GAA0B,UAAU;GAAa,gBAAgB;GAAM;EACvG,yBAAyB;GAAE,MAAM;GAA2B,UAAU;GAAa,gBAAgB;GAAM;EACzG,yBAAyB;GAAE,MAAM;GAA2B,UAAU;GAAa,gBAAgB;GAAM;EACzG,YAAY;GAAE,MAAM;GAAY,UAAU;GAAa,gBAAgB;GAAM;EAC7E,eAAe;GAAE,MAAM;GAAe,UAAU;GAAa,gBAAgB;GAAM;;EAEnF,aAAa;GAAE,MAAM;GAAuB,UAAU;GAAa,gBAAgB;GAAM;;EAEzF,OAAO;GAAE,MAAM;GAAoC,UAAU;GAAU,gBAAgB;GAAM;EAC7F,kBAAkB;GAAE,MAAM;GAAkB,UAAU;GAAc,gBAAgB;GAAM;EAC1F,0BAA0B;GAAE,MAAM;GAAgB,UAAU;GAAc,gBAAgB;GAAM;EAChG,iBAAiB;GAAE,MAAM;GAAoB,UAAU;GAAc,gBAAgB;GAAM;EAC3F,qBAAqB;GAAE,MAAM;GAAqB,UAAU;GAAc,gBAAgB;GAAM;EAChG,kBAAkB;GAAE,MAAM;GAA0B,UAAU;GAAS,eAAe;GAAM;EAC5F,gBAAgB;GAAE,MAAM;GAAwB,UAAU;GAAS,eAAe;GAAM,gBAAgB;GAAO;EAC/G,qBAAqB;GAAE,MAAM;GAA6B,UAAU;GAAS,eAAe;GAAM;EAClG,sBAAsB;GAAE,MAAM;GAA8B,UAAU;GAAS,eAAe;GAAM;EACrG;AAoCK,0BAAyB"}
1
+ {"version":3,"file":"index.js","names":["getPiAiModel","getPiAiProviders","getPiAiModels"],"sources":["../../../src/providers/index.ts"],"sourcesContent":["/**\n * Model provider module - integrates built-in models with custom models from models.json\n */\n\nimport {\n\tgetModel as getPiAiModel,\n\tgetModels as getPiAiModels,\n\tgetProviders as getPiAiProviders,\n\ttype Model,\n\ttype Api,\n} from '@earendil-works/pi-ai';\nimport type { Config } from '../config/schema.js';\nimport { getModelRegistry } from './model-registry.js';\nimport { CredentialResolver, resolveApiKey, hasCredentials } from '../auth/credentials.js';\nimport { hasProviderAuthOnDiskSync } from '../auth/sync-provider-auth.js';\nimport { getApiKeyFromEnv } from './env-keys.js';\nimport { getProviderRegistry } from './plugin-registry.js';\nimport type { ProviderModelDefinition } from '../extensions/types/providers.js';\n\nexport { getApiKeyFromEnv, PROVIDER_ENV_MAP } from './env-keys.js';\n\n/** Sentinel base URL: model is served by an extension {@link ProviderPluginRegistry} provider. */\nexport const EXTENSION_PROVIDER_BASE_URL = 'extension://provider-plugin';\n\n/** Map a plugin registry model to the pi-ai {@link Model} shape. */\nexport function pluginModelToModel(providerId: string, definition: ProviderModelDefinition): Model<Api> {\n\treturn {\n\t\tprovider: providerId,\n\t\tid: definition.id,\n\t\tname: definition.name,\n\t\tapi: 'openai-completions' as Api,\n\t\tbaseUrl: EXTENSION_PROVIDER_BASE_URL,\n\t\treasoning: false,\n\t\tinput: definition.supportsImages ? (['text', 'image'] as ('text' | 'image')[]) : (['text'] as ('text' | 'image')[]),\n\t\tcontextWindow: definition.contextWindow ?? 128000,\n\t\tmaxTokens: definition.maxOutputTokens ?? 4096,\n\t\tcost: {\n\t\t\tinput: definition.pricing?.input ?? 0,\n\t\t\toutput: definition.pricing?.output ?? 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t},\n\t} as Model<Api>;\n}\n\n/**\n * Get API key synchronously: checks registry (models.json) first, then environment variables.\n * Use this for Agent's getApiKey callback which must be synchronous.\n */\nexport function getApiKeySync(provider: string): string | undefined {\n const pluginRegistry = getProviderRegistry();\n if (pluginRegistry.has(provider)) return 'extension-managed';\n\n const registry = getModelRegistry();\n const registryKey = registry.getApiKey(provider);\n if (registryKey) {\n return registryKey;\n }\n return getApiKeyFromEnv(provider);\n}\n\n/**\n * Resolve model reference. Supports:\n * - \"provider/modelId\" format\n * - \"modelId\" auto-detection via pi-ai or custom models\n * @throws if model not found\n */\nexport function resolveModel(ref: string): Model<Api> {\n\t// First try ModelRegistry (includes custom models)\n\tconst registry = getModelRegistry();\n\tconst customModel = registry.resolve(ref);\n\tif (customModel) {\n\t\treturn customModel;\n\t}\n\n\tif (ref.includes('/')) {\n\t\tconst [provider, modelId] = ref.split('/');\n\t\tconst piAiModel = getPiAiModel(provider as any, modelId as any);\n\t\tif (piAiModel) return piAiModel as Model<Api>;\n\n\t\tconst pluginRegistry = getProviderRegistry();\n\t\tconst plugin = pluginRegistry.get(provider);\n\t\tif (plugin) {\n\t\t\tconst pluginModel = plugin.models.find(m => m.id === modelId);\n\t\t\tif (pluginModel) return pluginModelToModel(provider, pluginModel);\n\t\t}\n\t\tthrow new Error(`Model not found: ${ref}`);\n\t}\n\n\tfor (const provider of getPiAiProviders()) {\n\t\ttry {\n\t\t\tconst models = getPiAiModels(provider);\n\t\t\tconst found = models.find(m => m.id === ref);\n\t\t\tif (found) return found as Model<Api>;\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\tconst pluginRegistry = getProviderRegistry();\n\tfor (const plugin of pluginRegistry.listAll()) {\n\t\tconst found = plugin.models.find(m => m.id === ref);\n\t\tif (found) return pluginModelToModel(plugin.id, found);\n\t}\n\n\tthrow new Error(`Model not found: ${ref}. Use format: provider/model-id`);\n}\n\nexport function getModelsByProvider(provider: string): readonly Model<Api>[] {\n\tconst registry = getModelRegistry();\n\tconst fromRegistry = registry.getAll().filter(m => m.provider === provider);\n\tconst plugin = getProviderRegistry().get(provider);\n\tif (!plugin) return fromRegistry;\n\tconst pluginModels = plugin.models.map(m => pluginModelToModel(provider, m));\n\treturn [...fromRegistry, ...pluginModels];\n}\n\nexport function getAllProviders(): string[] {\n\tconst registry = getModelRegistry();\n\tconst providers = new Set<string>();\n\n\t// Add built-in providers\n\tfor (const p of getPiAiProviders()) {\n\t\tproviders.add(p);\n\t}\n\n\t// Add custom providers from registry\n\tfor (const m of registry.getAll()) {\n\t\tproviders.add(m.provider);\n\t}\n\n\tfor (const plugin of getProviderRegistry().listAll()) {\n\t\tproviders.add(plugin.id);\n\t}\n\n\treturn Array.from(providers);\n}\n\nexport async function getApiKey(provider: string): Promise<string | undefined> {\n\tif (getProviderRegistry().has(provider)) return 'extension-managed';\n\n\t// Use new credential resolver first (checks: agent private > global > oauth > env)\n\tconst credentialKey = await resolveApiKey(provider);\n\tif (credentialKey) {\n\t\treturn credentialKey;\n\t}\n\n\t// Check registry for custom providers (from models.json)\n\tconst registry = getModelRegistry();\n\tconst registryKey = registry.getApiKey(provider);\n\tif (registryKey) {\n\t\treturn registryKey;\n\t}\n\n\t// Fallback to environment variables\n\treturn getApiKeyFromEnv(provider);\n}\n\n/**\n * Synchronous version for use in non-async contexts\n * Only checks environment variables and registry, not credential system\n */\nexport function isProviderConfiguredSync(provider: string): boolean {\n\tif (getProviderRegistry().has(provider)) return true;\n\n\t// Check registry for custom providers\n\tconst registry = getModelRegistry();\n\tif (registry.getApiKey(provider)) {\n\t\treturn true;\n\t}\n\t// Check environment variables\n\tif (getApiKeyFromEnv(provider)) {\n\t\treturn true;\n\t}\n\t// Gateway UI / CLI store keys in auth-profiles.json (async CredentialResolver); sync path for fallback list\n\treturn hasProviderAuthOnDiskSync(provider);\n}\n\nexport async function isProviderConfigured(provider: string): Promise<boolean> {\n if (getProviderRegistry().has(provider)) return true;\n\n // Check registry first for custom providers (from models.json)\n const registry = getModelRegistry();\n if (registry.getApiKey(provider)) {\n return true;\n }\n return await hasCredentials(provider);\n}\n\n/** Where runtime {@link getApiKey} resolves the key from (no secret values). */\nexport type ProviderActiveKeySource = 'none' | 'agent' | 'gateway' | 'oauth' | 'env' | 'models_json' | 'extension';\n\nexport type ProviderAuthMode = ProviderActiveKeySource;\nexport type ProviderAuthStatus = 'connected' | 'expired' | 'not_connected';\n\nexport interface ProviderAuthState {\n authMode: ProviderAuthMode;\n authStatus: ProviderAuthStatus;\n expiresAt?: number;\n}\n\nexport async function getProviderActiveKeySource(provider: string): Promise<ProviderActiveKeySource> {\n const authState = await getProviderAuthState(provider);\n return authState.authMode;\n}\n\nexport async function getProviderAuthState(provider: string): Promise<ProviderAuthState> {\n if (getProviderRegistry().has(provider)) {\n return { authMode: 'extension', authStatus: 'connected' };\n }\n\n const resolver = new CredentialResolver();\n const fromCredentials = await resolver.resolveApiKeySource(provider);\n if (fromCredentials === 'agent') return { authMode: 'agent', authStatus: 'connected' };\n if (fromCredentials === 'global') return { authMode: 'gateway', authStatus: 'connected' };\n if (fromCredentials === 'oauth') {\n const token = await resolver.loadOAuthTokenRecord(provider);\n const expired = Boolean(token?.expiresAt && token.expiresAt < Date.now());\n return {\n authMode: 'oauth',\n authStatus: expired ? 'expired' : 'connected',\n ...(token?.expiresAt ? { expiresAt: token.expiresAt } : {}),\n };\n }\n if (fromCredentials === 'env') return { authMode: 'env', authStatus: 'connected' };\n\n const expiredOAuthToken = await resolver.loadOAuthTokenRecord(provider);\n if (expiredOAuthToken?.expiresAt && expiredOAuthToken.expiresAt < Date.now()) {\n return {\n authMode: 'oauth',\n authStatus: 'expired',\n expiresAt: expiredOAuthToken.expiresAt,\n };\n }\n\n const registry = getModelRegistry();\n if (registry.getApiKey(provider)) {\n return { authMode: 'models_json', authStatus: 'connected' };\n }\n\n return { authMode: 'none', authStatus: 'not_connected' };\n}\n\nexport async function getConfiguredProviders(): Promise<string[]> {\n\tconst allProviders = getAllProviders();\n\tconst configured: string[] = [];\n\tfor (const p of allProviders) {\n\t\tif (await isProviderConfigured(p)) {\n\t\t\tconfigured.push(p);\n\t\t}\n\t}\n\treturn configured;\n}\n\nexport function getAllModels(): readonly Model<Api>[] {\n\tconst registry = getModelRegistry();\n\tconst registryModels = registry.getAll();\n\tconst pluginProviders = getProviderRegistry().listAll();\n\tif (pluginProviders.length === 0) return registryModels;\n\n\tconst existingIds = new Set(registryModels.map(m => `${m.provider}/${m.id}`));\n\tconst merged: Model<Api>[] = [...registryModels];\n\tfor (const plugin of pluginProviders) {\n\t\tfor (const model of plugin.models) {\n\t\t\tconst compositeId = `${plugin.id}/${model.id}`;\n\t\t\tif (!existingIds.has(compositeId)) {\n\t\t\t\tmerged.push(pluginModelToModel(plugin.id, model));\n\t\t\t\texistingIds.add(compositeId);\n\t\t\t}\n\t\t}\n\t}\n\treturn merged;\n}\n\nexport async function getAvailableModels(): Promise<readonly Model<Api>[]> {\n\tconst allModels = getAllModels();\n\tconst pluginRegistry = getProviderRegistry();\n\tconst available: Model<Api>[] = [];\n\n\tfor (const model of allModels) {\n\t\tif (pluginRegistry.has(model.provider)) {\n\t\t\tavailable.push(model);\n\t\t} else if (await isProviderConfigured(model.provider)) {\n\t\t\tavailable.push(model);\n\t\t}\n\t}\n\treturn available;\n}\n\nexport type { Model, Api } from '@earendil-works/pi-ai';\n\nexport type ProviderCategory = 'common' | 'specialty' | 'oauth' | 'enterprise' | 'extension';\n\nexport interface ProviderMeta {\n name: string;\n category: ProviderCategory;\n supportsOAuth?: boolean;\n supportsApiKey?: boolean;\n}\n\nexport const PROVIDER_META: Record<string, ProviderMeta> = {\n 'openai': { name: 'OpenAI (GPT-4, o1, o3)', category: 'common', supportsApiKey: true },\n 'anthropic': { name: 'Anthropic Claude', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'deepseek': { name: 'DeepSeek', category: 'common', supportsApiKey: true },\n 'google': { name: 'Google Gemini', category: 'common', supportsApiKey: true },\n 'groq': { name: 'Groq (Fast Inference)', category: 'common', supportsApiKey: true },\n 'minimax': { name: 'MiniMax', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'minimax-cn': { name: 'MiniMax CN', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'kimi-coding': { name: 'Kimi For Coding', category: 'common', supportsApiKey: true, supportsOAuth: true },\n 'xai': { name: 'xAI (Grok)', category: 'specialty', supportsApiKey: true },\n 'mistral': { name: 'Mistral AI', category: 'specialty', supportsApiKey: true },\n 'cerebras': { name: 'Cerebras', category: 'specialty', supportsApiKey: true },\n 'openrouter': { name: 'OpenRouter (Multi-provider)', category: 'specialty', supportsApiKey: true },\n 'huggingface': { name: 'Hugging Face', category: 'specialty', supportsApiKey: true },\n moonshotai: { name: 'Moonshot AI (Kimi · International)', category: 'common', supportsApiKey: true },\n 'moonshotai-cn': { name: 'Moonshot AI (Kimi · China)', category: 'common', supportsApiKey: true },\n fireworks: { name: 'Fireworks AI', category: 'specialty', supportsApiKey: true },\n together: { name: 'Together AI', category: 'specialty', supportsApiKey: true },\n 'cloudflare-workers-ai': { name: 'Cloudflare Workers AI', category: 'enterprise', supportsApiKey: true },\n 'cloudflare-ai-gateway': { name: 'Cloudflare AI Gateway', category: 'enterprise', supportsApiKey: true },\n xiaomi: { name: 'Xiaomi Mimo', category: 'specialty', supportsApiKey: true },\n 'xiaomi-token-plan-cn': { name: 'Xiaomi Token Plan (CN)', category: 'specialty', supportsApiKey: true },\n 'xiaomi-token-plan-ams': { name: 'Xiaomi Token Plan (AMS)', category: 'specialty', supportsApiKey: true },\n 'xiaomi-token-plan-sgp': { name: 'Xiaomi Token Plan (SGP)', category: 'specialty', supportsApiKey: true },\n 'opencode': { name: 'OpenCode', category: 'specialty', supportsApiKey: true },\n 'opencode-go': { name: 'OpenCode Go', category: 'specialty', supportsApiKey: true },\n /** DashScope (Alibaba) — image, speech, STT; not an LLM KnownProvider. */\n 'dashscope': { name: 'DashScope (Alibaba)', category: 'specialty', supportsApiKey: true },\n /** International GLM (api.z.ai). Auth: API key (ZAI_API_KEY); no published OAuth for this HTTP API. */\n 'zai': { name: 'Zhipu GLM (International · z.ai)', category: 'common', supportsApiKey: true },\n 'amazon-bedrock': { name: 'Amazon Bedrock', category: 'enterprise', supportsApiKey: true },\n 'azure-openai-responses': { name: 'Azure OpenAI', category: 'enterprise', supportsApiKey: true },\n 'google-vertex': { name: 'Google Vertex AI', category: 'enterprise', supportsApiKey: true },\n 'vercel-ai-gateway': { name: 'Vercel AI Gateway', category: 'enterprise', supportsApiKey: true },\n 'github-copilot': { name: 'GitHub Copilot (OAuth)', category: 'oauth', supportsOAuth: true, supportsApiKey: false },\n 'openai-codex': { name: 'OpenAI Codex (OAuth)', category: 'oauth', supportsOAuth: true, supportsApiKey: false },\n 'google-gemini-cli': { name: 'Google Gemini CLI (OAuth)', category: 'oauth', supportsOAuth: true, supportsApiKey: false },\n 'google-antigravity': { name: 'Google Antigravity (OAuth)', category: 'oauth', supportsOAuth: true, supportsApiKey: false },\n};\n\nexport function getSortedProviders(): string[] {\n const all = getAllProviders();\n const catOrder: Record<ProviderCategory, number> = { common: 0, specialty: 1, enterprise: 2, oauth: 3, extension: 4 };\n const pluginRegistry = getProviderRegistry();\n\n return [...all].sort((a, b) => {\n const catA = pluginRegistry.has(a) ? 'extension' : (PROVIDER_META[a]?.category ?? 'specialty');\n const catB = pluginRegistry.has(b) ? 'extension' : (PROVIDER_META[b]?.category ?? 'specialty');\n if (catOrder[catA] !== catOrder[catB]) {\n return catOrder[catA] - catOrder[catB];\n }\n return a.localeCompare(b);\n });\n}\n\nexport function getProviderDisplayName(provider: string): string {\n const plugin = getProviderRegistry().get(provider);\n if (plugin) return plugin.name;\n return PROVIDER_META[provider]?.name || provider;\n}\n\nexport function providerSupportsOAuth(provider: string): boolean {\n return PROVIDER_META[provider]?.supportsOAuth ?? false;\n}\n\nexport function providerSupportsApiKey(provider: string): boolean {\n return PROVIDER_META[provider]?.supportsApiKey ?? true;\n}\n\n// ============================================\n// Dynamic Default Model Resolution\n// ============================================\n\n/** Preferred default model when no explicit model is configured. */\nconst DEFAULT_FALLBACK_MODEL = 'deepseek/deepseek-v4-flash';\n\n/**\n * Get a default model reference.\n * Priority:\n * 1. Explicitly configured model in agents.defaults.model (if set and available)\n * 2. Default fallback: deepseek/deepseek-v4-flash\n */\nexport async function getDefaultModel(config?: Config | null | undefined): Promise<string> {\n const defaultModel = config?.agents?.defaults?.model;\n if (defaultModel) {\n const modelRef = typeof defaultModel === 'string' ? defaultModel : defaultModel.primary;\n if (modelRef) {\n const availableModels = await getAvailableModels();\n const configured = availableModels.find(m => \n `${m.provider}/${m.id}` === modelRef ||\n m.id === modelRef\n );\n if (configured) {\n return `${configured.provider}/${configured.id}`;\n }\n return modelRef;\n }\n }\n\n return DEFAULT_FALLBACK_MODEL;\n}\n\n/**\n * Synchronous default model resolution for constructors and sync code paths.\n * Uses catalog/registry only (no async credential checks).\n *\n * When no model is explicitly configured, returns the preferred default\n * (`deepseek/deepseek-v4-flash`) rather than picking an arbitrary first\n * model from the full catalog.\n */\nexport function getDefaultModelSync(config?: Config | null | undefined): string {\n const defaultModel = config?.agents?.defaults?.model;\n if (defaultModel) {\n const modelRef = typeof defaultModel === 'string' ? defaultModel : defaultModel.primary;\n if (modelRef) {\n return modelRef;\n }\n }\n\n return DEFAULT_FALLBACK_MODEL;\n}\n\n// Re-export ModelRegistry for advanced use cases\nexport { ModelRegistry, getModelRegistry, resetModelRegistry, prewarmModelRegistry } from './model-registry.js';\n"],"mappings":"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"}
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Combines binding rules, identity links, and config to pick an agent and session keys.
5
5
  */
6
+ import type { LocalizedText } from '../config/localized-text.js';
6
7
  import type { BindingRule, RouteInput, RouteResult } from './bindings.js';
7
8
  /**
8
9
  * Route context type (alias for RouteInput)
@@ -52,7 +53,8 @@ export interface AgentConfig {
52
53
  /** Registered agents */
53
54
  list?: Array<{
54
55
  id: string;
55
- name?: string;
56
+ name?: LocalizedText;
57
+ description?: LocalizedText;
56
58
  enabled?: boolean;
57
59
  [key: string]: unknown;
58
60
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-route.js","names":["resolveDefaultAgentIdFromConfig","resolveBindingRoute"],"sources":["../../../src/routing/resolve-route.ts"],"sourcesContent":["/**\n * Route resolution\n *\n * Combines binding rules, identity links, and config to pick an agent and session keys.\n */\n\nimport { resolveDefaultAgentId as resolveDefaultAgentIdFromConfig } from '../agent/agent-scope.js';\nimport type { Config } from '../config/schema.js';\nimport type { BindingRule, RouteInput, RouteResult } from './bindings.js';\n\n/**\n * Route context type (alias for RouteInput)\n */\nexport type RouteContext = RouteInput;\nimport { parseBindingRules, resolveRoute as resolveBindingRoute } from './bindings.js';\nimport { buildSessionKey, normalizeSessionKey, parseSessionKey } from './session-key.js';\nimport {\n buildAgentMainSessionKey,\n buildAgentPeerSessionKey,\n} from './agent-session-key.js';\nimport { normalizeAccountId } from './account-id.js';\n\n/**\n * Identity link map: canonical peer id -> aliases across channels.\n *\n * Shape: `{ canonicalName: [alias1, alias2, ...] }`\n */\nexport type IdentityLinks = Record<string, string[]>;\n\n/**\n * Session-related routing options.\n */\nexport interface SessionConfig {\n scope?: 'per-sender' | 'global';\n mainKey?: string;\n /** How DM sessions are scoped / merged */\n dmScope?: 'main' | 'per-peer' | 'per-channel-peer' | 'per-account-channel-peer';\n /** Cross-channel identity aliases */\n identityLinks?: IdentityLinks;\n resetTriggers?: string[];\n idleMinutes?: number;\n reset?: {\n mode?: 'daily' | 'idle';\n atHour?: number;\n idleMinutes?: number;\n };\n resetByType?: {\n direct?: SessionConfig['reset'];\n group?: SessionConfig['reset'];\n thread?: SessionConfig['reset'];\n };\n resetByChannel?: Record<string, NonNullable<SessionConfig['reset']>>;\n /** Optional session store tuning */\n storage?: {\n pruneAfterMs?: number;\n maxEntries?: number;\n };\n}\n\n/**\n * Agent list and default id from config.\n */\nexport interface AgentConfig {\n /** Default agent id */\n default?: string;\n /** Registered agents */\n list?: Array<{\n id: string;\n name?: string;\n enabled?: boolean;\n [key: string]: unknown;\n }>;\n}\n\n/**\n * Subset of app config used for routing.\n */\nexport interface RoutingConfig {\n agents?: AgentConfig;\n bindings?: BindingRule[] | any[];\n session?: SessionConfig;\n}\n\n/**\n * Input to `resolveRoute`.\n */\nexport interface ResolveRouteInput extends RouteInput {\n /** Routing config snapshot */\n config: RoutingConfig;\n /** Optional thread id for threaded channels */\n threadId?: string | null;\n}\n\n/**\n * Resolved route including session keys.\n */\nexport interface ResolveRouteResult extends RouteResult {\n /** Active session key for this turn */\n sessionKey: string;\n /** Main session key (DM merge target) */\n mainSessionKey: string;\n /** Whether routing used the main or a per-session key */\n lastRoutePolicy: 'main' | 'session';\n}\n\n/**\n * Apply identity links and return a canonical lowercased peer id.\n */\nexport function applyIdentityLinks(\n peerId: string,\n channel: string,\n identityLinks?: IdentityLinks\n): string {\n if (!identityLinks) {\n return peerId.toLowerCase();\n }\n \n const normalizedPeerId = peerId.trim().toLowerCase();\n if (!normalizedPeerId) {\n return normalizedPeerId;\n }\n \n const candidates = new Set<string>();\n candidates.add(normalizedPeerId);\n \n const channelPrefix = channel.trim().toLowerCase();\n if (channelPrefix) {\n candidates.add(`${channelPrefix}:${normalizedPeerId}`);\n }\n \n // Match any alias to its canonical id\n for (const [canonical, aliases] of Object.entries(identityLinks)) {\n if (!Array.isArray(aliases)) {\n continue;\n }\n \n for (const alias of aliases) {\n const normalizedAlias = alias.trim().toLowerCase();\n if (candidates.has(normalizedAlias)) {\n return canonical.trim().toLowerCase();\n }\n }\n }\n \n return normalizedPeerId;\n}\n\n/**\n * Default agent id from config (`agents.default`, `list[].default`, or `main`).\n */\nexport function getDefaultAgentId(config: RoutingConfig): string {\n return resolveDefaultAgentIdFromConfig(config as Config);\n}\n\n/**\n * Whether `agentId` appears in the enabled agent list (or list is absent).\n */\nexport function agentExists(agentId: string, config: RoutingConfig): boolean {\n if (!config.agents?.list) {\n return true; // No list: treat every id as valid\n }\n \n return config.agents.list.some(\n (agent) => agent.enabled !== false && agent.id.toLowerCase() === agentId.toLowerCase()\n );\n}\n\n/**\n * Return `agentId` if listed, otherwise the default agent id.\n */\nexport function pickFirstExistingAgentId(agentId: string, config: RoutingConfig): string {\n if (!agentId) {\n return getDefaultAgentId(config);\n }\n \n if (agentExists(agentId, config)) {\n return agentId.toLowerCase();\n }\n \n return getDefaultAgentId(config);\n}\n\n/**\n * Thin wrapper around `buildSessionKey` for route inputs.\n */\nexport function buildRouteSessionKey(\n agentId: string,\n channel: string,\n accountId: string,\n peerKind: string,\n peerId: string,\n threadId?: string | null,\n scopeId?: string | null,\n): string {\n return buildSessionKey({\n agentId,\n source: channel,\n accountId,\n peerKind,\n peerId,\n threadId: threadId || undefined,\n scopeId: scopeId || undefined,\n dmScope: peerKind === 'dm' || peerKind === 'direct' ? 'per-account-channel-peer' : undefined,\n });\n}\n\n/**\n * Map session vs main key to policy label.\n */\nexport function deriveLastRoutePolicy(\n sessionKey: string,\n mainSessionKey: string\n): 'main' | 'session' {\n return sessionKey === mainSessionKey ? 'main' : 'session';\n}\n\n/**\n * Resolve agent and session keys from channel context and config.\n */\nexport function resolveRoute(input: ResolveRouteInput): ResolveRouteResult {\n const { config, threadId } = input;\n \n const channel = (input.channel ?? '').trim().toLowerCase() || 'unknown';\n const accountId = normalizeAccountId(input.accountId);\n const peerKind = (input.peerKind ?? 'dm').toLowerCase();\n const rawPeerId = (input.peerId ?? '').trim();\n \n const peerId = applyIdentityLinks(rawPeerId, channel, config.session?.identityLinks ?? {});\n \n const rules = Array.isArray(config.bindings)\n ? parseBindingRules({ bindings: config.bindings })\n : [];\n \n const bindingResult = resolveBindingRoute(\n {\n channel,\n accountId,\n peerKind: input.peerKind,\n peerId: rawPeerId,\n guildId: input.guildId,\n teamId: input.teamId,\n memberRoleIds: input.memberRoleIds,\n },\n rules,\n getDefaultAgentId(config)\n );\n \n const agentId = pickFirstExistingAgentId(bindingResult.agentId, config);\n \n const dmScope = config.session?.dmScope ?? 'main';\n\n let sessionKey: string;\n let mainSessionKey: string;\n\n const identityLinks = config.session?.identityLinks;\n const mainKey = undefined;\n\n if (peerKind === 'dm' || peerKind === 'direct') {\n mainSessionKey = buildAgentMainSessionKey({ agentId, mainKey });\n\n sessionKey = buildAgentPeerSessionKey({\n agentId,\n mainKey,\n channel,\n accountId,\n peerKind: 'direct',\n peerId,\n identityLinks,\n dmScope,\n });\n } else {\n const mappedKind = peerKind === 'group' || peerKind === 'channel' ? peerKind : peerKind;\n sessionKey = buildAgentPeerSessionKey({\n agentId,\n mainKey,\n channel,\n accountId,\n peerKind: mappedKind as 'group' | 'channel',\n peerId,\n identityLinks,\n });\n mainSessionKey = buildAgentPeerSessionKey({\n agentId,\n mainKey,\n channel,\n accountId,\n peerKind: mappedKind as 'group' | 'channel',\n peerId: 'main',\n identityLinks,\n });\n }\n\n if (threadId && !sessionKey.includes(':thread:')) {\n sessionKey = `${sessionKey}:thread:${threadId.toLowerCase()}`;\n }\n \n sessionKey = normalizeSessionKey(sessionKey);\n mainSessionKey = normalizeSessionKey(mainSessionKey);\n \n return {\n ...bindingResult,\n agentId,\n sessionKey,\n mainSessionKey,\n lastRoutePolicy: deriveLastRoutePolicy(sessionKey, mainSessionKey),\n };\n}\n\n/**\n * Parse basic routing fields from a session key string.\n */\nexport function resolveRouteFromSessionKey(\n sessionKey: string,\n _config: RoutingConfig\n): { agentId: string; source: string; accountId: string; peerKind: string; peerId: string } | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) {\n return null;\n }\n \n return {\n agentId: parsed.agentId,\n source: parsed.source,\n accountId: parsed.accountId,\n peerKind: parsed.peerKind,\n peerId: parsed.peerId,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolve-route.js","names":["resolveDefaultAgentIdFromConfig","resolveBindingRoute"],"sources":["../../../src/routing/resolve-route.ts"],"sourcesContent":["/**\n * Route resolution\n *\n * Combines binding rules, identity links, and config to pick an agent and session keys.\n */\n\nimport { resolveDefaultAgentId as resolveDefaultAgentIdFromConfig } from '../agent/agent-scope.js';\nimport type { Config } from '../config/schema.js';\nimport type { LocalizedText } from '../config/localized-text.js';\nimport type { BindingRule, RouteInput, RouteResult } from './bindings.js';\n\n/**\n * Route context type (alias for RouteInput)\n */\nexport type RouteContext = RouteInput;\nimport { parseBindingRules, resolveRoute as resolveBindingRoute } from './bindings.js';\nimport { buildSessionKey, normalizeSessionKey, parseSessionKey } from './session-key.js';\nimport {\n buildAgentMainSessionKey,\n buildAgentPeerSessionKey,\n} from './agent-session-key.js';\nimport { normalizeAccountId } from './account-id.js';\n\n/**\n * Identity link map: canonical peer id -> aliases across channels.\n *\n * Shape: `{ canonicalName: [alias1, alias2, ...] }`\n */\nexport type IdentityLinks = Record<string, string[]>;\n\n/**\n * Session-related routing options.\n */\nexport interface SessionConfig {\n scope?: 'per-sender' | 'global';\n mainKey?: string;\n /** How DM sessions are scoped / merged */\n dmScope?: 'main' | 'per-peer' | 'per-channel-peer' | 'per-account-channel-peer';\n /** Cross-channel identity aliases */\n identityLinks?: IdentityLinks;\n resetTriggers?: string[];\n idleMinutes?: number;\n reset?: {\n mode?: 'daily' | 'idle';\n atHour?: number;\n idleMinutes?: number;\n };\n resetByType?: {\n direct?: SessionConfig['reset'];\n group?: SessionConfig['reset'];\n thread?: SessionConfig['reset'];\n };\n resetByChannel?: Record<string, NonNullable<SessionConfig['reset']>>;\n /** Optional session store tuning */\n storage?: {\n pruneAfterMs?: number;\n maxEntries?: number;\n };\n}\n\n/**\n * Agent list and default id from config.\n */\nexport interface AgentConfig {\n /** Default agent id */\n default?: string;\n /** Registered agents */\n list?: Array<{\n id: string;\n name?: LocalizedText;\n description?: LocalizedText;\n enabled?: boolean;\n [key: string]: unknown;\n }>;\n}\n\n/**\n * Subset of app config used for routing.\n */\nexport interface RoutingConfig {\n agents?: AgentConfig;\n bindings?: BindingRule[] | any[];\n session?: SessionConfig;\n}\n\n/**\n * Input to `resolveRoute`.\n */\nexport interface ResolveRouteInput extends RouteInput {\n /** Routing config snapshot */\n config: RoutingConfig;\n /** Optional thread id for threaded channels */\n threadId?: string | null;\n}\n\n/**\n * Resolved route including session keys.\n */\nexport interface ResolveRouteResult extends RouteResult {\n /** Active session key for this turn */\n sessionKey: string;\n /** Main session key (DM merge target) */\n mainSessionKey: string;\n /** Whether routing used the main or a per-session key */\n lastRoutePolicy: 'main' | 'session';\n}\n\n/**\n * Apply identity links and return a canonical lowercased peer id.\n */\nexport function applyIdentityLinks(\n peerId: string,\n channel: string,\n identityLinks?: IdentityLinks\n): string {\n if (!identityLinks) {\n return peerId.toLowerCase();\n }\n \n const normalizedPeerId = peerId.trim().toLowerCase();\n if (!normalizedPeerId) {\n return normalizedPeerId;\n }\n \n const candidates = new Set<string>();\n candidates.add(normalizedPeerId);\n \n const channelPrefix = channel.trim().toLowerCase();\n if (channelPrefix) {\n candidates.add(`${channelPrefix}:${normalizedPeerId}`);\n }\n \n // Match any alias to its canonical id\n for (const [canonical, aliases] of Object.entries(identityLinks)) {\n if (!Array.isArray(aliases)) {\n continue;\n }\n \n for (const alias of aliases) {\n const normalizedAlias = alias.trim().toLowerCase();\n if (candidates.has(normalizedAlias)) {\n return canonical.trim().toLowerCase();\n }\n }\n }\n \n return normalizedPeerId;\n}\n\n/**\n * Default agent id from config (`agents.default`, `list[].default`, or `main`).\n */\nexport function getDefaultAgentId(config: RoutingConfig): string {\n return resolveDefaultAgentIdFromConfig(config as Config);\n}\n\n/**\n * Whether `agentId` appears in the enabled agent list (or list is absent).\n */\nexport function agentExists(agentId: string, config: RoutingConfig): boolean {\n if (!config.agents?.list) {\n return true; // No list: treat every id as valid\n }\n \n return config.agents.list.some(\n (agent) => agent.enabled !== false && agent.id.toLowerCase() === agentId.toLowerCase()\n );\n}\n\n/**\n * Return `agentId` if listed, otherwise the default agent id.\n */\nexport function pickFirstExistingAgentId(agentId: string, config: RoutingConfig): string {\n if (!agentId) {\n return getDefaultAgentId(config);\n }\n \n if (agentExists(agentId, config)) {\n return agentId.toLowerCase();\n }\n \n return getDefaultAgentId(config);\n}\n\n/**\n * Thin wrapper around `buildSessionKey` for route inputs.\n */\nexport function buildRouteSessionKey(\n agentId: string,\n channel: string,\n accountId: string,\n peerKind: string,\n peerId: string,\n threadId?: string | null,\n scopeId?: string | null,\n): string {\n return buildSessionKey({\n agentId,\n source: channel,\n accountId,\n peerKind,\n peerId,\n threadId: threadId || undefined,\n scopeId: scopeId || undefined,\n dmScope: peerKind === 'dm' || peerKind === 'direct' ? 'per-account-channel-peer' : undefined,\n });\n}\n\n/**\n * Map session vs main key to policy label.\n */\nexport function deriveLastRoutePolicy(\n sessionKey: string,\n mainSessionKey: string\n): 'main' | 'session' {\n return sessionKey === mainSessionKey ? 'main' : 'session';\n}\n\n/**\n * Resolve agent and session keys from channel context and config.\n */\nexport function resolveRoute(input: ResolveRouteInput): ResolveRouteResult {\n const { config, threadId } = input;\n \n const channel = (input.channel ?? '').trim().toLowerCase() || 'unknown';\n const accountId = normalizeAccountId(input.accountId);\n const peerKind = (input.peerKind ?? 'dm').toLowerCase();\n const rawPeerId = (input.peerId ?? '').trim();\n \n const peerId = applyIdentityLinks(rawPeerId, channel, config.session?.identityLinks ?? {});\n \n const rules = Array.isArray(config.bindings)\n ? parseBindingRules({ bindings: config.bindings })\n : [];\n \n const bindingResult = resolveBindingRoute(\n {\n channel,\n accountId,\n peerKind: input.peerKind,\n peerId: rawPeerId,\n guildId: input.guildId,\n teamId: input.teamId,\n memberRoleIds: input.memberRoleIds,\n },\n rules,\n getDefaultAgentId(config)\n );\n \n const agentId = pickFirstExistingAgentId(bindingResult.agentId, config);\n \n const dmScope = config.session?.dmScope ?? 'main';\n\n let sessionKey: string;\n let mainSessionKey: string;\n\n const identityLinks = config.session?.identityLinks;\n const mainKey = undefined;\n\n if (peerKind === 'dm' || peerKind === 'direct') {\n mainSessionKey = buildAgentMainSessionKey({ agentId, mainKey });\n\n sessionKey = buildAgentPeerSessionKey({\n agentId,\n mainKey,\n channel,\n accountId,\n peerKind: 'direct',\n peerId,\n identityLinks,\n dmScope,\n });\n } else {\n const mappedKind = peerKind === 'group' || peerKind === 'channel' ? peerKind : peerKind;\n sessionKey = buildAgentPeerSessionKey({\n agentId,\n mainKey,\n channel,\n accountId,\n peerKind: mappedKind as 'group' | 'channel',\n peerId,\n identityLinks,\n });\n mainSessionKey = buildAgentPeerSessionKey({\n agentId,\n mainKey,\n channel,\n accountId,\n peerKind: mappedKind as 'group' | 'channel',\n peerId: 'main',\n identityLinks,\n });\n }\n\n if (threadId && !sessionKey.includes(':thread:')) {\n sessionKey = `${sessionKey}:thread:${threadId.toLowerCase()}`;\n }\n \n sessionKey = normalizeSessionKey(sessionKey);\n mainSessionKey = normalizeSessionKey(mainSessionKey);\n \n return {\n ...bindingResult,\n agentId,\n sessionKey,\n mainSessionKey,\n lastRoutePolicy: deriveLastRoutePolicy(sessionKey, mainSessionKey),\n };\n}\n\n/**\n * Parse basic routing fields from a session key string.\n */\nexport function resolveRouteFromSessionKey(\n sessionKey: string,\n _config: RoutingConfig\n): { agentId: string; source: string; accountId: string; peerKind: string; peerId: string } | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) {\n return null;\n }\n \n return {\n agentId: parsed.agentId,\n source: parsed.source,\n accountId: parsed.accountId,\n peerKind: parsed.peerKind,\n peerId: parsed.peerId,\n };\n}\n"],"mappings":"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"}
@@ -22,6 +22,7 @@ export declare class SessionStore {
22
22
  private compactor;
23
23
  private storeMutationDepth;
24
24
  private storeMutationChain;
25
+ private allSessionsMapCache?;
25
26
  /** Cache of per-agent sessions dirs to avoid re-resolution on every call. */
26
27
  private agentSessionsDirCache;
27
28
  constructor(options: SessionStoreOptions, windowConfig?: Partial<WindowConfig>, compactionConfig?: Partial<CompactionConfig>);
@@ -39,10 +40,11 @@ export declare class SessionStore {
39
40
  private transcriptPathForEntry;
40
41
  private readMapForKey;
41
42
  private readMap;
43
+ private invalidateAllSessionsMapCache;
44
+ private discoverSessionMapPaths;
42
45
  /**
43
- * OpenClaw-aligned: read sessions.json from ALL known agents and merge into a single map.
44
- * Used by aggregation queries (list, getByAgent, getByAccount, etc.) so the gateway UI
45
- * can display sessions across all agents.
46
+ * Unified cross-agent aggregation entry for global session views.
47
+ * Reads configured agents plus existing per-agent session maps under the state directory.
46
48
  */
47
49
  private readAllMaps;
48
50
  private getDiskEntry;