@capybara-hq/kernel 0.1.0-rc.2 → 0.1.0-rc.21

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 (1039) hide show
  1. package/bootstrap.json +94 -5
  2. package/dist/admin/admin-agents.controller.d.ts +28 -0
  3. package/dist/admin/admin-agents.controller.js +79 -0
  4. package/dist/admin/admin-connectors.controller.d.ts +29 -0
  5. package/dist/admin/admin-connectors.controller.js +79 -0
  6. package/dist/admin/admin-items.schemas.d.ts +42 -0
  7. package/dist/admin/admin-items.schemas.js +48 -0
  8. package/dist/admin/admin.controller.d.ts +52 -80
  9. package/dist/admin/admin.controller.js +112 -81
  10. package/dist/admin/admin.module.d.ts +4 -1
  11. package/dist/admin/admin.module.js +50 -4
  12. package/dist/admin/admin.schemas.d.ts +69 -0
  13. package/dist/admin/admin.schemas.js +53 -0
  14. package/dist/admin/audit.controller.d.ts +14 -0
  15. package/dist/admin/audit.controller.js +42 -0
  16. package/dist/admin/audit.schemas.d.ts +8 -0
  17. package/dist/admin/audit.schemas.js +17 -0
  18. package/dist/admin/extensions.controller.d.ts +34 -3
  19. package/dist/admin/extensions.controller.js +66 -8
  20. package/dist/admin/extensions.schemas.d.ts +7 -0
  21. package/dist/admin/extensions.schemas.js +10 -0
  22. package/dist/admin/health.service.d.ts +104 -0
  23. package/dist/admin/health.service.js +246 -0
  24. package/dist/admin/limit-defaults.d.ts +20 -0
  25. package/dist/admin/limit-defaults.js +10 -0
  26. package/dist/admin/overview.controller.d.ts +31 -0
  27. package/dist/admin/overview.controller.js +78 -0
  28. package/dist/admin/overview.schemas.d.ts +8 -0
  29. package/dist/admin/overview.schemas.js +13 -0
  30. package/dist/admin/overview.service.d.ts +112 -0
  31. package/dist/admin/overview.service.js +223 -0
  32. package/dist/admin/platform-audit.service.d.ts +41 -0
  33. package/dist/admin/platform-audit.service.js +70 -0
  34. package/dist/admin/setup-lock.d.ts +1 -1
  35. package/dist/admin/setup-lock.js +1 -1
  36. package/dist/admin/setup.controller.d.ts +1 -1
  37. package/dist/admin/setup.controller.js +1 -1
  38. package/dist/admin/setup.service.d.ts +4 -4
  39. package/dist/admin/setup.service.js +8 -8
  40. package/dist/admin/usage.service.d.ts +149 -0
  41. package/dist/admin/usage.service.js +321 -0
  42. package/dist/agents/admin-agents.d.ts +92 -0
  43. package/dist/agents/admin-agents.js +170 -0
  44. package/dist/agents/agent-lookup.d.ts +24 -0
  45. package/dist/agents/agent-lookup.js +46 -2
  46. package/dist/agents/agent-profile.service.d.ts +31 -0
  47. package/dist/agents/agent-profile.service.js +122 -0
  48. package/dist/agents/agents.controller.d.ts +53 -168
  49. package/dist/agents/agents.controller.js +163 -34
  50. package/dist/agents/agents.module.d.ts +6 -2
  51. package/dist/agents/agents.module.js +41 -7
  52. package/dist/agents/agents.schemas.d.ts +392 -0
  53. package/dist/agents/agents.schemas.js +126 -0
  54. package/dist/agents/agents.service.d.ts +121 -36
  55. package/dist/agents/agents.service.js +411 -121
  56. package/dist/agents/definition.d.ts +142 -21
  57. package/dist/agents/definition.js +389 -43
  58. package/dist/agents/from-template.d.ts +106 -0
  59. package/dist/agents/from-template.js +680 -0
  60. package/dist/agents/from-template.service.d.ts +72 -0
  61. package/dist/agents/from-template.service.js +178 -0
  62. package/dist/agents/instruction-files.d.ts +60 -0
  63. package/dist/agents/instruction-files.js +111 -0
  64. package/dist/agents/models.controller.d.ts +44 -6
  65. package/dist/agents/models.controller.js +84 -8
  66. package/dist/agents/org-templates.d.ts +186 -0
  67. package/dist/agents/org-templates.js +876 -0
  68. package/dist/agents/pending-connector-requests.d.ts +79 -0
  69. package/dist/agents/pending-connector-requests.js +215 -0
  70. package/dist/agents/pending-connectors.controller.d.ts +39 -0
  71. package/dist/agents/pending-connectors.controller.js +91 -0
  72. package/dist/agents/pending-connectors.service.d.ts +62 -0
  73. package/dist/agents/pending-connectors.service.js +282 -0
  74. package/dist/agents/profile.d.ts +114 -0
  75. package/dist/agents/profile.js +55 -0
  76. package/dist/agents/readiness.controller.d.ts +26 -0
  77. package/dist/agents/readiness.controller.js +56 -0
  78. package/dist/agents/templates.controller.d.ts +77 -5
  79. package/dist/agents/templates.controller.js +153 -7
  80. package/dist/agents/templates.d.ts +127 -23
  81. package/dist/agents/templates.js +482 -44
  82. package/dist/agents/templates.schemas.d.ts +20 -0
  83. package/dist/agents/templates.schemas.js +45 -0
  84. package/dist/background/background.controller.d.ts +38 -0
  85. package/dist/background/background.controller.js +88 -0
  86. package/dist/background/background.module.d.ts +10 -0
  87. package/dist/background/background.module.js +39 -0
  88. package/dist/background/background.schemas.d.ts +10 -0
  89. package/dist/background/background.schemas.js +16 -0
  90. package/dist/background/finish.d.ts +44 -0
  91. package/dist/background/finish.js +95 -0
  92. package/dist/background/launch-lock.d.ts +15 -0
  93. package/dist/background/launch-lock.js +40 -0
  94. package/dist/background/launch.d.ts +42 -0
  95. package/dist/background/launch.js +77 -0
  96. package/dist/background/poll.job.d.ts +56 -0
  97. package/dist/background/poll.job.js +195 -0
  98. package/dist/background/render.d.ts +23 -0
  99. package/dist/background/render.js +48 -0
  100. package/dist/background/session.d.ts +48 -0
  101. package/dist/background/session.js +80 -0
  102. package/dist/background/status.d.ts +35 -0
  103. package/dist/background/status.js +57 -0
  104. package/dist/background/stop.d.ts +68 -0
  105. package/dist/background/stop.js +244 -0
  106. package/dist/background/store.d.ts +167 -0
  107. package/dist/background/store.js +361 -0
  108. package/dist/background/store.module.d.ts +6 -0
  109. package/dist/background/store.module.js +22 -0
  110. package/dist/background/tasks.service.d.ts +109 -0
  111. package/dist/background/tasks.service.js +271 -0
  112. package/dist/background/wake-rules.d.ts +66 -0
  113. package/dist/background/wake-rules.js +113 -0
  114. package/dist/background/wake.d.ts +30 -0
  115. package/dist/background/wake.js +63 -0
  116. package/dist/catalog/admin-catalog.controller.d.ts +136 -52
  117. package/dist/catalog/admin-catalog.controller.js +275 -65
  118. package/dist/catalog/admin-catalog.schemas.d.ts +82 -0
  119. package/dist/catalog/admin-catalog.schemas.js +99 -0
  120. package/dist/catalog/appearance.schemas.d.ts +10 -0
  121. package/dist/catalog/appearance.schemas.js +20 -0
  122. package/dist/catalog/appearance.service.d.ts +105 -0
  123. package/dist/catalog/appearance.service.js +306 -0
  124. package/dist/catalog/catalog-content.d.ts +122 -35
  125. package/dist/catalog/catalog-content.js +122 -14
  126. package/dist/catalog/catalog-files.d.ts +42 -0
  127. package/dist/catalog/catalog-files.js +89 -0
  128. package/dist/catalog/catalog-import.module.d.ts +7 -0
  129. package/dist/catalog/catalog-import.module.js +63 -0
  130. package/dist/catalog/catalog-items.d.ts +122 -7
  131. package/dist/catalog/catalog-items.js +301 -13
  132. package/dist/catalog/catalog-scope.service.d.ts +293 -0
  133. package/dist/catalog/catalog-scope.service.js +1236 -0
  134. package/dist/catalog/catalog.module.d.ts +7 -9
  135. package/dist/catalog/catalog.module.js +47 -31
  136. package/dist/catalog/catalog.service.d.ts +122 -31
  137. package/dist/catalog/catalog.service.js +177 -105
  138. package/dist/catalog/claude/agents.js +15 -10
  139. package/dist/catalog/claude/translate.js +3 -0
  140. package/dist/catalog/cli.d.ts +11 -1
  141. package/dist/catalog/cli.js +29 -1
  142. package/dist/catalog/component-promotion.d.ts +96 -0
  143. package/dist/catalog/component-promotion.js +395 -0
  144. package/dist/catalog/component-scope.controller.d.ts +102 -0
  145. package/dist/catalog/component-scope.controller.js +257 -0
  146. package/dist/catalog/component-scope.schemas.d.ts +118 -0
  147. package/dist/catalog/component-scope.schemas.js +97 -0
  148. package/dist/catalog/component-scope.service.d.ts +394 -0
  149. package/dist/catalog/component-scope.service.js +1133 -0
  150. package/dist/catalog/component-state.d.ts +207 -0
  151. package/dist/catalog/component-state.js +484 -0
  152. package/dist/catalog/git-source.d.ts +9 -1
  153. package/dist/catalog/git-source.js +29 -2
  154. package/dist/catalog/job-ids.d.ts +30 -0
  155. package/dist/catalog/job-ids.js +25 -0
  156. package/dist/catalog/marketplace-fetch.d.ts +1 -1
  157. package/dist/catalog/marketplace-icons.d.ts +64 -0
  158. package/dist/catalog/marketplace-icons.js +131 -0
  159. package/dist/catalog/marketplaces.controller.d.ts +50 -41
  160. package/dist/catalog/marketplaces.controller.js +130 -46
  161. package/dist/catalog/marketplaces.schemas.d.ts +48 -0
  162. package/dist/catalog/marketplaces.schemas.js +53 -0
  163. package/dist/catalog/marketplaces.service.d.ts +110 -17
  164. package/dist/catalog/marketplaces.service.js +337 -29
  165. package/dist/catalog/org-catalog.controller.d.ts +114 -62
  166. package/dist/catalog/org-catalog.controller.js +191 -98
  167. package/dist/catalog/org-catalog.schemas.d.ts +104 -0
  168. package/dist/catalog/org-catalog.schemas.js +107 -0
  169. package/dist/catalog/org-catalog.service.d.ts +72 -33
  170. package/dist/catalog/org-catalog.service.js +367 -158
  171. package/dist/catalog/org-extension-switch.d.ts +41 -0
  172. package/dist/catalog/org-extension-switch.js +115 -0
  173. package/dist/catalog/org-extension.controller.d.ts +133 -0
  174. package/dist/catalog/org-extension.controller.js +313 -0
  175. package/dist/catalog/org-extension.schemas.d.ts +15 -0
  176. package/dist/catalog/org-extension.schemas.js +22 -0
  177. package/dist/catalog/org-plugin-uploads.controller.d.ts +77 -0
  178. package/dist/catalog/org-plugin-uploads.controller.js +200 -0
  179. package/dist/catalog/org-plugin-uploads.d.ts +71 -0
  180. package/dist/catalog/org-plugin-uploads.js +122 -0
  181. package/dist/catalog/org-plugin-uploads.schemas.d.ts +20 -0
  182. package/dist/catalog/org-plugin-uploads.schemas.js +32 -0
  183. package/dist/catalog/owned-items.d.ts +188 -0
  184. package/dist/catalog/owned-items.js +808 -0
  185. package/dist/catalog/owned-plugin-cleanup.d.ts +56 -0
  186. package/dist/catalog/owned-plugin-cleanup.js +194 -0
  187. package/dist/catalog/ownership.d.ts +172 -0
  188. package/dist/catalog/ownership.js +326 -0
  189. package/dist/catalog/platform-components.d.ts +243 -0
  190. package/dist/catalog/platform-components.js +872 -0
  191. package/dist/catalog/platform-migration.d.ts +115 -0
  192. package/dist/catalog/platform-migration.js +583 -0
  193. package/dist/catalog/plugin-import.d.ts +254 -47
  194. package/dist/catalog/plugin-import.js +810 -112
  195. package/dist/catalog/plugin-install.d.ts +205 -42
  196. package/dist/catalog/plugin-install.js +828 -126
  197. package/dist/catalog/plugin-package.d.ts +43 -4
  198. package/dist/catalog/plugin-package.js +194 -7
  199. package/dist/catalog/plugin-upgrade.d.ts +308 -225
  200. package/dist/catalog/plugin-upgrade.js +1325 -1131
  201. package/dist/catalog/request-rules.d.ts +65 -0
  202. package/dist/catalog/request-rules.js +217 -0
  203. package/dist/catalog/requests.service.d.ts +194 -118
  204. package/dist/catalog/requests.service.js +530 -343
  205. package/dist/catalog/reserved-names.d.ts +8 -0
  206. package/dist/catalog/reserved-names.js +42 -0
  207. package/dist/catalog/scope-orgs.d.ts +21 -0
  208. package/dist/catalog/scope-orgs.js +67 -0
  209. package/dist/catalog/scope-state.d.ts +35 -0
  210. package/dist/catalog/scope-state.js +87 -0
  211. package/dist/catalog/scope-sync-workers.d.ts +7 -0
  212. package/dist/catalog/scope-sync-workers.js +29 -0
  213. package/dist/catalog/scope-sync.d.ts +176 -0
  214. package/dist/catalog/scope-sync.js +576 -0
  215. package/dist/catalog/scope.d.ts +42 -0
  216. package/dist/catalog/scope.js +42 -0
  217. package/dist/catalog/unshare.d.ts +22 -0
  218. package/dist/catalog/unshare.js +119 -0
  219. package/dist/channels/actions.js +10 -3
  220. package/dist/channels/cards.d.ts +36 -10
  221. package/dist/channels/cards.js +100 -25
  222. package/dist/channels/channels.module.js +13 -2
  223. package/dist/channels/channels.schemas.d.ts +41 -0
  224. package/dist/channels/channels.schemas.js +40 -0
  225. package/dist/channels/connections.controller.d.ts +46 -37
  226. package/dist/channels/connections.controller.js +76 -34
  227. package/dist/channels/connections.service.d.ts +102 -10
  228. package/dist/channels/connections.service.js +271 -24
  229. package/dist/channels/inbound.d.ts +11 -1
  230. package/dist/channels/inbound.js +87 -7
  231. package/dist/channels/outbound.d.ts +14 -1
  232. package/dist/channels/outbound.js +64 -15
  233. package/dist/channels/phrases.d.ts +33 -2
  234. package/dist/channels/phrases.js +29 -1
  235. package/dist/channels/supervisor.d.ts +2 -0
  236. package/dist/channels/supervisor.js +4 -0
  237. package/dist/channels/threads.d.ts +5 -0
  238. package/dist/channels/threads.js +8 -0
  239. package/dist/channels/webhook.d.ts +21 -1
  240. package/dist/channels/webhook.js +39 -4
  241. package/dist/client/client-access.service.d.ts +102 -0
  242. package/dist/client/client-access.service.js +303 -0
  243. package/dist/client/client-builds.service.d.ts +109 -0
  244. package/dist/client/client-builds.service.js +229 -0
  245. package/dist/client/client-delivery.controller.d.ts +25 -0
  246. package/dist/client/client-delivery.controller.js +66 -0
  247. package/dist/client/client-files.d.ts +48 -0
  248. package/dist/client/client-files.js +136 -0
  249. package/dist/client/client-reports.cron.d.ts +13 -0
  250. package/dist/client/client-reports.cron.js +42 -0
  251. package/dist/client/client-reports.service.d.ts +30 -0
  252. package/dist/client/client-reports.service.js +102 -0
  253. package/dist/client/client-sign-in.service.d.ts +21 -0
  254. package/dist/client/client-sign-in.service.js +49 -0
  255. package/dist/client/client.controller.d.ts +49 -0
  256. package/dist/client/client.controller.js +129 -0
  257. package/dist/client/client.module.d.ts +8 -0
  258. package/dist/client/client.module.js +28 -0
  259. package/dist/client/client.schemas.d.ts +61 -0
  260. package/dist/client/client.schemas.js +27 -0
  261. package/dist/config/docs.js +5 -1
  262. package/dist/config/schema.d.ts +35 -2
  263. package/dist/config/schema.js +216 -21
  264. package/dist/conversations/access.d.ts +6 -4
  265. package/dist/conversations/access.js +29 -5
  266. package/dist/conversations/agent-tree.d.ts +46 -0
  267. package/dist/conversations/agent-tree.js +92 -0
  268. package/dist/conversations/artifacts.d.ts +1 -1
  269. package/dist/conversations/artifacts.js +1 -1
  270. package/dist/conversations/attachments.service.d.ts +30 -0
  271. package/dist/conversations/attachments.service.js +130 -5
  272. package/dist/conversations/conversations.controller.d.ts +79 -44
  273. package/dist/conversations/conversations.controller.js +133 -82
  274. package/dist/conversations/conversations.module.d.ts +1 -1
  275. package/dist/conversations/conversations.module.js +10 -7
  276. package/dist/conversations/conversations.schemas.d.ts +52 -0
  277. package/dist/conversations/conversations.schemas.js +79 -0
  278. package/dist/conversations/conversations.service.d.ts +161 -13
  279. package/dist/conversations/conversations.service.js +329 -57
  280. package/dist/conversations/device-status.d.ts +24 -0
  281. package/dist/conversations/device-status.js +71 -0
  282. package/dist/conversations/event-log.d.ts +45 -9
  283. package/dist/conversations/event-log.js +113 -6
  284. package/dist/conversations/events.d.ts +255 -9
  285. package/dist/conversations/events.js +250 -34
  286. package/dist/conversations/message-context.d.ts +79 -0
  287. package/dist/conversations/message-context.js +145 -0
  288. package/dist/conversations/queue.d.ts +5 -2
  289. package/dist/conversations/queue.js +24 -4
  290. package/dist/conversations/queue.service.d.ts +2 -2
  291. package/dist/conversations/queue.service.js +21 -3
  292. package/dist/conversations/titles.d.ts +12 -3
  293. package/dist/conversations/titles.js +40 -16
  294. package/dist/conversations/tree.d.ts +17 -0
  295. package/dist/conversations/tree.js +42 -0
  296. package/dist/ctx/create-ctx.d.ts +48 -5
  297. package/dist/ctx/create-ctx.js +174 -34
  298. package/dist/db/kernel-schema.d.ts +668 -27
  299. package/dist/db/pool.d.ts +5 -0
  300. package/dist/db/pool.js +3 -0
  301. package/dist/devices/access.d.ts +66 -0
  302. package/dist/devices/access.js +113 -0
  303. package/dist/devices/conversation-devices.d.ts +75 -0
  304. package/dist/devices/conversation-devices.js +230 -0
  305. package/dist/devices/devices.controller.d.ts +36 -0
  306. package/dist/devices/devices.controller.js +124 -0
  307. package/dist/devices/devices.module.d.ts +8 -0
  308. package/dist/devices/devices.module.js +34 -0
  309. package/dist/devices/devices.schemas.d.ts +3 -0
  310. package/dist/devices/devices.schemas.js +14 -0
  311. package/dist/devices/devices.service.d.ts +251 -0
  312. package/dist/devices/devices.service.js +791 -0
  313. package/dist/devices/dto.d.ts +25 -0
  314. package/dist/devices/dto.js +12 -0
  315. package/dist/devices/label.d.ts +11 -0
  316. package/dist/devices/label.js +25 -0
  317. package/dist/devices/legacy-cleanup.job.d.ts +34 -0
  318. package/dist/devices/legacy-cleanup.job.js +141 -0
  319. package/dist/errors.d.ts +215 -3
  320. package/dist/errors.js +215 -3
  321. package/dist/evals/evals.module.js +11 -2
  322. package/dist/evals/evals.schemas.d.ts +110 -0
  323. package/dist/evals/evals.schemas.js +107 -0
  324. package/dist/evals/reconstruct.d.ts +18 -9
  325. package/dist/evals/reconstruct.js +36 -10
  326. package/dist/evals/recorded-tools.d.ts +37 -1
  327. package/dist/evals/recorded-tools.js +120 -10
  328. package/dist/evals/run-replay.d.ts +20 -3
  329. package/dist/evals/run-replay.js +78 -14
  330. package/dist/evals/runs.controller.d.ts +2 -57
  331. package/dist/evals/runs.controller.js +1 -42
  332. package/dist/evals/runs.service.d.ts +8 -7
  333. package/dist/evals/runs.service.js +45 -30
  334. package/dist/evals/sets.controller.d.ts +2 -28
  335. package/dist/evals/sets.controller.js +1 -31
  336. package/dist/evals/sets.service.d.ts +7 -7
  337. package/dist/evals/sets.service.js +23 -17
  338. package/dist/evals/snapshot.d.ts +3 -2
  339. package/dist/evals/snapshot.js +17 -4
  340. package/dist/evals/step-replay.d.ts +4 -6
  341. package/dist/evals/step-replay.js +16 -9
  342. package/dist/evals/trajectory.d.ts +5 -3
  343. package/dist/evals/trajectory.js +4 -1
  344. package/dist/evals/trajectory.service.d.ts +8 -3
  345. package/dist/evals/trajectory.service.js +26 -14
  346. package/dist/evals/worker.d.ts +5 -5
  347. package/dist/evals/worker.js +9 -3
  348. package/dist/extensions/admin-ops.d.ts +81 -14
  349. package/dist/extensions/admin-ops.js +181 -47
  350. package/dist/extensions/admin-view.d.ts +57 -10
  351. package/dist/extensions/admin-view.js +249 -27
  352. package/dist/extensions/approvals.d.ts +12 -2
  353. package/dist/extensions/blobs.d.ts +14 -1
  354. package/dist/extensions/blobs.js +49 -5
  355. package/dist/extensions/bootstrap.d.ts +85 -10
  356. package/dist/extensions/bootstrap.js +215 -36
  357. package/dist/extensions/cli.d.ts +12 -0
  358. package/dist/extensions/cli.js +11 -2
  359. package/dist/extensions/config-hash.d.ts +10 -0
  360. package/dist/extensions/config-hash.js +38 -0
  361. package/dist/extensions/config-issues.d.ts +13 -1
  362. package/dist/extensions/config-issues.js +22 -5
  363. package/dist/extensions/config-json-schema.d.ts +43 -7
  364. package/dist/extensions/config-json-schema.js +124 -58
  365. package/dist/extensions/deployment-config-store.d.ts +6 -0
  366. package/dist/extensions/deployment-config.d.ts +94 -9
  367. package/dist/extensions/deployment-config.js +255 -34
  368. package/dist/extensions/display-name.d.ts +22 -0
  369. package/dist/extensions/display-name.js +77 -0
  370. package/dist/extensions/effective-config.d.ts +133 -0
  371. package/dist/extensions/effective-config.js +379 -0
  372. package/dist/extensions/extensions.module.js +31 -12
  373. package/dist/extensions/guide-places.d.ts +68 -0
  374. package/dist/extensions/guide-places.js +152 -0
  375. package/dist/extensions/hooks.d.ts +37 -4
  376. package/dist/extensions/hooks.js +82 -16
  377. package/dist/extensions/host.d.ts +105 -12
  378. package/dist/extensions/host.js +372 -87
  379. package/dist/extensions/installs.d.ts +30 -9
  380. package/dist/extensions/installs.js +57 -16
  381. package/dist/extensions/instance.d.ts +17 -2
  382. package/dist/extensions/instance.js +21 -0
  383. package/dist/extensions/ledger.d.ts +1 -1
  384. package/dist/extensions/locks.d.ts +12 -0
  385. package/dist/extensions/locks.js +26 -0
  386. package/dist/extensions/org-config.d.ts +97 -7
  387. package/dist/extensions/org-config.js +215 -45
  388. package/dist/extensions/org-levels.d.ts +37 -0
  389. package/dist/extensions/org-levels.js +71 -0
  390. package/dist/extensions/org-overrides.d.ts +151 -0
  391. package/dist/extensions/org-overrides.js +524 -0
  392. package/dist/extensions/package.d.ts +18 -4
  393. package/dist/extensions/package.js +56 -3
  394. package/dist/extensions/permission-set.d.ts +35 -5
  395. package/dist/extensions/permission-set.js +36 -5
  396. package/dist/extensions/permission-view.d.ts +106 -13
  397. package/dist/extensions/permission-view.js +33 -15
  398. package/dist/extensions/plugin-guides.d.ts +282 -0
  399. package/dist/extensions/plugin-guides.js +554 -0
  400. package/dist/extensions/plugin-icons.d.ts +54 -0
  401. package/dist/extensions/plugin-icons.js +125 -0
  402. package/dist/extensions/plugin-items.d.ts +21 -0
  403. package/dist/extensions/plugin-items.js +42 -0
  404. package/dist/extensions/point-defaults.controller.d.ts +28 -0
  405. package/dist/extensions/point-defaults.controller.js +69 -0
  406. package/dist/extensions/point-defaults.d.ts +73 -0
  407. package/dist/extensions/point-defaults.js +236 -0
  408. package/dist/extensions/point-defaults.schemas.d.ts +9 -0
  409. package/dist/extensions/point-defaults.schemas.js +14 -0
  410. package/dist/extensions/registry.d.ts +1 -1
  411. package/dist/extensions/registry.js +2 -0
  412. package/dist/extensions/resolve.d.ts +51 -1
  413. package/dist/extensions/resolve.js +85 -3
  414. package/dist/extensions/single-provider.d.ts +92 -0
  415. package/dist/extensions/single-provider.js +180 -0
  416. package/dist/extensions/sources.d.ts +13 -2
  417. package/dist/extensions/sources.js +2 -0
  418. package/dist/extensions/state.module.d.ts +9 -0
  419. package/dist/extensions/state.module.js +46 -0
  420. package/dist/extensions/unavailable.d.ts +50 -0
  421. package/dist/extensions/unavailable.js +115 -0
  422. package/dist/files/previewers.controller.js +2 -3
  423. package/dist/files/previewers.d.ts +10 -3
  424. package/dist/files/previewers.js +40 -11
  425. package/dist/files/previewers.schemas.d.ts +10 -0
  426. package/dist/files/previewers.schemas.js +9 -0
  427. package/dist/http/api.module.js +76 -4
  428. package/dist/http/app.d.ts +11 -2
  429. package/dist/http/app.js +31 -5
  430. package/dist/http/gateway-routes.d.ts +7 -3
  431. package/dist/http/gateway-routes.js +6 -3
  432. package/dist/http/nest-logger.d.ts +6 -5
  433. package/dist/http/nest-logger.js +24 -10
  434. package/dist/http/plugin-dispatch.d.ts +4 -1
  435. package/dist/http/plugin-dispatch.js +14 -2
  436. package/dist/identity/accounts.d.ts +77 -31
  437. package/dist/identity/accounts.js +133 -52
  438. package/dist/identity/audit.d.ts +2 -1
  439. package/dist/identity/auth.controller.js +9 -5
  440. package/dist/identity/auth.guard.d.ts +13 -5
  441. package/dist/identity/auth.guard.js +35 -9
  442. package/dist/identity/cookies.d.ts +26 -5
  443. package/dist/identity/cookies.js +58 -6
  444. package/dist/identity/identity.module.d.ts +1 -1
  445. package/dist/identity/identity.module.js +7 -4
  446. package/dist/identity/initial-passwords.controller.d.ts +22 -0
  447. package/dist/identity/initial-passwords.controller.js +49 -0
  448. package/dist/identity/initial-passwords.d.ts +61 -0
  449. package/dist/identity/initial-passwords.job.d.ts +16 -0
  450. package/dist/identity/initial-passwords.job.js +46 -0
  451. package/dist/identity/initial-passwords.js +159 -0
  452. package/dist/identity/me.controller.d.ts +17 -1
  453. package/dist/identity/me.controller.js +45 -4
  454. package/dist/identity/me.schemas.d.ts +24 -0
  455. package/dist/identity/me.schemas.js +25 -0
  456. package/dist/identity/principal.d.ts +11 -3
  457. package/dist/identity/principal.js +11 -3
  458. package/dist/identity/sessions.d.ts +12 -0
  459. package/dist/identity/sessions.js +45 -0
  460. package/dist/identity/settings.d.ts +99 -12
  461. package/dist/identity/settings.js +234 -25
  462. package/dist/identity/{enterprise-admin.d.ts → super-admin.d.ts} +5 -5
  463. package/dist/identity/{enterprise-admin.js → super-admin.js} +24 -24
  464. package/dist/identity/write-actor.d.ts +11 -0
  465. package/dist/identity/write-actor.js +7 -0
  466. package/dist/inbox/inbox.module.d.ts +4 -1
  467. package/dist/inbox/inbox.module.js +7 -3
  468. package/dist/inbox/inbox.schemas.d.ts +8 -0
  469. package/dist/inbox/inbox.schemas.js +11 -0
  470. package/dist/inbox/inbox.service.d.ts +64 -4
  471. package/dist/inbox/inbox.service.js +74 -4
  472. package/dist/inbox/org-notices.d.ts +63 -0
  473. package/dist/inbox/org-notices.js +91 -0
  474. package/dist/inbox/platform-inbox.controller.d.ts +23 -0
  475. package/dist/inbox/platform-inbox.controller.js +58 -0
  476. package/dist/inbox/platform-inbox.service.d.ts +157 -0
  477. package/dist/inbox/platform-inbox.service.js +281 -0
  478. package/dist/inbox/platform-notices.d.ts +19 -0
  479. package/dist/inbox/platform-notices.js +9 -0
  480. package/dist/index.d.ts +6 -2
  481. package/dist/index.js +7 -1
  482. package/dist/infra/cli/kernel-cli.module.js +9 -1
  483. package/dist/infra/config/kernel-config.service.js +32 -1
  484. package/dist/infra/database/database.module.js +1 -0
  485. package/dist/infra/database/xact-lock.d.ts +1 -1
  486. package/dist/infra/database/xact-lock.js +1 -1
  487. package/dist/infra/events/kernel-events.d.ts +31 -3
  488. package/dist/infra/lifecycle/process-guards.js +4 -0
  489. package/dist/infra/lock/lock.service.d.ts +8 -0
  490. package/dist/infra/lock/lock.service.js +27 -0
  491. package/dist/infra/logging/logging.module.d.ts +4 -2
  492. package/dist/infra/logging/logging.module.js +24 -6
  493. package/dist/infra/presence/presence.module.d.ts +6 -0
  494. package/dist/infra/presence/presence.module.js +22 -0
  495. package/dist/infra/presence/replica-presence.d.ts +87 -0
  496. package/dist/infra/presence/replica-presence.js +189 -0
  497. package/dist/infra/queue/add-job-once.d.ts +14 -0
  498. package/dist/infra/queue/add-job-once.js +16 -0
  499. package/dist/infra/queue/redis-connection.d.ts +6 -0
  500. package/dist/infra/queue/redis-connection.js +7 -0
  501. package/dist/infra/redis/redis.module.js +6 -2
  502. package/dist/infra/throttler/named-throttlers.d.ts +12 -0
  503. package/dist/infra/throttler/named-throttlers.js +67 -0
  504. package/dist/infra/throttler/throttler.guard.js +1 -1
  505. package/dist/infra/tokens.d.ts +6 -1
  506. package/dist/infra/tokens.js +6 -1
  507. package/dist/kernel.module.js +15 -0
  508. package/dist/lifecycle/kernel-lifecycle.d.ts +9 -3
  509. package/dist/lifecycle/kernel-lifecycle.js +51 -11
  510. package/dist/local-devices/device-auth.controller.d.ts +42 -0
  511. package/dist/local-devices/device-auth.controller.js +116 -0
  512. package/dist/local-devices/device-auth.d.ts +119 -0
  513. package/dist/local-devices/device-auth.js +401 -0
  514. package/dist/local-devices/device-auth.module.d.ts +7 -0
  515. package/dist/local-devices/device-auth.module.js +25 -0
  516. package/dist/local-devices/device-routes.d.ts +36 -0
  517. package/dist/local-devices/device-routes.js +90 -0
  518. package/dist/local-devices/dto.d.ts +66 -0
  519. package/dist/local-devices/dto.js +44 -0
  520. package/dist/local-devices/keys.d.ts +25 -0
  521. package/dist/local-devices/keys.js +25 -0
  522. package/dist/local-devices/local-device-gateway.d.ts +134 -0
  523. package/dist/local-devices/local-device-gateway.js +542 -0
  524. package/dist/local-devices/local-device-provider.d.ts +89 -0
  525. package/dist/local-devices/local-device-provider.js +151 -0
  526. package/dist/local-devices/local-devices.module.d.ts +16 -0
  527. package/dist/local-devices/local-devices.module.js +44 -0
  528. package/dist/local-devices/machines.d.ts +118 -0
  529. package/dist/local-devices/machines.js +366 -0
  530. package/dist/local-devices/relay.d.ts +100 -0
  531. package/dist/local-devices/relay.js +373 -0
  532. package/dist/local-devices/targets.d.ts +28 -0
  533. package/dist/local-devices/targets.js +73 -0
  534. package/dist/log.d.ts +13 -1
  535. package/dist/log.js +93 -17
  536. package/dist/models/embeddings.d.ts +82 -0
  537. package/dist/models/embeddings.js +326 -0
  538. package/dist/models/enablement.controller.d.ts +40 -0
  539. package/dist/models/enablement.controller.js +74 -0
  540. package/dist/models/enablement.d.ts +101 -0
  541. package/dist/models/enablement.js +264 -0
  542. package/dist/models/models.module.d.ts +5 -1
  543. package/dist/models/models.module.js +16 -4
  544. package/dist/models/org-models.schemas.d.ts +8 -0
  545. package/dist/models/org-models.schemas.js +22 -0
  546. package/dist/models/prices.d.ts +8 -6
  547. package/dist/models/prices.js +11 -9
  548. package/dist/models/purposes.controller.d.ts +6 -4
  549. package/dist/models/purposes.controller.js +13 -7
  550. package/dist/models/purposes.d.ts +72 -14
  551. package/dist/models/purposes.js +115 -34
  552. package/dist/orgs/directory-sync.d.ts +14 -5
  553. package/dist/orgs/directory-sync.js +41 -17
  554. package/dist/orgs/offboarding.d.ts +12 -8
  555. package/dist/orgs/offboarding.js +54 -33
  556. package/dist/orgs/org-lock.d.ts +5 -0
  557. package/dist/orgs/org-lock.js +13 -0
  558. package/dist/orgs/org.guard.d.ts +10 -0
  559. package/dist/orgs/org.guard.js +43 -32
  560. package/dist/orgs/orgs.controller.d.ts +2 -5
  561. package/dist/orgs/orgs.controller.js +3 -6
  562. package/dist/orgs/orgs.module.js +3 -0
  563. package/dist/orgs/orgs.schemas.d.ts +8 -0
  564. package/dist/orgs/orgs.schemas.js +16 -0
  565. package/dist/orgs/orgs.service.d.ts +23 -8
  566. package/dist/orgs/orgs.service.js +38 -16
  567. package/dist/orgs/roles.d.ts +1 -1
  568. package/dist/orgs/roles.js +5 -1
  569. package/dist/orgs/settings.controller.d.ts +1 -1
  570. package/dist/orgs/settings.service.d.ts +66 -6
  571. package/dist/orgs/settings.service.js +89 -10
  572. package/dist/overrides.d.ts +16 -1
  573. package/dist/overrides.js +10 -0
  574. package/dist/platform/actor.d.ts +48 -0
  575. package/dist/platform/actor.js +100 -0
  576. package/dist/platform/agent-docs.d.ts +18 -0
  577. package/dist/platform/agent-docs.js +59 -0
  578. package/dist/platform/availability.d.ts +14 -0
  579. package/dist/platform/availability.js +43 -0
  580. package/dist/platform/errors.d.ts +28 -0
  581. package/dist/platform/errors.js +65 -0
  582. package/dist/platform/exclusions.d.ts +11 -0
  583. package/dist/platform/exclusions.js +774 -0
  584. package/dist/platform/method.d.ts +6 -0
  585. package/dist/platform/method.js +15 -0
  586. package/dist/platform/operation.d.ts +169 -0
  587. package/dist/platform/operation.js +102 -0
  588. package/dist/platform/ops/accounts.ops.d.ts +32 -0
  589. package/dist/platform/ops/accounts.ops.js +291 -0
  590. package/dist/platform/ops/admin-items.ops.d.ts +37 -0
  591. package/dist/platform/ops/admin-items.ops.js +278 -0
  592. package/dist/platform/ops/admin.ops.d.ts +38 -0
  593. package/dist/platform/ops/admin.ops.js +497 -0
  594. package/dist/platform/ops/agent-skills.ops.d.ts +50 -0
  595. package/dist/platform/ops/agent-skills.ops.js +509 -0
  596. package/dist/platform/ops/agent-templates.ops.d.ts +17 -0
  597. package/dist/platform/ops/agent-templates.ops.js +272 -0
  598. package/dist/platform/ops/agents.ops.d.ts +37 -0
  599. package/dist/platform/ops/agents.ops.js +730 -0
  600. package/dist/platform/ops/approvals.ops.d.ts +30 -0
  601. package/dist/platform/ops/approvals.ops.js +116 -0
  602. package/dist/platform/ops/background.ops.d.ts +14 -0
  603. package/dist/platform/ops/background.ops.js +134 -0
  604. package/dist/platform/ops/catalog-admin.ops.d.ts +92 -0
  605. package/dist/platform/ops/catalog-admin.ops.js +682 -0
  606. package/dist/platform/ops/channels.ops.d.ts +16 -0
  607. package/dist/platform/ops/channels.ops.js +195 -0
  608. package/dist/platform/ops/components.ops.d.ts +46 -0
  609. package/dist/platform/ops/components.ops.js +854 -0
  610. package/dist/platform/ops/connectors.ops.d.ts +17 -0
  611. package/dist/platform/ops/connectors.ops.js +190 -0
  612. package/dist/platform/ops/conversations.ops.d.ts +30 -0
  613. package/dist/platform/ops/conversations.ops.js +470 -0
  614. package/dist/platform/ops/devices.ops.d.ts +26 -0
  615. package/dist/platform/ops/devices.ops.js +311 -0
  616. package/dist/platform/ops/errors.d.ts +16 -0
  617. package/dist/platform/ops/errors.js +30 -0
  618. package/dist/platform/ops/evals.ops.d.ts +28 -0
  619. package/dist/platform/ops/evals.ops.js +783 -0
  620. package/dist/platform/ops/extensions-admin.ops.d.ts +25 -0
  621. package/dist/platform/ops/extensions-admin.ops.js +372 -0
  622. package/dist/platform/ops/extensions.ops.d.ts +25 -0
  623. package/dist/platform/ops/extensions.ops.js +347 -0
  624. package/dist/platform/ops/inbox.ops.d.ts +25 -0
  625. package/dist/platform/ops/inbox.ops.js +381 -0
  626. package/dist/platform/ops/marketplaces.ops.d.ts +22 -0
  627. package/dist/platform/ops/marketplaces.ops.js +466 -0
  628. package/dist/platform/ops/models.ops.d.ts +25 -0
  629. package/dist/platform/ops/models.ops.js +232 -0
  630. package/dist/platform/ops/org-plugins.ops.d.ts +35 -0
  631. package/dist/platform/ops/org-plugins.ops.js +388 -0
  632. package/dist/platform/ops/org-settings.ops.d.ts +18 -0
  633. package/dist/platform/ops/org-settings.ops.js +127 -0
  634. package/dist/platform/ops/orgs.ops.d.ts +28 -0
  635. package/dist/platform/ops/orgs.ops.js +298 -0
  636. package/dist/platform/ops/other-orgs.d.ts +20 -0
  637. package/dist/platform/ops/other-orgs.js +72 -0
  638. package/dist/platform/ops/paging.d.ts +14 -0
  639. package/dist/platform/ops/paging.js +20 -0
  640. package/dist/platform/ops/platform.ops.d.ts +30 -0
  641. package/dist/platform/ops/platform.ops.js +231 -0
  642. package/dist/platform/ops/plugins.ops.d.ts +35 -0
  643. package/dist/platform/ops/plugins.ops.js +476 -0
  644. package/dist/platform/ops/previewers.ops.d.ts +14 -0
  645. package/dist/platform/ops/previewers.ops.js +109 -0
  646. package/dist/platform/ops/requests.ops.d.ts +14 -0
  647. package/dist/platform/ops/requests.ops.js +191 -0
  648. package/dist/platform/ops/role.d.ts +4 -0
  649. package/dist/platform/ops/role.js +10 -0
  650. package/dist/platform/ops/setup.ops.d.ts +24 -0
  651. package/dist/platform/ops/setup.ops.js +212 -0
  652. package/dist/platform/ops/skills.ops.d.ts +17 -0
  653. package/dist/platform/ops/skills.ops.js +160 -0
  654. package/dist/platform/ops/summary.d.ts +10 -0
  655. package/dist/platform/ops/summary.js +18 -0
  656. package/dist/platform/ops/themes.ops.d.ts +14 -0
  657. package/dist/platform/ops/themes.ops.js +47 -0
  658. package/dist/platform/ops/ui.ops.d.ts +8 -0
  659. package/dist/platform/ops/ui.ops.js +41 -0
  660. package/dist/platform/ops/unavailable.d.ts +23 -0
  661. package/dist/platform/ops/unavailable.js +118 -0
  662. package/dist/platform/parity.d.ts +33 -0
  663. package/dist/platform/parity.js +272 -0
  664. package/dist/platform/platform-ops.module.d.ts +43 -0
  665. package/dist/platform/platform-ops.module.js +142 -0
  666. package/dist/platform/platform.module.d.ts +8 -0
  667. package/dist/platform/platform.module.js +25 -0
  668. package/dist/platform/registry.d.ts +30 -0
  669. package/dist/platform/registry.js +111 -0
  670. package/dist/platform/route-schemas.d.ts +18 -0
  671. package/dist/platform/route-schemas.js +85 -0
  672. package/dist/roles/gateway-role.d.ts +18 -3
  673. package/dist/roles/gateway-role.js +33 -5
  674. package/dist/roles/gateway.module.d.ts +2 -1
  675. package/dist/roles/gateway.module.js +6 -3
  676. package/dist/roles/worker-role.d.ts +6 -4
  677. package/dist/roles/worker-role.js +14 -7
  678. package/dist/roles/worker.module.d.ts +1 -1
  679. package/dist/roles/worker.module.js +21 -1
  680. package/dist/runtime/context/build.d.ts +21 -13
  681. package/dist/runtime/context/build.js +106 -29
  682. package/dist/runtime/context/claude-tools.d.ts +4 -1
  683. package/dist/runtime/context/claude-tools.js +13 -12
  684. package/dist/runtime/context/compaction.js +24 -1
  685. package/dist/runtime/context/device-paths.d.ts +40 -0
  686. package/dist/runtime/context/device-paths.js +48 -0
  687. package/dist/runtime/context/legacy-text.d.ts +22 -0
  688. package/dist/runtime/context/legacy-text.js +37 -0
  689. package/dist/runtime/context/reminders.d.ts +2 -0
  690. package/dist/runtime/context/reminders.js +2 -0
  691. package/dist/runtime/context/run-context.d.ts +65 -0
  692. package/dist/runtime/context/run-context.js +215 -0
  693. package/dist/runtime/context/skills.d.ts +29 -11
  694. package/dist/runtime/context/skills.js +46 -32
  695. package/dist/runtime/context/tool-changes.d.ts +27 -0
  696. package/dist/runtime/context/tool-changes.js +60 -0
  697. package/dist/runtime/hook-calls.d.ts +8 -1
  698. package/dist/runtime/hook-calls.js +40 -7
  699. package/dist/runtime/langgraph-loop.d.ts +3 -1
  700. package/dist/runtime/langgraph-loop.js +36 -17
  701. package/dist/runtime/loop.d.ts +35 -6
  702. package/dist/runtime/maintenance.d.ts +9 -4
  703. package/dist/runtime/maintenance.js +35 -8
  704. package/dist/runtime/model-call.d.ts +54 -11
  705. package/dist/runtime/model-call.js +105 -28
  706. package/dist/runtime/providers.d.ts +149 -29
  707. package/dist/runtime/providers.js +332 -63
  708. package/dist/runtime/run-context.service.d.ts +65 -0
  709. package/dist/runtime/run-context.service.js +236 -0
  710. package/dist/runtime/run-control.d.ts +11 -1
  711. package/dist/runtime/run-control.js +12 -0
  712. package/dist/runtime/run-drafts.d.ts +85 -0
  713. package/dist/runtime/run-drafts.js +230 -0
  714. package/dist/runtime/run-finish.d.ts +1 -1
  715. package/dist/runtime/run-jobs.d.ts +3 -5
  716. package/dist/runtime/run-jobs.js +7 -9
  717. package/dist/runtime/run-queue.d.ts +62 -5
  718. package/dist/runtime/run-queue.js +313 -40
  719. package/dist/runtime/run-worker.d.ts +19 -8
  720. package/dist/runtime/run-worker.js +105 -44
  721. package/dist/runtime/runtime.module.js +13 -3
  722. package/dist/runtime/step-log.d.ts +15 -4
  723. package/dist/runtime/step-log.js +24 -7
  724. package/dist/runtime/stream.controller.d.ts +23 -1
  725. package/dist/runtime/stream.controller.js +60 -3
  726. package/dist/runtime/stream.d.ts +4 -1
  727. package/dist/runtime/stream.js +27 -3
  728. package/dist/runtime/subagents/child.d.ts +55 -7
  729. package/dist/runtime/subagents/child.js +105 -7
  730. package/dist/runtime/subagents/definition.d.ts +31 -26
  731. package/dist/runtime/subagents/definition.js +89 -142
  732. package/dist/runtime/subagents/deliveries.d.ts +71 -0
  733. package/dist/runtime/subagents/deliveries.js +228 -0
  734. package/dist/runtime/subagents/dispatch.d.ts +116 -27
  735. package/dist/runtime/subagents/dispatch.js +213 -142
  736. package/dist/runtime/subagents/limits.d.ts +24 -5
  737. package/dist/runtime/subagents/limits.js +40 -6
  738. package/dist/runtime/subagents/messages.d.ts +95 -0
  739. package/dist/runtime/subagents/messages.js +328 -0
  740. package/dist/runtime/subagents/org-agents.js +2 -1
  741. package/dist/runtime/subagents/preamble.d.ts +4 -4
  742. package/dist/runtime/subagents/preamble.js +9 -7
  743. package/dist/runtime/subagents/progress.d.ts +8 -6
  744. package/dist/runtime/subagents/progress.js +11 -6
  745. package/dist/runtime/subagents/render.d.ts +30 -0
  746. package/dist/runtime/subagents/render.js +53 -0
  747. package/dist/runtime/subagents/roster.d.ts +25 -8
  748. package/dist/runtime/subagents/roster.js +48 -12
  749. package/dist/runtime/subagents/skill-fork.d.ts +4 -3
  750. package/dist/runtime/subagents/skill-fork.js +9 -5
  751. package/dist/runtime/subagents/status.d.ts +14 -0
  752. package/dist/runtime/subagents/status.js +23 -0
  753. package/dist/runtime/subagents/subagents.service.d.ts +9 -6
  754. package/dist/runtime/subagents/subagents.service.js +13 -8
  755. package/dist/runtime/usage-report-jobs.d.ts +88 -0
  756. package/dist/runtime/usage-report-jobs.js +158 -0
  757. package/dist/runtime/usage-report.d.ts +35 -39
  758. package/dist/runtime/usage-report.js +171 -97
  759. package/dist/runtime/waits.d.ts +148 -39
  760. package/dist/runtime/waits.js +426 -112
  761. package/dist/sandbox/jobs.d.ts +40 -13
  762. package/dist/sandbox/jobs.js +57 -20
  763. package/dist/sandbox/leases.d.ts +37 -54
  764. package/dist/sandbox/leases.js +71 -115
  765. package/dist/sandbox/providers.d.ts +34 -7
  766. package/dist/sandbox/providers.js +54 -9
  767. package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
  768. package/dist/sandbox/sandbox-file-reader.js +98 -0
  769. package/dist/sandbox/sandbox.module.d.ts +3 -2
  770. package/dist/sandbox/sandbox.module.js +10 -4
  771. package/dist/sandbox/sandbox.service.d.ts +345 -132
  772. package/dist/sandbox/sandbox.service.js +883 -399
  773. package/dist/sandbox/unreachable.d.ts +30 -0
  774. package/dist/sandbox/unreachable.js +108 -0
  775. package/dist/setup/kernel-checks.d.ts +91 -0
  776. package/dist/setup/kernel-checks.js +295 -0
  777. package/dist/setup/oauth-token-probe.d.ts +47 -0
  778. package/dist/setup/oauth-token-probe.js +108 -0
  779. package/dist/setup/redact-check.d.ts +18 -0
  780. package/dist/setup/redact-check.js +46 -0
  781. package/dist/setup/setup-check-registry.d.ts +86 -0
  782. package/dist/setup/setup-check-registry.js +75 -0
  783. package/dist/setup/setup-checks.controller.d.ts +28 -0
  784. package/dist/setup/setup-checks.controller.js +78 -0
  785. package/dist/setup/setup-checks.schemas.d.ts +38 -0
  786. package/dist/setup/setup-checks.schemas.js +43 -0
  787. package/dist/setup/setup-checks.service.d.ts +144 -0
  788. package/dist/setup/setup-checks.service.js +683 -0
  789. package/dist/setup/setup-guides.controller.d.ts +18 -0
  790. package/dist/setup/setup-guides.controller.js +48 -0
  791. package/dist/setup/setup-guides.schemas.d.ts +10 -0
  792. package/dist/setup/setup-guides.schemas.js +6 -0
  793. package/dist/setup/setup-guides.service.d.ts +126 -0
  794. package/dist/setup/setup-guides.service.js +315 -0
  795. package/dist/setup/setup.module.d.ts +8 -0
  796. package/dist/setup/setup.module.js +31 -0
  797. package/dist/skills/agent-skills.controller.d.ts +48 -0
  798. package/dist/skills/agent-skills.controller.js +131 -0
  799. package/dist/skills/agent-skills.service.d.ts +262 -0
  800. package/dist/skills/agent-skills.service.js +996 -0
  801. package/dist/skills/catalog.d.ts +14 -11
  802. package/dist/skills/catalog.js +32 -50
  803. package/dist/skills/changes.controller.d.ts +12 -22
  804. package/dist/skills/changes.controller.js +27 -56
  805. package/dist/skills/changes.service.d.ts +69 -115
  806. package/dist/skills/changes.service.js +249 -598
  807. package/dist/skills/conversation-catalog.d.ts +53 -0
  808. package/dist/skills/conversation-catalog.js +149 -0
  809. package/dist/skills/invocable.controller.d.ts +1 -1
  810. package/dist/skills/invocable.controller.js +2 -2
  811. package/dist/skills/invocation-keys.d.ts +1 -1
  812. package/dist/skills/invocation-keys.js +1 -1
  813. package/dist/skills/invoke.d.ts +3 -3
  814. package/dist/skills/invoke.js +21 -8
  815. package/dist/skills/package.d.ts +15 -1
  816. package/dist/skills/package.js +14 -1
  817. package/dist/skills/skills.controller.d.ts +32 -6
  818. package/dist/skills/skills.controller.js +64 -11
  819. package/dist/skills/skills.module.js +25 -3
  820. package/dist/skills/skills.schemas.d.ts +44 -0
  821. package/dist/skills/skills.schemas.js +68 -0
  822. package/dist/skills/skills.service.d.ts +123 -26
  823. package/dist/skills/skills.service.js +493 -76
  824. package/dist/skills/usable.d.ts +82 -0
  825. package/dist/skills/usable.js +388 -0
  826. package/dist/themes/render-css.d.ts +13 -0
  827. package/dist/themes/render-css.js +135 -0
  828. package/dist/themes/theme-delivery.service.d.ts +36 -0
  829. package/dist/themes/theme-delivery.service.js +116 -0
  830. package/dist/themes/theme-state.d.ts +116 -0
  831. package/dist/themes/theme-state.js +221 -0
  832. package/dist/themes/theme-usage.d.ts +61 -0
  833. package/dist/themes/theme-usage.js +98 -0
  834. package/dist/themes/themes-public.controller.d.ts +33 -0
  835. package/dist/themes/themes-public.controller.js +98 -0
  836. package/dist/themes/themes.controller.d.ts +13 -0
  837. package/dist/themes/themes.controller.js +38 -0
  838. package/dist/themes/themes.module.d.ts +3 -0
  839. package/dist/themes/themes.module.js +21 -0
  840. package/dist/themes/themes.schemas.d.ts +3 -0
  841. package/dist/themes/themes.schemas.js +2 -0
  842. package/dist/themes/themes.service.d.ts +50 -0
  843. package/dist/themes/themes.service.js +99 -0
  844. package/dist/time-zone.d.ts +5 -0
  845. package/dist/time-zone.js +19 -0
  846. package/dist/tools/admin-connectors.d.ts +69 -0
  847. package/dist/tools/admin-connectors.js +135 -0
  848. package/dist/tools/approvals.controller.js +2 -3
  849. package/dist/tools/approvals.schemas.d.ts +21 -0
  850. package/dist/tools/approvals.schemas.js +15 -0
  851. package/dist/tools/approvals.service.d.ts +26 -2
  852. package/dist/tools/approvals.service.js +44 -5
  853. package/dist/tools/builtin-names.d.ts +104 -0
  854. package/dist/tools/builtin-names.js +164 -0
  855. package/dist/tools/builtins/agent.d.ts +27 -25
  856. package/dist/tools/builtins/agent.js +66 -63
  857. package/dist/tools/builtins/ask-user.d.ts +1 -1
  858. package/dist/tools/builtins/ask-user.js +5 -4
  859. package/dist/tools/builtins/builtin-tools.d.ts +23 -4
  860. package/dist/tools/builtins/builtin-tools.js +78 -12
  861. package/dist/tools/builtins/current-time.d.ts +26 -0
  862. package/dist/tools/builtins/current-time.js +99 -0
  863. package/dist/tools/builtins/handback.d.ts +28 -0
  864. package/dist/tools/builtins/handback.js +53 -0
  865. package/dist/tools/builtins/platform.d.ts +51 -0
  866. package/dist/tools/builtins/platform.js +451 -0
  867. package/dist/tools/builtins/read-tool-output.d.ts +1 -1
  868. package/dist/tools/builtins/read-tool-output.js +2 -2
  869. package/dist/tools/builtins/sandbox/bash.d.ts +5 -5
  870. package/dist/tools/builtins/sandbox/bash.js +291 -39
  871. package/dist/tools/builtins/sandbox/common.d.ts +135 -36
  872. package/dist/tools/builtins/sandbox/common.js +222 -62
  873. package/dist/tools/builtins/sandbox/deliver.d.ts +1 -1
  874. package/dist/tools/builtins/sandbox/deliver.js +19 -16
  875. package/dist/tools/builtins/sandbox/edit.d.ts +1 -1
  876. package/dist/tools/builtins/sandbox/edit.js +64 -50
  877. package/dist/tools/builtins/sandbox/file-state.d.ts +34 -0
  878. package/dist/tools/builtins/sandbox/file-state.js +34 -0
  879. package/dist/tools/builtins/sandbox/glob-match.d.ts +1 -1
  880. package/dist/tools/builtins/sandbox/glob-match.js +1 -1
  881. package/dist/tools/builtins/sandbox/glob.d.ts +1 -1
  882. package/dist/tools/builtins/sandbox/glob.js +17 -12
  883. package/dist/tools/builtins/sandbox/grep.d.ts +7 -4
  884. package/dist/tools/builtins/sandbox/grep.js +153 -34
  885. package/dist/tools/builtins/sandbox/index.d.ts +9 -5
  886. package/dist/tools/builtins/sandbox/index.js +13 -5
  887. package/dist/tools/builtins/sandbox/read.d.ts +2 -2
  888. package/dist/tools/builtins/sandbox/read.js +29 -19
  889. package/dist/tools/builtins/sandbox/task-output.d.ts +79 -0
  890. package/dist/tools/builtins/sandbox/task-output.js +334 -0
  891. package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
  892. package/dist/tools/builtins/sandbox/task-stop.js +113 -0
  893. package/dist/tools/builtins/sandbox/write.d.ts +1 -1
  894. package/dist/tools/builtins/sandbox/write.js +51 -44
  895. package/dist/tools/builtins/send-message.d.ts +56 -0
  896. package/dist/tools/builtins/send-message.js +100 -0
  897. package/dist/tools/builtins/skill-load.d.ts +29 -18
  898. package/dist/tools/builtins/skill-load.js +125 -73
  899. package/dist/tools/builtins/tool-search.d.ts +13 -9
  900. package/dist/tools/builtins/tool-search.js +25 -14
  901. package/dist/tools/builtins/web/errors.d.ts +8 -0
  902. package/dist/tools/builtins/web/errors.js +53 -0
  903. package/dist/tools/builtins/web/extract.d.ts +62 -0
  904. package/dist/tools/builtins/web/extract.js +271 -0
  905. package/dist/tools/builtins/web/fetch.d.ts +55 -0
  906. package/dist/tools/builtins/web/fetch.js +175 -0
  907. package/dist/tools/builtins/web/index.d.ts +13 -0
  908. package/dist/tools/builtins/web/index.js +12 -0
  909. package/dist/tools/builtins/web/page-cache.d.ts +25 -0
  910. package/dist/tools/builtins/web/page-cache.js +77 -0
  911. package/dist/tools/builtins/web/search.d.ts +25 -0
  912. package/dist/tools/builtins/web/search.js +210 -0
  913. package/dist/tools/builtins/web/url.d.ts +17 -0
  914. package/dist/tools/builtins/web/url.js +43 -0
  915. package/dist/tools/connector-account-policies.d.ts +51 -0
  916. package/dist/tools/connector-account-policies.js +165 -0
  917. package/dist/tools/connectors.controller.d.ts +74 -9
  918. package/dist/tools/connectors.controller.js +133 -16
  919. package/dist/tools/connectors.schemas.d.ts +27 -0
  920. package/dist/tools/connectors.schemas.js +34 -0
  921. package/dist/tools/connectors.service.d.ts +101 -8
  922. package/dist/tools/connectors.service.js +266 -28
  923. package/dist/tools/credentials.d.ts +23 -0
  924. package/dist/tools/credentials.js +90 -1
  925. package/dist/tools/display-names.d.ts +9 -0
  926. package/dist/tools/display-names.js +24 -0
  927. package/dist/tools/drift.js +5 -5
  928. package/dist/tools/elicitation.js +1 -1
  929. package/dist/tools/extension-tools.controller.d.ts +14 -0
  930. package/dist/tools/extension-tools.controller.js +37 -0
  931. package/dist/tools/info.d.ts +61 -4
  932. package/dist/tools/info.js +119 -33
  933. package/dist/tools/legacy-data.d.ts +34 -0
  934. package/dist/tools/legacy-data.js +76 -0
  935. package/dist/tools/legacy-keys.d.ts +51 -0
  936. package/dist/tools/legacy-keys.js +114 -0
  937. package/dist/tools/naming.d.ts +1 -5
  938. package/dist/tools/naming.js +1 -7
  939. package/dist/tools/oauth/flow-paths.d.ts +1 -1
  940. package/dist/tools/oauth/flow-paths.js +1 -1
  941. package/dist/tools/oauth/flow.js +1 -1
  942. package/dist/tools/oauth/tokens.d.ts +54 -7
  943. package/dist/tools/oauth/tokens.js +87 -17
  944. package/dist/tools/org-tool-switches.d.ts +89 -0
  945. package/dist/tools/org-tool-switches.js +201 -0
  946. package/dist/tools/outputs.js +1 -1
  947. package/dist/tools/pipeline.d.ts +67 -47
  948. package/dist/tools/pipeline.js +319 -380
  949. package/dist/tools/policy.d.ts +30 -23
  950. package/dist/tools/policy.js +142 -64
  951. package/dist/tools/questions.controller.d.ts +1 -1
  952. package/dist/tools/questions.controller.js +1 -1
  953. package/dist/tools/questions.service.d.ts +23 -2
  954. package/dist/tools/questions.service.js +104 -2
  955. package/dist/tools/registry.d.ts +189 -68
  956. package/dist/tools/registry.js +318 -236
  957. package/dist/tools/resources.d.ts +1 -1
  958. package/dist/tools/resources.js +1 -1
  959. package/dist/tools/tools.module.js +31 -3
  960. package/dist/tools/validate.js +1 -1
  961. package/dist/ui/admin-ui.controller.d.ts +56 -0
  962. package/dist/ui/admin-ui.controller.js +192 -0
  963. package/dist/ui/arrangement.d.ts +65 -0
  964. package/dist/ui/arrangement.js +171 -0
  965. package/dist/ui/embed-token.d.ts +2 -2
  966. package/dist/ui/embed-token.js +30 -5
  967. package/dist/ui/registry.d.ts +42 -17
  968. package/dist/ui/registry.js +46 -14
  969. package/dist/ui/slots.controller.d.ts +11 -0
  970. package/dist/ui/slots.controller.js +25 -0
  971. package/dist/ui/ui-stream.d.ts +20 -3
  972. package/dist/ui/ui-stream.js +25 -7
  973. package/dist/ui/ui.schemas.d.ts +70 -0
  974. package/dist/ui/ui.schemas.js +65 -0
  975. package/dist/ui/views.controller.d.ts +35 -49
  976. package/dist/ui/views.controller.js +48 -70
  977. package/dist/ui/views.d.ts +112 -18
  978. package/dist/ui/views.js +254 -47
  979. package/package.json +6 -3
  980. package/src/migrations/0063_tool_keys_claude_code.sql +220 -0
  981. package/src/migrations/0064_account_last_org.sql +7 -0
  982. package/src/migrations/0065_conversation_archive.sql +8 -0
  983. package/src/migrations/0066_extension_display_name.sql +4 -0
  984. package/src/migrations/0067_agent_profiles.sql +20 -0
  985. package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
  986. package/src/migrations/0069_attachments_agent_idx.sql +3 -0
  987. package/src/migrations/0070_background_tasks.sql +37 -0
  988. package/src/migrations/0071_background_tasks_reserve.sql +6 -0
  989. package/src/migrations/0072_background_tasks_stopping.sql +5 -0
  990. package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
  991. package/src/migrations/0074_subagent_tree.sql +50 -0
  992. package/src/migrations/0075_plugin_scope.sql +61 -0
  993. package/src/migrations/0076_skills_created_by_nullable.sql +6 -0
  994. package/src/migrations/0077_scope_requests.sql +52 -0
  995. package/src/migrations/0078_scope_grant_switches.sql +18 -0
  996. package/src/migrations/0079_scope_live_install_switches.sql +19 -0
  997. package/src/migrations/0080_rename_enterprise_admin.sql +18 -0
  998. package/src/migrations/0081_platform_components.sql +129 -0
  999. package/src/migrations/0082_platform_migration.sql +33 -0
  1000. package/src/migrations/0083_plugin_scope_sync_failures.sql +15 -0
  1001. package/src/migrations/0084_org_owned_items.sql +85 -0
  1002. package/src/migrations/0085_agent_templates_phase4.sql +45 -0
  1003. package/src/migrations/0086_org_layering.sql +30 -0
  1004. package/src/migrations/0087_approval_summary.sql +6 -0
  1005. package/src/migrations/0088_environments.sql +80 -0
  1006. package/src/migrations/0089_model_kinds_usage.sql +38 -0
  1007. package/src/migrations/0090_initial_passwords.sql +27 -0
  1008. package/src/migrations/0091_account_time_zone.sql +4 -0
  1009. package/src/migrations/0092_tools_default_on.sql +27 -0
  1010. package/src/migrations/0093_connected_environments.sql +37 -0
  1011. package/src/migrations/0094_environment_paths_idx.sql +7 -0
  1012. package/src/migrations/0095_environment_files_resets.sql +5 -0
  1013. package/src/migrations/0096_environment_demoted_default.sql +18 -0
  1014. package/src/migrations/0097_extension_installs_enabled_at.sql +16 -0
  1015. package/src/migrations/0098_single_provider_running_first.sql +41 -0
  1016. package/src/migrations/0099_channel_connection_guide_ack.sql +6 -0
  1017. package/src/migrations/0100_catalog_files.sql +13 -0
  1018. package/src/migrations/0101_marketplace_icons.sql +32 -0
  1019. package/src/migrations/0102_themes.sql +64 -0
  1020. package/src/migrations/0103_ui_theme_setting.sql +7 -0
  1021. package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
  1022. package/src/migrations/0105_devices_rename.sql +136 -0
  1023. package/src/migrations/0106_setup_guides_v2.sql +10 -0
  1024. package/src/migrations/0107_extension_client_builds.sql +50 -0
  1025. package/src/migrations/0108_question_skipped.sql +6 -0
  1026. package/src/migrations/0109_audit_platform_idx.sql +5 -0
  1027. package/src/migrations/0110_usage_time_idx.sql +8 -0
  1028. package/src/migrations/0111_agent_skill_sharing.sql +22 -0
  1029. package/src/migrations/0112_skill_share_requests.sql +4 -0
  1030. package/dist/catalog/upgrade-targets.d.ts +0 -56
  1031. package/dist/catalog/upgrade-targets.js +0 -90
  1032. package/dist/conversations/sandbox-status.d.ts +0 -24
  1033. package/dist/conversations/sandbox-status.js +0 -52
  1034. package/dist/extensions/org-config.controller.d.ts +0 -24
  1035. package/dist/extensions/org-config.controller.js +0 -62
  1036. package/dist/tools/builtins/sandbox/names.d.ts +0 -7
  1037. package/dist/tools/builtins/sandbox/names.js +0 -20
  1038. package/dist/tools/builtins/skill-write.d.ts +0 -59
  1039. package/dist/tools/builtins/skill-write.js +0 -244
