@capybara-hq/kernel 0.0.0-stage → 0.1.0-rc.1

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 (637) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +20 -2
  4. package/bootstrap.json +26 -0
  5. package/dist/admin/admin.controller.d.ts +139 -0
  6. package/dist/admin/admin.controller.js +238 -0
  7. package/dist/admin/extensions.controller.d.ts +100 -0
  8. package/dist/admin/extensions.controller.js +226 -0
  9. package/dist/admin/setup.controller.d.ts +36 -0
  10. package/dist/admin/setup.controller.js +106 -0
  11. package/dist/admin/setup.service.d.ts +47 -0
  12. package/dist/admin/setup.service.js +90 -0
  13. package/dist/agents/agents.controller.d.ts +205 -0
  14. package/dist/agents/agents.controller.js +144 -0
  15. package/dist/agents/agents.service.d.ts +177 -0
  16. package/dist/agents/agents.service.js +677 -0
  17. package/dist/agents/definition.d.ts +198 -0
  18. package/dist/agents/definition.js +152 -0
  19. package/dist/agents/models.controller.d.ts +13 -0
  20. package/dist/agents/models.controller.js +31 -0
  21. package/dist/agents/templates.controller.d.ts +15 -0
  22. package/dist/agents/templates.controller.js +37 -0
  23. package/dist/agents/templates.d.ts +83 -0
  24. package/dist/agents/templates.js +173 -0
  25. package/dist/catalog/admin-catalog.controller.d.ts +143 -0
  26. package/dist/catalog/admin-catalog.controller.js +245 -0
  27. package/dist/catalog/catalog.service.d.ts +430 -0
  28. package/dist/catalog/catalog.service.js +882 -0
  29. package/dist/catalog/claude/agents.d.ts +27 -0
  30. package/dist/catalog/claude/agents.js +270 -0
  31. package/dist/catalog/claude/commands.d.ts +50 -0
  32. package/dist/catalog/claude/commands.js +179 -0
  33. package/dist/catalog/claude/manifest.d.ts +69 -0
  34. package/dist/catalog/claude/manifest.js +134 -0
  35. package/dist/catalog/claude/marketplace.d.ts +35 -0
  36. package/dist/catalog/claude/marketplace.js +194 -0
  37. package/dist/catalog/claude/mcp.d.ts +33 -0
  38. package/dist/catalog/claude/mcp.js +226 -0
  39. package/dist/catalog/claude/report.d.ts +86 -0
  40. package/dist/catalog/claude/report.js +121 -0
  41. package/dist/catalog/claude/translate.d.ts +56 -0
  42. package/dist/catalog/claude/translate.js +857 -0
  43. package/dist/catalog/cli.d.ts +46 -0
  44. package/dist/catalog/cli.js +211 -0
  45. package/dist/catalog/git-source.d.ts +127 -0
  46. package/dist/catalog/git-source.js +520 -0
  47. package/dist/catalog/marketplace-fetch.d.ts +99 -0
  48. package/dist/catalog/marketplace-fetch.js +203 -0
  49. package/dist/catalog/marketplaces.controller.d.ts +97 -0
  50. package/dist/catalog/marketplaces.controller.js +197 -0
  51. package/dist/catalog/marketplaces.service.d.ts +340 -0
  52. package/dist/catalog/marketplaces.service.js +1217 -0
  53. package/dist/catalog/org-catalog.controller.d.ts +128 -0
  54. package/dist/catalog/org-catalog.controller.js +277 -0
  55. package/dist/catalog/org-catalog.service.d.ts +246 -0
  56. package/dist/catalog/org-catalog.service.js +625 -0
  57. package/dist/catalog/plugin-import.d.ts +436 -0
  58. package/dist/catalog/plugin-import.js +1069 -0
  59. package/dist/catalog/plugin-install.d.ts +447 -0
  60. package/dist/catalog/plugin-install.js +1333 -0
  61. package/dist/catalog/plugin-package.d.ts +143 -0
  62. package/dist/catalog/plugin-package.js +382 -0
  63. package/dist/catalog/plugin-upgrade.d.ts +222 -0
  64. package/dist/catalog/plugin-upgrade.js +1219 -0
  65. package/dist/catalog/requests.service.d.ts +162 -0
  66. package/dist/catalog/requests.service.js +504 -0
  67. package/dist/catalog/schema-path.d.ts +7 -0
  68. package/dist/catalog/schema-path.js +63 -0
  69. package/dist/catalog/upgrade-targets.d.ts +59 -0
  70. package/dist/catalog/upgrade-targets.js +90 -0
  71. package/dist/catalog/versions.d.ts +31 -0
  72. package/dist/catalog/versions.js +44 -0
  73. package/dist/channels/actions.d.ts +37 -0
  74. package/dist/channels/actions.js +121 -0
  75. package/dist/channels/cards.d.ts +44 -0
  76. package/dist/channels/cards.js +156 -0
  77. package/dist/channels/connections.controller.d.ts +59 -0
  78. package/dist/channels/connections.controller.js +115 -0
  79. package/dist/channels/connections.service.d.ts +187 -0
  80. package/dist/channels/connections.service.js +534 -0
  81. package/dist/channels/features.d.ts +12 -0
  82. package/dist/channels/features.js +22 -0
  83. package/dist/channels/inbound.d.ts +120 -0
  84. package/dist/channels/inbound.js +332 -0
  85. package/dist/channels/outbound.d.ts +140 -0
  86. package/dist/channels/outbound.js +1126 -0
  87. package/dist/channels/phrases.d.ts +100 -0
  88. package/dist/channels/phrases.js +81 -0
  89. package/dist/channels/supervisor.d.ts +182 -0
  90. package/dist/channels/supervisor.js +625 -0
  91. package/dist/channels/threads.d.ts +29 -0
  92. package/dist/channels/threads.js +104 -0
  93. package/dist/channels/webhook-url.d.ts +16 -0
  94. package/dist/channels/webhook-url.js +34 -0
  95. package/dist/channels/webhook.controller.d.ts +19 -0
  96. package/dist/channels/webhook.controller.js +72 -0
  97. package/dist/channels/webhook.d.ts +73 -0
  98. package/dist/channels/webhook.js +274 -0
  99. package/dist/config/docs.d.ts +9 -0
  100. package/dist/config/docs.js +85 -0
  101. package/dist/config/env.d.ts +6 -0
  102. package/dist/config/env.js +73 -0
  103. package/dist/config/fields.d.ts +42 -0
  104. package/dist/config/fields.js +214 -0
  105. package/dist/config/file-source.d.ts +3 -0
  106. package/dist/config/file-source.js +31 -0
  107. package/dist/config/load.d.ts +20 -0
  108. package/dist/config/load.js +98 -0
  109. package/dist/config/nacos-source.d.ts +31 -0
  110. package/dist/config/nacos-source.js +229 -0
  111. package/dist/config/schema.d.ts +237 -0
  112. package/dist/config/schema.js +1192 -0
  113. package/dist/config/sources.d.ts +18 -0
  114. package/dist/config/sources.js +59 -0
  115. package/dist/config/types.d.ts +9 -0
  116. package/dist/config/types.js +7 -0
  117. package/dist/conversations/artifacts.d.ts +52 -0
  118. package/dist/conversations/artifacts.js +137 -0
  119. package/dist/conversations/attachments.controller.d.ts +28 -0
  120. package/dist/conversations/attachments.controller.js +96 -0
  121. package/dist/conversations/attachments.service.d.ts +180 -0
  122. package/dist/conversations/attachments.service.js +578 -0
  123. package/dist/conversations/conversations.controller.d.ts +288 -0
  124. package/dist/conversations/conversations.controller.js +607 -0
  125. package/dist/conversations/conversations.service.d.ts +321 -0
  126. package/dist/conversations/conversations.service.js +754 -0
  127. package/dist/conversations/event-log.d.ts +122 -0
  128. package/dist/conversations/event-log.js +259 -0
  129. package/dist/conversations/events.d.ts +856 -0
  130. package/dist/conversations/events.js +639 -0
  131. package/dist/conversations/files.d.ts +92 -0
  132. package/dist/conversations/files.js +213 -0
  133. package/dist/conversations/outputs-read.d.ts +34 -0
  134. package/dist/conversations/outputs-read.js +37 -0
  135. package/dist/conversations/queue.d.ts +169 -0
  136. package/dist/conversations/queue.js +527 -0
  137. package/dist/conversations/sandbox-status.d.ts +21 -0
  138. package/dist/conversations/sandbox-status.js +24 -0
  139. package/dist/conversations/selection.d.ts +46 -0
  140. package/dist/conversations/selection.js +75 -0
  141. package/dist/conversations/titles.d.ts +93 -0
  142. package/dist/conversations/titles.js +423 -0
  143. package/dist/ctx/bus.d.ts +16 -0
  144. package/dist/ctx/bus.js +101 -0
  145. package/dist/ctx/create-ctx.d.ts +109 -0
  146. package/dist/ctx/create-ctx.js +592 -0
  147. package/dist/ctx/kv.d.ts +4 -0
  148. package/dist/ctx/kv.js +31 -0
  149. package/dist/ctx/lock.d.ts +4 -0
  150. package/dist/ctx/lock.js +39 -0
  151. package/dist/ctx/namespace.d.ts +6 -0
  152. package/dist/ctx/namespace.js +12 -0
  153. package/dist/ctx/queue.d.ts +25 -0
  154. package/dist/ctx/queue.js +151 -0
  155. package/dist/ctx/secrets.d.ts +45 -0
  156. package/dist/ctx/secrets.js +175 -0
  157. package/dist/db/kernel-schema.d.ts +1122 -0
  158. package/dist/db/kernel-schema.js +5 -0
  159. package/dist/db/migrator.d.ts +28 -0
  160. package/dist/db/migrator.js +140 -0
  161. package/dist/db/pool.d.ts +21 -0
  162. package/dist/db/pool.js +28 -0
  163. package/dist/db/sql-statements.d.ts +34 -0
  164. package/dist/db/sql-statements.js +168 -0
  165. package/dist/errors.d.ts +211 -0
  166. package/dist/errors.js +213 -0
  167. package/dist/evals/budget.d.ts +61 -0
  168. package/dist/evals/budget.js +181 -0
  169. package/dist/evals/diff.d.ts +55 -0
  170. package/dist/evals/diff.js +133 -0
  171. package/dist/evals/judge.d.ts +53 -0
  172. package/dist/evals/judge.js +149 -0
  173. package/dist/evals/plan.d.ts +47 -0
  174. package/dist/evals/plan.js +77 -0
  175. package/dist/evals/reconstruct.d.ts +137 -0
  176. package/dist/evals/reconstruct.js +322 -0
  177. package/dist/evals/recorded-tools.d.ts +54 -0
  178. package/dist/evals/recorded-tools.js +167 -0
  179. package/dist/evals/run-replay.d.ts +86 -0
  180. package/dist/evals/run-replay.js +502 -0
  181. package/dist/evals/runs.controller.d.ts +100 -0
  182. package/dist/evals/runs.controller.js +199 -0
  183. package/dist/evals/runs.service.d.ts +275 -0
  184. package/dist/evals/runs.service.js +836 -0
  185. package/dist/evals/sets.controller.d.ts +61 -0
  186. package/dist/evals/sets.controller.js +183 -0
  187. package/dist/evals/sets.service.d.ts +128 -0
  188. package/dist/evals/sets.service.js +558 -0
  189. package/dist/evals/snapshot.d.ts +76 -0
  190. package/dist/evals/snapshot.js +207 -0
  191. package/dist/evals/step-replay.d.ts +113 -0
  192. package/dist/evals/step-replay.js +181 -0
  193. package/dist/evals/trajectory.controller.d.ts +41 -0
  194. package/dist/evals/trajectory.controller.js +88 -0
  195. package/dist/evals/trajectory.d.ts +144 -0
  196. package/dist/evals/trajectory.js +338 -0
  197. package/dist/evals/trajectory.service.d.ts +108 -0
  198. package/dist/evals/trajectory.service.js +323 -0
  199. package/dist/evals/worker.d.ts +101 -0
  200. package/dist/evals/worker.js +790 -0
  201. package/dist/extensions/admin-ops.d.ts +105 -0
  202. package/dist/extensions/admin-ops.js +353 -0
  203. package/dist/extensions/admin-view.d.ts +79 -0
  204. package/dist/extensions/admin-view.js +327 -0
  205. package/dist/extensions/approvals.d.ts +119 -0
  206. package/dist/extensions/approvals.js +205 -0
  207. package/dist/extensions/blobs.d.ts +45 -0
  208. package/dist/extensions/blobs.js +160 -0
  209. package/dist/extensions/bootstrap.d.ts +176 -0
  210. package/dist/extensions/bootstrap.js +535 -0
  211. package/dist/extensions/cli.d.ts +61 -0
  212. package/dist/extensions/cli.js +165 -0
  213. package/dist/extensions/config-json-schema.d.ts +9 -0
  214. package/dist/extensions/config-json-schema.js +73 -0
  215. package/dist/extensions/deployment-config.d.ts +138 -0
  216. package/dist/extensions/deployment-config.js +394 -0
  217. package/dist/extensions/enabled.d.ts +31 -0
  218. package/dist/extensions/enabled.js +55 -0
  219. package/dist/extensions/hooks.d.ts +161 -0
  220. package/dist/extensions/hooks.js +425 -0
  221. package/dist/extensions/host.d.ts +577 -0
  222. package/dist/extensions/host.js +2071 -0
  223. package/dist/extensions/inflight.d.ts +69 -0
  224. package/dist/extensions/inflight.js +277 -0
  225. package/dist/extensions/installs.d.ts +166 -0
  226. package/dist/extensions/installs.js +253 -0
  227. package/dist/extensions/instance.d.ts +167 -0
  228. package/dist/extensions/instance.js +329 -0
  229. package/dist/extensions/ledger.d.ts +30 -0
  230. package/dist/extensions/ledger.js +50 -0
  231. package/dist/extensions/lifecycle.d.ts +80 -0
  232. package/dist/extensions/lifecycle.js +152 -0
  233. package/dist/extensions/migrate-lock.d.ts +51 -0
  234. package/dist/extensions/migrate-lock.js +100 -0
  235. package/dist/extensions/org-config.controller.d.ts +24 -0
  236. package/dist/extensions/org-config.controller.js +59 -0
  237. package/dist/extensions/org-config.d.ts +169 -0
  238. package/dist/extensions/org-config.js +520 -0
  239. package/dist/extensions/package.d.ts +115 -0
  240. package/dist/extensions/package.js +246 -0
  241. package/dist/extensions/permission-set.d.ts +117 -0
  242. package/dist/extensions/permission-set.js +184 -0
  243. package/dist/extensions/permission-view.d.ts +194 -0
  244. package/dist/extensions/permission-view.js +284 -0
  245. package/dist/extensions/registry.d.ts +24 -0
  246. package/dist/extensions/registry.js +56 -0
  247. package/dist/extensions/resolve.d.ts +12 -0
  248. package/dist/extensions/resolve.js +71 -0
  249. package/dist/extensions/sources.d.ts +69 -0
  250. package/dist/extensions/sources.js +120 -0
  251. package/dist/extensions/sync.d.ts +40 -0
  252. package/dist/extensions/sync.js +242 -0
  253. package/dist/files/previewers.controller.d.ts +46 -0
  254. package/dist/files/previewers.controller.js +105 -0
  255. package/dist/files/previewers.d.ts +100 -0
  256. package/dist/files/previewers.js +250 -0
  257. package/dist/http/api.module.d.ts +114 -0
  258. package/dist/http/api.module.js +448 -0
  259. package/dist/http/app.d.ts +77 -0
  260. package/dist/http/app.js +220 -0
  261. package/dist/http/error.filter.d.ts +21 -0
  262. package/dist/http/error.filter.js +85 -0
  263. package/dist/http/gateway.module.d.ts +15 -0
  264. package/dist/http/gateway.module.js +13 -0
  265. package/dist/http/health.controller.d.ts +26 -0
  266. package/dist/http/health.controller.js +79 -0
  267. package/dist/http/nest-logger.d.ts +20 -0
  268. package/dist/http/nest-logger.js +28 -0
  269. package/dist/http/plugin-dispatch.d.ts +68 -0
  270. package/dist/http/plugin-dispatch.js +200 -0
  271. package/dist/http/tokens.d.ts +131 -0
  272. package/dist/http/tokens.js +131 -0
  273. package/dist/http/zod.pipe.d.ts +8 -0
  274. package/dist/http/zod.pipe.js +14 -0
  275. package/dist/identity/accounts.d.ts +93 -0
  276. package/dist/identity/accounts.js +367 -0
  277. package/dist/identity/api-tokens.d.ts +73 -0
  278. package/dist/identity/api-tokens.js +197 -0
  279. package/dist/identity/audit.d.ts +23 -0
  280. package/dist/identity/audit.js +41 -0
  281. package/dist/identity/auth.controller.d.ts +40 -0
  282. package/dist/identity/auth.controller.js +130 -0
  283. package/dist/identity/auth.guard.d.ts +41 -0
  284. package/dist/identity/auth.guard.js +162 -0
  285. package/dist/identity/binding.d.ts +47 -0
  286. package/dist/identity/binding.js +84 -0
  287. package/dist/identity/cookies.d.ts +16 -0
  288. package/dist/identity/cookies.js +32 -0
  289. package/dist/identity/credential-check.d.ts +17 -0
  290. package/dist/identity/credential-check.js +24 -0
  291. package/dist/identity/enterprise-admin.d.ts +14 -0
  292. package/dist/identity/enterprise-admin.js +27 -0
  293. package/dist/identity/external.d.ts +106 -0
  294. package/dist/identity/external.js +337 -0
  295. package/dist/identity/hash.d.ts +2 -0
  296. package/dist/identity/hash.js +3 -0
  297. package/dist/identity/login.d.ts +6 -0
  298. package/dist/identity/login.js +20 -0
  299. package/dist/identity/me.controller.d.ts +61 -0
  300. package/dist/identity/me.controller.js +159 -0
  301. package/dist/identity/passwords.d.ts +6 -0
  302. package/dist/identity/passwords.js +37 -0
  303. package/dist/identity/principal.d.ts +56 -0
  304. package/dist/identity/principal.js +38 -0
  305. package/dist/identity/rate-limit.d.ts +55 -0
  306. package/dist/identity/rate-limit.js +121 -0
  307. package/dist/identity/sessions.d.ts +84 -0
  308. package/dist/identity/sessions.js +306 -0
  309. package/dist/identity/settings.d.ts +121 -0
  310. package/dist/identity/settings.js +248 -0
  311. package/dist/identity/sso.controller.d.ts +59 -0
  312. package/dist/identity/sso.controller.js +192 -0
  313. package/dist/identity/sso.d.ts +83 -0
  314. package/dist/identity/sso.js +202 -0
  315. package/dist/inbox/inbox.service.d.ts +43 -0
  316. package/dist/inbox/inbox.service.js +57 -0
  317. package/dist/index.d.ts +69 -0
  318. package/dist/index.js +67 -0
  319. package/dist/kernel.d.ts +259 -0
  320. package/dist/kernel.js +1326 -0
  321. package/dist/log-config.d.ts +9 -0
  322. package/dist/log-config.js +27 -0
  323. package/dist/log-context.d.ts +32 -0
  324. package/dist/log-context.js +40 -0
  325. package/dist/log-loki.d.ts +34 -0
  326. package/dist/log-loki.js +145 -0
  327. package/dist/log.d.ts +79 -0
  328. package/dist/log.js +303 -0
  329. package/dist/models/prices.d.ts +53 -0
  330. package/dist/models/prices.js +121 -0
  331. package/dist/models/purposes.controller.d.ts +19 -0
  332. package/dist/models/purposes.controller.js +56 -0
  333. package/dist/models/purposes.d.ts +140 -0
  334. package/dist/models/purposes.js +267 -0
  335. package/dist/net/proxy.d.ts +44 -0
  336. package/dist/net/proxy.js +162 -0
  337. package/dist/net/public-address.d.ts +60 -0
  338. package/dist/net/public-address.js +272 -0
  339. package/dist/net/safe-fetch.d.ts +38 -0
  340. package/dist/net/safe-fetch.js +140 -0
  341. package/dist/orgs/directory-sync.controller.d.ts +14 -0
  342. package/dist/orgs/directory-sync.controller.js +40 -0
  343. package/dist/orgs/directory-sync.d.ts +166 -0
  344. package/dist/orgs/directory-sync.js +558 -0
  345. package/dist/orgs/invitations.controller.d.ts +58 -0
  346. package/dist/orgs/invitations.controller.js +137 -0
  347. package/dist/orgs/invitations.service.d.ts +80 -0
  348. package/dist/orgs/invitations.service.js +217 -0
  349. package/dist/orgs/members.service.d.ts +63 -0
  350. package/dist/orgs/members.service.js +234 -0
  351. package/dist/orgs/offboarding.d.ts +141 -0
  352. package/dist/orgs/offboarding.js +398 -0
  353. package/dist/orgs/org-lock.d.ts +32 -0
  354. package/dist/orgs/org-lock.js +46 -0
  355. package/dist/orgs/org.guard.d.ts +35 -0
  356. package/dist/orgs/org.guard.js +95 -0
  357. package/dist/orgs/orgs.controller.d.ts +78 -0
  358. package/dist/orgs/orgs.controller.js +192 -0
  359. package/dist/orgs/orgs.service.d.ts +61 -0
  360. package/dist/orgs/orgs.service.js +209 -0
  361. package/dist/orgs/roles.d.ts +13 -0
  362. package/dist/orgs/roles.js +58 -0
  363. package/dist/orgs/settings.controller.d.ts +20 -0
  364. package/dist/orgs/settings.controller.js +53 -0
  365. package/dist/orgs/settings.service.d.ts +120 -0
  366. package/dist/orgs/settings.service.js +178 -0
  367. package/dist/packages/npm.d.ts +50 -0
  368. package/dist/packages/npm.js +218 -0
  369. package/dist/packages/provenance.d.ts +47 -0
  370. package/dist/packages/provenance.js +147 -0
  371. package/dist/packages/tgz.d.ts +76 -0
  372. package/dist/packages/tgz.js +225 -0
  373. package/dist/runtime/abort.d.ts +32 -0
  374. package/dist/runtime/abort.js +71 -0
  375. package/dist/runtime/checkpointer.d.ts +32 -0
  376. package/dist/runtime/checkpointer.js +243 -0
  377. package/dist/runtime/context/build.d.ts +254 -0
  378. package/dist/runtime/context/build.js +693 -0
  379. package/dist/runtime/context/claude-tools.d.ts +11 -0
  380. package/dist/runtime/context/claude-tools.js +24 -0
  381. package/dist/runtime/context/compaction.d.ts +26 -0
  382. package/dist/runtime/context/compaction.js +195 -0
  383. package/dist/runtime/context/pairing.d.ts +11 -0
  384. package/dist/runtime/context/pairing.js +82 -0
  385. package/dist/runtime/context/reminders.d.ts +5 -0
  386. package/dist/runtime/context/reminders.js +11 -0
  387. package/dist/runtime/context/skills.d.ts +105 -0
  388. package/dist/runtime/context/skills.js +283 -0
  389. package/dist/runtime/context/tokens.d.ts +19 -0
  390. package/dist/runtime/context/tokens.js +104 -0
  391. package/dist/runtime/conversation-lock.d.ts +40 -0
  392. package/dist/runtime/conversation-lock.js +97 -0
  393. package/dist/runtime/hook-calls.d.ts +84 -0
  394. package/dist/runtime/hook-calls.js +136 -0
  395. package/dist/runtime/langgraph-loop.d.ts +52 -0
  396. package/dist/runtime/langgraph-loop.js +247 -0
  397. package/dist/runtime/loop.d.ts +93 -0
  398. package/dist/runtime/loop.js +7 -0
  399. package/dist/runtime/maintenance.d.ts +80 -0
  400. package/dist/runtime/maintenance.js +148 -0
  401. package/dist/runtime/media-cache.d.ts +21 -0
  402. package/dist/runtime/media-cache.js +51 -0
  403. package/dist/runtime/model-call.d.ts +176 -0
  404. package/dist/runtime/model-call.js +1049 -0
  405. package/dist/runtime/providers.d.ts +86 -0
  406. package/dist/runtime/providers.js +224 -0
  407. package/dist/runtime/quota.d.ts +29 -0
  408. package/dist/runtime/quota.js +70 -0
  409. package/dist/runtime/run-finish.d.ts +45 -0
  410. package/dist/runtime/run-finish.js +69 -0
  411. package/dist/runtime/run-queue.d.ts +121 -0
  412. package/dist/runtime/run-queue.js +493 -0
  413. package/dist/runtime/run-worker.d.ts +138 -0
  414. package/dist/runtime/run-worker.js +806 -0
  415. package/dist/runtime/stream.controller.d.ts +34 -0
  416. package/dist/runtime/stream.controller.js +122 -0
  417. package/dist/runtime/stream.d.ts +63 -0
  418. package/dist/runtime/stream.js +262 -0
  419. package/dist/runtime/subagents/child.d.ts +80 -0
  420. package/dist/runtime/subagents/child.js +225 -0
  421. package/dist/runtime/subagents/definition.d.ts +103 -0
  422. package/dist/runtime/subagents/definition.js +392 -0
  423. package/dist/runtime/subagents/dispatch.d.ts +69 -0
  424. package/dist/runtime/subagents/dispatch.js +502 -0
  425. package/dist/runtime/subagents/limits.d.ts +23 -0
  426. package/dist/runtime/subagents/limits.js +34 -0
  427. package/dist/runtime/subagents/org-agents.d.ts +50 -0
  428. package/dist/runtime/subagents/org-agents.js +70 -0
  429. package/dist/runtime/subagents/preamble.d.ts +7 -0
  430. package/dist/runtime/subagents/preamble.js +11 -0
  431. package/dist/runtime/subagents/progress.d.ts +26 -0
  432. package/dist/runtime/subagents/progress.js +36 -0
  433. package/dist/runtime/subagents/roster.d.ts +55 -0
  434. package/dist/runtime/subagents/roster.js +111 -0
  435. package/dist/runtime/subagents/skill-fork.d.ts +123 -0
  436. package/dist/runtime/subagents/skill-fork.js +259 -0
  437. package/dist/runtime/usage-report.d.ts +163 -0
  438. package/dist/runtime/usage-report.js +551 -0
  439. package/dist/runtime/waits.d.ts +233 -0
  440. package/dist/runtime/waits.js +883 -0
  441. package/dist/sandbox/leases.d.ts +169 -0
  442. package/dist/sandbox/leases.js +515 -0
  443. package/dist/sandbox/lifecycle.d.ts +21 -0
  444. package/dist/sandbox/lifecycle.js +68 -0
  445. package/dist/sandbox/sandbox.service.d.ts +249 -0
  446. package/dist/sandbox/sandbox.service.js +908 -0
  447. package/dist/skills/catalog.d.ts +149 -0
  448. package/dist/skills/catalog.js +405 -0
  449. package/dist/skills/changes.controller.d.ts +56 -0
  450. package/dist/skills/changes.controller.js +136 -0
  451. package/dist/skills/changes.service.d.ts +175 -0
  452. package/dist/skills/changes.service.js +805 -0
  453. package/dist/skills/invocable.controller.d.ts +23 -0
  454. package/dist/skills/invocable.controller.js +50 -0
  455. package/dist/skills/invocation-keys.d.ts +23 -0
  456. package/dist/skills/invocation-keys.js +44 -0
  457. package/dist/skills/invoke.d.ts +135 -0
  458. package/dist/skills/invoke.js +286 -0
  459. package/dist/skills/package.d.ts +87 -0
  460. package/dist/skills/package.js +227 -0
  461. package/dist/skills/skills.controller.d.ts +40 -0
  462. package/dist/skills/skills.controller.js +110 -0
  463. package/dist/skills/skills.service.d.ts +192 -0
  464. package/dist/skills/skills.service.js +567 -0
  465. package/dist/storage/prefix.d.ts +36 -0
  466. package/dist/storage/prefix.js +104 -0
  467. package/dist/storage/purge.d.ts +27 -0
  468. package/dist/storage/purge.js +75 -0
  469. package/dist/storage/storage.service.d.ts +44 -0
  470. package/dist/storage/storage.service.js +145 -0
  471. package/dist/text.d.ts +13 -0
  472. package/dist/text.js +13 -0
  473. package/dist/tools/approvals.controller.d.ts +53 -0
  474. package/dist/tools/approvals.controller.js +128 -0
  475. package/dist/tools/approvals.service.d.ts +172 -0
  476. package/dist/tools/approvals.service.js +568 -0
  477. package/dist/tools/breaker.d.ts +28 -0
  478. package/dist/tools/breaker.js +72 -0
  479. package/dist/tools/builtins/agent.d.ts +81 -0
  480. package/dist/tools/builtins/agent.js +223 -0
  481. package/dist/tools/builtins/ask-user.d.ts +28 -0
  482. package/dist/tools/builtins/ask-user.js +139 -0
  483. package/dist/tools/builtins/read-tool-output.d.ts +18 -0
  484. package/dist/tools/builtins/read-tool-output.js +108 -0
  485. package/dist/tools/builtins/sandbox/bash.d.ts +16 -0
  486. package/dist/tools/builtins/sandbox/bash.js +137 -0
  487. package/dist/tools/builtins/sandbox/common.d.ts +116 -0
  488. package/dist/tools/builtins/sandbox/common.js +306 -0
  489. package/dist/tools/builtins/sandbox/deliver.d.ts +5 -0
  490. package/dist/tools/builtins/sandbox/deliver.js +180 -0
  491. package/dist/tools/builtins/sandbox/edit.d.ts +4 -0
  492. package/dist/tools/builtins/sandbox/edit.js +98 -0
  493. package/dist/tools/builtins/sandbox/glob-match.d.ts +25 -0
  494. package/dist/tools/builtins/sandbox/glob-match.js +240 -0
  495. package/dist/tools/builtins/sandbox/glob.d.ts +4 -0
  496. package/dist/tools/builtins/sandbox/glob.js +104 -0
  497. package/dist/tools/builtins/sandbox/grep.d.ts +9 -0
  498. package/dist/tools/builtins/sandbox/grep.js +133 -0
  499. package/dist/tools/builtins/sandbox/index.d.ts +12 -0
  500. package/dist/tools/builtins/sandbox/index.js +27 -0
  501. package/dist/tools/builtins/sandbox/names.d.ts +7 -0
  502. package/dist/tools/builtins/sandbox/names.js +20 -0
  503. package/dist/tools/builtins/sandbox/read.d.ts +8 -0
  504. package/dist/tools/builtins/sandbox/read.js +121 -0
  505. package/dist/tools/builtins/sandbox/write.d.ts +4 -0
  506. package/dist/tools/builtins/sandbox/write.js +88 -0
  507. package/dist/tools/builtins/skill-load.d.ts +53 -0
  508. package/dist/tools/builtins/skill-load.js +446 -0
  509. package/dist/tools/builtins/skill-write.d.ts +59 -0
  510. package/dist/tools/builtins/skill-write.js +244 -0
  511. package/dist/tools/builtins/tool-search.d.ts +31 -0
  512. package/dist/tools/builtins/tool-search.js +171 -0
  513. package/dist/tools/connector-content.controller.d.ts +34 -0
  514. package/dist/tools/connector-content.controller.js +78 -0
  515. package/dist/tools/connector-content.d.ts +104 -0
  516. package/dist/tools/connector-content.js +267 -0
  517. package/dist/tools/connectors.controller.d.ts +210 -0
  518. package/dist/tools/connectors.controller.js +234 -0
  519. package/dist/tools/connectors.service.d.ts +303 -0
  520. package/dist/tools/connectors.service.js +1102 -0
  521. package/dist/tools/credentials.d.ts +139 -0
  522. package/dist/tools/credentials.js +351 -0
  523. package/dist/tools/drift.d.ts +141 -0
  524. package/dist/tools/drift.js +539 -0
  525. package/dist/tools/elicitation.d.ts +81 -0
  526. package/dist/tools/elicitation.js +266 -0
  527. package/dist/tools/endpoint.d.ts +18 -0
  528. package/dist/tools/endpoint.js +115 -0
  529. package/dist/tools/info.d.ts +67 -0
  530. package/dist/tools/info.js +113 -0
  531. package/dist/tools/naming.d.ts +27 -0
  532. package/dist/tools/naming.js +47 -0
  533. package/dist/tools/oauth/discovery.d.ts +65 -0
  534. package/dist/tools/oauth/discovery.js +307 -0
  535. package/dist/tools/oauth/flow.d.ts +63 -0
  536. package/dist/tools/oauth/flow.js +237 -0
  537. package/dist/tools/oauth/oauth.controller.d.ts +31 -0
  538. package/dist/tools/oauth/oauth.controller.js +95 -0
  539. package/dist/tools/oauth/tokens.d.ts +111 -0
  540. package/dist/tools/oauth/tokens.js +258 -0
  541. package/dist/tools/outputs.d.ts +61 -0
  542. package/dist/tools/outputs.js +156 -0
  543. package/dist/tools/pipeline.d.ts +322 -0
  544. package/dist/tools/pipeline.js +1236 -0
  545. package/dist/tools/policy.d.ts +60 -0
  546. package/dist/tools/policy.js +236 -0
  547. package/dist/tools/questions.controller.d.ts +24 -0
  548. package/dist/tools/questions.controller.js +58 -0
  549. package/dist/tools/questions.service.d.ts +103 -0
  550. package/dist/tools/questions.service.js +295 -0
  551. package/dist/tools/redact.d.ts +20 -0
  552. package/dist/tools/redact.js +60 -0
  553. package/dist/tools/registry.d.ts +246 -0
  554. package/dist/tools/registry.js +481 -0
  555. package/dist/tools/resources.d.ts +16 -0
  556. package/dist/tools/resources.js +76 -0
  557. package/dist/tools/specs.d.ts +14 -0
  558. package/dist/tools/specs.js +31 -0
  559. package/dist/tools/validate.d.ts +20 -0
  560. package/dist/tools/validate.js +183 -0
  561. package/dist/tools/view.d.ts +43 -0
  562. package/dist/tools/view.js +225 -0
  563. package/dist/ui/embed-token.d.ts +31 -0
  564. package/dist/ui/embed-token.js +132 -0
  565. package/dist/ui/registry.d.ts +58 -0
  566. package/dist/ui/registry.js +90 -0
  567. package/dist/ui/ui-stream.d.ts +37 -0
  568. package/dist/ui/ui-stream.js +162 -0
  569. package/dist/ui/views.controller.d.ts +81 -0
  570. package/dist/ui/views.controller.js +261 -0
  571. package/dist/ui/views.d.ts +155 -0
  572. package/dist/ui/views.js +306 -0
  573. package/dist/version.d.ts +5 -0
  574. package/dist/version.js +6 -0
  575. package/package.json +69 -3
  576. package/src/migrations/0001_init.sql +8 -0
  577. package/src/migrations/0002_secrets.sql +8 -0
  578. package/src/migrations/0003_identity.sql +117 -0
  579. package/src/migrations/0004_plugin_org_config_nullable.sql +3 -0
  580. package/src/migrations/0005_agents.sql +27 -0
  581. package/src/migrations/0006_conversations.sql +37 -0
  582. package/src/migrations/0007_checkpoints.sql +26 -0
  583. package/src/migrations/0008_runs_trigger_seq.sql +3 -0
  584. package/src/migrations/0009_runs_active_unique.sql +4 -0
  585. package/src/migrations/0010_tools.sql +125 -0
  586. package/src/migrations/0011_approvals_spec.sql +10 -0
  587. package/src/migrations/0012_run_waits.sql +24 -0
  588. package/src/migrations/0013_app_tools_source.sql +4 -0
  589. package/src/migrations/0014_tool_specs.sql +8 -0
  590. package/src/migrations/0015_tool_drift.sql +20 -0
  591. package/src/migrations/0016_plugin_permissions.sql +11 -0
  592. package/src/migrations/0017_identity_sources.sql +6 -0
  593. package/src/migrations/0018_channels.sql +74 -0
  594. package/src/migrations/0019_identity_trust.sql +17 -0
  595. package/src/migrations/0020_channel_card_lookup.sql +10 -0
  596. package/src/migrations/0021_org_storage_purge.sql +9 -0
  597. package/src/migrations/0022_attachments.sql +29 -0
  598. package/src/migrations/0023_sandboxes.sql +37 -0
  599. package/src/migrations/0024_skills.sql +46 -0
  600. package/src/migrations/0025_skills_catalog_idx.sql +6 -0
  601. package/src/migrations/0026_skill_changes.sql +34 -0
  602. package/src/migrations/0027_model_prices.sql +18 -0
  603. package/src/migrations/0028_evals.sql +105 -0
  604. package/src/migrations/0029_model_purposes.sql +18 -0
  605. package/src/migrations/0030_plugin_installs.sql +73 -0
  606. package/src/migrations/0031_plugin_snapshot_source.sql +6 -0
  607. package/src/migrations/0032_plugin_deployment_configs.sql +10 -0
  608. package/src/migrations/0033_rename_apps_to_connectors.sql +25 -0
  609. package/src/migrations/0034_directory.sql +49 -0
  610. package/src/migrations/0035_catalogs.sql +40 -0
  611. package/src/migrations/0036_app_import_staging.sql +17 -0
  612. package/src/migrations/0037_directory_installs.sql +92 -0
  613. package/src/migrations/0038_channel_inbound_mode.sql +6 -0
  614. package/src/migrations/0039_channel_card_pending.sql +8 -0
  615. package/src/migrations/0040_usage_report_sweep.sql +10 -0
  616. package/src/migrations/0041_rename_plugins_to_extensions.sql +38 -0
  617. package/src/migrations/0042_rename_directory_to_catalog.sql +80 -0
  618. package/src/migrations/0043_rename_app_installs_to_plugin_installs.sql +20 -0
  619. package/src/migrations/0044_catalog_one_kind.sql +90 -0
  620. package/src/migrations/0045_plugin_dependencies.sql +9 -0
  621. package/src/migrations/0046_git_sources.sql +25 -0
  622. package/src/migrations/0047_marketplace_entry_heads.sql +7 -0
  623. package/src/migrations/0048_marketplace_entry_trees.sql +8 -0
  624. package/src/migrations/0049_plugin_upgrades.sql +37 -0
  625. package/src/migrations/0050_bootstrap.sql +53 -0
  626. package/src/migrations/0051_claude_compat.sql +26 -0
  627. package/src/migrations/0052_conversations_account_agent_idx.sql +5 -0
  628. package/src/migrations/0053_subagents.sql +73 -0
  629. package/src/migrations/0054_subagent_wait_expiry.sql +7 -0
  630. package/src/migrations/0055_subagent_parent_indexes.sql +10 -0
  631. package/src/migrations/0056_approval_reason.sql +3 -0
  632. package/src/migrations/0057_conversation_titles.sql +11 -0
  633. package/src/migrations/0058_conversation_selection.sql +4 -0
  634. package/src/migrations/0059_conversation_queue.sql +22 -0
  635. package/src/migrations/0060_file_previewers.sql +10 -0
  636. package/src/migrations/0061_queue_selection.sql +5 -0
  637. package/src/migrations/0062_queued_skill.sql +8 -0
