@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,169 @@
1
+ import type { Logger } from "@capybara-hq/plugin-sdk";
2
+ import type { Redis } from "ioredis";
3
+ import { ApiError } from "../errors.js";
4
+ /**
5
+ * Shared and exclusive sandbox leases (sub-agents spec §10.3): the Redis protocol that lets several
6
+ * holders use one conversation's running sandbox concurrently (shared leases: commands, file reads
7
+ * and writes) while lifecycle changes (create, resume, rebuild, suspend, destroy) stay alone
8
+ * (exclusive leases). Keys, conversation = the sandbox conversation:
9
+ *
10
+ * - `capk:sandbox:<conv>`: the lock key kept from before (rollout, §10.8): an exclusive holder's
11
+ * token, or `shared` while shared leases exist. An older replica's `SET NX` therefore never gets
12
+ * in while shared leases live, and its token blocks new shared leases.
13
+ * - `capk:sandbox-users:<conv>`: sorted set of shared lease tokens scored by their expiry (ms, Redis
14
+ * server clock).
15
+ * - `capk:sandbox-intent:<conv>`: an exclusive waiter's token (writer preference); while it exists
16
+ * no new shared lease is granted.
17
+ * - `capk:sandbox-holders:<conv>`: hash token → expiry of holders that may have commands running
18
+ * (the per-holder replacement of the single busy marker `capk:sandbox-busy:<conv>`, still read).
19
+ * - `capk:sandbox-path:<conv>:<sha256hex(path)>`: a short per-path lease around the kernel's own
20
+ * read-modify-write of one file.
21
+ *
22
+ * Every script purges expired shared leases first, and every key carries a TTL. A holder renews
23
+ * every TTL / 3 (a timer for a lease this process holds) and treats "no successful renewal for
24
+ * 2/3 TTL" as lost: its `signal` aborts with {@link SandboxLockLostError}.
25
+ */
26
+ /** Prefix of the lock key `capk:sandbox:<conversationId>`. */
27
+ export declare const SANDBOX_LOCK_PREFIX = "capk:sandbox:";
28
+ /** TTL of a lease (ms): renewed every TTL / 3 while held. */
29
+ export declare const SANDBOX_LOCK_TTL_MS = 60000;
30
+ /**
31
+ * Prefix of the single "sandbox in use" marker of kernels before phase C,
32
+ * `capk:sandbox-busy:<conversationId>`: present while such a holder ran commands. Found by an
33
+ * exclusive acquirer, it means that holder crashed and its commands may still run.
34
+ *
35
+ * Holders of this kernel write it too, with the value {@link BUSY_V2}, while any holder record
36
+ * exists (spec §10.8): this kernel ignores that value, and a replica from before phase C that
37
+ * takes the lock after a new holder crashed sees a marker and kills everything, which is safe
38
+ * because it only gets the lock when no shared lease is live.
39
+ */
40
+ export declare const SANDBOX_BUSY_PREFIX = "capk:sandbox-busy:";
41
+ /** Value of the busy marker written by holders of this kernel (ignored by it). */
42
+ export declare const BUSY_V2 = "v2";
43
+ /** The sandbox of this conversation is busy: waited longer than `sandbox.lockWaitSeconds` (409). */
44
+ export declare const sandboxBusy: () => ApiError;
45
+ /** A lease was lost while held, or the record changed under it (generation mismatch). */
46
+ export declare class SandboxLockLostError extends Error {
47
+ /** `lock`: the lease was lost (renewal failed or none succeeded in time); `generation`: the record's fence changed. */
48
+ readonly reason: "lock" | "generation";
49
+ constructor(conversationId: string, reason?: "lock" | "generation");
50
+ }
51
+ /** Another holder kept writing the same file longer than the path lease wait (model-visible text). */
52
+ export declare class SandboxPathBusyError extends Error {
53
+ constructor();
54
+ }
55
+ export type LeaseKind = "shared" | "exclusive";
56
+ /** A lease this process holds. */
57
+ export interface SandboxLease {
58
+ readonly kind: LeaseKind;
59
+ readonly token: string;
60
+ readonly conversationId: string;
61
+ /** Aborts with {@link SandboxLockLostError} when the lease is lost. */
62
+ readonly signal: AbortSignal;
63
+ /** Renews once now; false when the lease is lost (the signal then aborts). */
64
+ renew(): Promise<boolean>;
65
+ /** Stops renewing and releases the lease (only this holder's). */
66
+ release(): Promise<void>;
67
+ /** exclusive → shared in one Lua step (same token, same signal); throws if not exclusive. */
68
+ downgrade(): Promise<SandboxLease>;
69
+ }
70
+ export interface SandboxLeasesDeps {
71
+ redis: Redis;
72
+ log: Pick<Logger, "warn">;
73
+ /** Lease TTL (ms), default {@link SANDBOX_LOCK_TTL_MS}. */
74
+ ttlMs?: number;
75
+ /** Poll interval while waiting (ms, jittered up to 2×), default 100. */
76
+ pollMs?: number;
77
+ /** Path lease TTL (ms), default 30 s; tests make it shorter. */
78
+ pathTtlMs?: number;
79
+ }
80
+ /** A path lease this process holds. */
81
+ export interface SandboxPathLease {
82
+ /** Aborts with {@link SandboxLockLostError} when a renewal finds the lease gone. */
83
+ readonly signal: AbortSignal;
84
+ release(): Promise<void>;
85
+ }
86
+ /** The lease protocol; no state in process beyond the leases this process holds. */
87
+ export declare class SandboxLeases {
88
+ private readonly redis;
89
+ private readonly log;
90
+ private readonly ttlMs;
91
+ private readonly pollMs;
92
+ private readonly pathTtlMs;
93
+ constructor(d: SandboxLeasesDeps);
94
+ /**
95
+ * TTL of an intent (ms): 4 × the poll interval (a jittered wait is up to 2 × the interval plus
96
+ * round trips), at least {@link INTENT_TTL_FLOOR_MS} so event-loop lag cannot let it lapse.
97
+ */
98
+ private get intentTtlMs();
99
+ private keys;
100
+ private wait;
101
+ /**
102
+ * A shared lease (`cap` holders at most), polling until one is free; `sandbox.busy` after
103
+ * `capMs`, the signal's reason when it aborts. Not granted while an exclusive lease, an older
104
+ * replica's lock token or another caller's intent exists.
105
+ */
106
+ acquireShared(conv: string, cap: number, signal: AbortSignal, capMs: number): Promise<SandboxLease>;
107
+ /**
108
+ * An exclusive lease, polling until no other lease exists; `sandbox.busy` after `capMs` (no cap
109
+ * when omitted), the signal's reason when it aborts. With `intent` the waiter sets the intent
110
+ * (refreshed while it waits) so new shared leases stop being granted; it is cleared on success
111
+ * and when the wait ends otherwise.
112
+ */
113
+ acquireExclusive(conv: string, signal: AbortSignal, capMs?: number, o?: {
114
+ intent?: boolean;
115
+ }): Promise<SandboxLease>;
116
+ /**
117
+ * One try at an exclusive lease; null when any lease exists. Without `o` (sweeps) it sets no
118
+ * intent. With `o.token` the lease (and, when `o.intent`, the intent set on failure) carries that
119
+ * token, so one waiter can alternate between this and {@link tryShared} with the same token.
120
+ */
121
+ tryExclusive(conv: string, o?: {
122
+ token?: string;
123
+ intent?: boolean;
124
+ }): Promise<SandboxLease | null>;
125
+ /**
126
+ * One try at a shared lease (`cap` holders at most); null when it is not granted. `token` is the
127
+ * lease token; a waiter that set an intent with the same token is let through its own intent
128
+ * (it should then {@link clearIntent}).
129
+ */
130
+ tryShared(conv: string, cap: number, token?: string): Promise<SandboxLease | null>;
131
+ /** Deletes the conversation's intent when it is `token`'s. */
132
+ clearIntent(conv: string, token: string): Promise<void>;
133
+ /** Whether the single busy marker of a kernel before phase C is present (v2 does not count). */
134
+ legacyMarker(conv: string): Promise<boolean>;
135
+ /** Live shared leases of the conversation. */
136
+ sharedCount(conv: string): Promise<number>;
137
+ /**
138
+ * Records (or refreshes) a holder that may run commands (before its provider calls); `ttlMs`
139
+ * bounds the record. Also keeps the v2 busy marker for older replicas ({@link BUSY_V2}).
140
+ */
141
+ registerHolder(conv: string, token: string, ttlMs: number): Promise<void>;
142
+ /** Deletes a holder record (after its provider calls); the v2 marker goes with the last one. */
143
+ unregisterHolder(conv: string, token: string): Promise<void>;
144
+ /**
145
+ * Tags of holders without a live lease, plus whether an older kernel's single busy marker was
146
+ * present. Atomically claims them (removes the fields) so only one caller kills. That marker is
147
+ * claimed only when `exclusiveToken` is the caller's exclusive lease (no shared lease live).
148
+ */
149
+ claimDeadHolders(conv: string, exclusiveToken?: string): Promise<{
150
+ tags: string[];
151
+ legacyMarker: boolean;
152
+ }>;
153
+ /**
154
+ * A short lease on one path of the sandbox (the kernel's own read-modify-write of a file); waits
155
+ * up to `capMs`, then throws {@link SandboxPathBusyError}. Renewed while held; a renewal that
156
+ * finds it gone warns and aborts its `signal`.
157
+ */
158
+ pathLease(conv: string, path: string, signal: AbortSignal, capMs: number): Promise<SandboxPathLease>;
159
+ /**
160
+ * Wraps a just-acquired lease: renews every TTL / 3 and remembers the last successful renewal;
161
+ * the lease is lost when a renewal reports it gone or none succeeded for 2/3 TTL (leaving margin
162
+ * so the abort takes effect before the key expires).
163
+ */
164
+ private hold;
165
+ private lose;
166
+ private renew;
167
+ /** A lease view of `s` as `kind`; a view stops working once the lease changes kind. */
168
+ private view;
169
+ }
@@ -0,0 +1,515 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { ApiError } from "../errors.js";
3
+ /**
4
+ * Shared and exclusive sandbox leases (sub-agents spec §10.3): the Redis protocol that lets several
5
+ * holders use one conversation's running sandbox concurrently (shared leases: commands, file reads
6
+ * and writes) while lifecycle changes (create, resume, rebuild, suspend, destroy) stay alone
7
+ * (exclusive leases). Keys, conversation = the sandbox conversation:
8
+ *
9
+ * - `capk:sandbox:<conv>`: the lock key kept from before (rollout, §10.8): an exclusive holder's
10
+ * token, or `shared` while shared leases exist. An older replica's `SET NX` therefore never gets
11
+ * in while shared leases live, and its token blocks new shared leases.
12
+ * - `capk:sandbox-users:<conv>`: sorted set of shared lease tokens scored by their expiry (ms, Redis
13
+ * server clock).
14
+ * - `capk:sandbox-intent:<conv>`: an exclusive waiter's token (writer preference); while it exists
15
+ * no new shared lease is granted.
16
+ * - `capk:sandbox-holders:<conv>`: hash token → expiry of holders that may have commands running
17
+ * (the per-holder replacement of the single busy marker `capk:sandbox-busy:<conv>`, still read).
18
+ * - `capk:sandbox-path:<conv>:<sha256hex(path)>`: a short per-path lease around the kernel's own
19
+ * read-modify-write of one file.
20
+ *
21
+ * Every script purges expired shared leases first, and every key carries a TTL. A holder renews
22
+ * every TTL / 3 (a timer for a lease this process holds) and treats "no successful renewal for
23
+ * 2/3 TTL" as lost: its `signal` aborts with {@link SandboxLockLostError}.
24
+ */
25
+ /** Prefix of the lock key `capk:sandbox:<conversationId>`. */
26
+ export const SANDBOX_LOCK_PREFIX = "capk:sandbox:";
27
+ /** TTL of a lease (ms): renewed every TTL / 3 while held. */
28
+ export const SANDBOX_LOCK_TTL_MS = 60_000;
29
+ /**
30
+ * Prefix of the single "sandbox in use" marker of kernels before phase C,
31
+ * `capk:sandbox-busy:<conversationId>`: present while such a holder ran commands. Found by an
32
+ * exclusive acquirer, it means that holder crashed and its commands may still run.
33
+ *
34
+ * Holders of this kernel write it too, with the value {@link BUSY_V2}, while any holder record
35
+ * exists (spec §10.8): this kernel ignores that value, and a replica from before phase C that
36
+ * takes the lock after a new holder crashed sees a marker and kills everything, which is safe
37
+ * because it only gets the lock when no shared lease is live.
38
+ */
39
+ export const SANDBOX_BUSY_PREFIX = "capk:sandbox-busy:";
40
+ /** Value of the busy marker written by holders of this kernel (ignored by it). */
41
+ export const BUSY_V2 = "v2";
42
+ const USERS_PREFIX = "capk:sandbox-users:";
43
+ const INTENT_PREFIX = "capk:sandbox-intent:";
44
+ const HOLDERS_PREFIX = "capk:sandbox-holders:";
45
+ const PATH_PREFIX = "capk:sandbox-path:";
46
+ /** TTL of a path lease (ms); renewed while held. */
47
+ const PATH_LEASE_TTL_MS = 30_000;
48
+ /** Shortest life of an exclusive waiter's intent (ms): outlasts event-loop lag between polls. */
49
+ const INTENT_TTL_FLOOR_MS = 2000;
50
+ /** The sandbox of this conversation is busy: waited longer than `sandbox.lockWaitSeconds` (409). */
51
+ export const sandboxBusy = () => new ApiError("sandbox.busy", "The sandbox for this conversation is busy with another command; wait for it to finish and try again");
52
+ /** A lease was lost while held, or the record changed under it (generation mismatch). */
53
+ export class SandboxLockLostError extends Error {
54
+ /** `lock`: the lease was lost (renewal failed or none succeeded in time); `generation`: the record's fence changed. */
55
+ reason;
56
+ constructor(conversationId, reason = "lock") {
57
+ super(reason === "lock"
58
+ ? `sandbox lock lost for conversation ${conversationId}`
59
+ : `sandbox state of conversation ${conversationId} changed concurrently (generation mismatch)`);
60
+ this.name = "SandboxLockLostError";
61
+ this.reason = reason;
62
+ }
63
+ }
64
+ /** Another holder kept writing the same file longer than the path lease wait (model-visible text). */
65
+ export class SandboxPathBusyError extends Error {
66
+ constructor() {
67
+ super("another sub-agent is writing this file; try again");
68
+ this.name = "SandboxPathBusyError";
69
+ }
70
+ }
71
+ const NOW = `local t = redis.call('TIME'); local now = tonumber(t[1]) * 1000 + math.floor(tonumber(t[2]) / 1000)`;
72
+ /** Drops expired shared leases; drops a `shared` lock value no live shared lease backs. */
73
+ const PURGE = `
74
+ redis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)
75
+ local cur = redis.call('GET', KEYS[1])
76
+ if cur == 'shared' and redis.call('ZCARD', KEYS[2]) == 0 then
77
+ redis.call('DEL', KEYS[1])
78
+ cur = false
79
+ end`;
80
+ /**
81
+ * Keeps the users set and the `shared` lock value alive for at least `ttl` without shortening a
82
+ * longer TTL another holder set (holders may use different TTLs).
83
+ */
84
+ const KEEP_SHARED = `
85
+ if redis.call('PTTL', KEYS[2]) < ttl then redis.call('PEXPIRE', KEYS[2], ttl) end
86
+ local lt = redis.call('PTTL', KEYS[1])
87
+ if redis.call('GET', KEYS[1]) ~= 'shared' or lt < ttl then
88
+ redis.call('SET', KEYS[1], 'shared', 'PX', math.max(ttl, lt))
89
+ end`;
90
+ /** KEYS lock, users, intent; ARGV token, cap, ttl. */
91
+ const ACQUIRE_SHARED = `${NOW}
92
+ ${PURGE}
93
+ if cur and cur ~= 'shared' then return 0 end
94
+ local it = redis.call('GET', KEYS[3])
95
+ if it and it ~= ARGV[1] then return 0 end
96
+ if redis.call('ZCARD', KEYS[2]) >= tonumber(ARGV[2]) then return 0 end
97
+ local ttl = tonumber(ARGV[3])
98
+ redis.call('ZADD', KEYS[2], now + ttl, ARGV[1])
99
+ ${KEEP_SHARED}
100
+ return 1`;
101
+ /** KEYS lock, users, intent; ARGV token, ttl, setIntent (0/1), intentTtl. */
102
+ const ACQUIRE_EXCLUSIVE = `${NOW}
103
+ ${PURGE}
104
+ if cur or redis.call('ZCARD', KEYS[2]) > 0 then
105
+ if ARGV[3] == '1' then
106
+ local it = redis.call('GET', KEYS[3])
107
+ if (not it) or it == ARGV[1] then
108
+ redis.call('SET', KEYS[3], ARGV[1], 'PX', tonumber(ARGV[4]))
109
+ end
110
+ end
111
+ return 0
112
+ end
113
+ redis.call('SET', KEYS[1], ARGV[1], 'PX', tonumber(ARGV[2]))
114
+ if redis.call('GET', KEYS[3]) == ARGV[1] then redis.call('DEL', KEYS[3]) end
115
+ return 1`;
116
+ /** KEYS lock, users; ARGV token, ttl, kind. Shared: lost when the member is gone or the lock holds a token. */
117
+ const RENEW = `${NOW}
118
+ local ttl = tonumber(ARGV[2])
119
+ if ARGV[3] == 'exclusive' then
120
+ if redis.call('GET', KEYS[1]) == ARGV[1] then
121
+ redis.call('PEXPIRE', KEYS[1], ttl)
122
+ return 1
123
+ end
124
+ return 0
125
+ end
126
+ redis.call('ZREMRANGEBYSCORE', KEYS[2], '-inf', now)
127
+ if not redis.call('ZSCORE', KEYS[2], ARGV[1]) then return 0 end
128
+ local cur = redis.call('GET', KEYS[1])
129
+ if cur and cur ~= 'shared' then
130
+ redis.call('ZREM', KEYS[2], ARGV[1])
131
+ return 0
132
+ end
133
+ redis.call('ZADD', KEYS[2], 'XX', now + ttl, ARGV[1])
134
+ ${KEEP_SHARED}
135
+ return 1`;
136
+ /** KEYS lock, users; ARGV token, kind. */
137
+ const RELEASE = `${NOW}
138
+ if ARGV[2] == 'exclusive' then
139
+ if redis.call('GET', KEYS[1]) == ARGV[1] then redis.call('DEL', KEYS[1]) end
140
+ return 1
141
+ end
142
+ redis.call('ZREM', KEYS[2], ARGV[1])
143
+ ${PURGE}
144
+ return 1`;
145
+ /** KEYS lock, users; ARGV token, ttl. */
146
+ const DOWNGRADE = `${NOW}
147
+ if redis.call('GET', KEYS[1]) ~= ARGV[1] then return 0 end
148
+ local ttl = tonumber(ARGV[2])
149
+ redis.call('ZADD', KEYS[2], now + ttl, ARGV[1])
150
+ ${KEEP_SHARED}
151
+ return 1`;
152
+ /** KEYS lock, users; returns the live shared count. */
153
+ const COUNT = `${NOW}
154
+ ${PURGE}
155
+ return redis.call('ZCARD', KEYS[2])`;
156
+ /**
157
+ * KEYS holders, busy; ARGV token, ttl. Also writes the busy marker as v2 (kept for at least `ttl`)
158
+ * unless an older kernel's holder owns it.
159
+ */
160
+ const REGISTER = `${NOW}
161
+ local ttl = tonumber(ARGV[2])
162
+ redis.call('HSET', KEYS[1], ARGV[1], now + ttl)
163
+ if redis.call('PTTL', KEYS[1]) < ttl then redis.call('PEXPIRE', KEYS[1], ttl) end
164
+ local b = redis.call('GET', KEYS[2])
165
+ if (not b) or (b == '${BUSY_V2}' and redis.call('PTTL', KEYS[2]) < ttl) then
166
+ redis.call('SET', KEYS[2], '${BUSY_V2}', 'PX', ttl)
167
+ end
168
+ return 1`;
169
+ /** Deletes the v2 busy marker (key `busy`) once no holder record (hash `holders`) is left. */
170
+ const dropV2 = (holders, busy) => `
171
+ if redis.call('HLEN', ${holders}) == 0 and redis.call('GET', ${busy}) == '${BUSY_V2}' then
172
+ redis.call('DEL', ${busy})
173
+ end`;
174
+ /** KEYS holders, busy; ARGV token. */
175
+ const UNREGISTER = `
176
+ redis.call('HDEL', KEYS[1], ARGV[1])
177
+ ${dropV2("KEYS[1]", "KEYS[2]")}
178
+ return 1`;
179
+ /**
180
+ * KEYS lock, users, holders, busy; ARGV the caller's exclusive token (or ''). Removes and returns
181
+ * the holder fields without a live lease (expired fields are dropped without being returned: the
182
+ * in-sandbox time limit ended their commands); claims an older kernel's busy marker only for the
183
+ * caller holding the exclusive lease (`cur == token`, so no shared lease is live and killing
184
+ * everything cannot hit a live sibling). The v2 marker is never claimed; it goes with the last
185
+ * record. Returns { legacy, tag… }.
186
+ */
187
+ const CLAIM = `${NOW}
188
+ ${PURGE}
189
+ local out = {0}
190
+ local f = redis.call('HGETALL', KEYS[3])
191
+ for i = 1, #f, 2 do
192
+ local tok = f[i]
193
+ if tonumber(f[i + 1]) <= now then
194
+ redis.call('HDEL', KEYS[3], tok)
195
+ elseif tok ~= cur and not redis.call('ZSCORE', KEYS[2], tok) then
196
+ redis.call('HDEL', KEYS[3], tok)
197
+ table.insert(out, tok)
198
+ end
199
+ end
200
+ ${dropV2("KEYS[3]", "KEYS[4]")}
201
+ local b = redis.call('GET', KEYS[4])
202
+ if ARGV[1] ~= '' and cur == ARGV[1] and b and b ~= '${BUSY_V2}' then
203
+ redis.call('DEL', KEYS[4])
204
+ out[1] = 1
205
+ end
206
+ return out`;
207
+ /** Compare-and-delete / compare-and-expire of a single-valued key. */
208
+ const DEL_IF = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0`;
209
+ const EXPIRE_IF = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) end return 0`;
210
+ const abortReason = (signal) => signal.reason ?? new DOMException("This operation was aborted", "AbortError");
211
+ function sleep(ms, signal) {
212
+ return new Promise((resolve, reject) => {
213
+ if (signal.aborted) {
214
+ reject(abortReason(signal));
215
+ return;
216
+ }
217
+ const onAbort = () => {
218
+ clearTimeout(t);
219
+ reject(abortReason(signal));
220
+ };
221
+ const t = setTimeout(() => {
222
+ signal.removeEventListener("abort", onAbort);
223
+ resolve();
224
+ }, ms);
225
+ signal.addEventListener("abort", onAbort, { once: true });
226
+ });
227
+ }
228
+ /** The lease protocol; no state in process beyond the leases this process holds. */
229
+ export class SandboxLeases {
230
+ redis;
231
+ log;
232
+ ttlMs;
233
+ pollMs;
234
+ pathTtlMs;
235
+ constructor(d) {
236
+ this.redis = d.redis;
237
+ this.log = d.log;
238
+ this.ttlMs = d.ttlMs ?? SANDBOX_LOCK_TTL_MS;
239
+ this.pollMs = d.pollMs ?? 100;
240
+ this.pathTtlMs = d.pathTtlMs ?? PATH_LEASE_TTL_MS;
241
+ }
242
+ /**
243
+ * TTL of an intent (ms): 4 × the poll interval (a jittered wait is up to 2 × the interval plus
244
+ * round trips), at least {@link INTENT_TTL_FLOOR_MS} so event-loop lag cannot let it lapse.
245
+ */
246
+ get intentTtlMs() {
247
+ return Math.max(this.pollMs * 4, INTENT_TTL_FLOOR_MS);
248
+ }
249
+ keys(conv) {
250
+ return [`${SANDBOX_LOCK_PREFIX}${conv}`, `${USERS_PREFIX}${conv}`, `${INTENT_PREFIX}${conv}`];
251
+ }
252
+ wait(signal) {
253
+ return sleep(this.pollMs + Math.floor(Math.random() * this.pollMs), signal);
254
+ }
255
+ /**
256
+ * A shared lease (`cap` holders at most), polling until one is free; `sandbox.busy` after
257
+ * `capMs`, the signal's reason when it aborts. Not granted while an exclusive lease, an older
258
+ * replica's lock token or another caller's intent exists.
259
+ */
260
+ async acquireShared(conv, cap, signal, capMs) {
261
+ const token = randomUUID();
262
+ const deadline = Date.now() + capMs;
263
+ for (;;) {
264
+ if (signal.aborted)
265
+ throw abortReason(signal);
266
+ const ok = await this.redis.eval(ACQUIRE_SHARED, 3, ...this.keys(conv), token, cap, this.ttlMs);
267
+ if (Number(ok) === 1)
268
+ return this.hold(conv, token, "shared");
269
+ if (Date.now() + this.pollMs > deadline)
270
+ throw sandboxBusy();
271
+ await this.wait(signal);
272
+ }
273
+ }
274
+ /**
275
+ * An exclusive lease, polling until no other lease exists; `sandbox.busy` after `capMs` (no cap
276
+ * when omitted), the signal's reason when it aborts. With `intent` the waiter sets the intent
277
+ * (refreshed while it waits) so new shared leases stop being granted; it is cleared on success
278
+ * and when the wait ends otherwise.
279
+ */
280
+ async acquireExclusive(conv, signal, capMs, o = {}) {
281
+ const token = randomUUID();
282
+ const deadline = capMs === undefined ? Number.POSITIVE_INFINITY : Date.now() + capMs;
283
+ const keys = this.keys(conv);
284
+ try {
285
+ for (;;) {
286
+ if (signal.aborted)
287
+ throw abortReason(signal);
288
+ const ok = await this.redis.eval(ACQUIRE_EXCLUSIVE, 3, ...keys, token, this.ttlMs, o.intent ? 1 : 0, this.intentTtlMs);
289
+ if (Number(ok) === 1)
290
+ return this.hold(conv, token, "exclusive");
291
+ if (Date.now() + this.pollMs > deadline)
292
+ throw sandboxBusy();
293
+ await this.wait(signal);
294
+ }
295
+ }
296
+ catch (e) {
297
+ if (o.intent) {
298
+ await this.redis
299
+ .eval(DEL_IF, 1, keys[2], token)
300
+ .catch((err) => this.log.warn("cannot clear sandbox intent", { conversationId: conv, err }));
301
+ }
302
+ throw e;
303
+ }
304
+ }
305
+ /**
306
+ * One try at an exclusive lease; null when any lease exists. Without `o` (sweeps) it sets no
307
+ * intent. With `o.token` the lease (and, when `o.intent`, the intent set on failure) carries that
308
+ * token, so one waiter can alternate between this and {@link tryShared} with the same token.
309
+ */
310
+ async tryExclusive(conv, o = {}) {
311
+ const token = o.token ?? randomUUID();
312
+ const ok = await this.redis.eval(ACQUIRE_EXCLUSIVE, 3, ...this.keys(conv), token, this.ttlMs, o.intent ? 1 : 0, this.intentTtlMs);
313
+ return Number(ok) === 1 ? this.hold(conv, token, "exclusive") : null;
314
+ }
315
+ /**
316
+ * One try at a shared lease (`cap` holders at most); null when it is not granted. `token` is the
317
+ * lease token; a waiter that set an intent with the same token is let through its own intent
318
+ * (it should then {@link clearIntent}).
319
+ */
320
+ async tryShared(conv, cap, token = randomUUID()) {
321
+ const ok = await this.redis.eval(ACQUIRE_SHARED, 3, ...this.keys(conv), token, cap, this.ttlMs);
322
+ return Number(ok) === 1 ? this.hold(conv, token, "shared") : null;
323
+ }
324
+ /** Deletes the conversation's intent when it is `token`'s. */
325
+ async clearIntent(conv, token) {
326
+ await this.redis.eval(DEL_IF, 1, this.keys(conv)[2], token);
327
+ }
328
+ /** Whether the single busy marker of a kernel before phase C is present (v2 does not count). */
329
+ async legacyMarker(conv) {
330
+ const v = await this.redis.get(`${SANDBOX_BUSY_PREFIX}${conv}`);
331
+ return v !== null && v !== BUSY_V2;
332
+ }
333
+ /** Live shared leases of the conversation. */
334
+ async sharedCount(conv) {
335
+ const [lock, users] = this.keys(conv);
336
+ return Number(await this.redis.eval(COUNT, 2, lock, users));
337
+ }
338
+ /**
339
+ * Records (or refreshes) a holder that may run commands (before its provider calls); `ttlMs`
340
+ * bounds the record. Also keeps the v2 busy marker for older replicas ({@link BUSY_V2}).
341
+ */
342
+ async registerHolder(conv, token, ttlMs) {
343
+ await this.redis.eval(REGISTER, 2, `${HOLDERS_PREFIX}${conv}`, `${SANDBOX_BUSY_PREFIX}${conv}`, token, ttlMs);
344
+ }
345
+ /** Deletes a holder record (after its provider calls); the v2 marker goes with the last one. */
346
+ async unregisterHolder(conv, token) {
347
+ await this.redis.eval(UNREGISTER, 2, `${HOLDERS_PREFIX}${conv}`, `${SANDBOX_BUSY_PREFIX}${conv}`, token);
348
+ }
349
+ /**
350
+ * Tags of holders without a live lease, plus whether an older kernel's single busy marker was
351
+ * present. Atomically claims them (removes the fields) so only one caller kills. That marker is
352
+ * claimed only when `exclusiveToken` is the caller's exclusive lease (no shared lease live).
353
+ */
354
+ async claimDeadHolders(conv, exclusiveToken) {
355
+ const [lock, users] = this.keys(conv);
356
+ const r = (await this.redis.eval(CLAIM, 4, lock, users, `${HOLDERS_PREFIX}${conv}`, `${SANDBOX_BUSY_PREFIX}${conv}`, exclusiveToken ?? ""));
357
+ const [legacy, ...tags] = r;
358
+ return { tags, legacyMarker: Number(legacy) === 1 };
359
+ }
360
+ /**
361
+ * A short lease on one path of the sandbox (the kernel's own read-modify-write of a file); waits
362
+ * up to `capMs`, then throws {@link SandboxPathBusyError}. Renewed while held; a renewal that
363
+ * finds it gone warns and aborts its `signal`.
364
+ */
365
+ async pathLease(conv, path, signal, capMs) {
366
+ const key = `${PATH_PREFIX}${conv}:${createHash("sha256").update(path).digest("hex")}`;
367
+ const token = randomUUID();
368
+ const ttl = this.pathTtlMs;
369
+ const deadline = Date.now() + capMs;
370
+ for (;;) {
371
+ if (signal.aborted)
372
+ throw abortReason(signal);
373
+ if ((await this.redis.set(key, token, "PX", ttl, "NX")) === "OK")
374
+ break;
375
+ if (Date.now() + this.pollMs > deadline)
376
+ throw new SandboxPathBusyError();
377
+ await this.wait(signal);
378
+ }
379
+ const ctl = new AbortController();
380
+ const timer = setInterval(() => {
381
+ this.redis
382
+ .eval(EXPIRE_IF, 1, key, token, ttl)
383
+ .then((ok) => {
384
+ if (Number(ok) === 1 || ctl.signal.aborted)
385
+ return;
386
+ this.log.warn("sandbox path lease lost", { conversationId: conv });
387
+ ctl.abort(new SandboxLockLostError(conv));
388
+ clearInterval(timer);
389
+ })
390
+ .catch((err) => this.log.warn("sandbox path lease renew failed", { conversationId: conv, err }));
391
+ }, Math.max(1, Math.floor(ttl / 3)));
392
+ timer.unref();
393
+ return {
394
+ signal: ctl.signal,
395
+ release: async () => {
396
+ clearInterval(timer);
397
+ await this.redis.eval(DEL_IF, 1, key, token);
398
+ },
399
+ };
400
+ }
401
+ /**
402
+ * Wraps a just-acquired lease: renews every TTL / 3 and remembers the last successful renewal;
403
+ * the lease is lost when a renewal reports it gone or none succeeded for 2/3 TTL (leaving margin
404
+ * so the abort takes effect before the key expires).
405
+ */
406
+ hold(conv, token, kind) {
407
+ const ctl = new AbortController();
408
+ const staleMs = Math.floor((this.ttlMs * 2) / 3);
409
+ let inFlight = false;
410
+ const s = {
411
+ kind,
412
+ token,
413
+ conv,
414
+ ctl,
415
+ lastOk: Date.now(),
416
+ released: false,
417
+ stop: () => {
418
+ clearInterval(timer);
419
+ clearInterval(watchdog);
420
+ },
421
+ };
422
+ const stale = () => Date.now() - s.lastOk > staleMs;
423
+ const tick = () => {
424
+ if (stale())
425
+ this.lose(s);
426
+ if (ctl.signal.aborted) {
427
+ s.stop();
428
+ return;
429
+ }
430
+ if (inFlight)
431
+ return;
432
+ inFlight = true;
433
+ void this.renew(s).finally(() => {
434
+ inFlight = false;
435
+ });
436
+ };
437
+ const timer = setInterval(tick, Math.max(1, Math.floor(this.ttlMs / 3)));
438
+ timer.unref();
439
+ // a finer check (at most every 5 s): notices a renewal that hangs
440
+ const watchdog = setInterval(() => {
441
+ if (stale())
442
+ this.lose(s);
443
+ if (ctl.signal.aborted)
444
+ s.stop();
445
+ }, Math.min(5000, Math.max(1, Math.floor(this.ttlMs / 6))));
446
+ watchdog.unref();
447
+ return this.view(s, kind);
448
+ }
449
+ lose(s) {
450
+ if (s.ctl.signal.aborted)
451
+ return;
452
+ this.log.warn("sandbox lease lost", { conversationId: s.conv, kind: s.kind });
453
+ s.ctl.abort(new SandboxLockLostError(s.conv));
454
+ s.stop();
455
+ }
456
+ async renew(s) {
457
+ if (s.ctl.signal.aborted)
458
+ return false;
459
+ const [lock, users] = this.keys(s.conv);
460
+ // Redis computes the new expiry when it runs the script: freshness counts from the send
461
+ const sent = Date.now();
462
+ try {
463
+ const ok = Number(await this.redis.eval(RENEW, 2, lock, users, s.token, this.ttlMs, s.kind));
464
+ if (ok === 1)
465
+ s.lastOk = Math.max(s.lastOk, sent);
466
+ else
467
+ this.lose(s);
468
+ }
469
+ catch (err) {
470
+ this.log.warn("sandbox lease renew failed", { conversationId: s.conv, err });
471
+ }
472
+ if (Date.now() - s.lastOk > Math.floor((this.ttlMs * 2) / 3))
473
+ this.lose(s);
474
+ return !s.ctl.signal.aborted;
475
+ }
476
+ /** A lease view of `s` as `kind`; a view stops working once the lease changes kind. */
477
+ view(s, kind) {
478
+ const [lock, users] = this.keys(s.conv);
479
+ const current = () => s.kind === kind;
480
+ return {
481
+ kind,
482
+ token: s.token,
483
+ conversationId: s.conv,
484
+ signal: s.ctl.signal,
485
+ renew: () => (current() ? this.renew(s) : Promise.resolve(false)),
486
+ // Any view releases the lease in its current kind (a caller that kept only the exclusive
487
+ // view after a downgrade must not leak the shared lease, which would renew forever)
488
+ release: async () => {
489
+ if (s.released)
490
+ return;
491
+ s.released = true;
492
+ s.stop();
493
+ await this.redis
494
+ .eval(RELEASE, 2, lock, users, s.token, s.kind)
495
+ .catch((err) => this.log.warn("cannot release sandbox lease", { conversationId: s.conv, err }));
496
+ },
497
+ downgrade: async () => {
498
+ if (!current() || s.kind !== "exclusive" || s.released) {
499
+ throw new Error("sandbox lease is not exclusive");
500
+ }
501
+ if (s.ctl.signal.aborted)
502
+ throw abortReason(s.ctl.signal);
503
+ const sent = Date.now();
504
+ const ok = Number(await this.redis.eval(DOWNGRADE, 2, lock, users, s.token, this.ttlMs));
505
+ if (ok !== 1) {
506
+ this.lose(s);
507
+ throw new SandboxLockLostError(s.conv);
508
+ }
509
+ s.kind = "shared";
510
+ s.lastOk = Math.max(s.lastOk, sent);
511
+ return this.view(s, "shared");
512
+ },
513
+ };
514
+ }
515
+ }