@@ -7,37 +7,63 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { createHash, randomUUID } from "node:crypto";
10
+ // The platform plugin update (plugin and component management spec §7, plugin scope phase 2,
11
+ // Task 7): a platform plugin has one version for the deployment, so an update is previewed and
12
+ // confirmed once by the super admin, per component. The per-org upgrade routes are retired (ruling
13
+ // F2).
14
+ import { createHash } from "node:crypto";
15
+ import { SDK_VERSION } from "@capybara-hq/plugin-sdk";
11
16
  import { Inject, Injectable } from "@nestjs/common";
12
17
  import { sql } from "kysely";
13
- import { putTemplateVersionIn } from "../agents/templates.js";
14
18
  import { ApiError } from "../errors.js";
15
- import { isExtensionEnabled } from "../extensions/enabled.js";
16
- import { OrgExtensionConfigs } from "../extensions/org-config.js";
19
+ import { ExtensionAdminOps } from "../extensions/admin-ops.js";
20
+ import { ExtensionBlobs } from "../extensions/blobs.js";
21
+ import { permissionSetOfPackage, readExtensionPackage } from "../extensions/package.js";
22
+ import { blockingKeys, diffPermissions } from "../extensions/permission-set.js";
23
+ import { PluginGuides } from "../extensions/plugin-guides.js";
17
24
  import { ExtensionRegistry } from "../extensions/registry.js";