@@ -0,0 +1,1333 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { EXTENSION_POINTS, parseExtensionPoint, parseSkillMd, SKILL_ENTRY_FILE, } from "@capybara-hq/plugin-sdk";
3
+ import { sql } from "kysely";
4
+ import { normalizeToolRef } from "../agents/definition.js";
5
+ import { putTemplateVersionIn, revertTemplateVersionIn, softDeleteTemplateIn, } from "../agents/templates.js";
6
+ import { ApiError } from "../errors.js";
7
+ import { isExtensionEnabled } from "../extensions/enabled.js";
8
+ import { audit } from "../identity/audit.js";
9
+ import { lockOrg } from "../orgs/org-lock.js";
10
+ import { packagePath } from "../packages/tgz.js";
11
+ import { skillConflictError, skillConflicts, } from "../skills/skills.service.js";
12
+ import { lockOrgForActions, readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, requiredActions, skillOf, templateConnectorInput, templatePluginProblem, } from "./org-catalog.service.js";
13
+ import { assertItemInstallable, disabledPluginItems, pluginItemDisabled, } from "./requests.service.js";
14
+ import { upgradeInfo } from "./upgrade-targets.js";
15
+ import { maxSatisfying, satisfiesRange } from "./versions.js";
16
+ /** How deep plugin dependencies may nest (the root plugin's dependencies are level 1). */
17
+ export const DEPENDENCY_DEPTH_LIMIT = 10;
18
+ /** A failed install step, carried up through the dependency installs to the root install. */
19
+ export class InstallFailure extends Error {
20
+ step;
21
+ cause;
22
+ dependency;
23
+ rolledBack;
24
+ constructor(step, cause,
25
+ /** The dependency path when a dependency failed (outermost first). */
26
+ dependency, rolledBack) {
27
+ super("plugin install failed");
28
+ this.step = step;
29
+ this.cause = cause;
30
+ this.dependency = dependency;
31
+ this.rolledBack = rolledBack;
32
+ }
33
+ get code() {
34
+ return this.cause instanceof ApiError ? this.cause.code : "internal";
35
+ }
36
+ }
37
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
38
+ const ALL_ACTIONS = [
39
+ "org.extensions.manage",
40
+ "connectors.manage",
41
+ "skills.manage",
42
+ "agents.manage",
43
+ ];
44
+ const installNotFound = () => new ApiError("plugin.install_not_found", "Plugin install not found");
45
+ const fail = (path, message) => new ApiError("validation_failed", "Request validation failed", [{ path, message }]);
46
+ const isUniqueViolation = (e) => typeof e === "object" && e !== null && e.code === "23505";
47
+ const unresolved = (name, range, why) => new ApiError("catalog.dependency_unresolved", `Dependency ${name}@${range} cannot be resolved: ${why}`, [{ dependency: name, range }]);
48
+ const dependencyDisabled = (name) => new ApiError("catalog.dependency_disabled", `Dependency ${name} is disabled by the enterprise admin, so this plugin cannot be installed`, [{ dependency: name }]);
49
+ /**
50
+ * The dependency `d` resolved against the catalog: the highest imported version (of the live item
51
+ * with that name) satisfying its range. No such item or version -> `catalog.dependency_unresolved`;
52
+ * the item disabled -> `catalog.dependency_disabled` (the enterprise admin's ban is not bypassed,
53
+ * spec §10).
54
+ */
55
+ async function resolveOne(db, d) {
56
+ const item = await db
57
+ .selectFrom("catalog_items")
58
+ .select(["id", "policy"])
59
+ .where("name", "=", d.name)
60
+ .where("deleted_at", "is", null)
61
+ .executeTakeFirst();
62
+ if (!item) {
63
+ throw unresolved(d.name, d.version, "it is not in the enterprise catalog; ask the enterprise admin to import it");
64
+ }
65
+ if (item.policy === "disabled")
66
+ throw dependencyDisabled(d.name);
67
+ const versions = await db
68
+ .selectFrom("catalog_item_versions")
69
+ .select(["version", "manifest", "imported_at"])
70
+ .where("item_id", "=", item.id)
71
+ .execute();
72
+ const v = maxSatisfying(versions.map((x) => ({ ...x, importedAt: x.imported_at })), d.version);
73
+ if (!v) {
74
+ throw unresolved(d.name, d.version, "no imported version satisfies the range; ask the enterprise admin to import one");
75
+ }
76
+ return {
77
+ name: d.name,
78
+ range: d.version,
79
+ itemId: item.id,
80
+ version: v.version,
81
+ manifest: v.manifest,
82
+ };
83
+ }
84
+ /**
85
+ * Resolves a plugin's dependencies against the catalog: for each one the highest imported version
86
+ * of the live item with that name satisfying its range. The whole dependency tree is checked: a
87
+ * dependency nothing satisfies -> `catalog.dependency_unresolved`; a disabled one ->
88
+ * `catalog.dependency_disabled`; a cycle (`a` -> `b` -> `a`, by name) or nesting deeper than
89
+ * {@link DEPENDENCY_DEPTH_LIMIT} -> `catalog.dependency_cycle`. Returns the direct dependencies.
90
+ */
91
+ export async function resolveDependencies(db, manifest) {
92
+ // Explored subtrees by name@version: their height and the names in them (a memo hit whose names
93
+ // meet the current path is a cycle)
94
+ const memo = new Map();
95
+ const direct = [];
96
+ const tooDeep = () => new ApiError("catalog.dependency_cycle", `Plugin dependencies nest more than ${DEPENDENCY_DEPTH_LIMIT} levels deep`);
97
+ const cycle = (path, name) => new ApiError("catalog.dependency_cycle", `Plugin dependencies form a cycle: ${[...path.slice(path.indexOf(name)), name].join(" -> ")}`, [{ cycle: [...path.slice(path.indexOf(name)), name] }]);
98
+ const visit = async (m, path) => {
99
+ let height = 0;
100
+ const names = new Set();
101
+ for (const d of m.dependencies ?? []) {
102
+ if (path.includes(d.name))
103
+ throw cycle(path, d.name);
104
+ if (path.length > DEPENDENCY_DEPTH_LIMIT)
105
+ throw tooDeep();
106
+ const r = await resolveOne(db, d);
107
+ if (path.length === 1) {
108
+ direct.push({ name: r.name, range: r.range, itemId: r.itemId, version: r.version });
109
+ }
110
+ const key = `${r.name}@${r.version}`;
111
+ let sub = memo.get(key);
112
+ if (sub) {
113
+ const hit = path.find((p) => sub?.names.has(p));
114
+ if (hit !== undefined)
115
+ throw cycle(path, hit);
116
+ }
117
+ else {
118
+ sub = await visit(r.manifest, [...path, d.name]);
119
+ memo.set(key, sub);
120
+ }
121
+ if (path.length + sub.height > DEPENDENCY_DEPTH_LIMIT)
122
+ throw tooDeep();
123
+ height = Math.max(height, sub.height + 1);
124
+ names.add(d.name);
125
+ for (const n of sub.names)
126
+ names.add(n);
127
+ }
128
+ return { height, names };
129
+ };
130
+ await visit(manifest, [manifest.name]);
131
+ return direct;
132
+ }
133
+ /**
134
+ * The input a template needs that the org did not give: a static credential (the org's for an
135
+ * org-owned template; for a personal one the installer's own, stored as their personal credential,
136
+ * since creating the connector lists its tools with it), or the OAuth client of a
137
+ * client-credentials / manual-client template. Undefined when nothing is missing.
138
+ * `personalCredential: false` keeps a personal static template optional (an existing connector
139
+ * whose template's auth changed: its members keep giving their own).
140
+ */
141
+ export function missingTemplateInput(template, given, o = {}) {
142
+ const auth = template.auth;
143
+ if (auth.type === "oauth_client" || (auth.type === "oauth_code" && auth.client === "manual")) {
144
+ return given?.client === undefined ? "client" : undefined;
145
+ }
146
+ if (auth.type === "none" || auth.type === "oauth_code")
147
+ return undefined;
148
+ if (auth.ownership !== "org" && o.personalCredential === false)
149
+ return undefined;
150
+ return given?.credential === undefined ? "credential" : undefined;
151
+ }
152
+ /** `e` with its validation paths under `at` (input errors of a template's connector). */
153
+ export function under(e, at) {
154
+ if (!(e instanceof ApiError) || e.code !== "validation_failed" || !Array.isArray(e.details)) {
155
+ return e;
156
+ }
157
+ return new ApiError(e.code, e.message, e.details.map((d) => typeof d === "object" && d !== null && typeof d.path === "string"
158
+ ? { ...d, path: [at, d.path].filter(Boolean).join(".") }
159
+ : d));
160
+ }
161
+ /**
162
+ * Org install and uninstall of plugins (spec §10, C31). Before anything is written the install is
163
+ * planned for the whole dependency tree ({@link resolveDependencies}; the extension deployed, the
164
+ * template input valid, else a 4xx). Install, in order, per plugin:
165
+ * 1. dependencies, depth first: each one not yet installed in the org gets its own nested
166
+ * `plugin_installs` row, recorded as a `plugin` component of the parent (created=true); one
167
+ * already installed is recorded created=false;
168
+ * 2. the plugin's extension, enabled in the org (`catalog.not_deployed` when the deployment does
169
+ * not run it; already enabled is recorded created=false);
170
+ * 3. connectors from the templates (credentials / clients go to secrets, default policies are
171
+ * recorded `policy_source 'template'`);
172
+ * 4. the skills (an org skill of the same name from the same catalog item gets a new version,
173
+ * recorded created=false with skill_version; one of other provenance, the org's own or another
174
+ * item's, refuses the install while planning with `plugin.skill_conflict`, spec §9.2).
175
+ * Approval follows the installed plugin's policy only (spec §10): dependency components are
176
+ * re-checked against the root item's policy and their own item's disabled state. Each component is
177
+ * recorded in `plugin_install_components` as soon as it exists; if any step fails, the components
178
+ * this install created (nested dependency installs included) are undone in reverse order, the
179
+ * install is marked failed (error holds only the step and the error code) and 502
180
+ * `plugin.install_failed` is thrown. Uninstall removes only components with created=true that
181
+ * nothing else uses, and reports what it kept and why. No in-process state; the org row lock
182
+ * serializes writes within one org. In-place upgrades are run by `PluginUpgrader`
183
+ * (plugin-upgrade.ts), which reuses the planning, install and ledger methods here (the ones without
184
+ * `private` are internal to the catalog, not an API).
185
+ */
186
+ export class PluginInstaller {
187
+ d;
188
+ constructor(d) {
189
+ this.d = d;
190
+ }
191
+ /** The installer's dependencies (shared with `PluginUpgrader`). */
192
+ get deps() {
193
+ return this.d;
194
+ }
195
+ /**
196
+ * The checks an install runs before it creates anything, without the org's input (for the org
197
+ * catalog to run before a request is created): dependency resolution, the extensions deployed and
198
+ * the template connectors available. Returns the org actions the whole install needs.
199
+ */
200
+ async preflight(orgId, actor, itemId, version) {
201
+ const plan = await this.plan(orgId, actor, itemId, version, null, "", undefined);
202
+ await this.assertNoSkillConflicts(orgId, plan);
203
+ return { actions: planActions(plan) };
204
+ }
205
+ async install(orgId, actor, itemId, version, input, opts = {}) {
206
+ const plan = await this.plan(orgId, actor, itemId, version, input, "", undefined);
207
+ await this.assertNoSkillConflicts(orgId, plan);
208
+ let installId;
209
+ try {
210
+ installId = await this.execute(orgId, actor, plan, opts, {
211
+ actions: planActions(plan),
212
+ });
213
+ }
214
+ catch (e) {
215
+ if (!(e instanceof InstallFailure))
216
+ throw e;
217
+ // A dependency another install is still installing: retryable, a 409 as it is
218
+ if (e.cause instanceof ApiError && e.cause.code === "plugin.install_in_progress") {
219
+ throw e.cause;
220
+ }
221
+ const dep = e.dependency.length > 0 ? ` in dependency ${e.dependency.join(" > ")}` : "";
222
+ throw new ApiError("plugin.install_failed", `Installing the plugin failed at step ${e.step}${dep} (${e.code}); the components it created were removed`, [
223
+ {
224
+ step: e.step,
225
+ code: e.code,
226
+ ...(e.dependency.length > 0 && { dependency: e.dependency.join(" > ") }),
227
+ ...(e.cause instanceof ApiError && { message: e.cause.message }),
228
+ ...(e.cause instanceof ApiError &&
229
+ e.cause.details !== undefined && { causeDetails: e.cause.details }),
230
+ rolledBack: e.rolledBack,
231
+ },
232
+ ]);
233
+ }
234
+ const [view] = await this.list(orgId, installId);
235
+ if (!view)
236
+ throw installNotFound();
237
+ return view;
238
+ }
239
+ /**
240
+ * Refuses a plan whose skills would overwrite a skill of other provenance (spec §9.2, Decision
241
+ * 8) with 409 `plugin.skill_conflict`, before anything is written: for the plugin and each
242
+ * dependency it installs, an org skill of the same name that is the org's own or another item's,
243
+ * and a name two plugins of the same plan both bring (the one installed first is the source).
244
+ */
245
+ async assertNoSkillConflicts(orgId, plan) {
246
+ const conflicts = await planSkillConflicts(this.d.db, orgId, plan);
247
+ if (conflicts.length > 0)
248
+ throw skillConflictError(conflicts);
249
+ }
250
+ /** The connector checks of templates against what this replica loads right now. */
251
+ templateDeps() {
252
+ const versions = new Map(this.d.host.manifests().map((m) => [m.name, m.version]));
253
+ return { findConnector: this.d.findConnector, pluginVersion: (n) => versions.get(n) };
254
+ }
255
+ /**
256
+ * Plans the install of one plugin version and, depth first, of its dependencies not yet
257
+ * installed in the org. `input` null skips the input checks (preflight); dependencies always get
258
+ * an empty input. `at` prefixes validation paths (`dependencies.<name>.` for a dependency);
259
+ * `approvedBy` is the root item for a dependency.
260
+ */
261
+ async plan(orgId, actor, itemId, version, input, at, approvedBy) {
262
+ if (!UUID_RE.test(itemId))
263
+ throw new ApiError("catalog.item_not_found", "Plugin not found");
264
+ const v = await this.d.db
265
+ .selectFrom("catalog_item_versions as v")
266
+ .innerJoin("catalog_items as i", "i.id", "v.item_id")
267
+ .select([
268
+ "i.name",
269
+ "v.manifest",
270
+ "v.object_prefix",
271
+ "v.files",
272
+ "v.version",
273
+ "v.source_format",
274
+ ])
275
+ .where("v.item_id", "=", itemId)
276
+ .where("v.version", "=", version)
277
+ .where("i.deleted_at", "is", null)
278
+ .executeTakeFirst();
279
+ if (!v)
280
+ throw new ApiError("catalog.item_not_found", "Plugin version not found");
281
+ const manifest = v.manifest;
282
+ const deps = await resolveDependencies(this.d.db, manifest);
283
+ const extension = await this.planExtension(orgId, manifest);
284
+ const templates = await readCatalogTemplates(this.d.storage, v);
285
+ const skillNames = await catalogSkillNames(this.d.storage, v, manifest);
286
+ const agentTemplates = await readCatalogAgentTemplates(this.d.storage, v);
287
+ const connectors = new Map();
288
+ if (input === null) {
289
+ for (const ct of templates) {
290
+ const problem = templatePluginProblem(this.templateDeps(), ct.file.template);
291
+ if (problem !== undefined)
292
+ throw new ApiError("catalog.not_deployed", problem);
293
+ }
294
+ }
295
+ else {
296
+ this.checkInput(manifest, templates, input, at);
297
+ for (const ct of templates) {
298
+ connectors.set(ct.id, this.planConnector(actor, { itemId, version, name: manifest.name }, ct, input, at, approvedBy));
299
+ }
300
+ }
301
+ const dependencies = [];
302
+ for (const dep of deps) {
303
+ if (await this.liveDependency(this.d.db, orgId, dep)) {
304
+ dependencies.push({ dep, plan: null });
305
+ continue;
306
+ }
307
+ dependencies.push({
308
+ dep,
309
+ plan: await this.plan(orgId, actor, dep.itemId, dep.version, input === null ? null : {}, `${at}dependencies.${dep.name}.`, approvedBy ?? itemId),
310
+ });
311
+ }
312
+ return {
313
+ itemId,
314
+ name: v.name,
315
+ version,
316
+ manifest,
317
+ row: v,
318
+ sourceFormat: v.source_format,
319
+ extension,
320
+ extensionConfig: input?.extensionConfig,
321
+ templates,
322
+ connectors,
323
+ skillNames,
324
+ agentTemplates,
325
+ dependencies,
326
+ };
327
+ }
328
+ /**
329
+ * The connector input of one template from the org's input (C30), checked without a connection:
330
+ * the template's connector extension deployed, the input valid, the credential / OAuth client it
331
+ * needs given (else 400; a dependency's says to install it on its own first).
332
+ */
333
+ planConnector(actor, plugin, ct, input, at, approvedBy) {
334
+ const path = `${at}connectors.${ct.id}`;
335
+ const given = input.connectors?.[ct.id];
336
+ const ci = templateConnectorInput(this.templateDeps(), ct.file.template, {
337
+ itemId: plugin.itemId,
338
+ version: plugin.version,
339
+ templateId: ct.id,
340
+ ...(approvedBy !== undefined && { approvedBy }),
341
+ }, given, ct.file.displayName.en, path);
342
+ const missing = missingTemplateInput(ct.file.template, given);
343
+ if (missing !== undefined) {
344
+ const personal = missing === "credential" &&
345
+ ct.file.template.auth.ownership !== "org";
346
+ const needs = missing === "client"
347
+ ? "an OAuth client"
348
+ : personal
349
+ ? "the installer's own credential"
350
+ : "an org credential";
351
+ throw fail(`${path}.${missing}`, approvedBy !== undefined
352
+ ? `Dependency ${plugin.name} has a connector template that needs ${needs}; install ${plugin.name} on its own first`
353
+ : personal
354
+ ? "This connector template uses each member's own credential; give yours (it lists the tools and is stored as your personal credential)"
355
+ : `This connector template needs ${needs}`);
356
+ }
357
+ try {
358
+ this.d.connectors.checkCreateInput(actor, ci);
359
+ }
360
+ catch (e) {
361
+ throw under(e, path);
362
+ }
363
+ return ci;
364
+ }
365
+ /**
366
+ * The org's live install of a dependency's item (installing or installed), or undefined. One at a
367
+ * version outside the dependency's range -> `catalog.dependency_unresolved` (org upgrades arrive in
368
+ * phase A).
369
+ */
370
+ async liveDependency(db, orgId, dep) {
371
+ const live = await this.findLiveDependency(db, orgId, dep, false);
372
+ if (!live)
373
+ return undefined;
374
+ if (!satisfiesRange(live.version, dep.range)) {
375
+ throw unresolved(dep.name, dep.range, `this organization has version ${live.version} installed; uninstall it to install a matching version`);
376
+ }
377
+ return live.id;
378
+ }
379
+ /**
380
+ * The org's live (not failed) install of a dependency's item; `lock` takes it FOR SHARE (the
381
+ * caller holds the org row lock).
382
+ */
383
+ async findLiveDependency(db, orgId, dep, lock) {
384
+ let q = db
385
+ .selectFrom("plugin_installs")
386
+ .select(["id", "version", "status"])
387
+ .where("org_id", "=", orgId)
388
+ .where("item_id", "=", dep.itemId)
389
+ .where("status", "<>", "failed");
390
+ if (lock)
391
+ q = q.forShare();
392
+ return q.executeTakeFirst();
393
+ }
394
+ /**
395
+ * Records an install of `dep` as a `plugin` component of the install, checked and written in one
396
+ * transaction under the org row lock, so a concurrent uninstall or a sibling install's rollback
397
+ * cannot leave the component pointing at a removed install. `created` false: the org's existing
398
+ * install, if any (returns its id, undefined when there is none); it must be `installed`
399
+ * (`installing` -> 409 `plugin.install_in_progress`, retry later) and in range. `created` true:
400
+ * `child`, the dependency install this install just created, must still exist.
401
+ */
402
+ async recordDependency(orgId, dep, record, child) {
403
+ return this.d.db.transaction().execute(async (t) => {
404
+ await lockOrg(t, orgId);
405
+ const live = await this.findLiveDependency(t, orgId, dep, true);
406
+ if (child !== undefined) {
407
+ if (live?.id !== child || live.status !== "installed") {
408
+ throw unresolved(dep.name, dep.range, "its install in this organization was removed meanwhile; try again");
409
+ }
410
+ await record({ kind: "plugin", ref: child, created: true, skillVersion: null }, t);
411
+ return child;
412
+ }
413
+ if (!live)
414
+ return undefined;
415
+ if (!satisfiesRange(live.version, dep.range)) {
416
+ throw unresolved(dep.name, dep.range, `this organization has version ${live.version} installed; uninstall it to install a matching version`);
417
+ }
418
+ if (live.status !== "installed") {
419
+ throw new ApiError("plugin.install_in_progress", `Dependency ${dep.name} is being installed by another install in this organization; try again when it finishes. An install stuck for longer than catalog.staleInstallMinutes can be removed by uninstalling it`);
420
+ }
421
+ await record({ kind: "plugin", ref: live.id, created: false, skillVersion: null }, t);
422
+ return live.id;
423
+ });
424
+ }
425
+ /**
426
+ * Runs a plan: the install row, then the steps (see the class comment). Throws
427
+ * {@link InstallFailure} once the row exists (after rolling back); errors before it propagate
428
+ * as they are. Returns the install id.
429
+ */
430
+ async execute(orgId, actor, plan, opts, ctx) {
431
+ const { itemId, version, manifest } = plan;
432
+ // The install row (role and item policy re-checked under the lock; one live install per org
433
+ // and item)
434
+ const installId = randomUUID();
435
+ await this.d.db.transaction().execute(async (t) => {
436
+ await lockOrgForActions(t, orgId, actor, ctx.actions);
437
+ if (ctx.approvedBy !== undefined) {
438
+ // A dependency disabled (or removed) since it was planned
439
+ const own = await t
440
+ .selectFrom("catalog_items")
441
+ .select(["policy", "deleted_at"])
442
+ .where("id", "=", itemId)
443
+ .forShare()
444
+ .executeTakeFirst();
445
+ if (!own || own.deleted_at !== null) {
446
+ throw unresolved(plan.name, plan.version, "it was removed from the enterprise catalog");
447
+ }
448
+ if (own.policy === "disabled")
449
+ throw dependencyDisabled(plan.name);
450
+ }
451
+ await assertItemInstallable(t, orgId, itemId, ctx.approvedBy ?? itemId);
452
+ const live = await t
453
+ .selectFrom("plugin_installs")
454
+ .select("id")
455
+ .where("org_id", "=", orgId)
456
+ .where("item_id", "=", itemId)
457
+ .where("status", "<>", "failed")
458
+ .executeTakeFirst();
459
+ if (live)
460
+ throw alreadyInstalled();
461
+ try {
462
+ await t
463
+ .insertInto("plugin_installs")
464
+ .values({
465
+ id: installId,
466
+ org_id: orgId,
467
+ item_id: itemId,
468
+ version,
469
+ status: "installing",
470
+ installed_by: actor,
471
+ })
472
+ .execute();
473
+ }
474
+ catch (e) {
475
+ if (isUniqueViolation(e))
476
+ throw alreadyInstalled();
477
+ throw e;
478
+ }
479
+ });
480
+ const done = [];
481
+ // Records a component in the given transaction (the install row already cleaned up -> the FK
482
+ // fails, the install aborts and rolls back)
483
+ const record = async (c, t) => {
484
+ // Into `done` first: a component that exists is rolled back even when recording it fails
485
+ // (enabling an extension and recording it share one transaction and fail together)
486
+ done.push(c);
487
+ await (t ?? this.d.db)
488
+ .insertInto("plugin_install_components")
489
+ .values({
490
+ install_id: installId,
491
+ kind: c.kind,
492
+ ref: c.ref,
493
+ created: c.created,
494
+ skill_version: c.skillVersion,
495
+ template_id: c.templateId ?? null,
496
+ })
497
+ .onConflict((oc) => oc.columns(["install_id", "kind", "ref"]).doNothing())
498
+ .execute();
499
+ };
500
+ const approvedBy = ctx.approvedBy ?? itemId;
501
+ const nested = ctx.approvedBy !== undefined;
502
+ let step = "dependencies";
503
+ try {
504
+ for (const { dep, plan: depPlan } of plan.dependencies) {
505
+ // Installed already (before, or by an earlier dependency): recorded, not reinstalled
506
+ if ((await this.recordDependency(orgId, dep, record)) !== undefined)
507
+ continue;
508
+ if (depPlan === null) {
509
+ throw unresolved(dep.name, dep.range, "its install in this organization was removed meanwhile; try again");
510
+ }
511
+ let child;
512
+ try {
513
+ child = await this.execute(orgId, actor, depPlan, opts, {
514
+ actions: requiredActions(depPlan.manifest),
515
+ approvedBy,
516
+ parent: installId,
517
+ });
518
+ }
519
+ catch (e) {
520
+ if (e instanceof InstallFailure) {
521
+ throw new InstallFailure(e.step, e.cause, [dep.name, ...e.dependency], e.rolledBack);
522
+ }
523
+ // Another install created it meanwhile: use that one
524
+ if (e instanceof ApiError &&
525
+ e.code === "plugin.already_installed" &&
526
+ (await this.recordDependency(orgId, dep, record)) !== undefined) {
527
+ continue;
528
+ }
529
+ throw new InstallFailure("dependencies", e, [dep.name], true);
530
+ }
531
+ await this.recordDependency(orgId, dep, record, child);
532
+ }
533
+ step = "extension";
534
+ if (plan.extension !== null) {
535
+ const p = plan.extension;
536
+ // "Enabled before" is decided under the org row lock (already enabled: put writes nothing,
537
+ // the config is untouched); the component is recorded in the same transaction
538
+ await this.d.orgConfigs.put(orgId, p, {
539
+ enabled: true,
540
+ ...(plan.extensionConfig !== undefined && { config: plan.extensionConfig }),
541
+ }, actor, opts.actorTokenId, {
542
+ viaItem: itemId,
543
+ ...(nested && { approvedBy }),
544
+ ifDisabled: { skipped: false },
545
+ inTransaction: (t, skipped) => record({ kind: "extension", ref: p, created: !skipped, skillVersion: null }, t),
546
+ });
547
+ }
548
+ step = "connectors";
549
+ for (const ct of plan.templates) {
550
+ const ci = plan.connectors.get(ct.id);
551
+ if (!ci)
552
+ throw new ApiError("internal", `Connector template ${ct.id} was not planned`);
553
+ const r = await this.d.connectors.create(orgId, actor, ci, opts.actorTokenId);
554
+ await record({
555
+ kind: "connector",
556
+ ref: r.connector.id,
557
+ created: true,
558
+ skillVersion: null,
559
+ templateId: ct.id,
560
+ });
561
+ }
562
+ step = "agent_templates";
563
+ for (const a of plan.agentTemplates) {
564
+ // Created (or a new version of the org's live template of this plugin and name) and
565
+ // recorded in one transaction, under the org row lock
566
+ await this.d.db.transaction().execute(async (t) => {
567
+ await lockOrg(t, orgId);
568
+ const r = await putTemplateVersionIn(t, {
569
+ orgId,
570
+ plugin: plan.name,
571
+ catalogItemId: itemId,
572
+ catalogVersion: version,
573
+ sourceFormat: plan.sourceFormat,
574
+ }, a.template);
575
+ await record({
576
+ kind: "agent_template",
577
+ ref: r.id,
578
+ created: r.created,
579
+ skillVersion: !r.created && r.changed ? r.version : null,
580
+ }, t);
581
+ });
582
+ }
583
+ step = "skills";
584
+ for (const s of manifest.skills ?? []) {
585
+ const files = await readCatalogFiles(this.d.storage, plan.row.object_prefix, plan.row.files, packagePath(s.path));
586
+ const r = await this.d.skills.createVersionFromFiles(orgId, actor, { ...skillOf(files), files }, {
587
+ catalog: {
588
+ itemId,
589
+ version,
590
+ ...(nested && { approvedBy }),
591
+ format: plan.sourceFormat,
592
+ suggestPinned: s.pinned === true,
593
+ },
594
+ ...opts,
595
+ });
596
+ await record({
597
+ kind: "skill",
598
+ ref: r.skill.id,
599
+ created: r.created,
600
+ skillVersion: r.created ? null : r.version,
601
+ });
602
+ }
603
+ await this.d.db.transaction().execute(async (t) => {
604
+ await lockOrg(t, orgId);
605
+ const updated = await t
606
+ .updateTable("plugin_installs")
607
+ .set({ status: "installed" })
608
+ .where("id", "=", installId)
609
+ .where("status", "=", "installing")
610
+ .executeTakeFirst();
611
+ // The install row was already cleaned up as stale: do not audit plugin.org_installed, roll
612
+ // back
613
+ if (updated.numUpdatedRows === 0n) {
614
+ throw new ApiError("plugin.install_not_found", "The install record was cleaned up as stale while the install was running");
615
+ }
616
+ await this.d.requests.resolveByInstall(t, orgId, itemId, actor, opts);
617
+ await audit(t, {
618
+ actorAccountId: actor,
619
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
620
+ orgId,
621
+ action: "plugin.org_installed",
622
+ target: installId,
623
+ detail: {
624
+ itemId,
625
+ name: plan.name,
626
+ version,
627
+ components: done,
628
+ ...(ctx.parent !== undefined && { dependencyOf: ctx.parent }),
629
+ },
630
+ });
631
+ });
632
+ return installId;
633
+ }
634
+ catch (e) {
635
+ const failure = e instanceof InstallFailure ? e : undefined;
636
+ const code = failure ? failure.code : e instanceof ApiError ? e.code : "internal";
637
+ const rollbackErrors = await this.rollback(orgId, actor, installId, done, opts);
638
+ await this.d.db.transaction().execute(async (t) => {
639
+ await t
640
+ .updateTable("plugin_installs")
641
+ .set({ status: "failed", error: JSON.stringify({ step, code }) })
642
+ .where("id", "=", installId)
643
+ .execute();
644
+ await audit(t, {
645
+ actorAccountId: actor,
646
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
647
+ orgId,
648
+ action: "plugin.org_install_failed",
649
+ target: installId,
650
+ detail: {
651
+ itemId,
652
+ name: plan.name,
653
+ version,
654
+ step,
655
+ code,
656
+ rolledBack: done,
657
+ rollbackErrors,
658
+ ...(ctx.parent !== undefined && { dependencyOf: ctx.parent }),
659
+ },
660
+ });
661
+ });
662
+ throw new InstallFailure(failure ? failure.step : step, failure ? failure.cause : e, failure ? failure.dependency : [], rollbackErrors.length === 0 && (failure?.rolledBack ?? true));
663
+ }
664
+ }
665
+ /**
666
+ * The extension to enable: the plugin's own, when it has one. Not loaded on this replica ->
667
+ * `catalog.not_deployed`; not yet enabled in this org while its own catalog item is disabled ->
668
+ * `catalog.item_disabled` (a disabled item is a master switch, Ruling M-5); already enabled is
669
+ * unaffected.
670
+ */
671
+ async planExtension(orgId, manifest) {
672
+ if (!manifest.extension)
673
+ return null;
674
+ const name = manifest.name;
675
+ if (!this.d.host.manifests().some((m) => m.name === name)) {
676
+ throw new ApiError("catalog.not_deployed", `The extension of plugin ${name} is not deployed; ask the enterprise admin to install and enable it`);
677
+ }
678
+ const enabled = await isExtensionEnabled(this.d.db, orgId, name);
679
+ if (!enabled && (await disabledPluginItems(this.d.db, [name])).length > 0) {
680
+ throw pluginItemDisabled([name]);
681
+ }
682
+ return name;
683
+ }
684
+ /** The input only names the plugin's template ids, and an extension config needs an extension. */
685
+ checkInput(manifest, templates, input, at) {
686
+ const ids = new Set(templates.map((c) => c.id));
687
+ for (const k of Object.keys(input.connectors ?? {})) {
688
+ if (!ids.has(k))
689
+ throw fail(`${at}connectors.${k}`, "No such connector template in this plugin");
690
+ }
691
+ if (input.extensionConfig !== undefined && !manifest.extension) {
692
+ throw fail(`${at}extensionConfig`, "This plugin has no extension");
693
+ }
694
+ }
695
+ /**
696
+ * Undoes the components this install created, in reverse order (the role is not re-checked: it
697
+ * undoes this install's own changes); returns the ones that failed. The component rows of undone
698
+ * (or already gone) components are deleted in the same transaction, so cleaning up the failed
699
+ * install later only handles what failed to undo or was kept, and does not undo again an extension
700
+ * an org admin re-enabled by hand or a skill version a user uploaded since.
701
+ */
702
+ async rollback(orgId, actor, installId, done, opts) {
703
+ const errors = [];
704
+ for (const c of [...done].reverse()) {
705
+ if (!c.created && c.skillVersion === null)
706
+ continue;
707
+ try {
708
+ const disabled = [];
709
+ await this.d.db.transaction().execute(async (t) => {
710
+ await lockOrg(t, orgId);
711
+ if (c.kind === "plugin") {
712
+ // A dependency install this install created: uninstalled by the usual rules
713
+ const sub = { removed: [], kept: [] };
714
+ await this.uninstallIn(t, orgId, actor, c.ref, {
715
+ report: sub,
716
+ removing: new Set([installId]),
717
+ from: installId,
718
+ authorize: false,
719
+ reason: "plugin_install_rollback",
720
+ opts,
721
+ });
722
+ disabled.push(...sub.removed.filter((r) => r.kind === "extension").map((r) => r.ref));
723
+ }
724
+ else {
725
+ // Another enabled extension depends on it (only it provides what that one needs in this
726
+ // org): kept (its component row too), so the other does not lose its dependency
727
+ if (c.kind === "extension" &&
728
+ (await this.d.orgConfigs.enabledDependents(t, orgId, c.ref)).length > 0) {
729
+ return;
730
+ }
731
+ const removed = await this.removeComponent(t, orgId, actor, c, "plugin_install_rollback");
732
+ if (removed && c.kind === "extension")
733
+ disabled.push(c.ref);
734
+ }
735
+ await t
736
+ .deleteFrom("plugin_install_components")
737
+ .where("install_id", "=", installId)
738
+ .where("kind", "=", c.kind)
739
+ .where("ref", "=", c.ref)
740
+ .execute();
741
+ });
742
+ for (const p of disabled)
743
+ await this.d.orgConfigs.afterDisable(orgId, p);
744
+ }
745
+ catch (e) {
746
+ errors.push({ kind: c.kind, ref: c.ref });
747
+ this.d.log?.warn("plugin install rollback step failed", {
748
+ orgId,
749
+ kind: c.kind,
750
+ ref: c.ref,
751
+ error: e instanceof Error ? e.message : String(e),
752
+ });
753
+ }
754
+ }
755
+ return errors;
756
+ }
757
+ /** Removes one component (one already gone is no error); returns whether anything changed. */
758
+ async removeComponent(t, orgId, actor, c, reason) {
759
+ if (c.kind === "extension") {
760
+ return this.d.orgConfigs.disableIn(t, orgId, c.ref, actor, reason);
761
+ }
762
+ if (c.kind === "connector") {
763
+ const live = await t
764
+ .selectFrom("connectors")
765
+ .select("id")
766
+ .where("id", "=", c.ref)
767
+ .where("org_id", "=", orgId)
768
+ .where("deleted_at", "is", null)
769
+ .executeTakeFirst();
770
+ if (!live)
771
+ return false;
772
+ await this.d.connectors.removeIn(t, orgId, c.ref, actor, undefined, reason);
773
+ return true;
774
+ }
775
+ if (c.kind === "plugin")
776
+ return false;
777
+ if (c.kind === "agent_template") {
778
+ if (c.created)
779
+ return softDeleteTemplateIn(t, orgId, c.ref);
780
+ if (c.skillVersion !== null) {
781
+ return revertTemplateVersionIn(t, orgId, c.ref, c.skillVersion);
782
+ }
783
+ return false;
784
+ }
785
+ if (c.created)
786
+ return this.d.skills.softDeleteIn(t, orgId, c.ref, actor, reason);
787
+ if (c.skillVersion !== null) {
788
+ return this.d.skills.revertVersionIn(t, orgId, c.ref, c.skillVersion, actor, reason);
789
+ }
790
+ return false;
791
+ }
792
+ /**
793
+ * Uninstall (the plugin's component actions, see `requiredActions`, re-checked under the org row
794
+ * lock, one transaction). Removes only components with created=true:
795
+ * - a skill is kept when an agent selects it (used_by_agents) or another plugin install in this
796
+ * org uses it too (used_by_other_install);
797
+ * - a connector is kept when an agent references it (used_by_agents);
798
+ * - an extension is kept when another plugin install uses it too (used_by_other_install), a
799
+ * connector still uses a connector it provides (used_by_connectors), or another enabled
800
+ * extension depends on it (used_by_plugins);
801
+ * - a dependency install (`plugin`) is uninstalled by these same rules, after the plugin's own
802
+ * components; it is kept when another install outside this uninstall depends on it
803
+ * (used_by_other_install);
804
+ * - components that existed before are kept (pre_existing).
805
+ * When kept for used_by_other_install, ownership passes to the oldest remaining install (its
806
+ * component row becomes created=true), so the uninstall order doesn't change the end result.
807
+ * Uninstalling a dependency another install still uses removes nothing and reports it kept
808
+ * (`plugin`, used_by_other_install). Deletes the install rows and audits `plugin.org_uninstalled`
809
+ * per install (detail has removed / kept). Can be uninstalled: installed; failed (already rolled
810
+ * back: clears the row and leftovers); installing for longer than `catalog.staleInstallMinutes`
811
+ * (a replica crashed midway) - the last two are cleaned up as a rollback (versions added to
812
+ * existing skills are reverted too). Still installing -> `plugin.install_in_progress`.
813
+ */
814
+ async uninstall(orgId, actor, installId, opts = {}) {
815
+ if (!UUID_RE.test(installId))
816
+ throw installNotFound();
817
+ const report = { removed: [], kept: [] };
818
+ await this.d.db.transaction().execute(async (t) => {
819
+ const outcome = await this.uninstallIn(t, orgId, actor, installId, {
820
+ report,
821
+ removing: new Set(),
822
+ authorize: true,
823
+ opts,
824
+ });
825
+ if (outcome === "missing")
826
+ throw installNotFound();
827
+ if (outcome === "kept") {
828
+ report.kept.push({ kind: "plugin", ref: installId, reason: "used_by_other_install" });
829
+ }
830
+ });
831
+ for (const r of report.removed) {
832
+ if (r.kind === "extension")
833
+ await this.d.orgConfigs.afterDisable(orgId, r.ref);
834
+ }
835
+ return report;
836
+ }
837
+ /**
838
+ * The oldest other live install (not in `removing`) that has component `kind` / `ref`, or
839
+ * undefined.
840
+ */
841
+ async heir(t, orgId, removing, kind, ref) {
842
+ let q = t
843
+ .selectFrom("plugin_install_components as c")
844
+ .innerJoin("plugin_installs as a", "a.id", "c.install_id")
845
+ .select("a.id")
846
+ .where("a.org_id", "=", orgId)
847
+ .where("a.status", "in", ["installing", "installed", "upgrading"])
848
+ .where("c.kind", "=", kind)
849
+ .where("c.ref", "=", ref);
850
+ if (removing.size > 0)
851
+ q = q.where("a.id", "not in", [...removing]);
852
+ const row = await q.orderBy("a.installed_at").orderBy("a.id").executeTakeFirst();
853
+ return row?.id;
854
+ }
855
+ /** Passes ownership of a component to `heir` (its component row becomes created=true). */
856
+ async handOver(t, heir, kind, ref) {
857
+ await t
858
+ .updateTable("plugin_install_components")
859
+ .set({ created: true })
860
+ .where("install_id", "=", heir)
861
+ .where("kind", "=", kind)
862
+ .where("ref", "=", ref)
863
+ .execute();
864
+ }
865
+ /**
866
+ * Uninstalls one install in the caller's transaction (see {@link uninstall}); dependency installs
867
+ * it created recurse. Its own components' outcomes go into `ctx.report` (and its audit); the
868
+ * outcome of the install itself is returned for the caller to report: `missing`, `kept` (another
869
+ * install outside this uninstall depends on it) or `removed`.
870
+ */
871
+ async uninstallIn(t, orgId, actor, installId, ctx) {
872
+ const { removing } = ctx;
873
+ if (removing.has(installId))
874
+ return "removed";
875
+ // This install's own slice of the report (its audit), also appended to the shared report
876
+ const mine = { removed: [], kept: [] };
877
+ const addRemoved = (x) => {
878
+ mine.removed.push(x);
879
+ ctx.report.removed.push(x);
880
+ };
881
+ const addKept = (x) => {
882
+ mine.kept.push(x);
883
+ ctx.report.kept.push(x);
884
+ };
885
+ const staleMinutes = this.d.staleInstallMinutes();
886
+ if (ctx.authorize) {
887
+ const target = await t
888
+ .selectFrom("plugin_installs as a")
889
+ .innerJoin("catalog_item_versions as v", (j) => j.onRef("v.item_id", "=", "a.item_id").onRef("v.version", "=", "a.version"))
890
+ .select("v.manifest")
891
+ .where("a.id", "=", installId)
892
+ .where("a.org_id", "=", orgId)
893
+ .executeTakeFirst();
894
+ await lockOrgForActions(t, orgId, actor, target ? requiredActions(target.manifest) : ALL_ACTIONS);
895
+ }
896
+ const row = await t
897
+ .selectFrom("plugin_installs as a")
898
+ .innerJoin("catalog_items as i", "i.id", "a.item_id")
899
+ .select([
900
+ "a.id",
901
+ "a.item_id",
902
+ "a.version",
903
+ "a.status",
904
+ "i.name",
905
+ sql `a.installed_at < now() - make_interval(mins => ${staleMinutes})`.as("stale"),
906
+ sql `coalesce((a.upgrade->>'startedAt')::timestamptz < now() - make_interval(mins => ${staleMinutes}), false)`.as("upgradeStale"),
907
+ "a.upgrade",
908
+ ])
909
+ .where("a.id", "=", installId)
910
+ .where("a.org_id", "=", orgId)
911
+ .forUpdate("a")
912
+ .executeTakeFirst();
913
+ if (!row)
914
+ return "missing";
915
+ // Another install outside this uninstall depends on it: kept (a dependency uninstall hands
916
+ // ownership over)
917
+ const user = await this.heir(t, orgId, new Set([...removing, installId]), "plugin", installId);
918
+ if (user !== undefined) {
919
+ if (ctx.from !== undefined)
920
+ await this.handOver(t, user, "plugin", installId);
921
+ return "kept";
922
+ }
923
+ if (row.status === "uninstalling" ||
924
+ (row.status === "installing" && !row.stale) ||
925
+ (row.status === "upgrading" && !row.upgradeStale)) {
926
+ throw new ApiError("plugin.install_in_progress", `This plugin install is still in progress; it can be cleaned up after ${staleMinutes} minutes`);
927
+ }
928
+ if (row.status === "upgrading") {
929
+ // An upgrade interrupted midway (a replica crashed): undone first, back at the old version
930
+ const undone = await this.undoUpgradeIn(t, orgId, actor, installId, journalEntries(row.upgrade), "plugin_upgrade_cleanup");
931
+ for (const x of undone)
932
+ addRemoved(x);
933
+ }
934
+ removing.add(installId);
935
+ // failed (already rolled back) and a stale installing (a replica crashed midway) are cleaned up
936
+ // as a rollback
937
+ const rollbackMode = row.status !== "installed" && row.status !== "upgrading";
938
+ const comps = await t
939
+ .selectFrom("plugin_install_components")
940
+ .select(["kind", "ref", "created", "skill_version"])
941
+ .where("install_id", "=", installId)
942
+ .orderBy("kind")
943
+ .orderBy("ref")
944
+ .execute();
945
+ // Skills and agent templates -> connectors -> extensions (in reverse requires order: dependents before providers)
946
+ // -> dependency installs (created ones first)
947
+ const extensionOrder = this.uninstallExtensionOrder(comps.filter((c) => c.kind === "extension").map((c) => c.ref));
948
+ const rank = (c) => c.kind === "skill" || c.kind === "agent_template"
949
+ ? 0
950
+ : c.kind === "connector"
951
+ ? 1
952
+ : c.kind === "extension"
953
+ ? 2 + extensionOrder.indexOf(c.ref)
954
+ : 1000 + (c.created ? 0 : 1);
955
+ comps.sort((a, b) => rank(a) - rank(b));
956
+ const agentRefs = await this.agentRefs(t, orgId);
957
+ for (const c of comps) {
958
+ const keep = (reason) => addKept({ kind: c.kind, ref: c.ref, reason });
959
+ let created = c.created;
960
+ if (c.kind === "plugin") {
961
+ // Re-read: an earlier dependency uninstall may have handed it over, or removed it
962
+ const now = await t
963
+ .selectFrom("plugin_install_components")
964
+ .select("created")
965
+ .where("install_id", "=", installId)
966
+ .where("kind", "=", "plugin")
967
+ .where("ref", "=", c.ref)
968
+ .executeTakeFirst();
969
+ const live = await t
970
+ .selectFrom("plugin_installs")
971
+ .select("id")
972
+ .where("id", "=", c.ref)
973
+ .where("org_id", "=", orgId)
974
+ .executeTakeFirst();
975
+ if (!now || !live)
976
+ continue;
977
+ created = now.created;
978
+ }
979
+ const comp = { kind: c.kind, ref: c.ref, created, skillVersion: c.skill_version };
980
+ if (!created) {
981
+ // Rollback cleanup: a version this install added to an existing skill is reverted; other
982
+ // pre-existing components are kept
983
+ if (rollbackMode &&
984
+ (c.kind === "skill" || c.kind === "agent_template") &&
985
+ c.skill_version !== null &&
986
+ (await this.removeComponent(t, orgId, actor, comp, "plugin_install_cleanup"))) {
987
+ addRemoved({ kind: c.kind, ref: c.ref });
988
+ continue;
989
+ }
990
+ keep("pre_existing");
991
+ continue;
992
+ }
993
+ if (c.kind === "plugin") {
994
+ const outcome = await this.uninstallIn(t, orgId, actor, c.ref, { ...ctx, from: installId });
995
+ if (outcome === "removed")
996
+ addRemoved({ kind: "plugin", ref: c.ref });
997
+ else if (outcome === "kept")
998
+ keep("used_by_other_install");
999
+ continue;
1000
+ }
1001
+ const reason = rollbackMode ? "plugin_install_cleanup" : (ctx.reason ?? "plugin_uninstall");
1002
+ const outcome = await this.decideRemovalIn(t, orgId, actor, comp, removing, agentRefs, reason);
1003
+ if (outcome === "removed")
1004
+ addRemoved({ kind: c.kind, ref: c.ref });
1005
+ else if (outcome !== "gone")
1006
+ keep(outcome);
1007
+ }
1008
+ await t.deleteFrom("plugin_installs").where("id", "=", installId).execute();
1009
+ await audit(t, {
1010
+ actorAccountId: actor,
1011
+ ...(ctx.opts.actorTokenId !== undefined && { actorTokenId: ctx.opts.actorTokenId }),
1012
+ orgId,
1013
+ action: "plugin.org_uninstalled",
1014
+ target: installId,
1015
+ detail: {
1016
+ itemId: row.item_id,
1017
+ name: row.name,
1018
+ version: row.version,
1019
+ status: row.status,
1020
+ removed: mine.removed,
1021
+ kept: mine.kept,
1022
+ ...(ctx.from !== undefined && { dependencyOf: ctx.from }),
1023
+ },
1024
+ });
1025
+ return "removed";
1026
+ }
1027
+ /**
1028
+ * The uninstall rules for one component an install created (a skill, connector or extension;
1029
+ * dependency installs recurse through {@link uninstallIn}), in the caller's transaction: kept
1030
+ * when an agent uses the skill / connector (`used_by_agents`), when another live install outside
1031
+ * `removing` has it (`used_by_other_install`, ownership handed over to the oldest one, so the
1032
+ * uninstall order doesn't change the result), when a connector still uses the extension's
1033
+ * connector kind (`used_by_connectors`) or another enabled extension depends on it
1034
+ * (`used_by_plugins`); otherwise removed (`removed`, or `gone` when it no longer existed). Used
1035
+ * by uninstall and by an upgrade's removals.
1036
+ */
1037
+ async decideRemovalIn(t, orgId, actor, c, removing, agentRefs, reason) {
1038
+ if (c.kind === "skill" && agentRefs.skills.has(c.ref))
1039
+ return "used_by_agents";
1040
+ if (c.kind === "agent_template" && agentRefs.templates.has(c.ref))
1041
+ return "used_by_agents";
1042
+ if (c.kind === "connector" && agentRefs.connectors.has(c.ref))
1043
+ return "used_by_agents";
1044
+ if (c.kind !== "connector") {
1045
+ const heir = await this.heir(t, orgId, removing, c.kind, c.ref);
1046
+ if (heir !== undefined) {
1047
+ await this.handOver(t, heir, c.kind, c.ref);
1048
+ return "used_by_other_install";
1049
+ }
1050
+ }
1051
+ if (c.kind === "extension") {
1052
+ const kinds = this.connectorKindsOf(c.ref);
1053
+ if (kinds.length > 0) {
1054
+ const used = await t
1055
+ .selectFrom("connectors")
1056
+ .select("id")
1057
+ .where("org_id", "=", orgId)
1058
+ .where("deleted_at", "is", null)
1059
+ .where("connector", "in", kinds)
1060
+ .executeTakeFirst();
1061
+ if (used)
1062
+ return "used_by_connectors";
1063
+ }
1064
+ if ((await this.d.orgConfigs.enabledDependents(t, orgId, c.ref)).length > 0) {
1065
+ return "used_by_plugins";
1066
+ }
1067
+ }
1068
+ return (await this.removeComponent(t, orgId, actor, c, reason)) ? "removed" : "gone";
1069
+ }
1070
+ /** The order extensions are uninstalled in: the reverse of the install (requires) order; extensions not loaded on this replica last. */
1071
+ uninstallExtensionOrder(refs) {
1072
+ const loaded = new Map(this.d.host.manifests().map((m) => [m.name, m]));
1073
+ const known = refs.filter((r) => loaded.has(r)).map((r) => loaded.get(r));
1074
+ return [...orderByRequires(known).reverse(), ...refs.filter((r) => !loaded.has(r))];
1075
+ }
1076
+ /** The org's plugin installs (failed ones included), newest first; `installId` picks one. */
1077
+ async list(orgId, installId) {
1078
+ let q = this.d.db
1079
+ .selectFrom("plugin_installs as a")
1080
+ .innerJoin("catalog_items as i", "i.id", "a.item_id")
1081
+ .select([
1082
+ "a.id",
1083
+ "a.item_id",
1084
+ "i.name",
1085
+ "a.version",
1086
+ "a.status",
1087
+ "a.installed_by",
1088
+ "a.installed_at",
1089
+ "a.error",
1090
+ ])
1091
+ .where("a.org_id", "=", orgId);
1092
+ if (installId !== undefined)
1093
+ q = q.where("a.id", "=", installId);
1094
+ const rows = await q.orderBy("a.installed_at", "desc").orderBy("a.id").execute();
1095
+ if (rows.length === 0)
1096
+ return [];
1097
+ const info = await upgradeInfo(this.d.db, this.d.host, rows.map((r) => ({ itemId: r.item_id, name: r.name, version: r.version })));
1098
+ const comps = await this.d.db
1099
+ .selectFrom("plugin_install_components")
1100
+ .select(["install_id", "kind", "ref", "created", "skill_version", "dropped"])
1101
+ .where("install_id", "in", rows.map((r) => r.id))
1102
+ .execute();
1103
+ return rows.map((r) => ({
1104
+ id: r.id,
1105
+ itemId: r.item_id,
1106
+ name: r.name,
1107
+ version: r.version,
1108
+ status: r.status,
1109
+ installedBy: r.installed_by,
1110
+ installedAt: r.installed_at.toISOString(),
1111
+ error: r.error ?? null,
1112
+ components: comps
1113
+ .filter((c) => c.install_id === r.id)
1114
+ .map((c) => ({
1115
+ kind: c.kind,
1116
+ ref: c.ref,
1117
+ created: c.created,
1118
+ skillVersion: c.skill_version,
1119
+ dropped: c.dropped,
1120
+ })),
1121
+ upgradeAvailable: info.get(`${r.item_id}@${r.version}`)?.upgradeAvailable ?? null,
1122
+ deploymentPending: info.get(`${r.item_id}@${r.version}`)?.deploymentPending ?? false,
1123
+ }));
1124
+ }
1125
+ /**
1126
+ * Undoes the steps of an upgrade in the caller's transaction (the caller holds the org row lock),
1127
+ * in reverse order: components it created are removed (a dependency install by the uninstall
1128
+ * rules, an extension another enabled extension depends on is kept), the ledger rows it added
1129
+ * are deleted, and versions it added to skills are reverted (`revertVersionIn` reverts exactly
1130
+ * one version, and an upgrade adds at most one per skill). Returns the components removed (an
1131
+ * extension among them needs `afterDisable` once committed). Used when an upgrade fails and when
1132
+ * an interrupted one is cleaned up.
1133
+ */
1134
+ async undoUpgradeIn(t, orgId, actor, installId, steps, reason) {
1135
+ const removed = [];
1136
+ const dropRow = (kind, ref) => t
1137
+ .deleteFrom("plugin_install_components")
1138
+ .where("install_id", "=", installId)
1139
+ .where("kind", "=", kind)
1140
+ .where("ref", "=", ref)
1141
+ .execute();
1142
+ for (const s of [...steps].reverse()) {
1143
+ if (s.op === "skill_version") {
1144
+ await this.d.skills.revertVersionIn(t, orgId, s.ref, s.version, actor, reason);
1145
+ continue;
1146
+ }
1147
+ if (s.op === "template_version") {
1148
+ await revertTemplateVersionIn(t, orgId, s.ref, s.version);
1149
+ continue;
1150
+ }
1151
+ if (s.op === "recorded") {
1152
+ await dropRow(s.kind, s.ref);
1153
+ continue;
1154
+ }
1155
+ if (s.kind === "plugin") {
1156
+ const sub = { removed: [], kept: [] };
1157
+ await dropRow(s.kind, s.ref);
1158
+ const outcome = await this.uninstallIn(t, orgId, actor, s.ref, {
1159
+ report: sub,
1160
+ removing: new Set([installId]),
1161
+ from: installId,
1162
+ authorize: false,
1163
+ reason,
1164
+ opts: {},
1165
+ });
1166
+ removed.push(...sub.removed.filter((r) => r.kind === "extension"));
1167
+ if (outcome === "removed")
1168
+ removed.push({ kind: "plugin", ref: s.ref });
1169
+ continue;
1170
+ }
1171
+ if (s.kind === "extension" &&
1172
+ (await this.d.orgConfigs.enabledDependents(t, orgId, s.ref)).length > 0) {
1173
+ // Another enabled extension needs it: kept, and stays this install's component
1174
+ continue;
1175
+ }
1176
+ const c = { kind: s.kind, ref: s.ref, created: true, skillVersion: null };
1177
+ if (await this.removeComponent(t, orgId, actor, c, reason)) {
1178
+ removed.push({ kind: s.kind, ref: s.ref });
1179
+ }
1180
+ await dropRow(s.kind, s.ref);
1181
+ }
1182
+ return removed;
1183
+ }
1184
+ /** The connector kinds an extension provides (the `connector@1` implementations loaded on this replica). */
1185
+ connectorKindsOf(plugin) {
1186
+ return this.d.host
1187
+ .list("connector")
1188
+ .filter((e) => e.plugin === plugin)
1189
+ .map((e) => e.impl?.kind)
1190
+ .filter((k) => typeof k === "string");
1191
+ }
1192
+ /**
1193
+ * The connectors, org skills and agent templates (`subagents.templates`) referenced by the
1194
+ * current definitions of the org's live agents.
1195
+ */
1196
+ async agentRefs(t, orgId) {
1197
+ const rows = await t
1198
+ .selectFrom("agents as a")
1199
+ .innerJoin("agent_versions as v", (j) => j.onRef("v.agent_id", "=", "a.id").onRef("v.version", "=", "a.current_version"))
1200
+ .select("v.definition")
1201
+ .where("a.org_id", "=", orgId)
1202
+ .where("a.deleted_at", "is", null)
1203
+ .execute();
1204
+ const connectors = new Set();
1205
+ const skills = new Set();
1206
+ const templates = new Set();
1207
+ for (const r of rows) {
1208
+ const def = r.definition;
1209
+ for (const ref of def?.tools ?? []) {
1210
+ const n = normalizeToolRef(ref);
1211
+ if (n?.kind === "connector" && typeof n.connectorId === "string") {
1212
+ connectors.add(n.connectorId);
1213
+ }
1214
+ }
1215
+ for (const s of def?.skills?.orgSkills ?? [])
1216
+ if (typeof s === "string")
1217
+ skills.add(s);
1218
+ for (const x of def?.subagents?.templates ?? [])
1219
+ if (typeof x === "string")
1220
+ templates.add(x);
1221
+ }
1222
+ return { connectors, skills, templates };
1223
+ }
1224
+ }
1225
+ /**
1226
+ * The skill names of a catalog version, read from each skill's SKILL.md in storage (the names are
1227
+ * not in plugin.json). Used by install planning and the upgrade preview.
1228
+ */
1229
+ export async function catalogSkillNames(storage, row, manifest) {
1230
+ const out = [];
1231
+ for (const s of manifest.skills ?? []) {
1232
+ const dir = packagePath(s.path);
1233
+ const entry = `${dir}/${SKILL_ENTRY_FILE}`;
1234
+ const files = await readCatalogFiles(storage, row.object_prefix, row.files.filter((f) => f.path === entry), dir);
1235
+ const file = files[0];
1236
+ const parsed = file
1237
+ ? parseSkillMd(new TextDecoder().decode(file.data), { invocationKeys: false })
1238
+ : undefined;
1239
+ if (!parsed || "errors" in parsed) {
1240
+ throw new ApiError("internal", `Stored skill ${dir} has no valid ${SKILL_ENTRY_FILE}`);
1241
+ }
1242
+ out.push(parsed.name);
1243
+ }
1244
+ return out;
1245
+ }
1246
+ /**
1247
+ * The skill conflicts of a plan, depth first in install order (dependencies before the plugin).
1248
+ * Exported for the upgrade preview.
1249
+ */
1250
+ export async function planSkillConflicts(db, orgId, plan) {
1251
+ const out = [];
1252
+ const seen = new Set();
1253
+ /** Skill name -> the plugin of this plan that brings it first. */
1254
+ const planned = new Map();
1255
+ const visit = async (p) => {
1256
+ for (const d of p.dependencies)
1257
+ if (d.plan)
1258
+ await visit(d.plan);
1259
+ for (const c of await skillConflicts(db, orgId, p.itemId, p.skillNames)) {
1260
+ if (!seen.has(c.skill))
1261
+ out.push(c);
1262
+ seen.add(c.skill);
1263
+ }
1264
+ for (const skill of p.skillNames) {
1265
+ const first = planned.get(skill);
1266
+ if (first === undefined)
1267
+ planned.set(skill, p);
1268
+ else if (first.itemId !== p.itemId && !seen.has(skill)) {
1269
+ out.push({ skill, source: { kind: "plugin", name: first.name, version: first.version } });
1270
+ seen.add(skill);
1271
+ }
1272
+ }
1273
+ };
1274
+ await visit(plan);
1275
+ return out;
1276
+ }
1277
+ const COMPONENT_KINDS = new Set([
1278
+ "extension",
1279
+ "connector",
1280
+ "skill",
1281
+ "plugin",
1282
+ "agent_template",
1283
+ ]);
1284
+ /** The journaled steps of `plugin_installs.upgrade` (malformed entries are skipped). */
1285
+ export function journalEntries(upgrade) {
1286
+ const done = upgrade?.done;
1287
+ if (!Array.isArray(done))
1288
+ return [];
1289
+ const out = [];
1290
+ for (const e of done) {
1291
+ if (typeof e !== "object" || e === null || typeof e.ref !== "string")
1292
+ continue;
1293
+ if ((e.op === "skill_version" || e.op === "template_version") &&
1294
+ typeof e.version === "number") {
1295
+ out.push({ op: e.op, ref: e.ref, version: e.version });
1296
+ }
1297
+ else if ((e.op === "created" || e.op === "recorded") &&
1298
+ typeof e.kind === "string" &&
1299
+ COMPONENT_KINDS.has(e.kind)) {
1300
+ out.push({ op: e.op, kind: e.kind, ref: e.ref });
1301
+ }
1302
+ }
1303
+ return out;
1304
+ }
1305
+ /** The org actions a plan needs: the union over the plugins it installs (spec §10). */
1306
+ export function planActions(plan) {
1307
+ const out = new Set(requiredActions(plan.manifest));
1308
+ for (const d of plan.dependencies) {
1309
+ if (d.plan)
1310
+ for (const a of planActions(d.plan))
1311
+ out.add(a);
1312
+ }
1313
+ return [...out];
1314
+ }
1315
+ const alreadyInstalled = () => new ApiError("plugin.already_installed", "This plugin is already installed in this organization; uninstall it before installing again");
1316
+ /**
1317
+ * Orders extensions by `requires`: when an org-scoped capability an extension needs is provided by
1318
+ * another extension in the set, the provider comes first; a cycle keeps the given order.
1319
+ */
1320
+ function orderByRequires(set) {
1321
+ const orgScoped = (ref) => EXTENSION_POINTS[parseExtensionPoint(ref).name]?.orgScoped === true;
1322
+ const placed = [];
1323
+ const rest = [...set];
1324
+ while (rest.length > 0) {
1325
+ const idx = rest.findIndex((m) => (m.requires ?? []).filter(orgScoped).every((ref) => {
1326
+ const providers = rest.filter((o) => o.name !== m.name && o.provides.includes(ref));
1327
+ return providers.length === 0;
1328
+ }));
1329
+ const [next] = rest.splice(idx === -1 ? 0 : idx, 1);
1330
+ placed.push(next.name);
1331
+ }
1332
+ return placed;
1333
+ }