@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,1069 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { PLUGIN_MANIFEST_FILE, parseSkillMd, SDK_VERSION, SKILL_ENTRY_FILE, } from "@capybara-hq/plugin-sdk";
3
+ import { sql } from "kysely";
4
+ import semver from "semver";
5
+ import { ApiError } from "../errors.js";
6
+ import { writeSnapshot } from "../extensions/approvals.js";
7
+ import { unrunMigrations } from "../extensions/migrate-lock.js";
8
+ import { ExtensionPackageError, permissionSetOfPackage, readExtensionPackage, } from "../extensions/package.js";
9
+ import { diffPermissions, permissionKeys, WARNINGS_KEY_PREFIX, } from "../extensions/permission-set.js";
10
+ import { reviewHash as extensionReviewHash, migrationWarnings, permissionGroups, permissionKeyItems, } from "../extensions/permission-view.js";
11
+ import { audit } from "../identity/audit.js";
12
+ import { lockEnterpriseAdmins, lockEnterpriseAdminsAndAuthorize, } from "../identity/enterprise-admin.js";
13
+ import { packPackageTgz } from "../packages/tgz.js";
14
+ import { checkSkillFiles, SkillArchiveError } from "../skills/package.js";
15
+ import { endpointCredentialIssues } from "../tools/endpoint.js";
16
+ import { canonicalJson } from "../tools/registry.js";
17
+ import { assertPluginNameOwner, catalogObjectPrefix, itemView, permissionsMismatch, precheckCatalogImport, putCatalogFiles, templateProblems, writeCatalogVersion, } from "./catalog.service.js";
18
+ import { CLAUDE_PLUGIN_FILE, claudeContentDigest, } from "./claude/manifest.js";
19
+ import { CLAUDE_MCP_CONNECTOR } from "./claude/mcp.js";
20
+ import { item as reportItem, sortItems, } from "./claude/report.js";
21
+ import { isClaudePluginTree, translateClaudePlugin } from "./claude/translate.js";
22
+ import { PluginPackageError, packagePath, readPluginFiles, readPluginPackage, } from "./plugin-package.js";
23
+ import { maxSatisfying, satisfiesRange } from "./versions.js";
24
+ /**
25
+ * Enterprise import of a plugin (package ecosystem spec §4.3): one review for every plugin, whatever
26
+ * it contains (an extension, skills, connector templates, dependencies), confirmed once.
27
+ *
28
+ * prepare: the tgz is always hostile input, and **nothing is written until every check passes**
29
+ * (F-AD): `readPluginPackage` (plugin.json, package.json, listed / scanned skills and templates,
30
+ * icons; a pre-0.1.0 tgz is `catalog.legacy_format`), the extension's module graph and migrations
31
+ * (`readExtensionPackage`), the extension's deployment state (config-declared names are
32
+ * refused; name ownership; a downgrade to a version with unrun migrations is refused), each skill by
33
+ * the skill package rules, each connector template against the connector loaded on this replica (or
34
+ * statically when the connector is provided by the plugin's own extension or by a dependency), the
35
+ * catalog name / version conflicts and the actor still an enterprise admin. Dependencies are only
36
+ * resolved against the imported catalog versions for the review (an unresolved one does not block
37
+ * the import, spec §4.1); they are never fetched. Only then is the extension handed to
38
+ * `ExtensionInstalls.ingest` (source `catalog`), the skill / template / icon files written to object
39
+ * storage, a staging row written (24 hours) and `catalog.plugin_staged` audited.
40
+ *
41
+ * confirm: `reviewHash` must match the staging; in one transaction it deletes the staging row
42
+ * (confirm works once), locks the extension name and recomputes its deployment state (installed /
43
+ * upgraded / swapped after the review -> `plugin.review_stale`), writes the extension's permission
44
+ * snapshot when it is installed by this import (or upgraded with `deployExtension`; source web, audit
45
+ * `extension.permissions_approved` carrying the plugin), and creates the item or adds the version
46
+ * (`manifest` = the normalized plugin.json, `extension_sha256`, the files under the staging prefix).
47
+ * After the commit, with `deployExtension`, the plugin's own extension is installed on the
48
+ * deployment and enabled (`ExtensionAdminOps.install` + `enable`; each replica reconciles, no
49
+ * restart); a failure is returned, not thrown, and an upgrade that was not installed gets its old
50
+ * snapshot back.
51
+ *
52
+ * Expired staging is cleaned by the maintenance job `app-staging-cleanup`
53
+ * ({@link PluginImport.cleanupExpired}): it deletes the staging row and its object prefix; a stored
54
+ * extension package stays in the content-addressed `extension_packages` (the same as importing the
55
+ * same tgz again; without an install record referencing it it is never loaded).
56
+ */
57
+ /** How long a staged import is kept. */
58
+ export const PLUGIN_STAGING_TTL_MS = 24 * 3600_000;
59
+ /** Staging rows cleaned per batch. */
60
+ const CLEANUP_BATCH = 100;
61
+ /** Root license files, in the order they are looked for. */
62
+ const LICENSE_FILES = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE"];
63
+ /** The largest license text stored (bytes); a longer one is omitted (`licenseTruncated`). */
64
+ export const LICENSE_TEXT_MAX_BYTES = 65536;
65
+ /** The provenance of every import that is not from npm. */
66
+ const NOT_NPM = { provenance: { checked: false, reason: "not an npm package" } };
67
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
68
+ const notFound = () => new ApiError("plugin.staging_not_found", "This plugin import was not found or has expired (staged imports are kept for 24 hours); prepare it again");
69
+ const invalid = (problems) => new ApiError("catalog.invalid_manifest", `Invalid plugin package: ${problems[0] ?? "unknown problem"}`, problems.slice(0, 50));
70
+ const mismatch = (what) => new ApiError("marketplace.package_mismatch", `The downloaded package does not match the marketplace entry: ${what}`);
71
+ /**
72
+ * The API error of a plugin package that cannot be read in a catalog path: the pre-0.1.0 format ->
73
+ * `catalog.legacy_format`, anything else -> `catalog.invalid_manifest` (issues as details).
74
+ */
75
+ export function catalogPackageError(e) {
76
+ const legacy = e.code === "catalog.legacy_format" || e.code === "extension.legacy_format";
77
+ if (legacy)
78
+ return new ApiError("catalog.legacy_format", e.message, e.issues);
79
+ return invalid(e.issues);
80
+ }
81
+ /** Runs a package read, mapping its errors with {@link catalogPackageError}. */
82
+ async function readingPackage(f) {
83
+ try {
84
+ return await f();
85
+ }
86
+ catch (e) {
87
+ if (e instanceof PluginPackageError || e instanceof ExtensionPackageError) {
88
+ throw catalogPackageError(e);
89
+ }
90
+ throw e;
91
+ }
92
+ }
93
+ const fileOf = (path, data) => ({
94
+ path,
95
+ size: data.byteLength,
96
+ sha256: createHash("sha256").update(data).digest("hex"),
97
+ data,
98
+ });
99
+ /**
100
+ * The enterprise-admin re-check under the lock. `actor` null is the CLI (the operator of the
101
+ * deployment: no account to re-check, audits carry no actor), like `ExtensionInstalls.ingest`.
102
+ */
103
+ const authorizeActor = (trx, actor) => actor === null ? lockEnterpriseAdmins(trx) : lockEnterpriseAdminsAndAuthorize(trx, actor);
104
+ export class PluginImport {
105
+ d;
106
+ constructor(d) {
107
+ this.d = d;
108
+ }
109
+ /**
110
+ * Reads, checks and stages a plugin (see the file header). Returns the staging id and review.
111
+ * `fallback` (a marketplace archive): a tgz without plugin.json may be skills only, named,
112
+ * versioned and published by it (the version defaulting to `0.0.0-sha256.h<12>`, spec §5.3).
113
+ */
114
+ async prepare(actor, bytes, source, opts = {}) {
115
+ const maxBytes = this.d.config().packages.maxBytes;
116
+ const pkg = await readingPackage(() => readPluginPackage(bytes, {
117
+ maxBytes,
118
+ hostSdkVersion: SDK_VERSION,
119
+ ...(opts.fallback !== undefined && { fallback: opts.fallback }),
120
+ }));
121
+ const { fallback: _, ...rest } = opts;
122
+ return this.prepareContents(actor, pkg, { bytes, size: bytes.byteLength }, source, rest);
123
+ }
124
+ /**
125
+ * Checks and stages a plugin read from a git commit (`files` relative to the plugin root; see
126
+ * `git-source.ts`): the same checks as a tgz ({@link readPluginFiles}), with package.json
127
+ * optional (spec §4.1) and a skills-only tree without plugin.json named by `fallback` (Decision
128
+ * 3). A plugin with an extension needs package.json (its peerDependencies ranges are checked as
129
+ * for a tgz) and its build output; its files are packed into a deterministic tgz, which is what
130
+ * the package store keeps and the deployment installs. Returns what {@link prepare} returns.
131
+ */
132
+ async prepareGit(actor, files, source, opts) {
133
+ const { fallback: _, claude, ...rest } = opts;
134
+ if (!files.has(PLUGIN_MANIFEST_FILE) && isClaudePluginTree(files, claude)) {
135
+ // A Claude Code plugin (spec §8): translated, then read like any git plugin; the content
136
+ // digest is over the files as read and the entry's components, never the generated tree
137
+ // (Review Focus 5, claudeContentDigest)
138
+ const t = await readingPackage(async () => translateClaudePlugin(files, {
139
+ fallbackName: opts.fallback.name,
140
+ fallbackVersion: opts.fallback.version,
141
+ fallbackPublisher: opts.fallback.publisher,
142
+ ...(opts.fallback.entryVersion !== undefined && {
143
+ entryVersion: opts.fallback.entryVersion,
144
+ }),
145
+ ...(claude !== undefined && { overlay: claude }),
146
+ allowInsecureLoopback: this.d.config().marketplaces.allowInsecureLoopback,
147
+ }));
148
+ const translated = await readingPackage(async () => readPluginFiles(t.tree, {
149
+ hostSdkVersion: SDK_VERSION,
150
+ requirePackageJson: false,
151
+ invocationKeys: false,
152
+ }));
153
+ const contents = {
154
+ ...translated,
155
+ contentDigest: claudeContentDigest(files, claude, t.translatorRevision),
156
+ };
157
+ let size = 0;
158
+ for (const data of files.values())
159
+ size += data.byteLength;
160
+ return this.prepareContents(actor, contents, { size }, source, {
161
+ ...rest,
162
+ claude: t.report,
163
+ ...(t.repository !== undefined && { repository: t.repository }),
164
+ });
165
+ }
166
+ const contents = await readingPackage(async () => readPluginFiles(files, {
167
+ hostSdkVersion: SDK_VERSION,
168
+ requirePackageJson: false,
169
+ fallbackName: opts.fallback.name,
170
+ fallbackVersion: opts.fallback.version,
171
+ fallbackPublisher: opts.fallback.publisher,
172
+ }));
173
+ const next = {
174
+ ...rest,
175
+ // both manifests: the native one is read (spec §4.1) and the review says so
176
+ ...(files.has(PLUGIN_MANIFEST_FILE) &&
177
+ files.has(CLAUDE_PLUGIN_FILE) && { claudeManifestIgnored: true }),
178
+ };
179
+ if (contents.manifest.extension !== undefined) {
180
+ if (!files.has("package.json")) {
181
+ throw invalid([
182
+ "package.json not found: a plugin with an extension needs package.json with peerDependencies (@capybara-hq/plugin-sdk, zod), which are checked against this host",
183
+ ]);
184
+ }
185
+ return this.prepare(actor, await packPackageTgz(files), source, next);
186
+ }
187
+ let size = 0;
188
+ for (const data of files.values())
189
+ size += data.byteLength;
190
+ return this.prepareContents(actor, contents, { size }, source, next);
191
+ }
192
+ /**
193
+ * The checks and writes of prepare after the package was read. `tgz.bytes` is the package tgz
194
+ * (required with an extension: it is stored in the package store); `size` is recorded.
195
+ */
196
+ async prepareContents(actor, pkg, tgz, source, opts) {
197
+ const maxBytes = this.d.config().packages.maxBytes;
198
+ // 1. Every check (nothing written)
199
+ const m = pkg.manifest;
200
+ const packageName = packageNameOf(pkg);
201
+ const format = opts.claude !== undefined ? "claude-code" : "capybara";
202
+ this.checkExpected(pkg, opts.expect, format);
203
+ const bytes = tgz.bytes;
204
+ if (m.extension && bytes === undefined) {
205
+ throw new Error("an extension plugin is prepared from its package tgz");
206
+ }
207
+ const ext = m.extension && bytes !== undefined
208
+ ? await readingPackage(() => readExtensionPackage(bytes, { maxBytes, hostSdkVersion: SDK_VERSION }))
209
+ : undefined;
210
+ const content = this.checkContent(pkg, format);
211
+ const { existing, version } = await precheckCatalogImport(this.d.db, m.name, {
212
+ packageName,
213
+ version: m.version,
214
+ contentDigest: pkg.contentDigest,
215
+ ...(source.integrity !== undefined && { integrity: source.integrity }),
216
+ ...(source.detail !== undefined && { detail: source.detail }),
217
+ ...(source.marketplaceId !== undefined && { marketplaceId: source.marketplaceId }),
218
+ });
219
+ // The catalog name space is the extension name space: a declarative plugin cannot take a
220
+ // config-declared or another package's extension name either
221
+ this.assertNotDeclared(m.name);
222
+ await assertPluginNameOwner(this.d.db, m.name, packageName);
223
+ let extension;
224
+ if (ext) {
225
+ extension = await this.reviewExtension(ext, await this.extensionState(m.name, packageName));
226
+ await assertDowngradable(this.d.db, extension, ext.migrationSql);
227
+ }
228
+ if (content.files.length > 0)
229
+ this.d.storage.assertAvailable();
230
+ const src = {
231
+ kind: source.kind,
232
+ ref: source.ref,
233
+ ...(source.integrity !== undefined && { integrity: source.integrity }),
234
+ ...(source.marketplaceId !== undefined && { marketplaceId: source.marketplaceId }),
235
+ ...(source.detail !== undefined && { detail: source.detail }),
236
+ };
237
+ const dependencies = await this.resolveDependencies(m);
238
+ let translation;
239
+ if (opts.claude !== undefined) {
240
+ const added = [
241
+ ...dependencies
242
+ .filter((d) => d.resolved === null)
243
+ .map((d) => reportItem("dependency_not_imported", "plugin", d.name, {
244
+ detail: { version: d.range },
245
+ })),
246
+ ...content.notLoaded.map((id) => reportItem("connector_not_loaded", "mcpServer", id, {
247
+ path: content.templatePaths.get(id),
248
+ detail: { connector: CLAUDE_MCP_CONNECTOR.plugin },
249
+ })),
250
+ ];
251
+ translation = { ...opts.claude, items: sortItems([...opts.claude.items, ...added]) };
252
+ }
253
+ const license = licenseOf(pkg.files);
254
+ const review = {
255
+ name: m.name,
256
+ format,
257
+ ...(translation !== undefined && { translation }),
258
+ ...(opts.claudeManifestIgnored === true && { claudeManifestIgnored: true }),
259
+ ...(opts.repository !== undefined && { repository: opts.repository }),
260
+ ...(license.text !== undefined && { licenseText: license.text }),
261
+ ...(license.truncated && { licenseTruncated: true }),
262
+ version,
263
+ ...(version !== m.version && { pluginVersion: m.version }),
264
+ contentDigest: pkg.contentDigest,
265
+ packageName,
266
+ publisher: m.publisher,
267
+ description: m.description,
268
+ ...(m.displayName !== undefined && { displayName: m.displayName }),
269
+ ...(m.license !== undefined && { license: m.license }),
270
+ ...(m.homepage !== undefined && { homepage: m.homepage }),
271
+ ...(extension && { extension }),
272
+ skills: content.skills,
273
+ connectorTemplates: content.templates,
274
+ ...(content.agentTemplates.length > 0 && { agentTemplates: content.agentTemplates }),
275
+ dependencies,
276
+ source: src,
277
+ provenance: (opts.provenance ?? NOT_NPM).provenance,
278
+ ...(opts.provenance?.warning !== undefined && { provenanceWarning: opts.provenance.warning }),
279
+ sensitive: extension?.sensitive === true,
280
+ reviewHash: "",
281
+ };
282
+ review.reviewHash = pluginReviewHash(review, content.files);
283
+ // Re-check under the lock that the actor is still an enterprise admin before writing
284
+ await this.d.db.transaction().execute((trx) => authorizeActor(trx, actor));
285
+ // 2. Write: the extension into the package store, files into object storage, the staging row
286
+ const stagingId = randomUUID();
287
+ const itemId = existing?.id ?? randomUUID();
288
+ if (ext) {
289
+ const bootstrap = src.kind === "bootstrap";
290
+ await this.d.installs.ingest(actor, bytes, bootstrap
291
+ ? { source: "bootstrap", ref: src.ref }
292
+ : {
293
+ source: "catalog",
294
+ ref: src.marketplaceId !== undefined ? `catalog:${src.ref}` : `${itemId}@${version}`,
295
+ }, {
296
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
297
+ ...(bootstrap && { store: "pg" }),
298
+ });
299
+ }
300
+ let objectPrefix = null;
301
+ if (content.files.length > 0) {
302
+ objectPrefix = catalogObjectPrefix(itemId, version, stagingId);
303
+ await putCatalogFiles(this.d.storage, objectPrefix, content.files);
304
+ }
305
+ const keys = ext
306
+ ? permissionKeys(permissionSetOfPackage(ext))
307
+ .filter((k) => !k.startsWith(WARNINGS_KEY_PREFIX))
308
+ .sort()
309
+ : [];
310
+ const mismatch = permissionsMismatch(opts.expect?.marketplace?.permissions, ext ? keys : undefined);
311
+ const staged = {
312
+ itemId,
313
+ packageName,
314
+ version,
315
+ contentDigest: pkg.contentDigest,
316
+ size: tgz.size,
317
+ manifest: normalizedManifest(pkg),
318
+ files: content.files.map(({ path, size, sha256 }) => ({ path, size, sha256 })),
319
+ ...(mismatch !== null && { permissionsMismatch: mismatch }),
320
+ sourceFormat: format,
321
+ ...(translation !== undefined && { translation }),
322
+ ...(license.text !== undefined && { licenseText: license.text }),
323
+ ...(license.truncated && { licenseTruncated: true }),
324
+ };
325
+ await this.d.db.transaction().execute(async (trx) => {
326
+ await authorizeActor(trx, actor);
327
+ await trx
328
+ .insertInto("plugin_import_staging")
329
+ .values({
330
+ id: stagingId,
331
+ review: JSON.stringify(review),
332
+ manifest: JSON.stringify(staged),
333
+ object_prefix: objectPrefix,
334
+ source: JSON.stringify(src),
335
+ created_by: actor,
336
+ expires_at: new Date(Date.now() + PLUGIN_STAGING_TTL_MS),
337
+ })
338
+ .execute();
339
+ await audit(trx, {
340
+ ...(actor !== null && { actorAccountId: actor }),
341
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
342
+ action: "catalog.plugin_staged",
343
+ target: stagingId,
344
+ detail: {
345
+ stagingId,
346
+ name: m.name,
347
+ version,
348
+ source: src.kind,
349
+ sourceRef: src.ref,
350
+ ...(src.integrity !== undefined && { integrity: src.integrity }),
351
+ ...(src.marketplaceId !== undefined && { marketplaceId: src.marketplaceId }),
352
+ reviewHash: review.reviewHash,
353
+ ...(extension && {
354
+ extension: { sha256: extension.sha256, action: extension.action },
355
+ }),
356
+ },
357
+ });
358
+ });
359
+ return { stagingId, review, permissionKeys: keys, permissionsMismatch: mismatch };
360
+ }
361
+ /** Fetches a plugin from the configured npm registry (verified by `dist.integrity`), then prepares it. */
362
+ async prepareNpm(actor, input, opts = {}) {
363
+ const got = await this.d.installs.fetchNpm(input);
364
+ const provenance = await this.d.installs.fetchProvenance({
365
+ package: input.package,
366
+ version: got.version,
367
+ integrity: got.integrity,
368
+ });
369
+ const r = await this.prepare(actor, got.bytes, {
370
+ kind: "npm",
371
+ ref: `${input.package}@${got.version}`,
372
+ integrity: got.integrity,
373
+ detail: { registry: new URL(this.d.config().packages.npmRegistry).origin },
374
+ }, { ...opts, expect: { packageName: input.package }, provenance: { provenance } });
375
+ return { stagingId: r.stagingId, review: r.review };
376
+ }
377
+ /**
378
+ * A staged import's review, as prepare returned it, with a marketplace import's permission
379
+ * mismatch when there is one (the import page loads `?staging=<id>` with it). Unknown, expired or
380
+ * already confirmed -> 404 `plugin.staging_not_found`.
381
+ */
382
+ async staged(stagingId) {
383
+ if (!UUID_RE.test(stagingId))
384
+ throw notFound();
385
+ const row = await this.d.db
386
+ .selectFrom("plugin_import_staging")
387
+ .select(["id", "review", "manifest"])
388
+ .where("id", "=", stagingId)
389
+ .where("expires_at", ">", sql `now()`)
390
+ .executeTakeFirst();
391
+ if (!row)
392
+ throw notFound();
393
+ const mismatch = row.manifest.permissionsMismatch;
394
+ return {
395
+ stagingId: row.id,
396
+ review: row.review,
397
+ ...(mismatch !== undefined && mismatch !== null && { permissionsMismatch: mismatch }),
398
+ };
399
+ }
400
+ /**
401
+ * Confirms a staged import (see the file header). Returns the committed catalog item and, with
402
+ * `deployExtension`, the extension's deploy result (a failed deploy is returned, not thrown).
403
+ */
404
+ async confirm(actor, stagingId, input, opts = {}) {
405
+ if (!UUID_RE.test(stagingId))
406
+ throw notFound();
407
+ const row = await this.d.db
408
+ .selectFrom("plugin_import_staging")
409
+ .selectAll()
410
+ .where("id", "=", stagingId)
411
+ .where("expires_at", ">", sql `now()`)
412
+ .executeTakeFirst();
413
+ if (!row)
414
+ throw notFound();
415
+ const review = row.review;
416
+ const staged = row.manifest;
417
+ const src = row.source;
418
+ const stale = () => new ApiError("plugin.review_stale", `The review of plugin ${review.name} ${review.version} is out of date (it changed, or its extension on this deployment changed since it was prepared); review it again`);
419
+ if (input.reviewHash !== review.reviewHash)
420
+ throw stale();
421
+ const manifest = withPinned(staged.manifest, review, input.pinnedSuggestions);
422
+ const reviewed = review.extension;
423
+ // Read the stored package outside the transaction (unless it was already installed as is)
424
+ const stored = reviewed && reviewed.action !== "keep" ? await this.readStored(reviewed.sha256) : undefined;
425
+ const committed = await this.d.db.transaction().execute(async (trx) => {
426
+ await authorizeActor(trx, actor);
427
+ const used = await trx
428
+ .deleteFrom("plugin_import_staging")
429
+ .where("id", "=", stagingId)
430
+ .where("expires_at", ">", sql `now()`)
431
+ .returning("id")
432
+ .executeTakeFirst();
433
+ if (!used)
434
+ throw notFound();
435
+ let now;
436
+ let previous;
437
+ if (reviewed) {
438
+ // The same per-name advisory lock as ExtensionAdminOps.install; under it the action is
439
+ // recomputed from the current deployment state (installed / upgraded / swapped since the
440
+ // review -> stale)
441
+ await sql `SELECT pg_advisory_xact_lock(hashtext(${`capybara.plugin-install:${reviewed.name}`}))`.execute(trx);
442
+ now = {
443
+ ...reviewed,
444
+ ...actionOf(await this.extensionState(reviewed.name, staged.packageName, trx), reviewed.sha256),
445
+ };
446
+ if (pluginReviewHash({ ...review, extension: now }, staged.files) !== review.reviewHash) {
447
+ throw stale();
448
+ }
449
+ // A foreseeable deploy failure (a downgrade to a version with unrun migrations) is refused
450
+ // before anything is written
451
+ if (input.deployExtension && now.action === "upgrade" && stored) {
452
+ await assertDowngradable(trx, now, stored.migrationSql);
453
+ }
454
+ if (now.action === "install" || (now.action === "upgrade" && input.deployExtension)) {
455
+ previous = await this.approve(trx, actor, review, staged, now, stored, opts);
456
+ }
457
+ }
458
+ const m = manifest;
459
+ const itemId = await writeCatalogVersion(trx, {
460
+ actor,
461
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
462
+ name: m.name,
463
+ packageName: staged.packageName,
464
+ version: staged.version,
465
+ contentDigest: staged.contentDigest,
466
+ manifest: m,
467
+ listing: {
468
+ publisher: m.publisher,
469
+ description: m.description,
470
+ displayName: m.displayName ?? null,
471
+ },
472
+ newItemId: staged.itemId,
473
+ source: {
474
+ source: src.kind,
475
+ ref: src.ref,
476
+ ...(src.integrity !== undefined && { integrity: src.integrity }),
477
+ ...(src.detail !== undefined && { detail: src.detail }),
478
+ },
479
+ ...(src.marketplaceId !== undefined && { marketplaceId: src.marketplaceId }),
480
+ objectPrefix: row.object_prefix,
481
+ extensionSha256: reviewed?.sha256 ?? null,
482
+ files: staged.files,
483
+ size: staged.size,
484
+ sourceFormat: staged.sourceFormat ?? "capybara",
485
+ translation: staged.translation ?? null,
486
+ licenseText: staged.licenseText ?? null,
487
+ licenseTruncated: staged.licenseTruncated === true,
488
+ auditDetail: {
489
+ stagingId,
490
+ deployExtension: input.deployExtension,
491
+ suggestedPinned: pinnedNames(manifest, review),
492
+ ...(now && { extension: { name: now.name, version: now.version, action: now.action } }),
493
+ },
494
+ });
495
+ return { itemId, now, previous };
496
+ });
497
+ const { now } = committed;
498
+ const deploy = input.deployExtension && now && now.action !== "keep"
499
+ ? await this.deploy(actor, review, committed.itemId, now, committed.previous, opts, input.enable ?? true)
500
+ : null;
501
+ return { item: await this.itemView(committed.itemId), deploy };
502
+ }
503
+ /**
504
+ * Writes the extension's permission snapshot for the version being imported (source web; cli
505
+ * without an actor; bootstrap for the bootstrap step), after
506
+ * registering the name owner like ExtensionAdminOps.install (Ruling P3). Returns the snapshot it
507
+ * overwrote (null when there was none).
508
+ */
509
+ async approve(trx, actor, review, staged, p, pkg, opts) {
510
+ const set = permissionSetOfPackage(pkg);
511
+ await sql `SELECT pg_advisory_xact_lock(hashtext(${`capybara.plugin-approve:${p.name}`}))`.execute(trx);
512
+ const snapRow = await trx
513
+ .selectFrom("extension_permission_snapshots")
514
+ .selectAll()
515
+ .where("plugin", "=", p.name)
516
+ .forUpdate()
517
+ .executeTakeFirst();
518
+ await trx
519
+ .insertInto("extension_name_owners")
520
+ .values({ name: p.name, package_name: staged.packageName })
521
+ .onConflict((oc) => oc.column("name").doNothing())
522
+ .execute();
523
+ await assertPluginNameOwner(trx, p.name, staged.packageName);
524
+ const { added, removed } = diffPermissions(snapRow?.permissions, set);
525
+ await writeSnapshot(trx, p.name, p.version, set, {
526
+ source: review.source.kind === "bootstrap" ? "bootstrap" : actor === null ? "cli" : "web",
527
+ approvedBy: actor,
528
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
529
+ audit: {
530
+ added,
531
+ removed,
532
+ sha256: p.sha256,
533
+ app: { name: review.name, version: review.version },
534
+ },
535
+ });
536
+ return snapRow
537
+ ? {
538
+ version: snapRow.version,
539
+ permissions: snapRow.permissions,
540
+ approvedBy: snapRow.approved_by,
541
+ approvedAt: snapRow.approved_at,
542
+ source: snapRow.source,
543
+ }
544
+ : null;
545
+ }
546
+ /**
547
+ * Deploys the plugin's extension after the commit: `install` (a new install is disabled, a version
548
+ * change keeps the desired state) then `enable`. Nothing is thrown; the result says what happened.
549
+ * When an upgrade was not installed, the snapshot overwritten at confirm is restored (the old
550
+ * version keeps running, F-O; audit `extension.permissions_restored`). Retry through the extension
551
+ * admin (the package is already in the package store) with the sha256 from the result.
552
+ */
553
+ async deploy(actor, review, itemId, p, previous, opts, enable) {
554
+ const base = {
555
+ name: p.name,
556
+ action: p.action,
557
+ version: p.version,
558
+ sha256: p.sha256,
559
+ };
560
+ const failure = (e) => e instanceof ApiError
561
+ ? { code: e.code, message: e.message }
562
+ : { code: "internal", message: "Internal error" };
563
+ try {
564
+ await this.d.ops.install(p.name, p.sha256, actor, opts.actorTokenId, {
565
+ catalogItemId: itemId,
566
+ });
567
+ }
568
+ catch (e) {
569
+ this.d.log.warn("installing a plugin's extension on the deployment failed", {
570
+ plugin: review.name,
571
+ err: e,
572
+ });
573
+ const result = { ...base, result: "failed", ...failure(e) };
574
+ if (p.action === "upgrade" && previous !== undefined) {
575
+ try {
576
+ if (!(await this.restoreSnapshot(actor, review, p, previous, opts))) {
577
+ result.note =
578
+ "snapshot_not_restored: the permission approval changed after the import was confirmed, so it was left as it is";
579
+ }
580
+ }
581
+ catch (err) {
582
+ this.d.log.error("cannot restore the permission snapshot of a plugin's extension", {
583
+ plugin: p.name,
584
+ err,
585
+ });
586
+ }
587
+ }
588
+ return result;
589
+ }
590
+ if (!enable)
591
+ return { ...base, result: "installed" };
592
+ try {
593
+ await this.d.ops.enable(p.name, actor, opts.actorTokenId);
594
+ return { ...base, result: "enabled" };
595
+ }
596
+ catch (e) {
597
+ this.d.log.warn("enabling a plugin's extension failed", { plugin: p.name, err: e });
598
+ return { ...base, result: "failed", ...failure(e) };
599
+ }
600
+ }
601
+ /**
602
+ * Restores the snapshot overwritten at confirm (deletes it when there was none before), audits
603
+ * `extension.permissions_restored`. Compare-and-write (row locked): restores only while the
604
+ * snapshot is still the one written at confirm (this version, approved by this actor); if someone
605
+ * approved or changed it meanwhile it is left alone and false is returned.
606
+ */
607
+ async restoreSnapshot(actor, review, p, prev, opts) {
608
+ return this.d.db.transaction().execute(async (trx) => {
609
+ await sql `SELECT pg_advisory_xact_lock(hashtext(${`capybara.plugin-approve:${p.name}`}))`.execute(trx);
610
+ const current = await trx
611
+ .selectFrom("extension_permission_snapshots")
612
+ .select(["version", "approved_by"])
613
+ .where("plugin", "=", p.name)
614
+ .forUpdate()
615
+ .executeTakeFirst();
616
+ if (current?.version !== p.version || current.approved_by !== actor)
617
+ return false;
618
+ if (prev) {
619
+ await trx
620
+ .updateTable("extension_permission_snapshots")
621
+ .set({
622
+ version: prev.version,
623
+ permissions: JSON.stringify(prev.permissions),
624
+ approved_by: prev.approvedBy,
625
+ approved_at: prev.approvedAt,
626
+ source: prev.source,
627
+ })
628
+ .where("plugin", "=", p.name)
629
+ .execute();
630
+ }
631
+ else {
632
+ await trx
633
+ .deleteFrom("extension_permission_snapshots")
634
+ .where("plugin", "=", p.name)
635
+ .execute();
636
+ }
637
+ await audit(trx, {
638
+ ...(actor !== null && { actorAccountId: actor }),
639
+ ...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
640
+ action: "extension.permissions_restored",
641
+ target: p.name,
642
+ detail: {
643
+ plugin: p.name,
644
+ restoredVersion: prev?.version ?? null,
645
+ reason: "plugin_deploy_failed",
646
+ app: { name: review.name, version: review.version },
647
+ },
648
+ });
649
+ return true;
650
+ });
651
+ }
652
+ /**
653
+ * Deletes expired staging rows and their object prefixes (maintenance job `app-staging-cleanup`).
654
+ * Row by row: objects first, then the row (a failed object delete keeps the row for the next
655
+ * round); concurrent replicas skip each other's rows (`SKIP LOCKED`). Returns the rows deleted.
656
+ */
657
+ async cleanupExpired() {
658
+ let total = 0;
659
+ for (;;) {
660
+ const batch = await this.d.db
661
+ .selectFrom("plugin_import_staging")
662
+ .select(["id", "object_prefix"])
663
+ .where("expires_at", "<=", sql `now()`)
664
+ .orderBy("expires_at")
665
+ .limit(CLEANUP_BATCH)
666
+ .execute();
667
+ if (batch.length === 0)
668
+ return total;
669
+ let progressed = 0;
670
+ for (const r of batch) {
671
+ try {
672
+ const done = await this.d.db.transaction().execute(async (trx) => {
673
+ const locked = await trx
674
+ .selectFrom("plugin_import_staging")
675
+ .select("id")
676
+ .where("id", "=", r.id)
677
+ .where("expires_at", "<=", sql `now()`)
678
+ .forUpdate()
679
+ .skipLocked()
680
+ .executeTakeFirst();
681
+ if (!locked)
682
+ return false;
683
+ if (r.object_prefix)
684
+ await this.d.storage.deployment().deletePrefix(r.object_prefix);
685
+ await trx.deleteFrom("plugin_import_staging").where("id", "=", r.id).execute();
686
+ return true;
687
+ });
688
+ if (done) {
689
+ total++;
690
+ progressed++;
691
+ }
692
+ }
693
+ catch (err) {
694
+ this.d.log.warn("cannot clean up an expired plugin import", { stagingId: r.id, err });
695
+ }
696
+ }
697
+ if (progressed === 0 || batch.length < CLEANUP_BATCH)
698
+ return total;
699
+ }
700
+ }
701
+ // ---------------------------------------------------------------------------------------------
702
+ /** npm / marketplace imports: the package must be the one that was asked for. */
703
+ checkExpected(pkg, expect, format = "capybara") {
704
+ const packageName = packageNameOf(pkg);
705
+ if (expect?.packageName !== undefined && packageName !== expect.packageName) {
706
+ throw invalid([
707
+ `package.json name "${packageName}" does not match the requested npm package ${expect.packageName}`,
708
+ ]);
709
+ }
710
+ const e = expect?.marketplace;
711
+ if (!e)
712
+ return;
713
+ if (e.packageName !== undefined && packageName !== e.packageName) {
714
+ throw mismatch(`package.json name is "${packageName}", the entry says ${e.packageName}`);
715
+ }
716
+ if (pkg.manifest.name !== e.name) {
717
+ throw mismatch(`the plugin is named "${pkg.manifest.name}", the entry says ${e.name}`);
718
+ }
719
+ // Claude Code's rule: plugin.json's version wins over the entry's (spec §5.3 order)
720
+ if (e.version !== null && format === "capybara" && pkg.manifest.version !== e.version) {
721
+ throw mismatch(`the plugin's version is ${pkg.manifest.version}, the entry says ${e.version}`);
722
+ }
723
+ }
724
+ /** A config-declared extension's name cannot be taken by an imported plugin. */
725
+ assertNotDeclared(name) {
726
+ const declared = this.d.declared().get(name);
727
+ if (declared === "config") {
728
+ throw new ApiError("extension.config_declared", `Extension ${name} is declared in the startup config (extensions); remove it there before importing a plugin with this name`);
729
+ }
730
+ }
731
+ /**
732
+ * Skills (by the skill package rules), connector templates (against the loaded connector) and
733
+ * icons; returns the files to store and the review's skills and templates.
734
+ */
735
+ checkContent(pkg, format = "capybara") {
736
+ const m = pkg.manifest;
737
+ const notLoaded = [];
738
+ const templatePaths = new Map();
739
+ const pinned = new Map((m.skills ?? []).map((s) => [packagePath(s.path), s.pinned === true]));
740
+ const problems = [];
741
+ const stored = new Map();
742
+ const keep = (path) => {
743
+ const data = pkg.files.get(path);
744
+ if (data !== undefined && !stored.has(path))
745
+ stored.set(path, fileOf(path, data));
746
+ };
747
+ if (m.icon !== undefined)
748
+ keep(packagePath(m.icon));
749
+ const skills = [];
750
+ for (const s of pkg.skills) {
751
+ const files = s.files.map((f) => fileOf(f, pkg.files.get(`${s.dir}/${f}`)));
752
+ try {
753
+ // A translated Claude Code plugin keeps its SKILL.md files as written: their invocation
754
+ // keys are read leniently (the translation report lists the ones ignored)
755
+ const invocationKeys = format !== "claude-code";
756
+ const r = checkSkillFiles(files, { invocationKeys });
757
+ const md = parseSkillMd(new TextDecoder().decode(pkg.files.get(`${s.dir}/${SKILL_ENTRY_FILE}`)), { invocationKeys });
758
+ skills.push({
759
+ name: r.name,
760
+ description: r.description,
761
+ body: "errors" in md ? "" : md.body,
762
+ path: `./${s.dir}`,
763
+ files: files.length,
764
+ suggestPinned: pinned.get(s.dir) === true,
765
+ });
766
+ for (const f of s.files)
767
+ keep(`${s.dir}/${f}`);
768
+ }
769
+ catch (e) {
770
+ if (!(e instanceof SkillArchiveError))
771
+ throw e;
772
+ for (const p of e.problems)
773
+ problems.push(`${s.dir}: ${p}`);
774
+ }
775
+ }
776
+ const local = new Set([
777
+ ...(m.extension ? [m.name] : []),
778
+ ...(m.dependencies ?? []).map((d) => d.name),
779
+ ]);
780
+ const templates = pkg.connectorTemplates.map((c) => {
781
+ const t = c.file.template;
782
+ const at = `${c.path}: template`;
783
+ // The result depends on which connectors the replica serving this request has loaded right
784
+ // now: it is the import-time gate, not the last word (an org install checks again against the
785
+ // connector of the moment)
786
+ const loaded = this.d.findConnector(t.plugin, t.connector);
787
+ if (loaded) {
788
+ problems.push(...templateProblems(loaded, t, at));
789
+ const v = this.d.pluginVersion?.(t.plugin);
790
+ if (t.pluginVersion !== undefined &&
791
+ v !== undefined &&
792
+ !satisfiesRange(v, t.pluginVersion)) {
793
+ problems.push(`${at}.pluginVersion: extension ${t.plugin} is loaded at version ${v}, which does not satisfy ${t.pluginVersion}`);
794
+ }
795
+ }
796
+ else if (local.has(t.plugin) ||
797
+ this.d.connectorsLoaded === false ||
798
+ (format === "claude-code" && t.plugin === CLAUDE_MCP_CONNECTOR.plugin)) {
799
+ // The connector comes with this plugin or a dependency (or this process loads no
800
+ // connectors, or a translated Claude template targets connector-mcp, which may not be
801
+ // loaded: Decisions 2): static checks only now
802
+ for (const leak of endpointCredentialIssues(t.endpoint)) {
803
+ problems.push(`${at}.${leak.path}: ${leak.message}`);
804
+ }
805
+ if (format === "claude-code")
806
+ notLoaded.push(c.id);
807
+ }
808
+ else {
809
+ problems.push(`${at}.connector: extension ${t.plugin} providing connector "${t.connector}" is not loaded in this deployment and is neither this plugin's extension nor a dependency`);
810
+ }
811
+ keep(c.path);
812
+ templatePaths.set(c.id, c.path);
813
+ if (c.file.icon !== undefined)
814
+ keep(packagePath(c.file.icon));
815
+ return {
816
+ id: c.id,
817
+ displayName: c.file.displayName,
818
+ description: c.file.description ?? "",
819
+ plugin: t.plugin,
820
+ connector: t.connector,
821
+ endpoint: t.endpoint,
822
+ auth: t.auth,
823
+ editableEndpoint: t.editableEndpoint ?? [],
824
+ endpointChecked: loaded !== undefined,
825
+ };
826
+ });
827
+ const agentTemplates = pkg.agentTemplates.map((a) => {
828
+ keep(a.path);
829
+ const { name, description, instructions, model, ...rest } = a.template;
830
+ return { name, path: a.path, description, instructions, model: model ?? "inherit", ...rest };
831
+ });
832
+ if (problems.length > 0)
833
+ throw invalid(problems);
834
+ return {
835
+ files: [...stored.values()],
836
+ templates,
837
+ skills,
838
+ agentTemplates,
839
+ notLoaded,
840
+ templatePaths,
841
+ };
842
+ }
843
+ /**
844
+ * The highest imported, non-deleted catalog version satisfying each dependency's range (null when
845
+ * none does).
846
+ */
847
+ async resolveDependencies(m) {
848
+ const deps = m.dependencies ?? [];
849
+ if (deps.length === 0)
850
+ return [];
851
+ const rows = await this.d.db
852
+ .selectFrom("catalog_items as i")
853
+ .innerJoin("catalog_item_versions as v", "v.item_id", "i.id")
854
+ .select(["i.name", "v.version", "v.imported_at"])
855
+ .where("i.deleted_at", "is", null)
856
+ .where("i.name", "in", deps.map((d) => d.name))
857
+ .execute();
858
+ return deps.map((d) => ({
859
+ name: d.name,
860
+ range: d.version,
861
+ resolved: maxSatisfying(rows
862
+ .filter((r) => r.name === d.name)
863
+ .map((r) => ({ version: r.version, importedAt: r.imported_at })), d.version)?.version ?? null,
864
+ }));
865
+ }
866
+ /**
867
+ * The extension's state on the deployment; installed from another npm package ->
868
+ * `extension.name_conflict`.
869
+ */
870
+ async extensionState(name, packageName, db = this.d.db) {
871
+ const r = await db
872
+ .selectFrom("extension_installs")
873
+ .innerJoin("extension_packages", "extension_packages.sha256", "extension_installs.sha256")
874
+ .select([
875
+ "extension_installs.version",
876
+ "extension_installs.sha256",
877
+ "extension_packages.package_name",
878
+ ])
879
+ .where("extension_installs.name", "=", name)
880
+ .executeTakeFirst();
881
+ if (r && r.package_name !== packageName) {
882
+ throw new ApiError("extension.name_conflict", `Extension ${name} is already installed from package ${r.package_name}; package ${packageName} cannot use it`);
883
+ }
884
+ return r
885
+ ? { kind: "installed", version: r.version, sha256: r.sha256, packageName: r.package_name }
886
+ : { kind: "absent" };
887
+ }
888
+ /** The review of the extension: permissions, the change against the snapshot, warnings, action. */
889
+ async reviewExtension(pkg, state) {
890
+ const name = pkg.section.name;
891
+ const snap = await this.d.db
892
+ .selectFrom("extension_permission_snapshots")
893
+ .select("permissions")
894
+ .where("plugin", "=", name)
895
+ .executeTakeFirst();
896
+ const set = permissionSetOfPackage(pkg);
897
+ const warnings = migrationWarnings(pkg.migrationSql, name);
898
+ const { added, removed } = diffPermissions(snap?.permissions, set);
899
+ const permissions = permissionGroups(set);
900
+ return {
901
+ name,
902
+ version: pkg.section.version,
903
+ sha256: pkg.sha256,
904
+ ...actionOf(state, pkg.sha256),
905
+ permissions,
906
+ added,
907
+ addedItems: permissionKeyItems(added),
908
+ removed,
909
+ warnings,
910
+ reviewHash: extensionReviewHash(set, warnings),
911
+ sensitive: permissions.some((g) => g.sensitive) || warnings.length > 0,
912
+ };
913
+ }
914
+ async readStored(sha256) {
915
+ const bytes = await this.d.blobs.get(sha256);
916
+ return readingPackage(() => readExtensionPackage(bytes, {
917
+ maxBytes: this.d.config().packages.maxBytes,
918
+ hostSdkVersion: SDK_VERSION,
919
+ }));
920
+ }
921
+ async itemView(itemId) {
922
+ const item = await this.d.db
923
+ .selectFrom("catalog_items")
924
+ .selectAll()
925
+ .where("id", "=", itemId)
926
+ .executeTakeFirstOrThrow();
927
+ const versions = await this.d.db
928
+ .selectFrom("catalog_item_versions")
929
+ .select(["version", "source", "imported_at"])
930
+ .where("item_id", "=", itemId)
931
+ .execute();
932
+ return itemView(item, versions);
933
+ }
934
+ }
935
+ /**
936
+ * plugin.json as stored on the version: skills and connector templates listed explicitly (what was
937
+ * scanned when the manifest omitted them), `pinned` kept from the listing.
938
+ */
939
+ function normalizedManifest(pkg) {
940
+ const pinned = new Map((pkg.manifest.skills ?? []).map((s) => [packagePath(s.path), s.pinned]));
941
+ const { agents: _, ...manifest } = pkg.manifest;
942
+ return {
943
+ ...manifest,
944
+ skills: pkg.skills.map((s) => {
945
+ const p = pinned.get(s.dir);
946
+ return { path: `./${s.dir}`, ...(p !== undefined && { pinned: p }) };
947
+ }),
948
+ connectorTemplates: pkg.connectorTemplates.map((t) => ({ path: `./${t.path}` })),
949
+ // listed only when there are any, so the stored manifests of other plugins stay as they were
950
+ ...(pkg.agentTemplates.length > 0 && {
951
+ agents: pkg.agentTemplates.map((a) => ({ path: `./${a.path}` })),
952
+ }),
953
+ };
954
+ }
955
+ /** package.json `name`, or null without one (a git plugin; never the string "undefined"). */
956
+ function packageNameOf(pkg) {
957
+ const name = pkg.packageJson.name;
958
+ return typeof name === "string" ? name : null;
959
+ }
960
+ /** The extension's deployment state -> action and current version. */
961
+ function actionOf(s, sha256) {
962
+ if (s.kind === "installed") {
963
+ return {
964
+ action: s.sha256 === sha256 ? "keep" : "upgrade",
965
+ deployed: true,
966
+ currentVersion: s.version,
967
+ currentSha256: s.sha256,
968
+ };
969
+ }
970
+ return { action: "install", deployed: false, currentVersion: null, currentSha256: null };
971
+ }
972
+ /**
973
+ * The review digest (first 32 hex of a sha256): name, version and content digest, the extension (sha256, action,
974
+ * current version / package, its own review digest), every template field, each skill's name / path
975
+ * / file count, the dependency resolution, the sha256 of every stored file, the format, the
976
+ * translation report's items, the sha256 of the license text and the two review flags
977
+ * (`claudeManifestIgnored`, `licenseTruncated`), and the agent templates when there are any.
978
+ * Exported for tests. `repository` is display only.
979
+ */
980
+ export function pluginReviewHash(r, files) {
981
+ const e = r.extension;
982
+ const lic = r.licenseText;
983
+ const canonical = JSON.stringify([
984
+ r.name,
985
+ r.version,
986
+ r.contentDigest,
987
+ e ? [e.name, e.sha256, e.action, e.currentVersion, e.currentSha256, e.reviewHash] : null,
988
+ r.connectorTemplates.map((t) => [
989
+ t.id,
990
+ t.plugin,
991
+ t.connector,
992
+ t.endpoint,
993
+ t.auth,
994
+ t.editableEndpoint,
995
+ ]),
996
+ r.skills.map((s) => [s.name, s.path, s.files]),
997
+ r.dependencies.map((d) => [d.name, d.range, d.resolved]),
998
+ [...files].sort((a, b) => a.path.localeCompare(b.path)).map((f) => [f.path, f.sha256]),
999
+ // phase B: the format, the translation report (in report order) and the license text
1000
+ r.format,
1001
+ r.translation?.items.map((i) => [i.group, i.component, i.name, i.path, i.code, i.detail]) ??
1002
+ null,
1003
+ lic !== undefined ? createHash("sha256").update(lic).digest("hex") : null,
1004
+ r.claudeManifestIgnored === true,
1005
+ r.licenseTruncated === true,
1006
+ // phase C: agent templates, only when there are any (earlier digests stay the same)
1007
+ ...((r.agentTemplates ?? []).length > 0
1008
+ ? [(r.agentTemplates ?? []).map((a) => [a.path, canonicalJson(a)])]
1009
+ : []),
1010
+ ]);
1011
+ return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
1012
+ }
1013
+ /**
1014
+ * A version change that is a downgrade (the imported version below the installed one) whose
1015
+ * version has migrations not run on this deployment -> `extension.downgrade_unsupported` (the same
1016
+ * rule as `ExtensionAdminOps.install`: extension migrations are expand-only, a downgrade can only go
1017
+ * back to a version whose migrations have all run).
1018
+ */
1019
+ async function assertDowngradable(db, p, migrationSql) {
1020
+ if (p.action !== "upgrade" || p.currentVersion === null)
1021
+ return;
1022
+ if (!semver.valid(p.currentVersion) || !semver.lt(p.version, p.currentVersion))
1023
+ return;
1024
+ const unrun = await unrunMigrations(db, p.name, migrationSql);
1025
+ if (unrun.length > 0) {
1026
+ throw new ApiError("extension.downgrade_unsupported", `The plugin's extension ${p.name} ${p.version} is older than the installed ${p.currentVersion}, and that version has migrations that were never run here (${unrun.join(", ")}); extension migrations only expand, so this downgrade is not possible`, unrun.map((file) => ({ file })));
1027
+ }
1028
+ }
1029
+ /** The root license text (spec §8.4; any plugin): the first of {@link LICENSE_FILES}, UTF-8. */
1030
+ function licenseOf(files) {
1031
+ for (const f of LICENSE_FILES) {
1032
+ const data = files.get(f);
1033
+ if (data === undefined)
1034
+ continue;
1035
+ if (data.byteLength > LICENSE_TEXT_MAX_BYTES)
1036
+ return { truncated: true };
1037
+ try {
1038
+ return { text: new TextDecoder("utf-8", { fatal: true }).decode(data), truncated: false };
1039
+ }
1040
+ catch {
1041
+ return { truncated: true };
1042
+ }
1043
+ }
1044
+ return { truncated: false };
1045
+ }
1046
+ /**
1047
+ * The manifest with the confirm's suggested pinning: `names` (each a skill of the review, else
1048
+ * `validation_failed`) marks those skills `pinned: true` and clears the flag on the others;
1049
+ * undefined keeps the plugin's flags.
1050
+ */
1051
+ function withPinned(m, review, names) {
1052
+ if (names === undefined)
1053
+ return m;
1054
+ const byName = new Map(review.skills.map((s) => [s.name, s.path]));
1055
+ const unknown = names.filter((n) => !byName.has(n));
1056
+ if (unknown.length > 0) {
1057
+ throw new ApiError("validation_failed", `pinnedSuggestions: ${unknown.map((n) => JSON.stringify(n)).join(", ")} ${unknown.length === 1 ? "is not a skill" : "are not skills"} of this plugin`);
1058
+ }
1059
+ const paths = new Set(names.map((n) => byName.get(n)));
1060
+ return {
1061
+ ...m,
1062
+ skills: (m.skills ?? []).map(({ pinned: _, ...s }) => paths.has(s.path) ? { ...s, pinned: true } : s),
1063
+ };
1064
+ }
1065
+ /** Names of the skills the stored manifest suggests pinning (for the audit). */
1066
+ function pinnedNames(m, review) {
1067
+ const byPath = new Map(review.skills.map((s) => [s.path, s.name]));
1068
+ return (m.skills ?? []).filter((s) => s.pinned === true).map((s) => byPath.get(s.path) ?? s.path);
1069
+ }