18
25
  import { audit } from "../identity/audit.js";
26
+ import { SuperAdminLock } from "../identity/super-admin.js";
27
+ import { addOrgNotice, itemDisplayName, notifySharedUpdate } from "../inbox/org-notices.js";
28
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
29
+ import { afterCommit } from "../infra/database/after-commit.js";
19
30
  import { KernelDb } from "../infra/database/kernel-db.js";
20
31
  import { TransactionHost } from "../infra/database/transaction-host.js";
21
32
  import { Transactional } from "../infra/database/transactional.decorator.js";
33
+ import { LockService } from "../infra/lock/lock.service.js";
22
34
  import { KERNEL_LOGGER } from "../infra/tokens.js";
23
35
  import { OrgLock } from "../orgs/org-lock.js";
24
36
  import { packagePath } from "../packages/tgz.js";
25
- import { SkillsService, skillConflictError } from "../skills/skills.service.js";
26
37
  import { StorageService } from "../storage/storage.service.js";
38
+ import { assertNotPlatformDefault, pickedByIn, themesPreview, } from "../themes/theme-usage.js";
39
+ import { canonicalJson } from "../tools/canonical-json.js";
27
40
  import { ConnectorsService } from "../tools/connectors.service.js";
28
- import { canonicalJson } from "../tools/registry.js";
29
- import { getPath, lockOrgForActions, orgTemplateView, readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, requiredActions, setPath, skillOf, templatePluginProblem, } from "./catalog-content.js";
30
- import { catalogSkillNames, InstallFailure, journalEntries, missingTemplateInput, PluginInstaller, planActions, planSkillConflicts, resolveDependencies, under, } from "./plugin-install.js";
31
- import { assertItemInstallable, CatalogRequestsService } from "./requests.service.js";
32
- import { deployedExtension, deploymentTarget } from "./upgrade-targets.js";
33
- import { satisfiesRange } from "./versions.js";
41
+ import { dirDigest, getPath, readCatalogAgentTemplates, readCatalogTemplates, readCatalogThemes, setPath, skillFilesDigest, templatePluginProblem, themeAssetsDigest, } from "./catalog-content.js";
42
+ import { markVersionApplied, versionUsable } from "./catalog-items.js";
43
+ import { agentsUsing, clashingOrgSkills, clashingPlatformSkills, componentOn, connectorsMadeFrom, connectorTemplatesOn, listedIn, loadComponents, loadScopeState, pluginFactsOf, switchOf, themeIdClashes, } from "./component-state.js";
44
+ import { CatalogItemLock } from "./locks.js";
45
+ import { itemPendingError, itemRemovedError } from "./ownership.js";
46
+ import { PlanStale, PlatformComponents } from "./platform-components.js";
47
+ import { catalogSkillNames, PluginInstaller } from "./plugin-install.js";
48
+ import { PluginScopeSync } from "./scope-sync.js";
49
+ import { compareVersions, satisfiesRange } from "./versions.js";
34
50
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
35
- const installNotFound = () => new ApiError("plugin.install_not_found", "Plugin install not found");
36
- const inProgress = () => new ApiError("plugin.install_in_progress", "This plugin install is being installed, upgraded or uninstalled; try again when it finishes");
37
- const stale = () => new ApiError("plugin.upgrade_stale", "The upgrade preview is out of date (the catalog version or this install's components changed); preview the upgrade again");
38
- const targetInvalid = (message, details) => new ApiError("plugin.upgrade_target_invalid", message, details);
39
- const fail = (path, message) => new ApiError("validation_failed", "Request validation failed", [{ path, message }]);
40
51
  const sha256 = (s) => createHash("sha256").update(s).digest("hex");
52
+ const itemNotFound = () => new ApiError("catalog.item_not_found", "Catalog item not found");
53
+ const stale = () => new ApiError("plugin.upgrade_stale", "The update preview is out of date (the catalog version, the platform components, a pin or a connector changed); preview the update again");
54
+ /** The pending version a super admin confirms was replaced by another upload (P3-11). */
55
+ const pendingStale = (name) => new ApiError("extension.approval_stale", `The pending upload of ${name} changed since it was reviewed; review it again`);
56
+ const targetInvalid = (message, details) => new ApiError("plugin.upgrade_target_invalid", message, details);
57
+ /** A version an org uploaded that was never applied or confirmed, or was rejected (P3-37, P3-52). */
58
+ const notApplied = (name, version) => targetInvalid(`Version ${version} of plugin ${name} was uploaded by an organization and never applied or confirmed (or was rejected); the generic update cannot apply it`, [{ reason: "org_version_not_applied", version }]);
59
+ const KIND_ORDER = {
60
+ skill: 0,
61
+ agent_template: 1,
62
+ connector_template: 2,
63
+ theme: 3,
64
+ };
65
+ /** How long one update holds its item (renewed while it runs). */
66
+ const UPDATE_LOCK_TTL_MS = 60_000;
41
67
  /**
42
68
  * What a credential of `auth` looks like: two auths with the same key take the same stored
43
69
  * credential (static: the type family, ownership and header names; OAuth: any change re-enters).
@@ -58,725 +84,680 @@ function credentialKey(auth) {
58
84
  }
59
85
  }
60
86
  /** The digest of all files of a catalog version. */
61
- const filesDigest = (row) => sha256(row.files
62
- .map((f) => `${f.path}\0${f.sha256}\n`)
63
- .sort()
64
- .join(""));
65
- /** The digest of the files under `dir/` of a catalog version (paths relative to it). */
66
- function dirDigest(row, dir) {
67
- const prefix = `${dir}/`;
68
- return sha256(row.files
69
- .filter((f) => f.path.startsWith(prefix))
70
- .map((f) => `${f.path.slice(prefix.length)}\0${f.sha256}\n`)
71
- .sort()
72
- .join(""));
73
- }
87
+ const filesDigest = (row) => skillFilesDigest(row.files.map((f) => ({ path: f.path, sha256: f.sha256 })));
74
88
  /**
75
- * Org in-place upgrade (and, for declarative plugins, rollback) of an installed plugin (package
76
- * ecosystem spec §10, Decisions 9–11):
77
- * - **preview** diffs the installed and the target version per component (extension, dependency
78
- * plugins, skills by name, connector templates by id) and returns the inputs needed and a
79
- * `diffHash`; the same checks as an install run first (dependencies resolve, extension and
80
- * template connectors deployed, skill conflicts -> `plugin.skill_conflict`), plus the target
81
- * rules (`plugin.upgrade_target_invalid`): an extension-bearing plugin can move only to the
82
- * catalog version carrying the deployment's extension, and installs depending on this plugin must
83
- * still accept the target;
84
- * - **upgrade** re-checks the `diffHash` (`plugin.upgrade_stale`), sets the install `upgrading`
85
- * under the org lock (another upgrade or an uninstall -> `plugin.install_in_progress`), then:
86
- * added dependencies (installed by the install flow), the added extension (enabled), added and
87
- * changed skills (a new version of the same skill; agents keep selecting it by id), added
88
- * connectors (created from the template), and in one final transaction the changed connectors
89
- * (template-owned fields only, `applyTemplateIn`), the removals by the uninstall rules, the new
90
- * version and the audit `plugin.org_upgraded`. Each step is journaled in
91
- * `plugin_installs.upgrade`; on failure the journal is undone (created components removed, skill
92
- * versions reverted; changed connectors were never committed), the install stays `installed` at
93
- * the old version with `error = { step, code }`, audit `plugin.org_upgrade_failed`, 502
94
- * `plugin.upgrade_failed`. An upgrade interrupted for longer than `catalog.staleInstallMinutes`
95
- * is undone by the next preview, upgrade or uninstall.
96
- * Approval follows the item's policy (the org catalog service creates the request). No in-process
97
- * state.
89
+ * The platform plugin update (spec §7; ruling F2 retires the per-org upgrades):
90
+ * - **preview** diffs the platform's current components of the plugin (the version recorded in
91
+ * `catalog_items.platform_synced_version`, components updated on their own included) against a
92
+ * catalog version: each skill (by name, by its files' digest), agent template (by name, canonical
93
+ * definition) and connector template (by id, canonical file) is `added`, `removed`, `same`, or
94
+ * `upgrade` / `downgrade` (the target's direction from the component's catalog version). It marks
95
+ * components updated on their own, components with their own scope and the orgs whose linked
96
+ * copy is pinned; lists removed components agents still use (they stay, spec §7.3), the org
97
+ * connectors made from changed templates (all orgs, ruling E2; `credentialsReset` when the
98
+ * credential shape changed), the templates it turns on that will need an org's input (added, or
99
+ * no longer shipped off) and those it turns off (now shipped off, on only by default), the
100
+ * deployment's extension move, and a `diffHash`. Refused: same version, a disabled or deleted
101
+ * item, a target without the extension the deployment runs for it or moving a config-declared
102
+ * one, a template moving to another connector, a plugin another platform plugin depends on
103
+ * outside its range, and an added skill whose name is taken where the plugin is on (rule 10).
104
+ * - **update** runs under a Redis lock per item (another update -> `plugin.install_in_progress`):
105
+ * the extension first (`ExtensionAdminOps.install`, enabled when the update installs it; added
106
+ * permissions wait for the admin's approval as for any extension upgrade), then one transaction
107
+ * under the item name's lock that recomputes the diff (`plugin.upgrade_stale` on a difference)
108
+ * and writes the platform versions (`PlatformComponents.syncFromVersion`, which records
109
+ * `platform_synced_version`), the pinned suggestions of unchanged skills, the removals (a
110
+ * component no agent uses is soft-deleted, so unpinned linked copies go with it; pinned copies
111
+ * keep their own version; a removed connector template leaves its connectors as the orgs' own),
112
+ * clears the pins of linked copies, fans changed templates out to every connector made from them
113
+ * (`applyTemplateIn`, under each org's row lock, credentials cleared only for a changed
114
+ * credential shape), moves every org install to the version and audits
115
+ * `plugin.platform_updated`; the scope sync of the item and its dependencies is enqueued after
116
+ * the commit (orgs get new dependencies by rule 9). A failure rolls the transaction back and
117
+ * puts the extension back where it was (audit `plugin.platform_update_failed`, 502
118
+ * `plugin.upgrade_failed`); files copied to `deployment/platform/` stay unreferenced.
119
+ * Storage is never read or written inside the transaction: the target's contents (and the
120
+ * extension package, for `needsApproval`) are read and the platform files copied
121
+ * (`PlatformComponents.prepareSync`) after the Redis lock is taken and before the transaction,
122
+ * which reuses them and writes rows only (`applySyncIn`). The confirm-time diff treats the
123
+ * extension this update moved as expected (the install record must point at that package).
124
+ * Recovery: a process that dies between the extension step and the transaction leaves the
125
+ * deployment on the target's extension with the platform components unchanged; a new preview
126
+ * then shows the extension unchanged (`extension: null`) and the components still to move, and
127
+ * confirming it completes the update (or an update back to the old version moves it back).
128
+ * No in-process state.
98
129
  */
99
- let PluginUpgrader = class PluginUpgrader {
100
- installer;
130
+ let PlatformPluginUpdater = class PlatformPluginUpdater {
101
131
  kdb;
132
+ txHost;
133
+ adminLock;
134
+ itemLock;
102
135
  orgLock;
136
+ locks;
103
137
  storage;
104
138
  registry;
105
- skills;
106
- orgConfigs;
107
- requests;
139
+ ops;
140
+ blobs;
141
+ config;
142
+ platform;
143
+ installer;
108
144
  connectors;
145
+ sync;
109
146
  log;
110
- txHost;
111
- constructor(
112
- /** Planning, installs, the ledger and the uninstall rules are the installer's. */
113
- installer, kdb,
114
- /** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
115
- orgLock, storage, registry, skills, orgConfigs, requests,
116
- /** Org connectors: installs create, upgrade and remove them. */
117
- connectors, log, txHost) {
118
- this.installer = installer;
147
+ guides;
148
+ constructor(kdb, txHost, adminLock, itemLock, orgLock, locks, storage, registry, ops,
149
+ /** Reads the target's extension package (its permissions, for `needsApproval`). */
150
+ blobs, config, platform,
151
+ /** Template dependencies (the deployed connector kinds). */
152
+ installer, connectors, sync, log,
153
+ /** The connector template guides an update changes (the preview's `guideChanges`). */
154
+ guides) {
119
155
  this.kdb = kdb;
156
+ this.txHost = txHost;
157
+ this.adminLock = adminLock;
158
+ this.itemLock = itemLock;
120
159
  this.orgLock = orgLock;
160
+ this.locks = locks;
121
161
  this.storage = storage;
122
162
  this.registry = registry;
123
- this.skills = skills;
124
- this.orgConfigs = orgConfigs;
125
- this.requests = requests;
163
+ this.ops = ops;
164
+ this.blobs = blobs;
165
+ this.config = config;
166
+ this.platform = platform;
167
+ this.installer = installer;
126
168
  this.connectors = connectors;
169
+ this.sync = sync;
127
170
  this.log = log;
128
- this.txHost = txHost;
171
+ this.guides = guides;
129
172
  }
130
- /** The kernel-schema handle of the current context (the transaction, else the pool). */
131
173
  get db() {
132
174
  return this.kdb.db;
133
175
  }
134
- /** The upgrade preview (see the class comment). */
135
- async preview(orgId, actor, installId, version) {
136
- await this.recoverInterrupted(orgId, actor, installId);
137
- const diff = await this.diff(orgId, actor, installId, version, null);
138
- await this.authorizeActions(orgId, actor, diff.actions);
176
+ /** The update preview (see the class comment); writes nothing. */
177
+ async preview(itemId, version) {
178
+ return this.withGuideChanges(await this.diff(itemId, version));
179
+ }
180
+ /** The preview of a diff with the template guides it changes. */
181
+ async withGuideChanges(d) {
139
182
  return {
140
- from: diff.from.version,
141
- to: diff.to.version,
142
- diffHash: diff.diffHash,
143
- components: diff.components,
144
- inputs: diff.inputs,
145
- templates: diff.templates.flatMap((c) => c.target !== undefined &&
146
- (diff.inputs.connectors.includes(c.id) || diff.inputs.credentialsChanged.includes(c.id))
147
- ? [orgTemplateView(c.id, c.target)]
148
- : []),
183
+ ...previewOf(d),
184
+ guideChanges: await this.guides.templateGuideChanges(d.itemId, d.from, d.to),
149
185
  };
150
186
  }
151
- /** Re-checks the actor's org actions under the org row lock (a transaction of its own). */
152
- async authorizeActions(orgId, actor, actions) {
153
- await lockOrgForActions(this.orgLock, orgId, actor, actions);
154
- }
155
187
  /**
156
- * The checks of {@link upgrade} before an approval request (the org catalog service creates one
157
- * for a needs_approval item without a grant): the diff with the input, the `diffHash`. Returns
158
- * the item and the org actions the upgrade needs.
188
+ * The update preview of a new version of an org's own plugin (ruling P3-12): the item must be
189
+ * owned by `orgId` (else 404, as for any item the org cannot see), the target carries no
190
+ * extension (an extension-bearing version waits for the super admin, plan Task 6), and a target
191
+ * dropping the deployment's extension is allowed (P3-12'). `staged` is the target read from the
192
+ * org's staging (pre-flight F3); without it the imported version `version` is read. The route
193
+ * authorizes the org admin; writes nothing.
159
194
  */
160
- async check(orgId, actor, installId, input) {
161
- await this.recoverInterrupted(orgId, actor, installId);
162
- const diff = await this.diff(orgId, actor, installId, input.version, input);
163
- if (diff.diffHash !== input.diffHash)
164
- throw stale();
165
- return { itemId: diff.itemId, name: diff.name, actions: diff.actions, diff };
195
+ async previewForOwner(orgId, itemId, version, staged) {
196
+ // An internal invariant, not reachable from input: the only caller (OrgPluginUploads) passes
197
+ // the staged target together with its own version (PluginImport.stagedTarget)
198
+ if (staged !== undefined && staged.version !== version) {
199
+ throw new Error(`internal: previewForOwner got the staged target of version ${staged.version} for version ${version}`);
200
+ }
201
+ const preview = await this.withGuideChanges(await this.diff(itemId, version, {
202
+ owner: { orgId },
203
+ ...(staged !== undefined && { target: staged }),
204
+ }));
205
+ // The owner sees the names of the orgs whose default a removed theme is (named like the other
206
+ // orgs the preview mentions, P3-14 / P3-19), but people only of its own org: other orgs'
207
+ // member counts were never disclosed to it. Display only: `diffHash` covers the full preview.
208
+ const themes = preview.themes === undefined
209
+ ? undefined
210
+ : await Promise.all(preview.themes.map(async (t) => ({
211
+ ...t,
212
+ pickedBy: await pickedByIn(this.db, t.id, new Set([orgId])),
213
+ })));
214
+ const owned = { ...preview, ...(themes !== undefined && { themes }) };
215
+ return { ...owned, orgNames: await this.orgNamesOf(owned) };
166
216
  }
167
217
  /**
168
- * Runs an upgrade (see the class comment); returns the install view. `checked` is the diff
169
- * {@link check} computed for the same input (re-verified under the org lock: the install's
170
- * version and ledger must be unchanged), otherwise it is computed here.
218
+ * Locks, in sorted order and before the item (ruling P3-50), every live org the update may
219
+ * write in: the orgs with connectors made from the plugin's connector templates and the item's
220
+ * owner org. `acting`: the owner org an owner update acts for, locked with its admin re-checked
221
+ * (`catalog.upload`; deleted -> 404 `org.not_found`). Returns the live orgs locked; another
222
+ * deleted one is skipped.
171
223
  */
172
- async upgrade(orgId, actor, installId, input, opts = {}, checked) {
173
- let diff;
174
- if (checked !== undefined &&
175
- checked.installId === installId &&
176
- checked.to.version === input.version &&
177
- checked.diffHash === input.diffHash) {
178
- diff = checked;
179
- }
180
- else {
181
- await this.recoverInterrupted(orgId, actor, installId);
182
- diff = await this.diff(orgId, actor, installId, input.version, input);
183
- if (diff.diffHash !== input.diffHash)
184
- throw stale();
185
- }
186
- const { itemId } = diff;
187
- const upgradeId = randomUUID();
188
- // Start: the install `upgrading`, under the org lock (one upgrade or uninstall at a time)
189
- await this.start(orgId, actor, installId, diff, upgradeId);
190
- const done = [];
191
- /** The step whose journal write found the journal gone (cleaned up as interrupted meanwhile). */
192
- let unjournaled;
193
- // Appends a step to the journal in the transaction of the context (the journal gone: the
194
- // upgrade was cleaned up as interrupted)
195
- const journal = async (s) => {
196
- done.push(s);
197
- unjournaled = s;
198
- const r = await this.db
199
- .updateTable("plugin_installs")
200
- .set({
201
- upgrade: sql `jsonb_set(upgrade, '{done}', (upgrade->'done') || ${JSON.stringify([s])}::jsonb)`,
202
- })
203
- .where("id", "=", installId)
204
- .where(sql `upgrade->>'id'`, "=", upgradeId)
205
- .executeTakeFirst();
206
- if (r.numUpdatedRows === 0n)
207
- throw cleanedUp();
208
- unjournaled = undefined;
209
- };
210
- // Records a component in the ledger and the journal, in the transaction of the context
211
- const record = (c) => this.recordIn(installId, journal, c);
212
- let step = "dependencies";
213
- const disabled = [];
214
- try {
215
- for (const dep of diff.dependencies.filter((x) => x.change === "added")) {
216
- const resolved = dep.resolved;
217
- const rec = (c) => record(c);
218
- if ((await this.installer.recordDependency(orgId, resolved, rec)) !== undefined)
219
- continue;
220
- if (!dep.plan) {
221
- throw new ApiError("catalog.dependency_unresolved", `Dependency ${dep.name}@${resolved.range} cannot be resolved: its install in this organization was removed meanwhile; try again`, [{ dependency: dep.name, range: resolved.range }]);
222
- }
223
- let child;
224
- try {
225
- child = await this.installer.execute(orgId, actor, dep.plan, opts, {
226
- actions: requiredActions(dep.plan.manifest),
227
- approvedBy: itemId,
228
- parent: installId,
229
- });
230
- }
231
- catch (e) {
232
- if (e instanceof InstallFailure) {
233
- throw new InstallFailure(e.step, e.cause, [dep.name, ...e.dependency], e.rolledBack);
234
- }
235
- if (e instanceof ApiError &&
236
- e.code === "plugin.already_installed" &&
237
- (await this.installer.recordDependency(orgId, resolved, rec)) !== undefined) {
238
- continue;
239
- }
240
- throw new InstallFailure("dependencies", e, [dep.name], true);
241
- }
242
- await this.installer.recordDependency(orgId, resolved, rec, child);
243
- }
244
- step = "extension";
245
- if (diff.extension?.change === "added") {
246
- await this.installer.enableExtension(orgId, diff.extension.name, {
247
- enabled: true,
248
- ...(input.extensionConfig !== undefined && { config: input.extensionConfig }),
249
- }, actor, opts.actorTokenId, { viaItem: itemId }, record);
250
- }
251
- step = "skills";
252
- for (const s of diff.skills) {
253
- if (s.change !== "added" && s.change !== "changed")
254
- continue;
255
- const files = await readCatalogFiles(this.storage, diff.to.object_prefix, diff.to.files, s.dir);
256
- const r = await this.skills.createVersionFromFiles(orgId, actor, { ...skillOf(files), files }, {
257
- catalog: {
258
- itemId,
259
- version: diff.to.version,
260
- format: diff.to.source_format,
261
- suggestPinned: s.suggestPinned === true,
262
- },
263
- ...opts,
264
- });
265
- await this.journalSkill(r, record, journal);
266
- }
267
- step = "agent_templates";
268
- for (const a of diff.agentTemplates) {
269
- if (a.change !== "added" && a.change !== "changed")
270
- continue;
271
- await this.upgradeAgentTemplate(orgId, diff, a.template, record, journal);
272
- }
273
- step = "connectors";
274
- for (const [templateId, ci] of diff.creates) {
275
- const r = await this.connectors.create(orgId, actor, ci, opts.actorTokenId);
276
- await this.recordStep(installId, journal, {
277
- kind: "connector",
278
- ref: r.connector.id,
279
- created: true,
280
- skillVersion: null,
281
- templateId,
282
- });
224
+ async lockUpdateOrgs(itemId, acting) {
225
+ const item = await this.db
226
+ .selectFrom("catalog_items")
227
+ .select("owner_org_id")
228
+ .where("id", "=", itemId)
229
+ .executeTakeFirst();
230
+ const made = await this.db
231
+ .selectFrom("connectors as c")
232
+ .innerJoin("platform_connector_templates as t", "t.id", "c.platform_template_id")
233
+ .select("c.org_id")
234
+ .distinct()
235
+ .where("t.catalog_item_id", "=", itemId)
236
+ .where("c.deleted_at", "is", null)
237
+ .execute();
238
+ const ids = new Set(made.map((r) => r.org_id));
239
+ if (item?.owner_org_id)
240
+ ids.add(item.owner_org_id);
241
+ if (acting !== undefined)
242
+ ids.add(acting.orgId);
243
+ const live = new Set();
244
+ for (const orgId of [...ids].sort()) {
245
+ if (orgId === acting?.orgId) {
246
+ await this.orgLock.lockAndAuthorize(orgId, acting.actor, "catalog.upload");
247
+ live.add(orgId);
283
248
  }
284
- const prepared = [];
285
- for (const tpl of diff.templates) {
286
- const update = diff.updates.get(tpl.id);
287
- if (!update || !tpl.connector)
288
- continue;
289
- try {
290
- prepared.push({
291
- connectorId: tpl.connector.id,
292
- p: await this.connectors.prepareTemplateUpdate(actor, tpl.connector.connector, update),
293
- });
294
- }
295
- catch (e) {
296
- throw under(e, `connectors.${tpl.id}`);
297
- }
249
+ else if (await this.orgLock.tryLock(orgId)) {
250
+ live.add(orgId);
298
251
  }
299
- // Finish, in one transaction: changed connectors, removals, the version switch
300
- await this.finish(orgId, actor, installId, upgradeId, diff, prepared, disabled, opts, () => {
301
- step = "finish";
302
- });
303
- }
304
- catch (e) {
305
- throw await this.failed(orgId, actor, installId, upgradeId, diff, { done, unjournaled }, step, e, opts);
306
252
  }
307
- for (const p of disabled)
308
- await this.orgConfigs.afterDisable(orgId, p);
309
- const [view] = await this.installer.list(orgId, installId);
310
- if (!view)
311
- throw installNotFound();
312
- return view;
253
+ return live;
254
+ }
255
+ /** The names of the orgs a preview mentions, by id (an owner's preview, see `orgNames`). */
256
+ async orgNamesOf(p) {
257
+ const ids = new Set([
258
+ ...(p.extension?.orgs ?? []),
259
+ ...p.connectorOrgs.map((c) => c.orgId),
260
+ ...p.components.flatMap((c) => [...c.pinnedOrgs, ...(c.kept ?? []).map((k) => k.orgId)]),
261
+ ...(p.themes ?? []).flatMap((t) => t.orgDefaults.map((o) => o.orgId)),
262
+ ]);
263
+ if (ids.size === 0)
264
+ return {};
265
+ const rows = await this.db
266
+ .selectFrom("orgs")
267
+ .select(["id", "name"])
268
+ .where("id", "in", [...ids])
269
+ .where("deleted_at", "is", null)
270
+ .execute();
271
+ return Object.fromEntries(rows.map((r) => [r.id, r.name]));
313
272
  }
314
273
  /**
315
- * Undoes a failed upgrade (its journal, in one transaction): the install back `installed` at the
316
- * old version with `error = { step, code }`, audit `plugin.org_upgrade_failed`. When the journal
317
- * is no longer this upgrade's (another request cleaned it up as interrupted and undid what it
318
- * journaled), only the step whose journal write failed is undone and the install row is left as
319
- * the cleanup set it. Returns the error to throw: 502 `plugin.upgrade_failed` (a dependency
320
- * another install is still installing: its 409 as it is).
274
+ * Applies a new version of an org's own plugin for its owner (ruling P3-12, see
275
+ * {@link previewForOwner}): the actor must be an admin of the owner org (`catalog.upload`,
276
+ * re-checked under the org row lock in the transaction), then the platform update as
277
+ * {@link update} does, also removing the extension when the target drops it. In the update's
278
+ * transaction the orgs using the shared plugin are told (`shared_item_updated`, one notice per
279
+ * plugin) and a pending version is cleared (pre-flight F7: the new version replaces it).
321
280
  */
322
- async failed(orgId, actor, installId, upgradeId, diff, steps, step, e, opts) {
323
- const failure = e instanceof InstallFailure ? e : undefined;
324
- const cause = failure ? failure.cause : e;
325
- const code = cause instanceof ApiError ? cause.code : "internal";
326
- const at = failure ? failure.step : step;
327
- let rolledBack = failure?.rolledBack ?? true;
328
- try {
329
- const removed = await this.undoFailed(orgId, actor, installId, upgradeId, diff, steps, { at, code }, opts);
330
- for (const r of removed) {
331
- if (r.kind === "extension")
332
- await this.orgConfigs.afterDisable(orgId, r.ref);
333
- }
281
+ async updateAsOwner(orgId, actor, itemId, version, diffHash, opts = {}) {
282
+ if (!UUID_RE.test(itemId))
283
+ throw itemNotFound();
284
+ await this.authorizeOwner(orgId, actor);
285
+ return this.locked(itemId, () => this.updateLocked(actor, itemId, { version, diffHash }, opts, { orgId }));
286
+ }
287
+ /** The actor is an admin of the owner org (a read of the membership under the org row lock). */
288
+ async authorizeOwner(orgId, actor) {
289
+ await this.orgLock.lockAndAuthorize(orgId, actor, "catalog.upload");
290
+ }
291
+ /** Runs `f` holding the item's update lock (another update -> `plugin.install_in_progress`). */
292
+ async locked(itemId, f) {
293
+ const handle = await this.locks.acquire(`plugin-update-${itemId}`, {
294
+ ttlMs: UPDATE_LOCK_TTL_MS,
295
+ });
296
+ if (!handle) {
297
+ throw new ApiError("plugin.install_in_progress", "This plugin is being updated; try again when that update finishes");
334
298
  }
335
- catch (u) {
336
- // Left `upgrading` with its journal: undone once it is older than catalog.staleInstallMinutes
337
- rolledBack = false;
338
- this.log.warn("plugin upgrade rollback failed", {
339
- orgId,
340
- installId,
341
- error: u instanceof Error ? u.message : String(u),
342
- });
299
+ try {
300
+ return await f();
343
301
  }
344
- if (cause instanceof ApiError && cause.code === "plugin.install_in_progress" && rolledBack) {
345
- return cause;
302
+ finally {
303
+ await handle.release();
346
304
  }
347
- const dependency = failure && failure.dependency.length > 0 ? failure.dependency : [];
348
- const dep = dependency.length > 0 ? ` in dependency ${dependency.join(" > ")}` : "";
349
- return new ApiError("plugin.upgrade_failed", rolledBack
350
- ? `Upgrading the plugin failed at step ${at}${dep} (${code}); this organization's components are back at version ${diff.from.version}`
351
- : `Upgrading the plugin failed at step ${at}${dep} (${code}) and rolling back failed; it is undone after catalog.staleInstallMinutes`, [
352
- {
353
- step: at,
354
- code,
355
- ...(dependency.length > 0 && { dependency: dependency.join(" > ") }),
356
- ...(cause instanceof ApiError && { message: cause.message }),
357
- ...(cause instanceof ApiError &&
358
- cause.details !== undefined && { causeDetails: cause.details }),
359
- rolledBack,
360
- },
361
- ]);
362
305
  }
363
306
  /**
364
- * An upgrade interrupted for longer than `catalog.staleInstallMinutes` (a replica crashed
365
- * midway): its journal is undone, the install back `installed` at the old version (error step
366
- * `interrupted`), audit `plugin.org_upgrade_failed`. Nothing to do otherwise.
307
+ * The super admin's confirmation of an org upload's pending new version (rulings P3-10, P3-21,
308
+ * pre-flight F8): the update to `version` as {@link update} does it (the super admin's, the
309
+ * extension installed or upgraded on the deployment), with the version allowed although it is
310
+ * pending. Its transaction re-reads the item under the row lock: removed meanwhile -> 409
311
+ * `catalog.item_removed`, the pending version replaced -> 409 `extension.approval_stale` (both
312
+ * undo the extension step); then the pending state is cleared and the owner told
313
+ * (`upload_confirmed`). The caller checked the digest of the version's package.
367
314
  */
368
- async recoverInterrupted(orgId, actor, installId) {
369
- if (!UUID_RE.test(installId))
370
- throw installNotFound();
371
- const removed = await this.undoInterrupted(orgId, actor, installId);
372
- for (const r of removed) {
373
- if (r.kind === "extension")
374
- await this.orgConfigs.afterDisable(orgId, r.ref);
375
- }
315
+ async updateToPending(actor, itemId, version, opts = {}) {
316
+ if (!UUID_RE.test(itemId))
317
+ throw itemNotFound();
318
+ return this.locked(itemId, () => this.updateToPendingLocked(actor, itemId, version, opts));
376
319
  }
377
- /** The upgrade's start: the install `upgrading` with an empty journal (a transaction of its own). */
378
- async start(orgId, actor, installId, diff, upgradeId) {
379
- const { itemId } = diff;
380
- const t = this.db;
381
- await lockOrgForActions(this.orgLock, orgId, actor, diff.actions);
382
- const row = await t
383
- .selectFrom("plugin_installs")
384
- .select(["status", "version"])
385
- .where("id", "=", installId)
386
- .where("org_id", "=", orgId)
387
- .forUpdate()
388
- .executeTakeFirst();
389
- if (!row || row.status === "failed")
390
- throw installNotFound();
391
- if (row.status !== "installed")
392
- throw inProgress();
393
- if (row.version !== diff.from.version ||
394
- ledgerHash(await ledgerOf(t, installId)) !== diff.ledgerHash) {
395
- throw stale();
396
- }
397
- await assertItemInstallable(t, orgId, itemId);
398
- await t
399
- .updateTable("plugin_installs")
400
- .set({
401
- status: "upgrading",
402
- upgrade: sql `jsonb_build_object('id', ${upgradeId}::text, 'from', ${diff.from.version}::text, 'to', ${diff.to.version}::text, 'startedAt', now(), 'done', '[]'::jsonb)`,
403
- })
404
- .where("id", "=", installId)
405
- .execute();
320
+ /**
321
+ * {@link updateToPending} for a caller already holding the item's update lock
322
+ * ({@link withItemLock}): the super admin's confirmation runs all its steps under it (ruling
323
+ * P3-35).
324
+ */
325
+ async updateToPendingLocked(actor, itemId, version, opts = {}) {
326
+ const d = await this.diff(itemId, version, { confirmPending: version });
327
+ return this.updateLocked(actor, itemId, { version, diffHash: d.diffHash }, opts, undefined, version);
406
328
  }
407
- /** A skill step's ledger and journal rows, in a transaction of their own. */
408
- async journalSkill(r, record, journal) {
409
- if (r.created) {
410
- await record({ kind: "skill", ref: r.skill.id, created: true, skillVersion: null });
411
- return;
412
- }
413
- await journal({ op: "skill_version", ref: r.skill.id, version: r.version });
414
- await record({ kind: "skill", ref: r.skill.id, created: false, skillVersion: r.version });
329
+ /**
330
+ * Runs `f` holding the item's update lock (the one every platform update of the item takes;
331
+ * another holder -> 409 `plugin.install_in_progress`): the super admin's confirmation of an org
332
+ * upload runs every step under it (ruling P3-35).
333
+ */
334
+ withItemLock(itemId, f) {
335
+ if (!UUID_RE.test(itemId))
336
+ throw itemNotFound();
337
+ return this.locked(itemId, f);
415
338
  }
416
339
  /**
417
- * An agent template step: the new version (or template) and its ledger and journal rows, under
418
- * the org row lock, in a transaction of their own.
340
+ * Runs an update confirmed with the preview's `diffHash` (see the class comment). A version an
341
+ * org upload waits with, or an item whose first version waits, -> 409 `catalog.item_pending`
342
+ * (confirmed in 待确认 instead, pre-flight F8).
419
343
  */
420
- async upgradeAgentTemplate(orgId, diff, template, record, journal) {
421
- const { itemId } = diff;
422
- const t = this.db;
423
- await this.orgLock.lock(orgId);
424
- const r = await putTemplateVersionIn(t, {
425
- orgId,
426
- plugin: diff.name,
427
- catalogItemId: itemId,
428
- catalogVersion: diff.to.version,
429
- sourceFormat: diff.to.source_format,
430
- }, template);
431
- if (r.created) {
432
- await record({ kind: "agent_template", ref: r.id, created: true, skillVersion: null });
433
- return;
434
- }
435
- if (r.changed)
436
- await journal({ op: "template_version", ref: r.id, version: r.version });
437
- await record({
438
- kind: "agent_template",
439
- ref: r.id,
440
- created: false,
441
- skillVersion: r.changed ? r.version : null,
442
- });
344
+ async update(actor, itemId, input, opts = {}) {
345
+ if (!UUID_RE.test(itemId))
346
+ throw itemNotFound();
347
+ return this.locked(itemId, () => this.updateLocked(actor, itemId, input, opts));
443
348
  }
444
- /** One ledger record (and its journal row) in a transaction of its own. */
445
- async recordStep(installId, journal, c) {
446
- await this.recordIn(installId, journal, c);
349
+ async updateLocked(actor, itemId, input, opts, owner, confirmPending) {
350
+ // Storage is read and written here, before any transaction (see the class comment)
351
+ const checked = await this.diff(itemId, input.version, {
352
+ ...(owner && { owner }),
353
+ ...(confirmPending !== undefined && { confirmPending }),
354
+ });
355
+ if (checked.diffHash !== input.diffHash)
356
+ throw stale();
357
+ const prepared = await this.platform.prepareSync(itemId, input.version);
358
+ const ext = checked.extension;
359
+ // The extension admin re-checks the actor: a super admin, or for an owner update the owner
360
+ // org's admin (and audits it with `ownerOrgId`)
361
+ const opsOwner = owner && { orgId: owner.orgId };
362
+ let step = "extension";
363
+ let extensionMoved = false;
364
+ let enabledHere = false;
365
+ let leftDisabled;
366
+ /** A removal: the extension was enabled before it (put back enabled on failure). */
367
+ let removedEnabled = false;
368
+ try {
369
+ if (ext?.sha256 === null) {
370
+ // The target drops the extension (P3-12'): disabled (drained), then uninstalled
371
+ const row = await this.db
372
+ .selectFrom("extension_installs")
373
+ .select("desired_state")
374
+ .where("name", "=", checked.name)
375
+ .executeTakeFirst();
376
+ removedEnabled = row?.desired_state === "enabled";
377
+ await this.ops.disable(checked.name, actor, opts.actorTokenId, opsOwner);
378
+ extensionMoved = true;
379
+ await this.ops.uninstall(checked.name, actor, opts.actorTokenId, opsOwner);
380
+ }
381
+ else if (ext) {
382
+ const installed = await this.ops.install(checked.name, ext.sha256, actor, opts.actorTokenId, { catalogItemId: itemId, ...(opsOwner && { owner: opsOwner }) });
383
+ extensionMoved = true;
384
+ // A new version newly providing a held single-provider point: left disabled by the install
385
+ leftDisabled = installed.leftDisabled;
386
+ if (ext.previous === null) {
387
+ // An occupied single-provider point leaves it installed disabled
388
+ const r = await this.ops.enableUnlessOccupied(checked.name, actor, opts.actorTokenId, opsOwner);
389
+ enabledHere = r.enabled;
390
+ if (!r.enabled)
391
+ leftDisabled = r.note;
392
+ }
393
+ }
394
+ step = "components";
395
+ const diff = await this.commit(actor, itemId, input, opts, { contents: checked.contents, extension: ext }, prepared, owner, confirmPending);
396
+ return {
397
+ itemId,
398
+ version: diff.to.version,
399
+ components: diff.components,
400
+ extension: extensionView(diff.extension),
401
+ connectorOrgs: diff.connectorOrgs,
402
+ ...(leftDisabled !== undefined && { extensionLeftDisabled: leftDisabled }),
403
+ };
404
+ }
405
+ catch (e) {
406
+ let rolledBack = true;
407
+ if (extensionMoved && ext) {
408
+ // An owner update was authorized at its start: the put-back is not checked again
409
+ const back = opsOwner && { ...opsOwner, rollback: true };
410
+ try {
411
+ if (ext.sha256 === null) {
412
+ // Removed by this update: the previous package reinstalled (as it was)
413
+ await this.ops.install(checked.name, ext.previous, actor, opts.actorTokenId, {
414
+ catalogItemId: itemId,
415
+ ...(back && { owner: back }),
416
+ });
417
+ if (removedEnabled)
418
+ await this.ops.enable(checked.name, actor, opts.actorTokenId, back);
419
+ }
420
+ else if (ext.previous !== null) {
421
+ await this.ops.install(checked.name, ext.previous, actor, opts.actorTokenId, {
422
+ ...(back && { owner: back }),
423
+ });
424
+ // The install left the enabled extension disabled (a held single-provider point): the
425
+ // previous version, which did not provide it, runs again
426
+ if (leftDisabled !== undefined) {
427
+ await this.ops.enable(checked.name, actor, opts.actorTokenId, back);
428
+ }
429
+ }
430
+ else {
431
+ // Installed by this update: an uninstall needs it disabled first
432
+ if (enabledHere)
433
+ await this.ops.disable(checked.name, actor, opts.actorTokenId, back);
434
+ await this.ops.uninstall(checked.name, actor, opts.actorTokenId, back);
435
+ }
436
+ }
437
+ catch (u) {
438
+ rolledBack = false;
439
+ this.log.error("putting a plugin's extension back after a failed update failed", {
440
+ itemId,
441
+ plugin: checked.name,
442
+ err: u,
443
+ });
444
+ }
445
+ }
446
+ const code = e instanceof ApiError ? e.code : "internal";
447
+ await this.auditFailure(actor, checked, { step, code, rolledBack }, opts);
448
+ // A refusal found under the lock (stale, disabled meanwhile, a conflict, a downgrade the
449
+ // extension's migrations refuse): returned as it is
450
+ if (e instanceof ApiError && e.status < 500 && rolledBack)
451
+ throw e;
452
+ throw new ApiError("plugin.upgrade_failed", rolledBack
453
+ ? `Updating the plugin failed at step ${step} (${code}); the platform is back at version ${checked.from ?? "(none)"}`
454
+ : `Updating the plugin failed at step ${step} (${code}) and putting its extension back failed; check the extension in the admin console`, [
455
+ {
456
+ step,
457
+ code,
458
+ ...(e instanceof ApiError && { message: e.message }),
459
+ ...(e instanceof ApiError && e.details !== undefined && { causeDetails: e.details }),
460
+ rolledBack,
461
+ },
462
+ ]);
463
+ }
447
464
  }
448
- /** A component's ledger row and its journal step, in the transaction of the context. */
449
- async recordIn(installId, journal, c) {
450
- const inserted = await this.db
451
- .insertInto("plugin_install_components")
452
- .values({
453
- install_id: installId,
454
- kind: c.kind,
455
- ref: c.ref,
456
- created: c.created,
457
- skill_version: c.skillVersion,
458
- template_id: c.templateId ?? null,
459
- })
460
- .onConflict((oc) => oc.columns(["install_id", "kind", "ref"]).doNothing())
461
- .returning("ref")
462
- .executeTakeFirst();
463
- if (c.created)
464
- await journal({ op: "created", kind: c.kind, ref: c.ref });
465
- else if (inserted)
466
- await journal({ op: "recorded", kind: c.kind, ref: c.ref });
465
+ /** `plugin.platform_update_failed`, in a transaction of its own. */
466
+ async auditFailure(actor, diff, f, opts) {
467
+ await audit(this.db, {
468
+ actorAccountId: actor,
469
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
470
+ action: "plugin.platform_update_failed",
471
+ target: diff.itemId,
472
+ detail: { name: diff.name, from: diff.from, to: diff.to.version, ...f },
473
+ });
467
474
  }
468
475
  /**
469
- * The upgrade's last transaction (under the org row lock): the changed connectors, the removals,
470
- * the pinned suggestions and the version switch, `plugin.org_upgraded`. `onFinish` marks the step
471
- * once the connectors are applied.
476
+ * The update's transaction (see the class comment): the super admin re-checked, the diff
477
+ * recomputed under the item name's lock, then every write. Returns the diff it applied.
472
478
  */
473
- async finish(orgId, actor, installId, upgradeId, diff, prepared, disabled, opts, onFinish) {
474
- const { itemId } = diff;
475
- const t = this.db;
476
- await lockOrgForActions(this.orgLock, orgId, actor, diff.actions);
477
- const row = await t
478
- .selectFrom("plugin_installs")
479
- .select(["status", sql `upgrade->>'id'`.as("upgradeId")])
480
- .where("id", "=", installId)
481
- .forUpdate()
479
+ async commit(actor, itemId, input, opts, pre, prepared, owner, confirmPending) {
480
+ if (owner === undefined)
481
+ await this.adminLock.lockAndAuthorize(actor);
482
+ // The global lock order (ruling P3-50): the orgs (sorted; the acting owner org's admin
483
+ // re-checked at its place), then the item, then its components; an org deletion locks its org
484
+ // row, then the items it owns
485
+ const liveOrgs = await this.lockUpdateOrgs(itemId, owner && { orgId: owner.orgId, actor });
486
+ const named = await this.db
487
+ .selectFrom("catalog_items")
488
+ .select("name")
489
+ .where("id", "=", itemId)
482
490
  .executeTakeFirst();
483
- if (row?.status !== "upgrading" || row.upgradeId !== upgradeId)
484
- throw cleanedUp();
485
- await assertItemInstallable(t, orgId, itemId);
486
- for (const c of prepared) {
487
- await this.connectors.applyTemplateIn(orgId, c.connectorId, actor, c.p, opts.actorTokenId);
491
+ if (!named)
492
+ throw itemNotFound();
493
+ await this.itemLock.lock(named.name);
494
+ let pendingOwner = null;
495
+ if (confirmPending !== undefined) {
496
+ // The super admin's confirmation (P3-21): still pending with the version they reviewed
497
+ const row = await this.db
498
+ .selectFrom("catalog_items")
499
+ .select(["deleted_at", "pending_version", "owner_org_id"])
500
+ .where("id", "=", itemId)
501
+ .forUpdate()
502
+ .executeTakeFirstOrThrow();
503
+ if (row.deleted_at !== null)
504
+ throw itemRemovedError(named.name);
505
+ if (row.pending_version !== confirmPending)
506
+ throw pendingStale(named.name);
507
+ pendingOwner = row.owner_org_id;
488
508
  }
489
- onFinish();
490
- for (const dep of diff.dependencies) {
491
- if (dep.change !== "changed" || !dep.child)
492
- continue;
493
- const live = await t
494
- .selectFrom("plugin_installs")
495
- .select("version")
496
- .where("id", "=", dep.child.installId)
497
- .forShare()
498
- .executeTakeFirst();
499
- if (!live || !satisfiesRange(live.version, dep.range)) {
500
- throw new ApiError("catalog.dependency_unresolved", `Dependency ${dep.name}@${dep.range} cannot be resolved: its install in this organization changed meanwhile; preview the upgrade again`, [{ dependency: dep.name, range: dep.range }]);
501
- }
509
+ const diff = await this.diff(itemId, input.version, {
510
+ pre,
511
+ ...(owner && { owner }),
512
+ ...(confirmPending !== undefined && { confirmPending }),
513
+ });
514
+ if (diff.diffHash !== input.diffHash)
515
+ throw stale();
516
+ const t = this.db;
517
+ // The platform versions (files copied before the transaction; rows only here)
518
+ try {
519
+ await this.platform.applySyncIn(prepared);
502
520
  }
503
- const report = await this.removeIn(orgId, actor, diff, disabled);
504
- // An unchanged skill gets no new version, so the target's pinned suggestion goes onto its
505
- // current version (when that version is this plugin's) here, with the version switch.
506
- for (const sk of diff.skills) {
507
- if (sk.change !== "unchanged" || !sk.ledger)
508
- continue;
521
+ catch (e) {
522
+ if (e instanceof PlanStale)
523
+ throw stale();
524
+ throw e;
525
+ }
526
+ for (const [id, pinned] of diff.suggest) {
509
527
  await t
510
- .updateTable("skill_versions as v")
511
- .set({ suggest_pinned: sk.suggestPinned === true })
512
- .from("skills as s")
528
+ .updateTable("platform_skill_versions as v")
529
+ .set({ suggest_pinned: pinned })
530
+ .from("platform_skills as s")
513
531
  .whereRef("s.id", "=", "v.skill_id")
514
532
  .whereRef("s.current_version", "=", "v.version")
515
- .where("s.id", "=", sk.ledger.ref)
516
- .where("s.org_id", "=", orgId)
517
- .where("v.catalog_item_id", "=", itemId)
518
- .where("v.suggest_pinned", "<>", sk.suggestPinned === true)
533
+ .where("s.id", "=", id)
534
+ .execute();
535
+ }
536
+ // Removals (spec §7.3): kept while agents use it; otherwise soft-deleted
537
+ const now = sql `now()`;
538
+ for (const c of diff.components) {
539
+ if (c.change !== "removed" || c.id === null || c.kept !== undefined)
540
+ continue;
541
+ const table = c.kind === "skill"
542
+ ? "platform_skills"
543
+ : c.kind === "agent_template"
544
+ ? "platform_agent_templates"
545
+ : c.kind === "theme"
546
+ ? "platform_themes"
547
+ : "platform_connector_templates";
548
+ await t
549
+ .updateTable(table)
550
+ .set({ deleted_at: now, updated_at: now })
551
+ .where("id", "=", c.id)
519
552
  .execute();
520
553
  }
554
+ // Pins of linked copies: moved to the plugin's version (spec §7.1)
555
+ for (const c of diff.components) {
556
+ if (c.change === "removed" || c.id === null || c.pinnedOrgs.length === 0)
557
+ continue;
558
+ if (c.kind === "skill") {
559
+ await t
560
+ .updateTable("skills")
561
+ .set({ pinned_version: null, updated_at: now })
562
+ .where("platform_skill_id", "=", c.id)
563
+ .where("org_id", "in", c.pinnedOrgs)
564
+ .where("pinned_version", "is not", null)
565
+ .execute();
566
+ }
567
+ else if (c.kind === "agent_template") {
568
+ await t
569
+ .updateTable("agent_templates")
570
+ .set({ pinned_version: null, updated_at: now })
571
+ .where("platform_template_id", "=", c.id)
572
+ .where("org_id", "in", c.pinnedOrgs)
573
+ .where("pinned_version", "is not", null)
574
+ .execute();
575
+ }
576
+ }
577
+ // Connectors made from changed templates, in every org (ruling E2)
578
+ const orgs = [...new Set(diff.connectors.map((c) => c.orgId))].sort();
579
+ for (const orgId of orgs) {
580
+ if (!liveOrgs.has(orgId)) {
581
+ // Locked before the item (P3-50): an org not locked then is deleted (its connectors go
582
+ // with it), or its connector was made since, which changes what the update does
583
+ const live = await t
584
+ .selectFrom("orgs")
585
+ .select("id")
586
+ .where("id", "=", orgId)
587
+ .where("deleted_at", "is", null)
588
+ .executeTakeFirst();
589
+ if (live === undefined)
590
+ continue;
591
+ throw stale();
592
+ }
593
+ for (const c of diff.connectors.filter((x) => x.orgId === orgId)) {
594
+ const prepared = await this.prepare(actor, itemId, diff.to.version, c);
595
+ await this.connectors.applyTemplateIn(orgId, c.id, actor, prepared, opts.actorTokenId);
596
+ }
597
+ }
598
+ // One version for the deployment: every live org install of the plugin moves with it (one
599
+ // still installing too, and a legacy `upgrading` row; an install marked installed later
600
+ // records the platform version current at its commit, PluginInstaller.markInstalled)
521
601
  await t
522
602
  .updateTable("plugin_installs")
523
- .set({ version: diff.to.version, status: "installed", upgrade: null, error: null })
524
- .where("id", "=", installId)
603
+ .set({ version: diff.to.version, error: null })
604
+ .where("item_id", "=", itemId)
605
+ .where("status", "in", ["installing", "installed", "upgrading"])
525
606
  .execute();
526
- await this.requests.resolveByInstall(orgId, itemId, actor, opts);
527
- await audit(t, {
528
- actorAccountId: actor,
529
- ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
530
- orgId,
531
- action: "plugin.org_upgraded",
532
- target: installId,
533
- detail: {
607
+ if (owner !== undefined || confirmPending !== undefined) {
608
+ // An org's version becomes its latest once applied or confirmed (ruling P3-34)
609
+ await t
610
+ .updateTable("catalog_items")
611
+ .set({ latest_version: diff.to.version })
612
+ .where("id", "=", itemId)
613
+ .execute();
614
+ await markVersionApplied(t, itemId, diff.to.version);
615
+ // An owner's new version replaces a version waiting for the super admin (pre-flight F7):
616
+ // their confirmation of it is then refused (409 extension.not_pending); the super admin's
617
+ // confirmation applied it
618
+ await t
619
+ .updateTable("catalog_items")
620
+ .set({ pending_version: null, pending_by: null, pending_at: null })
621
+ .where("id", "=", itemId)
622
+ .where("pending_version", "is not", null)
623
+ .execute();
624
+ }
625
+ if (confirmPending !== undefined && pendingOwner !== null) {
626
+ await addOrgNotice(t, pendingOwner, "upload_confirmed", {
534
627
  itemId,
535
628
  name: diff.name,
536
- from: diff.from.version,
537
- to: diff.to.version,
538
- components: diff.components,
539
- removed: report.removed,
540
- kept: report.kept,
541
- },
542
- });
543
- }
544
- /** Undoes a failed upgrade's journal (see {@link failed}) in a transaction of its own. */
545
- async undoFailed(orgId, actor, installId, upgradeId, diff, steps, f, opts) {
546
- const { at, code } = f;
547
- const t = this.db;
548
- await this.orgLock.lock(orgId);
549
- const row = await t
550
- .selectFrom("plugin_installs")
551
- .select(sql `upgrade->>'id'`.as("upgradeId"))
552
- .where("id", "=", installId)
553
- .forUpdate()
554
- .executeTakeFirst();
555
- const ours = row?.upgradeId === upgradeId;
556
- // Cleaned up meanwhile: only the step whose journal write found the journal gone is ours to
557
- // undo. When that step ran in the same transaction as its journal write (the extension
558
- // enable, the ledger rows), the transaction rolled back with the failed write, so undoing
559
- // it is a no-op (a never-enabled extension, a never-inserted row); undoing is still safe.
560
- const undo = ours ? steps.done : steps.unjournaled ? [steps.unjournaled] : [];
561
- const out = await this.installer.undoUpgradeIn(orgId, actor, installId, undo, "plugin_upgrade_rollback");
562
- await t
563
- .updateTable("plugin_installs")
564
- .set({ status: "installed", upgrade: null, error: JSON.stringify({ step: at, code }) })
565
- .where("id", "=", installId)
566
- .where(sql `upgrade->>'id'`, "=", upgradeId)
567
- .execute();
629
+ displayName: await itemDisplayName(t, itemId),
630
+ version: diff.to.version,
631
+ });
632
+ }
633
+ // The orgs using a shared org-owned plugin are told, one notice per plugin (a platform-owned
634
+ // plugin tells nobody); a plugin never synced before has no version to come from
635
+ if (diff.from !== null) {
636
+ await notifySharedUpdate(t, { kind: "plugin", id: itemId }, { name: diff.name, from: diff.from, to: diff.to.version });
637
+ }
568
638
  await audit(t, {
569
639
  actorAccountId: actor,
570
640
  ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
571
- orgId,
572
- action: "plugin.org_upgrade_failed",
573
- target: installId,
641
+ action: "plugin.platform_updated",
642
+ target: itemId,
574
643
  detail: {
575
- itemId: diff.itemId,
576
644
  name: diff.name,
577
- from: diff.from.version,
645
+ from: diff.from,
578
646
  to: diff.to.version,
579
- step: at,
580
- code,
581
- rolledBack: undo,
582
- ...(!ours && { cleanedUpMeanwhile: true }),
647
+ ...(owner !== undefined && { ownerOrgId: owner.orgId }),
648
+ components: diff.components.map((c) => ({
649
+ kind: c.kind,
650
+ name: c.name,
651
+ change: c.change,
652
+ ...(c.kept !== undefined && { kept: true }),
653
+ ...(c.pinnedOrgs.length > 0 && c.change !== "removed" && { unpinned: c.pinnedOrgs }),
654
+ })),
655
+ ...(diff.extension && { extension: extensionView(diff.extension) }),
656
+ connectors: diff.connectors.map((c) => ({
657
+ orgId: c.orgId,
658
+ id: c.id,
659
+ credentialsReset: c.credentialsReset,
660
+ })),
661
+ ...opts.auditDetail,
583
662
  },
584
663
  });
585
- return out;
586
- }
587
- /** Undoes an interrupted upgrade (see {@link recoverInterrupted}) in a transaction of its own. */
588
- async undoInterrupted(orgId, actor, installId) {
589
- const staleMinutes = this.installer.staleInstallMinutes();
590
- const t = this.db;
591
- const peek = await t
592
- .selectFrom("plugin_installs")
593
- .select("status")
594
- .where("id", "=", installId)
595
- .where("org_id", "=", orgId)
596
- .executeTakeFirst();
597
- if (peek?.status !== "upgrading")
598
- return [];
599
- await this.orgLock.lock(orgId);
600
- const row = await t
601
- .selectFrom("plugin_installs as a")
602
- .innerJoin("catalog_items as i", "i.id", "a.item_id")
603
- .select([
604
- "a.status",
605
- "a.upgrade",
606
- "a.item_id",
607
- "i.name",
608
- sql `coalesce((a.upgrade->>'startedAt')::timestamptz < now() - make_interval(mins => ${staleMinutes}), false)`.as("stale"),
609
- ])
610
- .where("a.id", "=", installId)
611
- .where("a.org_id", "=", orgId)
612
- .forUpdate("a")
613
- .executeTakeFirst();
614
- if (row?.status !== "upgrading" || !row.stale)
615
- return [];
616
- const steps = journalEntries(row.upgrade);
617
- const out = await this.installer.undoUpgradeIn(orgId, actor, installId, steps, "plugin_upgrade_cleanup");
618
- await t
619
- .updateTable("plugin_installs")
620
- .set({
621
- status: "installed",
622
- upgrade: null,
623
- error: JSON.stringify({ step: "interrupted", code: "internal" }),
624
- })
625
- .where("id", "=", installId)
626
- .execute();
627
- const j = row.upgrade;
628
- await audit(t, {
629
- actorAccountId: actor,
630
- orgId,
631
- action: "plugin.org_upgrade_failed",
632
- target: installId,
633
- detail: {
634
- itemId: row.item_id,
635
- name: row.name,
636
- from: j?.from ?? null,
637
- to: j?.to ?? null,
638
- step: "interrupted",
639
- code: "internal",
640
- rolledBack: steps,
641
- },
664
+ const enqueue = [itemId, ...diff.dependencies];
665
+ afterCommit(async () => {
666
+ for (const id of enqueue) {
667
+ try {
668
+ await this.sync.enqueueItem(id);
669
+ }
670
+ catch (err) {
671
+ this.log.warn("enqueueing the scope sync after a platform update failed", {
672
+ itemId: id,
673
+ err,
674
+ });
675
+ }
676
+ }
642
677
  });
643
- return out;
678
+ return diff;
644
679
  }
645
680
  /**
646
- * The removals of an upgrade, in the final transaction, by the uninstall rules
647
- * (`decideRemovalIn`): only components this install created are removed, and only when nothing
648
- * else uses them; one still used (by an agent, a connector or another extension) stays in the
649
- * ledger marked `dropped` ("no longer in the plugin", still this install's), every other removed
650
- * component leaves the ledger; a dropped component the target brings back is un-dropped. A dependency
651
- * install is uninstalled by the same rules (another install using it takes it over). Skills ->
652
- * connectors -> extension -> dependencies.
681
+ * The template update of one connector: the new endpoint preset with the org's values at the
682
+ * editable paths, the auth preset and default policies; credentials cleared (no new ones: they
683
+ * are the org's) when the credential shape changed.
653
684
  */
654
- async removeIn(orgId, actor, diff, disabled) {
655
- const report = { removed: [], kept: [] };
656
- const reason = "plugin_upgrade";
657
- const t = this.db;
658
- const keep = (kind, ref, why) => report.kept.push({ kind, ref, reason: why });
659
- const row = (kind, ref) => t
660
- .updateTable("plugin_install_components")
661
- .where("install_id", "=", diff.installId)
662
- .where("kind", "=", kind)
663
- .where("ref", "=", ref);
664
- const dropRow = (kind, ref) => t
665
- .deleteFrom("plugin_install_components")
666
- .where("install_id", "=", diff.installId)
667
- .where("kind", "=", kind)
668
- .where("ref", "=", ref)
669
- .execute();
670
- const removed = [
671
- ...diff.skills.flatMap((x) => (x.change === "removed" && x.ledger ? [x.ledger] : [])),
672
- ...diff.agentTemplates.flatMap((x) => (x.change === "removed" && x.ledger ? [x.ledger] : [])),
673
- ...diff.templates.flatMap((x) => (x.change === "removed" && x.ledger ? [x.ledger] : [])),
674
- ...(diff.extension?.change === "removed"
675
- ? diff.ledger.filter((l) => l.kind === "extension" && l.ref === diff.extension?.name)
676
- : []),
677
- ];
678
- const agentRefs = await this.installer.agentRefs(orgId);
679
- const self = new Set([diff.installId]);
680
- for (const l of removed) {
681
- if (!l.created) {
682
- await dropRow(l.kind, l.ref);
683
- keep(l.kind, l.ref, "pre_existing");
684
- continue;
685
- }
686
- const outcome = await this.installer.decideRemovalIn(orgId, actor, { kind: l.kind, ref: l.ref, created: true, skillVersion: null }, self, agentRefs, reason);
687
- if (outcome === "used_by_agents" ||
688
- outcome === "used_by_connectors" ||
689
- outcome === "used_by_plugins") {
690
- // Still used: stays this install's, marked dropped, so an uninstall (or a later upgrade
691
- // bringing it back) still finds it
692
- await row(l.kind, l.ref).set({ dropped: true }).execute();
693
- keep(l.kind, l.ref, outcome);
694
- continue;
695
- }
696
- await dropRow(l.kind, l.ref);
697
- if (outcome === "removed") {
698
- report.removed.push({ kind: l.kind, ref: l.ref });
699
- if (l.kind === "extension")
700
- disabled.push(l.ref);
701
- }
702
- else if (outcome !== "gone")
703
- keep(l.kind, l.ref, outcome);
704
- }
705
- // Components back in the plugin (dropped by an earlier upgrade, brought again by the target)
706
- const back = [
707
- ...diff.skills.flatMap((x) => x.change !== "removed" && x.ledger?.dropped
708
- ? [{ kind: "skill", ref: x.ledger.ref }]
709
- : []),
710
- ...diff.agentTemplates.flatMap((x) => x.change !== "removed" && x.ledger?.dropped
711
- ? [{ kind: "agent_template", ref: x.ledger.ref }]
712
- : []),
713
- ...diff.templates.flatMap((x) => x.change !== "removed" && x.ledger?.dropped
714
- ? [{ kind: "connector", ref: x.ledger.ref }]
715
- : []),
716
- ...(diff.extension && diff.extension.change !== "removed"
717
- ? diff.ledger
718
- .filter((l) => l.kind === "extension" && l.ref === diff.extension?.name && l.dropped)
719
- .map((l) => ({ kind: "extension", ref: l.ref }))
720
- : []),
721
- ];
722
- for (const b of back)
723
- await row(b.kind, b.ref).set({ dropped: false }).execute();
724
- for (const dep of diff.dependencies) {
725
- if (dep.change !== "removed" || !dep.child)
726
- continue;
727
- const ref = dep.child.installId;
728
- await dropRow("plugin", ref);
729
- if (!dep.child.ledger.created) {
730
- keep("plugin", ref, "pre_existing");
731
- continue;
732
- }
733
- const sub = { removed: [], kept: [] };
734
- const outcome = await this.installer.uninstallIn(orgId, actor, ref, {
735
- report: sub,
736
- removing: new Set(),
737
- from: diff.installId,
738
- authorize: false,
739
- reason,
740
- opts: {},
741
- });
742
- for (const r of sub.removed)
743
- if (r.kind === "extension")
744
- disabled.push(r.ref);
745
- if (outcome === "removed")
746
- report.removed.push({ kind: "plugin", ref });
747
- else if (outcome === "kept")
748
- keep("plugin", ref, "used_by_other_install");
685
+ async prepare(actor, itemId, version, c) {
686
+ const template = c.file.template;
687
+ const endpoint = structuredClone(template.endpoint);
688
+ for (const e of template.editableEndpoint ?? []) {
689
+ const v = getPath(c.endpoint, e);
690
+ if (v !== undefined)
691
+ setPath(endpoint, e, v);
749
692
  }
750
- return report;
693
+ const input = {
694
+ endpoint,
695
+ auth: template.auth,
696
+ template: {
697
+ itemId,
698
+ version,
699
+ templateId: c.templateId,
700
+ ...(template.defaultToolPolicies !== undefined && {
701
+ defaultToolPolicies: template.defaultToolPolicies,
702
+ }),
703
+ },
704
+ resetCredentials: false,
705
+ };
706
+ // Checked without credentials (nothing outbound); a changed shape then clears the old ones
707
+ const prepared = await this.connectors.prepareTemplateUpdate(actor, c.connector, input);
708
+ return c.credentialsReset
709
+ ? { ...prepared, input: { ...prepared.input, resetCredentials: true } }
710
+ : prepared;
751
711
  }
752
712
  /**
753
- * Computes an upgrade (see the class comment). `input` null: the preview (no input checks);
754
- * otherwise the connector inputs are built and checked as at install.
713
+ * Computes an update (see the class comment). `target`: the target read from an org's staging
714
+ * instead of `catalog_item_versions` (pre-flight F3). `owner`: an owner update (P3-12): the item
715
+ * must be the org's, an extension-bearing target is refused, and a target dropping the
716
+ * extension removes it.
755
717
  */
756
- async diff(orgId, actor, installId, version, input) {
718
+ async diff(itemId, version, o = {}) {
757
719
  const db = this.db;
758
- if (!UUID_RE.test(installId))
759
- throw installNotFound();
760
- const install = await db
761
- .selectFrom("plugin_installs as a")
762
- .innerJoin("catalog_items as i", "i.id", "a.item_id")
763
- .select(["a.item_id", "a.version", "a.status", "i.name", "i.policy", "i.deleted_at"])
764
- .where("a.id", "=", installId)
765
- .where("a.org_id", "=", orgId)
720
+ if (!UUID_RE.test(itemId))
721
+ throw itemNotFound();
722
+ const item = await db
723
+ .selectFrom("catalog_items")
724
+ .select([
725
+ "id",
726
+ "name",
727
+ "disabled",
728
+ "builtin",
729
+ "deleted_at",
730
+ "platform_synced_version",
731
+ "owner_org_id",
732
+ "awaiting_confirmation",
733
+ "pending_version",
734
+ "latest_version",
735
+ ])
736
+ .where("id", "=", itemId)
766
737
  .executeTakeFirst();
767
- if (!install || install.status === "failed")
768
- throw installNotFound();
769
- if (install.status !== "installed")
770
- throw inProgress();
771
- const itemId = install.item_id;
772
- const name = install.name;
773
- if (install.deleted_at !== null) {
774
- throw new ApiError("catalog.item_not_found", "This plugin is no longer in the enterprise catalog");
738
+ if (!item || item.deleted_at !== null)
739
+ throw itemNotFound();
740
+ // A version waiting for the super admin applies only through their confirmation (P3-9, F8)
741
+ if (o.confirmPending !== version) {
742
+ if (item.awaiting_confirmation)
743
+ throw itemPendingError(item.name);
744
+ if (item.pending_version !== null && item.pending_version === version) {
745
+ throw itemPendingError(item.name, version);
746
+ }
747
+ // The super admin's generic update of an org's plugin reaches only its applied version
748
+ // (`latest_version`, ruling P3-34/P3-37): one the org uploaded that was never applied or
749
+ // confirmed (a rejected or failed one) is not theirs to apply
750
+ if (o.owner === undefined && item.owner_org_id !== null && version !== item.latest_version) {
751
+ throw notApplied(item.name, version);
752
+ }
775
753
  }
776
- if (install.policy === "disabled") {
777
- throw new ApiError("catalog.item_disabled", "This catalog item is disabled by the enterprise admin and cannot be upgraded");
754
+ if (o.owner !== undefined && item.owner_org_id !== o.owner.orgId)
755
+ throw itemNotFound();
756
+ if (item.disabled) {
757
+ throw new ApiError("catalog.item_disabled", "This plugin is disabled on the deployment; enable it before updating");
778
758
  }
779
- const versions = await db
759
+ const name = item.name;
760
+ const versions = (await db
780
761
  .selectFrom("catalog_item_versions")
781
762
  .select([
782
763
  "version",
@@ -784,561 +765,774 @@ let PluginUpgrader = class PluginUpgrader {
784
765
  "object_prefix",
785
766
  "files",
786
767
  "extension_sha256",
787
- "imported_at",
788
- "source_format",
768
+ "applied_at",
769
+ "rejected_at",
789
770
  ])
790
771
  .where("item_id", "=", itemId)
791
- .execute();
792
- const fromRow = versions.find((v) => v.version === install.version);
793
- const toRow = versions.find((v) => v.version === version);
794
- if (!fromRow) {
795
- throw new ApiError("catalog.item_not_found", `The installed version ${install.version} is no longer in the enterprise catalog`);
796
- }
797
- if (!toRow)
772
+ .execute());
773
+ const to = o.target ?? versions.find((v) => v.version === version);
774
+ if (!to)
798
775
  throw new ApiError("catalog.item_not_found", `Version ${version} of this item not found`);
799
- const from = fromRow;
800
- const to = toRow;
801
- if (to.version === from.version) {
802
- // Confirming a preview whose upgrade already ran (a concurrent confirm won): stale
803
- if (input !== null)
776
+ // Whoever owns the item now (a transfer keeps the version rows), the generic update never
777
+ // reaches a version that was rejected or never applied (ruling P3-52)
778
+ const row = versions.find((v) => v.version === version);
779
+ if (o.owner === undefined && o.confirmPending !== version && row && !versionUsable(row)) {
780
+ throw notApplied(item.name, version);
781
+ }
782
+ if (o.owner !== undefined && to.manifest.extension) {
783
+ // An org's version with an extension waits for the super admin's confirmation (plan Task 6)
784
+ throw new ApiError("plugin.extension_upload_pending", `Version ${to.version} of plugin ${item.name} has an extension; it applies once a super admin confirms it`);
785
+ }
786
+ const from = item.platform_synced_version;
787
+ const fromRow = versions.find((v) => v.version === from);
788
+ if (from === to.version) {
789
+ // Confirming a preview whose update already ran (a concurrent confirm won): stale
790
+ if (o.pre)
804
791
  throw stale();
805
- throw targetInvalid(`This organization already has version ${from.version} installed`, [
806
- { reason: "same_version", version: from.version },
792
+ throw targetInvalid(`The platform already runs version ${from} of plugin ${name}`, [
793
+ { reason: "same_version", version: from },
807
794
  ]);
808
795
  }
809
- // Target rules (Decision 10)
810
- if (to.manifest.extension) {
811
- const deployed = await deployedExtension(db, this.registry, name);
812
- const allowed = deploymentTarget(versions, deployed);
813
- if (allowed?.version !== to.version) {
814
- throw targetInvalid(deployed === undefined
815
- ? `The deployment does not run the extension of plugin ${name}; ask the enterprise admin to install it first`
816
- : `The deployment runs the extension of plugin ${name} at version ${deployed.version}; this organization can upgrade only to ${allowed ? `catalog version ${allowed.version}` : "a catalog version carrying that extension, and none is imported"}`, [
817
- {
818
- reason: "deployment_version",
819
- deploymentVersion: deployed?.version ?? null,
820
- allowedVersion: allowed?.version ?? null,
821
- },
822
- ]);
796
+ const target = await this.extensionTarget(name, fromRow, to, o.owner !== undefined);
797
+ let extension;
798
+ if (o.pre) {
799
+ // The confirm's transaction: the extension this update moved is expected in place (a
800
+ // removed one gone)
801
+ if (o.pre.extension) {
802
+ const row = await db
803
+ .selectFrom("extension_installs")
804
+ .select("sha256")
805
+ .where("name", "=", name)
806
+ .executeTakeFirst();
807
+ if ((row?.sha256 ?? null) !== o.pre.extension.sha256)
808
+ throw stale();
809
+ extension = o.pre.extension;
823
810
  }
824
- }
825
- // Installs in this org depending on this one must accept the target
826
- const parents = await db
827
- .selectFrom("plugin_install_components as c")
828
- .innerJoin("plugin_installs as a", "a.id", "c.install_id")
829
- .innerJoin("catalog_item_versions as v", (j) => j.onRef("v.item_id", "=", "a.item_id").onRef("v.version", "=", "a.version"))
830
- .innerJoin("catalog_items as i", "i.id", "a.item_id")
831
- .select(["i.name", "v.manifest"])
832
- .where("c.kind", "=", "plugin")
833
- .where("c.ref", "=", installId)
834
- .where("a.org_id", "=", orgId)
835
- .where("a.status", "<>", "failed")
836
- .execute();
837
- for (const p of parents) {
838
- const range = p.manifest.dependencies?.find((x) => x.name === name)?.version;
839
- if (range !== undefined && !satisfiesRange(to.version, range)) {
840
- throw targetInvalid(`Plugin ${p.name} installed in this organization depends on ${name}@${range}, which version ${to.version} does not satisfy`, [{ reason: "dependent_range", dependent: p.name, range, version: to.version }]);
811
+ else {
812
+ if (target !== null)
813
+ throw stale();
814
+ extension = null;
841
815
  }
842
816
  }
843
- const ledger = await ledgerOf(db, installId);
844
- const components = [];
845
- // Extension
846
- let extension = null;
847
- let extensionConfig = false;
848
- if (from.manifest.extension || to.manifest.extension) {
849
- const change = !from.manifest.extension
850
- ? "added"
851
- : !to.manifest.extension
852
- ? "removed"
853
- : from.extension_sha256 !== to.extension_sha256 ||
854
- from.manifest.extension.manifest.version !== to.manifest.extension.manifest.version
855
- ? "changed"
856
- : "unchanged";
857
- extension = { change, name };
858
- if (change === "added") {
859
- await this.installer.planExtension(orgId, to.manifest);
860
- extensionConfig = !(await isExtensionEnabled(db, orgId, name));
861
- }
862
- components.push({
863
- kind: "extension",
864
- ref: name,
865
- change,
866
- ...(change === "changed" && {
867
- detail: `${from.manifest.extension?.manifest.version} -> ${to.manifest.extension?.manifest.version}`,
868
- }),
869
- });
817
+ else {
818
+ extension = target && {
819
+ ...target,
820
+ needsApproval: target.sha256 !== null && (await this.needsApproval(name, target.sha256)),
821
+ orgs: [],
822
+ };
870
823
  }
871
- // Dependencies (by plugin name)
872
- const resolved = await resolveDependencies(db, to.manifest);
873
- const childIds = ledger.filter((l) => l.kind === "plugin").map((l) => l.ref);
874
- const children = childIds.length === 0
875
- ? []
876
- : await db
877
- .selectFrom("plugin_installs as a")
878
- .innerJoin("catalog_items as i", "i.id", "a.item_id")
879
- .select(["a.id", "a.version", "i.name"])
880
- .where("a.id", "in", childIds)
881
- .execute();
882
- const childOf = (n) => {
883
- const c = children.find((x) => x.name === n);
884
- const l = c && ledger.find((x) => x.kind === "plugin" && x.ref === c.id);
885
- return c && l ? { installId: c.id, version: c.version, ledger: l } : undefined;
886
- };
887
- const dependencies = [];
888
- const fromDeps = from.manifest.dependencies ?? [];
889
- for (const r of resolved) {
890
- const before = fromDeps.find((x) => x.name === r.name);
891
- const child = childOf(r.name);
892
- if (before && child) {
893
- const change = before.version === r.range ? "unchanged" : "changed";
894
- if (change === "changed" && !satisfiesRange(child.version, r.range)) {
895
- throw new ApiError("catalog.dependency_unresolved", `Dependency ${r.name}@${r.range} cannot be resolved: this organization has version ${child.version} installed; upgrade it first`, [{ dependency: r.name, range: r.range }]);
896
- }
897
- dependencies.push({
898
- name: r.name,
899
- change,
900
- range: r.range,
901
- fromRange: before.version,
902
- resolved: r,
903
- child,
904
- });
824
+ const contents = o.pre?.contents ?? (await this.readContents(to));
825
+ const dependencies = await this.checkDependents(name, to);
826
+ // The platform's components of the plugin and the target's
827
+ const live = await this.liveComponents(itemId);
828
+ const components = [];
829
+ const suggest = new Map();
830
+ const direction = (fromVersion) => fromVersion === null || compareVersions(to.version, fromVersion) >= 0
831
+ ? "upgrade"
832
+ : "downgrade";
833
+ const added = (kind, n) => ({
834
+ kind,
835
+ id: null,
836
+ name: n,
837
+ change: "added",
838
+ from: null,
839
+ to: to.version,
840
+ updatedOnOwn: false,
841
+ ownScope: false,
842
+ pinnedOrgs: [],
843
+ });
844
+ // Skills (by name, by their files' digest)
845
+ const toSkills = contents.skillNames.map((n, i) => {
846
+ const entry = (to.manifest.skills ?? [])[i];
847
+ return {
848
+ name: n,
849
+ digest: dirDigest(to, packagePath(entry?.path)),
850
+ pinned: entry?.pinned === true,
851
+ };
852
+ });
853
+ for (const s of toSkills) {
854
+ const l = live.skills.find((x) => x.name === s.name);
855
+ if (!l) {
856
+ components.push(added("skill", s.name));
905
857
  continue;
906
858
  }
907
- // New (or its install was not recorded): installed by the install flow
908
- const live = await this.installer.liveDependency(db, orgId, r);
909
- dependencies.push({
910
- name: r.name,
911
- change: "added",
912
- range: r.range,
913
- resolved: r,
914
- plan: live !== undefined
915
- ? null
916
- : await this.installer.plan(orgId, actor, r.itemId, r.version, input === null ? null : {}, `dependencies.${r.name}.`, itemId),
859
+ const same = skillFilesDigest(l.manifest.files) === s.digest;
860
+ if (same && l.suggestPinned !== s.pinned)
861
+ suggest.set(l.id, s.pinned);
862
+ components.push({
863
+ kind: "skill",
864
+ id: l.id,
865
+ name: s.name,
866
+ change: same ? "same" : direction(l.catalogVersion),
867
+ from: l.catalogVersion,
868
+ to: to.version,
869
+ updatedOnOwn: l.updatedOnOwn,
870
+ ownScope: l.ownScope,
871
+ pinnedOrgs: [],
917
872
  });
918
873
  }
919
- for (const before of fromDeps) {
920
- if (resolved.some((r) => r.name === before.name))
874
+ for (const l of live.skills) {
875
+ if (toSkills.some((s) => s.name === l.name))
921
876
  continue;
922
- const child = childOf(before.name);
923
- dependencies.push({
924
- name: before.name,
877
+ components.push({
878
+ kind: "skill",
879
+ id: l.id,
880
+ name: l.name,
925
881
  change: "removed",
926
- fromRange: before.version,
927
- ...(child && { child }),
882
+ from: l.catalogVersion,
883
+ to: null,
884
+ updatedOnOwn: l.updatedOnOwn,
885
+ ownScope: l.ownScope,
886
+ pinnedOrgs: [],
928
887
  });
929
888
  }
930
- for (const dep of dependencies) {
889
+ // Agent templates (by name, canonical definition)
890
+ const toAgents = contents.agents;
891
+ for (const a of toAgents) {
892
+ const l = live.agentTemplates.find((x) => x.name === a.template.name);
893
+ if (!l) {
894
+ components.push(added("agent_template", a.template.name));
895
+ continue;
896
+ }
897
+ const same = canonicalJson(l.definition) === canonicalJson(a.template);
931
898
  components.push({
932
- kind: "plugin",
933
- ref: dep.name,
934
- change: dep.change,
935
- ...(dep.change === "changed" && { detail: `${dep.fromRange} -> ${dep.range}` }),
936
- ...(dep.change === "added" && {
937
- detail: dep.plan === null ? "already installed" : `installs ${dep.resolved?.version}`,
938
- }),
939
- });
940
- }
941
- // Skills (by name)
942
- const fromNames = await catalogSkillNames(this.storage, from, from.manifest);
943
- const toNames = await catalogSkillNames(this.storage, to, to.manifest);
944
- const dirOf = (m, i) => packagePath((m.skills ?? [])[i]?.path);
945
- const fromSkills = new Map(fromNames.map((n, i) => [n, dirOf(from.manifest, i)]));
946
- const toSkills = new Map(toNames.map((n, i) => [n, dirOf(to.manifest, i)]));
947
- const toPinned = new Set(toNames.filter((_, i) => (to.manifest.skills ?? [])[i]?.pinned === true));
948
- const skillRefs = ledger.filter((l) => l.kind === "skill").map((l) => l.ref);
949
- const liveSkills = skillRefs.length === 0
950
- ? []
951
- : await db
952
- .selectFrom("skills")
953
- .select(["id", "name"])
954
- .where("id", "in", skillRefs)
955
- .where("org_id", "=", orgId)
956
- .where("deleted_at", "is", null)
957
- .execute();
958
- const ledgerOfSkill = (n) => {
959
- const s = liveSkills.find((x) => x.name === n);
960
- return s && ledger.find((l) => l.kind === "skill" && l.ref === s.id);
961
- };
962
- const skills = [];
963
- for (const [n, dir] of toSkills) {
964
- const before = fromSkills.get(n);
965
- const l = ledgerOfSkill(n);
966
- const change = before === undefined
967
- ? "added"
968
- : dirDigest(from, before) === dirDigest(to, dir)
969
- ? "unchanged"
970
- : "changed";
971
- skills.push({
972
- name: n,
973
- change,
974
- dir,
975
- suggestPinned: toPinned.has(n),
976
- ...(l && { ledger: l }),
899
+ kind: "agent_template",
900
+ id: l.id,
901
+ name: l.name,
902
+ change: same ? "same" : direction(l.catalogVersion),
903
+ from: l.catalogVersion,
904
+ to: to.version,
905
+ updatedOnOwn: l.updatedOnOwn,
906
+ ownScope: l.ownScope,
907
+ pinnedOrgs: [],
977
908
  });
978
909
  }
979
- for (const n of fromSkills.keys()) {
980
- if (toSkills.has(n))
910
+ for (const l of live.agentTemplates) {
911
+ if (toAgents.some((a) => a.template.name === l.name))
981
912
  continue;
982
- const l = ledgerOfSkill(n);
983
- skills.push({ name: n, change: "removed", ...(l && { ledger: l }) });
984
- }
985
- for (const s of skills)
986
- components.push({ kind: "skill", ref: s.name, change: s.change });
987
- // The same skill conflict check as an install (spec §9.2)
988
- const conflicts = await planSkillConflicts(db, orgId, {
989
- itemId,
990
- name,
991
- version: to.version,
992
- skillNames: toNames,
993
- dependencies: dependencies
994
- .filter((x) => x.change === "added")
995
- .map((x) => ({ plan: x.plan ?? null })),
996
- });
997
- if (conflicts.length > 0)
998
- throw skillConflictError(conflicts);
999
- // Agent templates (by name; phase C)
1000
- const fromAgents = await readCatalogAgentTemplates(this.storage, from);
1001
- const toAgents = await readCatalogAgentTemplates(this.storage, to);
1002
- const templateRefs = ledger.filter((l) => l.kind === "agent_template").map((l) => l.ref);
1003
- const liveTemplates = templateRefs.length === 0
1004
- ? []
1005
- : await db
1006
- .selectFrom("agent_templates")
1007
- .select(["id", "name"])
1008
- .where("id", "in", templateRefs)
1009
- .where("org_id", "=", orgId)
1010
- .where("deleted_at", "is", null)
1011
- .execute();
1012
- const ledgerOfTemplate = (n) => {
1013
- const r = liveTemplates.find((x) => x.name === n);
1014
- return r && ledger.find((l) => l.kind === "agent_template" && l.ref === r.id);
1015
- };
1016
- const agentTemplates = [];
1017
- for (const a of toAgents) {
1018
- const n = a.template.name;
1019
- const before = fromAgents.find((x) => x.template.name === n);
1020
- const l = ledgerOfTemplate(n);
1021
- const change = before === undefined
1022
- ? "added"
1023
- : canonicalJson(before.template) === canonicalJson(a.template)
1024
- ? "unchanged"
1025
- : "changed";
1026
- agentTemplates.push({ name: n, change, template: a.template, ...(l && { ledger: l }) });
1027
- }
1028
- for (const a of fromAgents) {
1029
- const n = a.template.name;
1030
- if (toAgents.some((x) => x.template.name === n))
1031
- continue;
1032
- const l = ledgerOfTemplate(n);
1033
- agentTemplates.push({ name: n, change: "removed", ...(l && { ledger: l }) });
1034
- }
1035
- for (const a of agentTemplates) {
1036
- components.push({ kind: "agent_template", ref: a.name, change: a.change });
913
+ components.push({
914
+ kind: "agent_template",
915
+ id: l.id,
916
+ name: l.name,
917
+ change: "removed",
918
+ from: l.catalogVersion,
919
+ to: null,
920
+ updatedOnOwn: l.updatedOnOwn,
921
+ ownScope: l.ownScope,
922
+ pinnedOrgs: [],
923
+ });
1037
924
  }
1038
- // Connector templates (by id)
1039
- const fromTemplates = await readCatalogTemplates(this.storage, from);
1040
- const toTemplates = await readCatalogTemplates(this.storage, to);
1041
- // The install's connectors by template; a dropped one (kept only for the agents using it) counts
1042
- // only while its connector is live, so a template the target brings back maps onto it
1043
- const connectorRows = ledger.filter((l) => l.kind === "connector" && l.template_id !== null);
1044
- const liveConnectors = connectorRows.length === 0
1045
- ? []
1046
- : await db
1047
- .selectFrom("connectors")
1048
- .select(["id", "connector", "endpoint", "auth"])
1049
- .where("id", "in", connectorRows.map((l) => l.ref))
1050
- .where("org_id", "=", orgId)
1051
- .where("deleted_at", "is", null)
1052
- .execute();
1053
- const connectorOf = (templateId) => {
1054
- const rows = connectorRows.filter((x) => x.template_id === templateId);
1055
- const l = rows.find((x) => !x.dropped) ??
1056
- rows.find((x) => liveConnectors.some((c) => c.id === x.ref));
1057
- const c = l && liveConnectors.find((x) => x.id === l.ref);
1058
- return {
1059
- ...(l && { ledger: l }),
1060
- ...(c && {
1061
- connector: {
1062
- id: c.id,
1063
- connector: c.connector,
1064
- endpoint: c.endpoint,
1065
- auth: c.auth,
1066
- },
1067
- }),
1068
- };
1069
- };
925
+ // Connector templates (by id, canonical file) and the connectors made from them
926
+ const toTemplates = contents.templates;
1070
927
  const deps = this.installer.templateDeps();
1071
- const templates = [];
928
+ const connectors = [];
929
+ const shippedOff = (f) => f.defaultEnabled === false;
930
+ /** Templates whose `defaultEnabled` the target flips: id -> shipped off, and the platform id. */
931
+ const flipped = new Map();
932
+ const flippedIds = new Map();
1072
933
  for (const ct of toTemplates) {
1073
- const problem = templatePluginProblem(deps, ct.file.template);
1074
- const before = fromTemplates.find((x) => x.id === ct.id);
1075
- const mapped = connectorOf(ct.id);
1076
- if (!before && mapped.ledger?.dropped && mapped.connector) {
1077
- // Back in the plugin (an earlier upgrade dropped it, its connector was kept): the kept
1078
- // connector is updated to the template, not created again (its slug would be taken)
934
+ const l = live.templates.find((x) => x.name === ct.id);
935
+ if (!l) {
936
+ const problem = templatePluginProblem(deps, ct.file.template);
1079
937
  if (problem !== undefined)
1080
938
  throw new ApiError("catalog.not_deployed", problem);
1081
- if (mapped.connector.connector !== ct.file.template.connector) {
1082
- throw targetInvalid(`Connector template ${ct.id} comes back for another connector (${ct.file.template.plugin} "${ct.file.template.connector}"); uninstall the plugin and install the new version instead`, [
1083
- {
1084
- reason: "template_connector",
1085
- template: ct.id,
1086
- plugin: ct.file.template.plugin,
1087
- connector: ct.file.template.connector,
1088
- },
1089
- ]);
1090
- }
1091
- const credentialsChanged = credentialKey(mapped.connector.auth) !==
1092
- credentialKey(ct.file.template.auth);
1093
- templates.push({
1094
- id: ct.id,
1095
- change: "changed",
1096
- target: ct.file,
1097
- ...mapped,
1098
- credentialsChanged,
1099
- });
939
+ components.push(added("connector_template", ct.id));
1100
940
  continue;
1101
941
  }
1102
- if (!before) {
1103
- if (problem !== undefined)
1104
- throw new ApiError("catalog.not_deployed", problem);
1105
- templates.push({ id: ct.id, change: "added", target: ct.file, credentialsChanged: false });
942
+ const same = canonicalJson(l.file) === canonicalJson(ct.file);
943
+ components.push({
944
+ kind: "connector_template",
945
+ id: l.id,
946
+ name: ct.id,
947
+ change: same ? "same" : direction(l.version),
948
+ from: l.version,
949
+ to: to.version,
950
+ updatedOnOwn: false,
951
+ ownScope: l.ownScope,
952
+ pinnedOrgs: [],
953
+ });
954
+ if (same)
1106
955
  continue;
956
+ if (shippedOff(l.file) !== shippedOff(ct.file)) {
957
+ flipped.set(ct.id, shippedOff(ct.file));
958
+ flippedIds.set(ct.id, l.id);
1107
959
  }
1108
- const change = canonicalJson(before.file) === canonicalJson(ct.file) ? "unchanged" : "changed";
1109
- if (change === "changed") {
1110
- if (before.file.template.plugin !== ct.file.template.plugin ||
1111
- before.file.template.connector !== ct.file.template.connector) {
1112
- throw targetInvalid(`Connector template ${ct.id} moves to another connector (${ct.file.template.plugin} "${ct.file.template.connector}"); uninstall the plugin and install the new version instead`, [
1113
- {
1114
- reason: "template_connector",
1115
- template: ct.id,
1116
- plugin: ct.file.template.plugin,
1117
- connector: ct.file.template.connector,
1118
- },
1119
- ]);
1120
- }
1121
- if (problem !== undefined && mapped.connector) {
960
+ const before = l.file.template;
961
+ const after = ct.file.template;
962
+ if (before.plugin !== after.plugin || before.connector !== after.connector) {
963
+ throw targetInvalid(`Connector template ${ct.id} moves to another connector (${after.plugin} "${after.connector}"); remove the plugin and import the new version as a new plugin instead`, [
964
+ {
965
+ reason: "template_connector",
966
+ template: ct.id,
967
+ plugin: after.plugin,
968
+ connector: after.connector,
969
+ },
970
+ ]);
971
+ }
972
+ const made = await db
973
+ .selectFrom("connectors")
974
+ .select(["id", "org_id", "name", "connector", "endpoint", "auth"])
975
+ .where("platform_template_id", "=", l.id)
976
+ .where("deleted_at", "is", null)
977
+ .orderBy("org_id")
978
+ .orderBy("id")
979
+ .execute();
980
+ if (made.length > 0) {
981
+ const problem = templatePluginProblem(deps, after);
982
+ if (problem !== undefined)
1122
983
  throw new ApiError("catalog.not_deployed", problem);
1123
- }
1124
984
  }
1125
- const credentialsChanged = change === "changed" &&
1126
- mapped.connector !== undefined &&
1127
- credentialKey(mapped.connector.auth) !==
1128
- credentialKey(ct.file.template.auth);
1129
- templates.push({ id: ct.id, change, target: ct.file, ...mapped, credentialsChanged });
985
+ for (const c of made) {
986
+ connectors.push({
987
+ orgId: c.org_id,
988
+ id: c.id,
989
+ name: c.name,
990
+ connector: c.connector,
991
+ endpoint: c.endpoint,
992
+ templateId: ct.id,
993
+ file: ct.file,
994
+ credentialsReset: credentialKey(c.auth) !==
995
+ credentialKey(after.auth),
996
+ });
997
+ }
1130
998
  }
1131
- for (const ct of fromTemplates) {
1132
- if (toTemplates.some((x) => x.id === ct.id))
999
+ for (const l of live.templates) {
1000
+ if (toTemplates.some((x) => x.id === l.name))
1133
1001
  continue;
1134
- templates.push({
1135
- id: ct.id,
1002
+ components.push({
1003
+ kind: "connector_template",
1004
+ id: l.id,
1005
+ name: l.name,
1136
1006
  change: "removed",
1137
- ...connectorOf(ct.id),
1138
- credentialsChanged: false,
1007
+ from: l.version,
1008
+ to: null,
1009
+ updatedOnOwn: false,
1010
+ ownScope: l.ownScope,
1011
+ pinnedOrgs: [],
1139
1012
  });
1140
1013
  }
1141
- for (const c of templates) {
1014
+ // Themes (by id, canonical definition and their assets' digest)
1015
+ const toThemes = contents.themes;
1016
+ for (const th of toThemes) {
1017
+ const l = live.themes.find((x) => x.name === th.id);
1018
+ if (!l) {
1019
+ components.push(added("theme", th.id));
1020
+ continue;
1021
+ }
1022
+ const same = canonicalJson(l.definition) === canonicalJson(th.theme) &&
1023
+ l.assetsDigest === th.assetsDigest;
1142
1024
  components.push({
1143
- kind: "connector",
1144
- ref: c.id,
1145
- change: c.change,
1146
- ...(c.credentialsChanged && { detail: "credentials change" }),
1147
- ...(c.change !== "added" &&
1148
- c.change !== "removed" &&
1149
- !c.connector && { detail: "no connector in this organization" }),
1025
+ kind: "theme",
1026
+ id: l.id,
1027
+ name: th.id,
1028
+ change: same ? "same" : direction(l.catalogVersion),
1029
+ from: l.catalogVersion,
1030
+ to: to.version,
1031
+ updatedOnOwn: false,
1032
+ ownScope: l.ownScope,
1033
+ pinnedOrgs: [],
1150
1034
  });
1151
1035
  }
1152
- const inputs = {
1153
- connectors: templates.filter((c) => c.change === "added").map((c) => c.id),
1154
- credentialsChanged: templates.filter((c) => c.credentialsChanged).map((c) => c.id),
1155
- extensionConfig,
1156
- };
1157
- // The inputs (as at install), when given
1158
- const creates = new Map();
1159
- const updates = new Map();
1160
- if (input !== null) {
1161
- for (const k of Object.keys(input.connectors ?? {})) {
1162
- if (!inputs.connectors.includes(k) && !inputs.credentialsChanged.includes(k)) {
1163
- throw fail(`connectors.${k}`, "This upgrade takes no input for this connector template (only added templates and changed credentials)");
1164
- }
1036
+ for (const l of live.themes) {
1037
+ if (toThemes.some((x) => x.id === l.name))
1038
+ continue;
1039
+ components.push({
1040
+ kind: "theme",
1041
+ id: l.id,
1042
+ name: l.name,
1043
+ change: "removed",
1044
+ from: l.catalogVersion,
1045
+ to: null,
1046
+ updatedOnOwn: false,
1047
+ ownScope: l.ownScope,
1048
+ pinnedOrgs: [],
1049
+ });
1050
+ }
1051
+ // The orgs where the plugin is on (a live install; builtin: every org)
1052
+ const onOrgs = await this.pluginOrgs(itemId, item.builtin);
1053
+ if (extension)
1054
+ extension = { ...extension, orgs: onOrgs };
1055
+ // Rule 10: an added skill must not take a name already used where the plugin is on
1056
+ const conflicts = [];
1057
+ for (const c of components) {
1058
+ if (c.kind !== "skill" || c.change !== "added")
1059
+ continue;
1060
+ for (const orgId of onOrgs) {
1061
+ const ids = [
1062
+ ...(await clashingOrgSkills(db, orgId, { id: "", itemId }, c.name)),
1063
+ ...(await clashingPlatformSkills(db, orgId, { id: "" }, c.name)).map((x) => x.id),
1064
+ ];
1065
+ if (ids.length > 0)
1066
+ conflicts.push({ skill: c.name, orgId, conflicts: ids.sort() });
1165
1067
  }
1166
- if (input.extensionConfig !== undefined && !inputs.extensionConfig) {
1167
- throw fail("extensionConfig", "This upgrade does not enable the plugin's extension");
1068
+ }
1069
+ if (conflicts.length > 0) {
1070
+ throw new ApiError("plugin.skill_conflict", `The update adds skills whose names organizations already use where the plugin is on (${[...new Set(conflicts.map((c) => c.skill))].join(", ")}); remove or rename those skills first`, conflicts);
1071
+ }
1072
+ // Themes spec §7: an update never removes the platform default theme; rule 10 (§6): an added
1073
+ // theme must not take an id already on where the plugin is on
1074
+ const removedThemes = components
1075
+ .filter((c) => c.kind === "theme" && c.change === "removed" && c.id !== null)
1076
+ .map((c) => c.id);
1077
+ await assertNotPlatformDefault(db, removedThemes, () => "gone");
1078
+ const themeConflicts = [];
1079
+ for (const c of components) {
1080
+ if (c.kind !== "theme" || c.change !== "added")
1081
+ continue;
1082
+ for (const orgId of onOrgs) {
1083
+ const clash = await themeIdClashes(db, orgId, null, c.name);
1084
+ if (clash.length > 0) {
1085
+ themeConflicts.push({
1086
+ theme: c.name,
1087
+ orgId,
1088
+ themes: clash.map((x) => x.id),
1089
+ plugins: clash.map((x) => x.plugin ?? "platform"),
1090
+ });
1091
+ }
1168
1092
  }
1169
- for (const c of templates) {
1170
- if (c.change === "added") {
1171
- creates.set(c.id, this.installer.planConnector(actor, { itemId, version: to.version, name }, { id: c.id, file: c.target }, input, "", undefined));
1093
+ }
1094
+ if (themeConflicts.length > 0) {
1095
+ throw new ApiError("theme.id_taken", `The update adds themes whose ids are already on in organizations where the plugin is on (${[...new Set(themeConflicts.map((c) => c.theme))].join(", ")}); switch those themes off there first`, themeConflicts);
1096
+ }
1097
+ const themes = await this.themesLost(removedThemes);
1098
+ // Pinned copies (in orgs where the component is on) and removed components agents use
1099
+ for (const kind of ["skill", "agent_template"]) {
1100
+ const mine = components.filter((c) => c.kind === kind && c.id !== null);
1101
+ if (mine.length === 0)
1102
+ continue;
1103
+ const pins = kind === "skill"
1104
+ ? await db
1105
+ .selectFrom("skills")
1106
+ .select(["org_id", "platform_skill_id as platform_id"])
1107
+ .where("platform_skill_id", "in", mine.map((c) => c.id))
1108
+ .where("pinned_version", "is not", null)
1109
+ .where("deleted_at", "is", null)
1110
+ .execute()
1111
+ : await db
1112
+ .selectFrom("agent_templates")
1113
+ .select(["org_id", "platform_template_id as platform_id"])
1114
+ .where("platform_template_id", "in", mine.map((c) => c.id))
1115
+ .where("pinned_version", "is not", null)
1116
+ .where("deleted_at", "is", null)
1117
+ .execute();
1118
+ const facts = mine.map((c) => {
1119
+ const l = (kind === "skill" ? live.skills : live.agentTemplates).find((x) => x.id === c.id);
1120
+ return {
1121
+ id: c.id,
1122
+ itemId,
1123
+ scopeMode: l?.scopeMode ?? "follow",
1124
+ scope: l?.scope ?? "optional",
1125
+ disabled: l?.disabled ?? false,
1126
+ };
1127
+ });
1128
+ const st = await loadScopeState(db, kind, facts);
1129
+ for (const c of mine) {
1130
+ const f = facts.find((x) => x.id === c.id);
1131
+ if (!f)
1172
1132
  continue;
1173
- }
1174
- if (c.change !== "changed" || !c.connector || !c.target)
1133
+ const orgsPinned = [
1134
+ ...new Set(pins.filter((p) => p.platform_id === c.id).map((p) => p.org_id)),
1135
+ ];
1136
+ c.pinnedOrgs = orgsPinned
1137
+ .filter((orgId) => componentOn(f, pluginFactsOf(st, itemId, orgId), listedIn(st, f.id, orgId), switchOf(st, f.id, orgId)))
1138
+ .sort();
1139
+ if (c.change !== "removed")
1175
1140
  continue;
1176
- updates.set(c.id, this.templateUpdate(itemId, to.version, c, input));
1141
+ const kept = [];
1142
+ for (const orgId of await this.liveOrgs()) {
1143
+ const agents = await agentsUsing(db, orgId, kind, [c.id]);
1144
+ if (agents.length > 0) {
1145
+ kept.push({ orgId, agents: agents.map((a) => ({ id: a.id, name: a.name })) });
1146
+ }
1147
+ }
1148
+ if (kept.length > 0)
1149
+ c.kept = kept;
1177
1150
  }
1178
1151
  }
1179
- const actions = new Set([
1180
- ...requiredActions(from.manifest),
1181
- ...requiredActions(to.manifest),
1182
- ]);
1183
- for (const dep of dependencies)
1184
- if (dep.plan)
1185
- for (const a of planActions(dep.plan))
1186
- actions.add(a);
1187
- const removedDeps = dependencies.filter((x) => x.change === "removed" && x.child);
1188
- if (removedDeps.length > 0) {
1189
- const manifests = await db
1190
- .selectFrom("plugin_installs as a")
1191
- .innerJoin("catalog_item_versions as v", (j) => j.onRef("v.item_id", "=", "a.item_id").onRef("v.version", "=", "a.version"))
1192
- .select("v.manifest")
1193
- .where("a.id", "in", removedDeps.map((x) => x.child?.installId))
1194
- .execute();
1195
- for (const m of manifests)
1196
- for (const a of requiredActions(m.manifest))
1197
- actions.add(a);
1152
+ // The org connectors: changed templates' connectors, templates turned on (needing input) or off
1153
+ const addedTemplates = components
1154
+ .filter((c) => c.kind === "connector_template" && c.change === "added")
1155
+ .map((c) => c.name)
1156
+ // an added template shipped off is on nowhere (no org can have switched it on yet)
1157
+ .filter((id) => {
1158
+ const f = toTemplates.find((x) => x.id === id)?.file;
1159
+ return item.builtin || f === undefined || !shippedOff(f);
1160
+ });
1161
+ const byOrg = new Map();
1162
+ const entry = (orgId) => {
1163
+ const e = byOrg.get(orgId) ?? { orgId, connectors: [], needsConfig: [] };
1164
+ byOrg.set(orgId, e);
1165
+ return e;
1166
+ };
1167
+ for (const c of connectors) {
1168
+ entry(c.orgId).connectors.push({
1169
+ id: c.id,
1170
+ name: c.name,
1171
+ templateId: c.templateId,
1172
+ credentialsReset: c.credentialsReset,
1173
+ });
1198
1174
  }
1199
- const lh = ledgerHash(ledger);
1175
+ if (addedTemplates.length > 0) {
1176
+ for (const orgId of onOrgs) {
1177
+ const on = await connectorTemplatesOn(db, orgId, itemId, addedTemplates);
1178
+ if (on.size > 0)
1179
+ entry(orgId).needsConfig.push(...on);
1180
+ }
1181
+ }
1182
+ if (flipped.size > 0) {
1183
+ const ids = [...flipped.keys()].sort();
1184
+ for (const orgId of onOrgs) {
1185
+ const before = await connectorTemplatesOn(db, orgId, itemId, ids);
1186
+ const after = await connectorTemplatesOn(db, orgId, itemId, ids, flipped);
1187
+ for (const id of ids) {
1188
+ const platformId = flippedIds.get(id);
1189
+ if (after.has(id) && !before.has(id)) {
1190
+ // connectors already made from it (it was on before) need no input
1191
+ if ((await connectorsMadeFrom(db, orgId, platformId)) === 0) {
1192
+ entry(orgId).needsConfig.push(id);
1193
+ }
1194
+ }
1195
+ else if (before.has(id) && !after.has(id)) {
1196
+ const agents = await agentsUsing(db, orgId, "connector_template", [platformId]);
1197
+ const e = entry(orgId);
1198
+ e.turnedOff = [
1199
+ ...(e.turnedOff ?? []),
1200
+ {
1201
+ templateId: id,
1202
+ agents: agents.map((a) => ({ id: a.id, name: a.name })),
1203
+ connectorsKept: await connectorsMadeFrom(db, orgId, platformId),
1204
+ },
1205
+ ];
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+ const connectorOrgs = [...byOrg.values()]
1211
+ .map((e) => ({
1212
+ ...e,
1213
+ needsConfig: e.needsConfig.sort(),
1214
+ connectors: e.connectors.sort((a, b) => `${a.templateId}\0${a.id}`.localeCompare(`${b.templateId}\0${b.id}`)),
1215
+ }))
1216
+ .sort((a, b) => a.orgId.localeCompare(b.orgId));
1217
+ components.sort((a, b) => KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name));
1200
1218
  const diffHash = sha256(canonicalJson({
1201
- installId,
1202
- from: from.version,
1219
+ itemId,
1220
+ from,
1203
1221
  to: to.version,
1204
- fromFiles: filesDigest(from),
1205
- toFiles: filesDigest(to),
1206
- ledger: lh,
1222
+ manifest: to.manifest,
1223
+ files: filesDigest(to),
1224
+ // `needsApproval` is shown only: it does not change what the confirm does
1225
+ extension: extension && {
1226
+ from: extension.from,
1227
+ to: extension.to,
1228
+ orgs: extension.orgs,
1229
+ sha256: extension.sha256,
1230
+ },
1207
1231
  components,
1208
- inputs,
1232
+ connectors: connectors.map((c) => ({
1233
+ id: c.id,
1234
+ endpoint: c.endpoint,
1235
+ reset: c.credentialsReset,
1236
+ })),
1237
+ connectorOrgs,
1238
+ // only when present: a diff without themes keeps the hash it had before themes
1239
+ ...(themes !== undefined && { themes }),
1209
1240
  }));
1210
1241
  return {
1211
- installId,
1212
1242
  itemId,
1213
1243
  name,
1214
1244
  from,
1215
1245
  to,
1216
- ledger,
1246
+ components,
1217
1247
  extension,
1218
- skills,
1219
- agentTemplates,
1220
- templates,
1248
+ connectors,
1249
+ connectorOrgs,
1250
+ ...(themes !== undefined && { themes }),
1251
+ suggest,
1252
+ contents,
1221
1253
  dependencies,
1222
- components,
1223
- inputs,
1224
1254
  diffHash,
1225
- ledgerHash: lh,
1226
- actions: [...actions],
1227
- creates,
1228
- updates,
1229
1255
  };
1230
1256
  }
1231
1257
  /**
1232
- * The update of a changed template's connector: the new endpoint preset with the org's values at
1233
- * the editable paths, the new auth preset and policies; credentials only when the auth method or
1234
- * the credentials it needs changed (then the input must give what the template needs).
1258
+ * The target's skill names, agent templates, connector templates and themes, from object storage
1259
+ * (a theme failing its checks -> 400 `theme.invalid`, themes spec §6).
1235
1260
  */
1236
- templateUpdate(itemId, version, c, input) {
1237
- const template = c.target.template;
1238
- const connector = c.connector;
1239
- const path = `connectors.${c.id}`;
1240
- const given = input.connectors?.[c.id];
1241
- if (given !== undefined) {
1242
- for (const k of ["name", "slug", "endpoint"]) {
1243
- if (given[k] !== undefined) {
1244
- throw fail(`${path}.${k}`, "An existing connector keeps its name, slug and endpoint fields");
1245
- }
1261
+ async readContents(to) {
1262
+ return {
1263
+ skillNames: await catalogSkillNames(this.storage, to, to.manifest),
1264
+ agents: await readCatalogAgentTemplates(this.storage, to),
1265
+ templates: await readCatalogTemplates(this.storage, to),
1266
+ themes: await readCatalogThemes(this.storage, to),
1267
+ };
1268
+ }
1269
+ /**
1270
+ * Whether the target's extension package asks for permissions the approved snapshot does not
1271
+ * cover (no snapshot, or a blocking addition): it then loads only once approved (§7.4).
1272
+ */
1273
+ async needsApproval(name, sha) {
1274
+ if (this.config.get().extensionPermissions.autoApprove)
1275
+ return false;
1276
+ const pkg = await readExtensionPackage(await this.blobs.get(sha), {
1277
+ maxBytes: this.config.get().packages.maxBytes,
1278
+ hostSdkVersion: SDK_VERSION,
1279
+ });
1280
+ const snap = await this.db
1281
+ .selectFrom("extension_permission_snapshots")
1282
+ .select("permissions")
1283
+ .where("plugin", "=", name)
1284
+ .executeTakeFirst();
1285
+ if (!snap)
1286
+ return true;
1287
+ const { added } = diffPermissions(snap.permissions, permissionSetOfPackage(pkg));
1288
+ return blockingKeys(added).length > 0;
1289
+ }
1290
+ /**
1291
+ * The deployment's extension step: none when the target carries the extension the deployment
1292
+ * runs; a version without the extension the deployment runs for the plugin is refused, except
1293
+ * for an owner update (`allowRemove`, ruling P3-12': removing code runs none), which removes an
1294
+ * installed one; a move of a config-declared extension is refused.
1295
+ */
1296
+ async extensionTarget(name, from, to, allowRemove = false) {
1297
+ const want = to.manifest.extension;
1298
+ const row = await this.db
1299
+ .selectFrom("extension_installs")
1300
+ .select(["sha256", "version"])
1301
+ .where("name", "=", name)
1302
+ .executeTakeFirst();
1303
+ // Only an extension declared in the startup config runs without an install record; one this
1304
+ // replica still has loaded after its uninstall (until its reconcile) does not count
1305
+ const declared = this.registry.declared().get(name) === "config";
1306
+ const loaded = declared ? this.registry.manifests().find((m) => m.name === name) : undefined;
1307
+ if (!want) {
1308
+ if (allowRemove && from?.manifest.extension && row && !declared) {
1309
+ return { from: row.version, to: null, sha256: null, previous: row.sha256 };
1246
1310
  }
1311
+ if (from?.manifest.extension && (row || declared)) {
1312
+ throw targetInvalid(`Version ${to.version} of plugin ${name} has no extension, but the deployment runs it; uninstall the extension first`, [{ reason: "extension_removed", version: to.version }]);
1313
+ }
1314
+ return null;
1247
1315
  }
1248
- const endpoint = structuredClone(template.endpoint);
1249
- for (const e of template.editableEndpoint ?? []) {
1250
- const v = getPath(connector.endpoint, e);
1251
- if (v !== undefined)
1252
- setPath(endpoint, e, v);
1316
+ if (row) {
1317
+ if (row.sha256 === to.extension_sha256)
1318
+ return null;
1253
1319
  }
1254
- const auth = template.auth;
1255
- const oauth = auth.type === "oauth_code" || auth.type === "oauth_client";
1256
- if (c.credentialsChanged) {
1257
- const missing = missingTemplateInput(template, given, { personalCredential: false });
1258
- if (missing !== undefined) {
1259
- throw fail(`${path}.${missing}`, `The auth method of this connector template changed; it needs ${missing === "client" ? "an OAuth client" : "an org credential"}`);
1260
- }
1261
- if (oauth && given?.credential !== undefined) {
1262
- throw fail(`${path}.credential`, "This connector uses OAuth; provide client instead");
1263
- }
1264
- if (!oauth && given?.client !== undefined) {
1265
- throw fail(`${path}.client`, "This connector does not use OAuth; provide credential instead");
1266
- }
1320
+ else if (declared) {
1321
+ if (loaded?.version === want.manifest.version)
1322
+ return null;
1323
+ throw targetInvalid(`The extension of plugin ${name} is declared in the startup config at version ${loaded?.version ?? "(not loaded)"}; it cannot be moved to ${want.manifest.version} by an update`, [{ reason: "config_extension", deploymentVersion: loaded?.version ?? null }]);
1324
+ }
1325
+ if (to.extension_sha256 === null) {
1326
+ throw targetInvalid(`Version ${to.version} of plugin ${name} has no extension package`, [
1327
+ { reason: "extension_package", version: to.version },
1328
+ ]);
1267
1329
  }
1268
- const credential = oauth ? given?.client : given?.credential;
1269
1330
  return {
1270
- endpoint,
1271
- auth,
1272
- template: {
1273
- itemId,
1274
- version,
1275
- templateId: c.id,
1276
- ...(template.defaultToolPolicies !== undefined && {
1277
- defaultToolPolicies: template.defaultToolPolicies,
1278
- }),
1279
- },
1280
- resetCredentials: c.credentialsChanged,
1281
- ...(c.credentialsChanged && credential !== undefined && { credential }),
1331
+ from: row?.version ?? null,
1332
+ to: want.manifest.version,
1333
+ sha256: to.extension_sha256,
1334
+ previous: row?.sha256 ?? null,
1282
1335
  };
1283
1336
  }
1337
+ /**
1338
+ * Other platform plugins depending on this one must accept the target (their platform version's
1339
+ * dependency range); returns the catalog items of the target's own dependencies (their scope
1340
+ * sync is enqueued after the update: rule 9).
1341
+ */
1342
+ async checkDependents(name, to) {
1343
+ const rows = await this.db
1344
+ .selectFrom("catalog_items as i")
1345
+ .innerJoin("catalog_item_versions as v", (j) => j
1346
+ .onRef("v.item_id", "=", "i.id")
1347
+ .on(sql `v.version`, "=", sql `coalesce(i.platform_synced_version, i.latest_version)`))
1348
+ .select(["i.id", "i.name", "v.manifest"])
1349
+ .where("i.deleted_at", "is", null)
1350
+ .execute();
1351
+ for (const r of rows) {
1352
+ if (r.name === name)
1353
+ continue;
1354
+ const range = r.manifest.dependencies?.find((d) => d.name === name)?.version;
1355
+ if (range !== undefined && !satisfiesRange(to.version, range)) {
1356
+ throw targetInvalid(`Plugin ${r.name} depends on ${name}@${range}, which version ${to.version} does not satisfy`, [{ reason: "dependent_range", dependent: r.name, range, version: to.version }]);
1357
+ }
1358
+ }
1359
+ const wanted = new Set((to.manifest.dependencies ?? []).map((d) => d.name));
1360
+ return rows.filter((r) => wanted.has(r.name)).map((r) => r.id);
1361
+ }
1362
+ /** The plugin's live platform components with their current versions. */
1363
+ async liveComponents(itemId) {
1364
+ const db = this.db;
1365
+ const skills = (await db
1366
+ .selectFrom("platform_skills as p")
1367
+ .innerJoin("platform_skill_versions as v", (j) => j.onRef("v.skill_id", "=", "p.id").onRef("v.version", "=", "p.current_version"))
1368
+ .select([
1369
+ "p.id",
1370
+ "p.name",
1371
+ "p.scope_mode",
1372
+ "p.scope",
1373
+ "p.disabled",
1374
+ "v.manifest",
1375
+ "v.catalog_version",
1376
+ "v.updated_on_own",
1377
+ "v.suggest_pinned",
1378
+ ])
1379
+ .where("p.catalog_item_id", "=", itemId)
1380
+ .where("p.deleted_at", "is", null)
1381
+ .execute()).map((r) => ({
1382
+ id: r.id,
1383
+ name: r.name,
1384
+ scopeMode: r.scope_mode,
1385
+ scope: r.scope,
1386
+ disabled: r.disabled,
1387
+ manifest: r.manifest,
1388
+ catalogVersion: r.catalog_version,
1389
+ updatedOnOwn: r.updated_on_own,
1390
+ ownScope: r.scope_mode === "own",
1391
+ suggestPinned: r.suggest_pinned,
1392
+ }));
1393
+ const agentTemplates = (await db
1394
+ .selectFrom("platform_agent_templates as p")
1395
+ .innerJoin("platform_agent_template_versions as v", (j) => j.onRef("v.template_id", "=", "p.id").onRef("v.version", "=", "p.current_version"))
1396
+ .select([
1397
+ "p.id",
1398
+ "p.name",
1399
+ "p.scope_mode",
1400
+ "p.scope",
1401
+ "p.disabled",
1402
+ "v.definition",
1403
+ "v.catalog_version",
1404
+ "v.updated_on_own",
1405
+ ])
1406
+ .where("p.catalog_item_id", "=", itemId)
1407
+ .where("p.deleted_at", "is", null)
1408
+ .execute()).map((r) => ({
1409
+ id: r.id,
1410
+ name: r.name,
1411
+ scopeMode: r.scope_mode,
1412
+ scope: r.scope,
1413
+ disabled: r.disabled,
1414
+ definition: r.definition,
1415
+ catalogVersion: r.catalog_version,
1416
+ updatedOnOwn: r.updated_on_own,
1417
+ ownScope: r.scope_mode === "own",
1418
+ }));
1419
+ const templates = (await db
1420
+ .selectFrom("platform_connector_templates")
1421
+ .select(["id", "template_id", "current_version", "file", "scope_mode"])
1422
+ .where("catalog_item_id", "=", itemId)
1423
+ .where("deleted_at", "is", null)
1424
+ .execute()).map((r) => ({
1425
+ id: r.id,
1426
+ name: r.template_id,
1427
+ version: r.current_version,
1428
+ file: r.file,
1429
+ ownScope: r.scope_mode === "own",
1430
+ }));
1431
+ const themes = (await db
1432
+ .selectFrom("platform_themes as p")
1433
+ .innerJoin("platform_theme_versions as v", (j) => j.onRef("v.theme_id", "=", "p.id").onRef("v.version", "=", "p.current_version"))
1434
+ .leftJoin("catalog_item_versions as cv", (j) => j
1435
+ .onRef("cv.item_id", "=", "p.catalog_item_id")
1436
+ .onRef("cv.version", "=", "v.catalog_version"))
1437
+ .select(["p.id", "p.name", "p.scope_mode", "v.definition", "v.catalog_version", "cv.files"])
1438
+ .where("p.catalog_item_id", "=", itemId)
1439
+ .where("p.deleted_at", "is", null)
1440
+ .execute()).map((r) => ({
1441
+ id: r.id,
1442
+ name: r.name,
1443
+ definition: r.definition,
1444
+ catalogVersion: r.catalog_version,
1445
+ // no recorded files (should not happen): never equal, the update writes a new version
1446
+ assetsDigest: r.files === null ? null : themeAssetsDigest(r.files, r.definition),
1447
+ ownScope: r.scope_mode === "own",
1448
+ }));
1449
+ return { skills, agentTemplates, templates, themes };
1450
+ }
1451
+ /** The `themes` block of the preview: the orgs each of the removed themes `ids` is on in. */
1452
+ async themesLost(ids) {
1453
+ if (ids.length === 0)
1454
+ return undefined;
1455
+ const comps = await loadComponents(this.db, "theme", { ids });
1456
+ const st = await loadScopeState(this.db, "theme", comps);
1457
+ const orgs = await this.liveOrgs();
1458
+ const lost = new Map();
1459
+ for (const c of comps) {
1460
+ const on = orgs.filter((orgId) => componentOn(c, pluginFactsOf(st, c.itemId, orgId), listedIn(st, c.id, orgId), switchOf(st, c.id, orgId)));
1461
+ lost.set(c.id, new Set(on));
1462
+ }
1463
+ return themesPreview(this.db, lost);
1464
+ }
1465
+ /** The orgs where the plugin is on: a live install (builtin: every live org). */
1466
+ async pluginOrgs(itemId, builtin) {
1467
+ if (builtin)
1468
+ return this.liveOrgs();
1469
+ const rows = await this.db
1470
+ .selectFrom("plugin_installs as a")
1471
+ .innerJoin("orgs as o", "o.id", "a.org_id")
1472
+ .select("a.org_id")
1473
+ .distinct()
1474
+ .where("a.item_id", "=", itemId)
1475
+ .where("a.status", "in", ["installing", "installed", "upgrading"])
1476
+ .where("o.deleted_at", "is", null)
1477
+ .orderBy("a.org_id")
1478
+ .execute();
1479
+ return rows.map((r) => r.org_id);
1480
+ }
1481
+ async liveOrgs() {
1482
+ const rows = await this.db
1483
+ .selectFrom("orgs")
1484
+ .select("id")
1485
+ .where("deleted_at", "is", null)
1486
+ .orderBy("id")
1487
+ .execute();
1488
+ return rows.map((r) => r.id);
1489
+ }
1284
1490
  };
1285
1491
  __decorate([
1286
1492
  Transactional()
1287
- ], PluginUpgrader.prototype, "authorizeActions", null);
1288
- __decorate([
1289
- Transactional({ propagation: "requires_new" })
1290
- ], PluginUpgrader.prototype, "start", null);
1291
- __decorate([
1292
- Transactional({ propagation: "requires_new" })
1293
- ], PluginUpgrader.prototype, "journalSkill", null);
1294
- __decorate([
1295
- Transactional({ propagation: "requires_new" })
1296
- ], PluginUpgrader.prototype, "upgradeAgentTemplate", null);
1297
- __decorate([
1298
- Transactional({ propagation: "requires_new" })
1299
- ], PluginUpgrader.prototype, "recordStep", null);
1493
+ ], PlatformPluginUpdater.prototype, "authorizeOwner", null);
1300
1494
  __decorate([
1301
1495
  Transactional({ propagation: "requires_new" })
1302
- ], PluginUpgrader.prototype, "finish", null);
1496
+ ], PlatformPluginUpdater.prototype, "auditFailure", null);
1303
1497
  __decorate([
1304
1498
  Transactional({ propagation: "requires_new" })
1305
- ], PluginUpgrader.prototype, "undoFailed", null);
1306
- __decorate([
1307
- Transactional({ propagation: "requires_new" })
1308
- ], PluginUpgrader.prototype, "undoInterrupted", null);
1309
- __decorate([
1310
- Transactional({ propagation: "mandatory" })
1311
- ], PluginUpgrader.prototype, "removeIn", null);
1312
- PluginUpgrader = __decorate([
1499
+ ], PlatformPluginUpdater.prototype, "commit", null);
1500
+ PlatformPluginUpdater = __decorate([
1313
1501
  Injectable(),
1314
- __param(0, Inject(PluginInstaller)),
1315
- __param(1, Inject(KernelDb)),
1316
- __param(2, Inject(OrgLock)),
1317
- __param(3, Inject(StorageService)),
1318
- __param(4, Inject(ExtensionRegistry)),
1319
- __param(5, Inject(SkillsService)),
1320
- __param(6, Inject(OrgExtensionConfigs)),
1321
- __param(7, Inject(CatalogRequestsService)),
1322
- __param(8, Inject(ConnectorsService)),
1323
- __param(9, Inject(KERNEL_LOGGER)),
1324
- __param(10, Inject(TransactionHost))
1325
- ], PluginUpgrader);
1326
- export { PluginUpgrader };
1327
- const cleanedUp = () => new ApiError("plugin.install_not_found", "The upgrade was cleaned up as interrupted while it was running");
1328
- /** The install's ledger rows. */
1329
- function ledgerOf(db, installId) {
1330
- return db
1331
- .selectFrom("plugin_install_components")
1332
- .select(["kind", "ref", "created", "skill_version", "template_id", "dropped"])
1333
- .where("install_id", "=", installId)
1334
- .orderBy("kind")
1335
- .orderBy("ref")
1336
- .execute();
1502
+ __param(0, Inject(KernelDb)),
1503
+ __param(1, Inject(TransactionHost)),
1504
+ __param(2, Inject(SuperAdminLock)),
1505
+ __param(3, Inject(CatalogItemLock)),
1506
+ __param(4, Inject(OrgLock)),
1507
+ __param(5, Inject(LockService)),
1508
+ __param(6, Inject(StorageService)),
1509
+ __param(7, Inject(ExtensionRegistry)),
1510
+ __param(8, Inject(ExtensionAdminOps)),
1511
+ __param(9, Inject(ExtensionBlobs)),
1512
+ __param(10, Inject(KernelConfigService)),
1513
+ __param(11, Inject(PlatformComponents)),
1514
+ __param(12, Inject(PluginInstaller)),
1515
+ __param(13, Inject(ConnectorsService)),
1516
+ __param(14, Inject(PluginScopeSync)),
1517
+ __param(15, Inject(KERNEL_LOGGER)),
1518
+ __param(16, Inject(PluginGuides))
1519
+ ], PlatformPluginUpdater);
1520
+ export { PlatformPluginUpdater };
1521
+ /** The preview of a diff (without `guideChanges`, which reads PG). */
1522
+ function previewOf(d) {
1523
+ return {
1524
+ itemId: d.itemId,
1525
+ name: d.name,
1526
+ from: d.from,
1527
+ to: d.to.version,
1528
+ components: d.components,
1529
+ extension: extensionView(d.extension),
1530
+ connectorOrgs: d.connectorOrgs,
1531
+ ...(d.themes !== undefined && { themes: d.themes }),
1532
+ diffHash: d.diffHash,
1533
+ };
1337
1534
  }
1338
- /** A fingerprint of the ledger (order independent). */
1339
- function ledgerHash(rows) {
1340
- return sha256(rows
1341
- .map((r) => `${r.kind}\0${r.ref}\0${r.created}\0${r.skill_version}\0${r.template_id}\0${r.dropped}\n`)
1342
- .sort()
1343
- .join(""));
1535
+ /** The extension step as the preview and the result show it. */
1536
+ function extensionView(e) {
1537
+ return e && { from: e.from, to: e.to, orgs: e.orgs, needsApproval: e.needsApproval };
1344
1538
  }