@paperclipai/server 0.3.1 → 2026.3.17-canary.2

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 (335) hide show
  1. package/dist/adapters/registry.d.ts.map +1 -1
  2. package/dist/adapters/registry.js +23 -2
  3. package/dist/adapters/registry.js.map +1 -1
  4. package/dist/adapters/types.d.ts +1 -1
  5. package/dist/adapters/types.d.ts.map +1 -1
  6. package/dist/app.d.ts +4 -0
  7. package/dist/app.d.ts.map +1 -1
  8. package/dist/app.js +115 -3
  9. package/dist/app.js.map +1 -1
  10. package/dist/attachment-types.d.ts.map +1 -1
  11. package/dist/attachment-types.js +6 -0
  12. package/dist/attachment-types.js.map +1 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +9 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/home-paths.d.ts +5 -0
  17. package/dist/home-paths.d.ts.map +1 -1
  18. package/dist/home-paths.js +18 -0
  19. package/dist/home-paths.js.map +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +61 -42
  22. package/dist/index.js.map +1 -1
  23. package/dist/routes/access.d.ts.map +1 -1
  24. package/dist/routes/access.js +90 -1
  25. package/dist/routes/access.js.map +1 -1
  26. package/dist/routes/agents.d.ts.map +1 -1
  27. package/dist/routes/agents.js +64 -1
  28. package/dist/routes/agents.js.map +1 -1
  29. package/dist/routes/assets.d.ts.map +1 -1
  30. package/dist/routes/assets.js +191 -13
  31. package/dist/routes/assets.js.map +1 -1
  32. package/dist/routes/companies.d.ts.map +1 -1
  33. package/dist/routes/companies.js +10 -1
  34. package/dist/routes/companies.js.map +1 -1
  35. package/dist/routes/costs.d.ts.map +1 -1
  36. package/dist/routes/costs.js +160 -5
  37. package/dist/routes/costs.js.map +1 -1
  38. package/dist/routes/execution-workspaces.d.ts +3 -0
  39. package/dist/routes/execution-workspaces.d.ts.map +1 -0
  40. package/dist/routes/execution-workspaces.js +165 -0
  41. package/dist/routes/execution-workspaces.js.map +1 -0
  42. package/dist/routes/health.d.ts.map +1 -1
  43. package/dist/routes/health.js +3 -1
  44. package/dist/routes/health.js.map +1 -1
  45. package/dist/routes/index.d.ts +1 -0
  46. package/dist/routes/index.d.ts.map +1 -1
  47. package/dist/routes/index.js +1 -0
  48. package/dist/routes/index.js.map +1 -1
  49. package/dist/routes/instance-settings.d.ts +3 -0
  50. package/dist/routes/instance-settings.d.ts.map +1 -0
  51. package/dist/routes/instance-settings.js +46 -0
  52. package/dist/routes/instance-settings.js.map +1 -0
  53. package/dist/routes/issues.d.ts.map +1 -1
  54. package/dist/routes/issues.js +339 -4
  55. package/dist/routes/issues.js.map +1 -1
  56. package/dist/routes/plugin-ui-static.d.ts +69 -0
  57. package/dist/routes/plugin-ui-static.d.ts.map +1 -0
  58. package/dist/routes/plugin-ui-static.js +411 -0
  59. package/dist/routes/plugin-ui-static.js.map +1 -0
  60. package/dist/routes/plugins.d.ts +120 -0
  61. package/dist/routes/plugins.d.ts.map +1 -0
  62. package/dist/routes/plugins.js +1784 -0
  63. package/dist/routes/plugins.js.map +1 -0
  64. package/dist/routes/projects.d.ts.map +1 -1
  65. package/dist/routes/projects.js +5 -1
  66. package/dist/routes/projects.js.map +1 -1
  67. package/dist/services/access.d.ts +2 -2
  68. package/dist/services/activity-log.d.ts +3 -0
  69. package/dist/services/activity-log.d.ts.map +1 -1
  70. package/dist/services/activity-log.js +34 -0
  71. package/dist/services/activity-log.js.map +1 -1
  72. package/dist/services/agents.d.ts +39 -15
  73. package/dist/services/agents.d.ts.map +1 -1
  74. package/dist/services/agents.js +60 -8
  75. package/dist/services/agents.js.map +1 -1
  76. package/dist/services/approvals.d.ts +5 -5
  77. package/dist/services/approvals.d.ts.map +1 -1
  78. package/dist/services/approvals.js +11 -0
  79. package/dist/services/approvals.js.map +1 -1
  80. package/dist/services/assets.d.ts +4 -4
  81. package/dist/services/budgets.d.ts +38 -0
  82. package/dist/services/budgets.d.ts.map +1 -0
  83. package/dist/services/budgets.js +784 -0
  84. package/dist/services/budgets.js.map +1 -0
  85. package/dist/services/companies.d.ts +39 -418
  86. package/dist/services/companies.d.ts.map +1 -1
  87. package/dist/services/companies.js +157 -21
  88. package/dist/services/companies.js.map +1 -1
  89. package/dist/services/costs.d.ts +67 -3
  90. package/dist/services/costs.d.ts.map +1 -1
  91. package/dist/services/costs.js +185 -57
  92. package/dist/services/costs.js.map +1 -1
  93. package/dist/services/cron.d.ts +80 -0
  94. package/dist/services/cron.d.ts.map +1 -0
  95. package/dist/services/cron.js +300 -0
  96. package/dist/services/cron.js.map +1 -0
  97. package/dist/services/dashboard.d.ts +6 -0
  98. package/dist/services/dashboard.d.ts.map +1 -1
  99. package/dist/services/dashboard.js +9 -0
  100. package/dist/services/dashboard.js.map +1 -1
  101. package/dist/services/documents.d.ts +164 -0
  102. package/dist/services/documents.d.ts.map +1 -0
  103. package/dist/services/documents.js +382 -0
  104. package/dist/services/documents.js.map +1 -0
  105. package/dist/services/execution-workspace-policy.d.ts +2 -1
  106. package/dist/services/execution-workspace-policy.d.ts.map +1 -1
  107. package/dist/services/execution-workspace-policy.js +63 -15
  108. package/dist/services/execution-workspace-policy.js.map +1 -1
  109. package/dist/services/execution-workspaces.d.ts +19 -0
  110. package/dist/services/execution-workspaces.d.ts.map +1 -0
  111. package/dist/services/execution-workspaces.js +87 -0
  112. package/dist/services/execution-workspaces.js.map +1 -0
  113. package/dist/services/finance.d.ts +93 -0
  114. package/dist/services/finance.d.ts.map +1 -0
  115. package/dist/services/finance.js +120 -0
  116. package/dist/services/finance.js.map +1 -0
  117. package/dist/services/heartbeat.d.ts +11 -1
  118. package/dist/services/heartbeat.d.ts.map +1 -1
  119. package/dist/services/heartbeat.js +1292 -559
  120. package/dist/services/heartbeat.js.map +1 -1
  121. package/dist/services/index.d.ts +7 -0
  122. package/dist/services/index.d.ts.map +1 -1
  123. package/dist/services/index.js +7 -0
  124. package/dist/services/index.js.map +1 -1
  125. package/dist/services/instance-settings.d.ts +9 -0
  126. package/dist/services/instance-settings.d.ts.map +1 -0
  127. package/dist/services/instance-settings.js +80 -0
  128. package/dist/services/instance-settings.js.map +1 -0
  129. package/dist/services/issue-approvals.d.ts +1 -1
  130. package/dist/services/issues.d.ts +14 -2
  131. package/dist/services/issues.d.ts.map +1 -1
  132. package/dist/services/issues.js +161 -9
  133. package/dist/services/issues.js.map +1 -1
  134. package/dist/services/live-events.d.ts +5 -0
  135. package/dist/services/live-events.d.ts.map +1 -1
  136. package/dist/services/live-events.js +9 -0
  137. package/dist/services/live-events.js.map +1 -1
  138. package/dist/services/plugin-capability-validator.d.ts +108 -0
  139. package/dist/services/plugin-capability-validator.d.ts.map +1 -0
  140. package/dist/services/plugin-capability-validator.js +268 -0
  141. package/dist/services/plugin-capability-validator.js.map +1 -0
  142. package/dist/services/plugin-config-validator.d.ts +26 -0
  143. package/dist/services/plugin-config-validator.d.ts.map +1 -0
  144. package/dist/services/plugin-config-validator.js +41 -0
  145. package/dist/services/plugin-config-validator.js.map +1 -0
  146. package/dist/services/plugin-dev-watcher.d.ts +30 -0
  147. package/dist/services/plugin-dev-watcher.d.ts.map +1 -0
  148. package/dist/services/plugin-dev-watcher.js +241 -0
  149. package/dist/services/plugin-dev-watcher.js.map +1 -0
  150. package/dist/services/plugin-event-bus.d.ts +149 -0
  151. package/dist/services/plugin-event-bus.d.ts.map +1 -0
  152. package/dist/services/plugin-event-bus.js +258 -0
  153. package/dist/services/plugin-event-bus.js.map +1 -0
  154. package/dist/services/plugin-host-service-cleanup.d.ts +14 -0
  155. package/dist/services/plugin-host-service-cleanup.d.ts.map +1 -0
  156. package/dist/services/plugin-host-service-cleanup.js +37 -0
  157. package/dist/services/plugin-host-service-cleanup.js.map +1 -0
  158. package/dist/services/plugin-host-services.d.ts +13 -0
  159. package/dist/services/plugin-host-services.d.ts.map +1 -0
  160. package/dist/services/plugin-host-services.js +969 -0
  161. package/dist/services/plugin-host-services.js.map +1 -0
  162. package/dist/services/plugin-job-coordinator.d.ts +81 -0
  163. package/dist/services/plugin-job-coordinator.d.ts.map +1 -0
  164. package/dist/services/plugin-job-coordinator.js +172 -0
  165. package/dist/services/plugin-job-coordinator.js.map +1 -0
  166. package/dist/services/plugin-job-scheduler.d.ts +163 -0
  167. package/dist/services/plugin-job-scheduler.d.ts.map +1 -0
  168. package/dist/services/plugin-job-scheduler.js +454 -0
  169. package/dist/services/plugin-job-scheduler.js.map +1 -0
  170. package/dist/services/plugin-job-store.d.ts +208 -0
  171. package/dist/services/plugin-job-store.d.ts.map +1 -0
  172. package/dist/services/plugin-job-store.js +350 -0
  173. package/dist/services/plugin-job-store.js.map +1 -0
  174. package/dist/services/plugin-lifecycle.d.ts +203 -0
  175. package/dist/services/plugin-lifecycle.d.ts.map +1 -0
  176. package/dist/services/plugin-lifecycle.js +476 -0
  177. package/dist/services/plugin-lifecycle.js.map +1 -0
  178. package/dist/services/plugin-loader.d.ts +441 -0
  179. package/dist/services/plugin-loader.d.ts.map +1 -0
  180. package/dist/services/plugin-loader.js +1192 -0
  181. package/dist/services/plugin-loader.js.map +1 -0
  182. package/dist/services/plugin-log-retention.d.ts +20 -0
  183. package/dist/services/plugin-log-retention.d.ts.map +1 -0
  184. package/dist/services/plugin-log-retention.js +63 -0
  185. package/dist/services/plugin-log-retention.js.map +1 -0
  186. package/dist/services/plugin-manifest-validator.d.ts +79 -0
  187. package/dist/services/plugin-manifest-validator.d.ts.map +1 -0
  188. package/dist/services/plugin-manifest-validator.js +84 -0
  189. package/dist/services/plugin-manifest-validator.js.map +1 -0
  190. package/dist/services/plugin-registry.d.ts +2542 -0
  191. package/dist/services/plugin-registry.d.ts.map +1 -0
  192. package/dist/services/plugin-registry.js +539 -0
  193. package/dist/services/plugin-registry.js.map +1 -0
  194. package/dist/services/plugin-runtime-sandbox.d.ts +40 -0
  195. package/dist/services/plugin-runtime-sandbox.d.ts.map +1 -0
  196. package/dist/services/plugin-runtime-sandbox.js +154 -0
  197. package/dist/services/plugin-runtime-sandbox.js.map +1 -0
  198. package/dist/services/plugin-secrets-handler.d.ts +81 -0
  199. package/dist/services/plugin-secrets-handler.d.ts.map +1 -0
  200. package/dist/services/plugin-secrets-handler.js +275 -0
  201. package/dist/services/plugin-secrets-handler.js.map +1 -0
  202. package/dist/services/plugin-state-store.d.ts +92 -0
  203. package/dist/services/plugin-state-store.d.ts.map +1 -0
  204. package/dist/services/plugin-state-store.js +190 -0
  205. package/dist/services/plugin-state-store.js.map +1 -0
  206. package/dist/services/plugin-stream-bus.d.ts +29 -0
  207. package/dist/services/plugin-stream-bus.d.ts.map +1 -0
  208. package/dist/services/plugin-stream-bus.js +48 -0
  209. package/dist/services/plugin-stream-bus.js.map +1 -0
  210. package/dist/services/plugin-tool-dispatcher.d.ts +180 -0
  211. package/dist/services/plugin-tool-dispatcher.d.ts.map +1 -0
  212. package/dist/services/plugin-tool-dispatcher.js +224 -0
  213. package/dist/services/plugin-tool-dispatcher.js.map +1 -0
  214. package/dist/services/plugin-tool-registry.d.ts +192 -0
  215. package/dist/services/plugin-tool-registry.d.ts.map +1 -0
  216. package/dist/services/plugin-tool-registry.js +224 -0
  217. package/dist/services/plugin-tool-registry.js.map +1 -0
  218. package/dist/services/plugin-worker-manager.d.ts +260 -0
  219. package/dist/services/plugin-worker-manager.d.ts.map +1 -0
  220. package/dist/services/plugin-worker-manager.js +835 -0
  221. package/dist/services/plugin-worker-manager.js.map +1 -0
  222. package/dist/services/projects.d.ts +12 -1
  223. package/dist/services/projects.d.ts.map +1 -1
  224. package/dist/services/projects.js +100 -4
  225. package/dist/services/projects.js.map +1 -1
  226. package/dist/services/quota-windows.d.ts +9 -0
  227. package/dist/services/quota-windows.d.ts.map +1 -0
  228. package/dist/services/quota-windows.js +56 -0
  229. package/dist/services/quota-windows.js.map +1 -0
  230. package/dist/services/secrets.d.ts +5 -5
  231. package/dist/services/work-products.d.ts +14 -0
  232. package/dist/services/work-products.d.ts.map +1 -0
  233. package/dist/services/work-products.js +100 -0
  234. package/dist/services/work-products.js.map +1 -0
  235. package/dist/services/workspace-operation-log-store.d.ts +33 -0
  236. package/dist/services/workspace-operation-log-store.d.ts.map +1 -0
  237. package/dist/services/workspace-operation-log-store.js +110 -0
  238. package/dist/services/workspace-operation-log-store.js.map +1 -0
  239. package/dist/services/workspace-operations.d.ts +44 -0
  240. package/dist/services/workspace-operations.d.ts.map +1 -0
  241. package/dist/services/workspace-operations.js +204 -0
  242. package/dist/services/workspace-operations.js.map +1 -0
  243. package/dist/services/workspace-runtime.d.ts +43 -5
  244. package/dist/services/workspace-runtime.d.ts.map +1 -1
  245. package/dist/services/workspace-runtime.js +413 -30
  246. package/dist/services/workspace-runtime.js.map +1 -1
  247. package/dist/ui-branding.d.ts +10 -1
  248. package/dist/ui-branding.d.ts.map +1 -1
  249. package/dist/ui-branding.js +164 -14
  250. package/dist/ui-branding.js.map +1 -1
  251. package/dist/version.d.ts +2 -0
  252. package/dist/version.d.ts.map +1 -0
  253. package/dist/version.js +5 -0
  254. package/dist/version.js.map +1 -0
  255. package/package.json +32 -14
  256. package/skills/paperclip/SKILL.md +34 -20
  257. package/skills/paperclip-create-plugin/SKILL.md +101 -0
  258. package/ui-dist/assets/{_basePickBy-B0xbZITw.js → _basePickBy-Chytvk8p.js} +1 -1
  259. package/ui-dist/assets/{_baseUniq-Cfd5u3qc.js → _baseUniq-BVL8b2zA.js} +1 -1
  260. package/ui-dist/assets/{arc-7Xbu8tBF.js → arc-D5hJS7gD.js} +1 -1
  261. package/ui-dist/assets/{architectureDiagram-VXUJARFQ-C8F7ZRYc.js → architectureDiagram-VXUJARFQ-BFq3iN58.js} +1 -1
  262. package/ui-dist/assets/{blockDiagram-VD42YOAC-Dp08a65A.js → blockDiagram-VD42YOAC-B69ok7vL.js} +1 -1
  263. package/ui-dist/assets/{c4Diagram-YG6GDRKO-BJfslTgZ.js → c4Diagram-YG6GDRKO-FE0D9aiA.js} +1 -1
  264. package/ui-dist/assets/channel-CiKAknwU.js +1 -0
  265. package/ui-dist/assets/{chunk-4BX2VUAB-BhaYWH7e.js → chunk-4BX2VUAB-SAhF6g-l.js} +1 -1
  266. package/ui-dist/assets/{chunk-55IACEB6-BOzJUsYW.js → chunk-55IACEB6-yxKD70bX.js} +1 -1
  267. package/ui-dist/assets/{chunk-B4BG7PRW-CbVcziyE.js → chunk-B4BG7PRW-qB4VaAIx.js} +1 -1
  268. package/ui-dist/assets/{chunk-DI55MBZ5-DghPtP3y.js → chunk-DI55MBZ5-B4NIq1Ee.js} +1 -1
  269. package/ui-dist/assets/{chunk-FMBD7UC4-CB095Kfn.js → chunk-FMBD7UC4-B5kLoRe2.js} +1 -1
  270. package/ui-dist/assets/{chunk-QN33PNHL-DfpzfDqJ.js → chunk-QN33PNHL-BVVG2n_X.js} +1 -1
  271. package/ui-dist/assets/{chunk-QZHKN3VN-Df3d5z1y.js → chunk-QZHKN3VN-ncMubclp.js} +1 -1
  272. package/ui-dist/assets/{chunk-TZMSLE5B-B6tX6bZI.js → chunk-TZMSLE5B-DSyjQk8C.js} +1 -1
  273. package/ui-dist/assets/classDiagram-2ON5EDUG-B93oQX02.js +1 -0
  274. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-B93oQX02.js +1 -0
  275. package/ui-dist/assets/clone-Dld-3yq0.js +1 -0
  276. package/ui-dist/assets/{cose-bilkent-S5V4N54A-38wg_s9V.js → cose-bilkent-S5V4N54A-BAikNewC.js} +1 -1
  277. package/ui-dist/assets/{dagre-6UL2VRFP-DPVj3XLS.js → dagre-6UL2VRFP-j223UI8_.js} +1 -1
  278. package/ui-dist/assets/{diagram-PSM6KHXK-BJh89zUA.js → diagram-PSM6KHXK-Bhg27ZsN.js} +1 -1
  279. package/ui-dist/assets/{diagram-QEK2KX5R-Bw0m_j10.js → diagram-QEK2KX5R-BmtcrTHq.js} +1 -1
  280. package/ui-dist/assets/{diagram-S2PKOQOG-D7_M2F3u.js → diagram-S2PKOQOG-x8XuP7dH.js} +1 -1
  281. package/ui-dist/assets/{erDiagram-Q2GNP2WA-CLsjqTWP.js → erDiagram-Q2GNP2WA-Dc8mHvB-.js} +1 -1
  282. package/ui-dist/assets/{flowDiagram-NV44I4VS-BZZ7ezVB.js → flowDiagram-NV44I4VS-Br08v6xs.js} +1 -1
  283. package/ui-dist/assets/{ganttDiagram-JELNMOA3-KzsFuwBt.js → ganttDiagram-JELNMOA3-CksKhiV8.js} +1 -1
  284. package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-DSHtY7Vu.js → gitGraphDiagram-V2S2FVAM-DmitztAD.js} +1 -1
  285. package/ui-dist/assets/{graph-luIG1UAS.js → graph-Xzh7bNfz.js} +1 -1
  286. package/ui-dist/assets/{index-nFjLambq.js → index-B-MPJNeS.js} +1 -1
  287. package/ui-dist/assets/{index-RUBFVv6t.js → index-BAOC35TS.js} +1 -1
  288. package/ui-dist/assets/{index-CaR9XM4h.js → index-BCtrpKdJ.js} +1 -1
  289. package/ui-dist/assets/{index-itc7BfMy.js → index-B_mlwDCG.js} +1 -1
  290. package/ui-dist/assets/{index-1gX09-Fl.js → index-Bp8oJMwx.js} +1 -1
  291. package/ui-dist/assets/{index-B_3g3Rie.js → index-BqOfMG1d.js} +1 -1
  292. package/ui-dist/assets/{index-uR3zjYaD.js → index-C0hw7cTu.js} +1 -1
  293. package/ui-dist/assets/{index-Ds7vLTSK.js → index-C1ENEd6Y.js} +1 -1
  294. package/ui-dist/assets/{index-CeDCs_2i.js → index-CGB1OHQi.js} +1 -1
  295. package/ui-dist/assets/{index-DBy0vJy3.js → index-CZlfAt7E.js} +1 -1
  296. package/ui-dist/assets/{index-00kuG4sI.js → index-CeL-QouZ.js} +1 -1
  297. package/ui-dist/assets/{index-Bv4xCjxl.js → index-Cz0TxJbw.js} +1 -1
  298. package/ui-dist/assets/{index-DJf8diAA.js → index-DBxUCwDG.js} +1 -1
  299. package/ui-dist/assets/{index-DnIPDZLp.js → index-DGzQUMWC.js} +1 -1
  300. package/ui-dist/assets/{index-CbGtsjW7.js → index-DI9dat7c.js} +1 -1
  301. package/ui-dist/assets/{index-BfB4lKJN.js → index-DMpo-QSZ.js} +1 -1
  302. package/ui-dist/assets/{index-DcfLFstG.js → index-DQ6xyFaN.js} +1 -1
  303. package/ui-dist/assets/{index-B1ZMzzs0.js → index-DWMQ1Fyj.js} +1 -1
  304. package/ui-dist/assets/index-DbrFgvLd.css +1 -0
  305. package/ui-dist/assets/{index-C8XyGAr9.js → index-Er4i9aLN.js} +1 -1
  306. package/ui-dist/assets/{index-C2SZYIDA.js → index-I5K-yQx5.js} +1 -1
  307. package/ui-dist/assets/index-MuodIbDJ.js +1120 -0
  308. package/ui-dist/assets/{index-BeeKMqNU.js → index-auLN8BHe.js} +1 -1
  309. package/ui-dist/assets/{index-DoTq-BeR.js → index-xw01-Pl4.js} +1 -1
  310. package/ui-dist/assets/{infoDiagram-HS3SLOUP-CDEfWpme.js → infoDiagram-HS3SLOUP-DHb4TS1W.js} +1 -1
  311. package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-i7nsbg_Y.js → journeyDiagram-XKPGCS4Q-CLRBeXVU.js} +1 -1
  312. package/ui-dist/assets/{kanban-definition-3W4ZIXB7-DDlx1qVE.js → kanban-definition-3W4ZIXB7-CsQp6woE.js} +1 -1
  313. package/ui-dist/assets/{layout-C7AtmJzX.js → layout-DwD9tZ5r.js} +1 -1
  314. package/ui-dist/assets/{linear-C5jHT-WP.js → linear-DF9n_1d3.js} +1 -1
  315. package/ui-dist/assets/{mermaid.core-C8YQ4fcY.js → mermaid.core-CDm4Crkq.js} +4 -4
  316. package/ui-dist/assets/{mindmap-definition-VGOIOE7T-B9m9PuUg.js → mindmap-definition-VGOIOE7T-CVzU1x4i.js} +1 -1
  317. package/ui-dist/assets/{pieDiagram-ADFJNKIX-SvKywCSE.js → pieDiagram-ADFJNKIX-CWgl0Up8.js} +1 -1
  318. package/ui-dist/assets/{quadrantDiagram-AYHSOK5B-QfBPm7Y1.js → quadrantDiagram-AYHSOK5B-CcedyN6Q.js} +1 -1
  319. package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-DnfQQuwi.js → requirementDiagram-UZGBJVZJ-DZ4BVz3s.js} +1 -1
  320. package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-DvLkjzIW.js → sankeyDiagram-TZEHDZUN-CLHEx34B.js} +1 -1
  321. package/ui-dist/assets/{sequenceDiagram-WL72ISMW-5Lq2rWBc.js → sequenceDiagram-WL72ISMW-nmTJbPyy.js} +1 -1
  322. package/ui-dist/assets/{stateDiagram-FKZM4ZOC-DcYPHDyi.js → stateDiagram-FKZM4ZOC-_UGwFEqN.js} +1 -1
  323. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-CvFVoOUd.js +1 -0
  324. package/ui-dist/assets/{timeline-definition-IT6M3QCI-lrN4JmWa.js → timeline-definition-IT6M3QCI-Bibk8Daj.js} +1 -1
  325. package/ui-dist/assets/{treemap-GDKQZRPO-DFJKIBTQ.js → treemap-GDKQZRPO-Ch3rlaqT.js} +1 -1
  326. package/ui-dist/assets/{xychartDiagram-PRI3JC2R-CT47vtdm.js → xychartDiagram-PRI3JC2R-DBUudr-E.js} +1 -1
  327. package/ui-dist/index.html +4 -2
  328. package/skills/create-agent-adapter/SKILL.md +0 -718
  329. package/ui-dist/assets/channel-BViQDbSq.js +0 -1
  330. package/ui-dist/assets/classDiagram-2ON5EDUG-CjMCmxMT.js +0 -1
  331. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-CjMCmxMT.js +0 -1
  332. package/ui-dist/assets/clone-Dbn9wtE1.js +0 -1
  333. package/ui-dist/assets/index-BHP9dico.js +0 -1006
  334. package/ui-dist/assets/index-BfNaDZnn.css +0 -1
  335. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-nv50YYrl.js +0 -1
@@ -0,0 +1,784 @@
1
+ import { and, desc, eq, gte, inArray, lt, ne, sql } from "drizzle-orm";
2
+ import { agents, approvals, budgetIncidents, budgetPolicies, companies, costEvents, projects, } from "@paperclipai/db";
3
+ import { notFound, unprocessable } from "../errors.js";
4
+ import { logActivity } from "./activity-log.js";
5
+ function currentUtcMonthWindow(now = new Date()) {
6
+ const year = now.getUTCFullYear();
7
+ const month = now.getUTCMonth();
8
+ const start = new Date(Date.UTC(year, month, 1, 0, 0, 0, 0));
9
+ const end = new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0));
10
+ return { start, end };
11
+ }
12
+ function resolveWindow(windowKind, now = new Date()) {
13
+ if (windowKind === "lifetime") {
14
+ return {
15
+ start: new Date(Date.UTC(1970, 0, 1, 0, 0, 0, 0)),
16
+ end: new Date(Date.UTC(9999, 0, 1, 0, 0, 0, 0)),
17
+ };
18
+ }
19
+ return currentUtcMonthWindow(now);
20
+ }
21
+ function budgetStatusFromObserved(observedAmount, amount, warnPercent) {
22
+ if (amount <= 0)
23
+ return "ok";
24
+ if (observedAmount >= amount)
25
+ return "hard_stop";
26
+ if (observedAmount >= Math.ceil((amount * warnPercent) / 100))
27
+ return "warning";
28
+ return "ok";
29
+ }
30
+ function normalizeScopeName(scopeType, name) {
31
+ if (scopeType === "company")
32
+ return name;
33
+ return name.trim().length > 0 ? name : scopeType;
34
+ }
35
+ async function resolveScopeRecord(db, scopeType, scopeId) {
36
+ if (scopeType === "company") {
37
+ const row = await db
38
+ .select({
39
+ companyId: companies.id,
40
+ name: companies.name,
41
+ status: companies.status,
42
+ pauseReason: companies.pauseReason,
43
+ pausedAt: companies.pausedAt,
44
+ })
45
+ .from(companies)
46
+ .where(eq(companies.id, scopeId))
47
+ .then((rows) => rows[0] ?? null);
48
+ if (!row)
49
+ throw notFound("Company not found");
50
+ return {
51
+ companyId: row.companyId,
52
+ name: row.name,
53
+ paused: row.status === "paused" || Boolean(row.pausedAt),
54
+ pauseReason: row.pauseReason ?? null,
55
+ };
56
+ }
57
+ if (scopeType === "agent") {
58
+ const row = await db
59
+ .select({
60
+ companyId: agents.companyId,
61
+ name: agents.name,
62
+ status: agents.status,
63
+ pauseReason: agents.pauseReason,
64
+ })
65
+ .from(agents)
66
+ .where(eq(agents.id, scopeId))
67
+ .then((rows) => rows[0] ?? null);
68
+ if (!row)
69
+ throw notFound("Agent not found");
70
+ return {
71
+ companyId: row.companyId,
72
+ name: row.name,
73
+ paused: row.status === "paused",
74
+ pauseReason: row.pauseReason ?? null,
75
+ };
76
+ }
77
+ const row = await db
78
+ .select({
79
+ companyId: projects.companyId,
80
+ name: projects.name,
81
+ pauseReason: projects.pauseReason,
82
+ pausedAt: projects.pausedAt,
83
+ })
84
+ .from(projects)
85
+ .where(eq(projects.id, scopeId))
86
+ .then((rows) => rows[0] ?? null);
87
+ if (!row)
88
+ throw notFound("Project not found");
89
+ return {
90
+ companyId: row.companyId,
91
+ name: row.name,
92
+ paused: Boolean(row.pausedAt),
93
+ pauseReason: row.pauseReason ?? null,
94
+ };
95
+ }
96
+ async function computeObservedAmount(db, policy) {
97
+ if (policy.metric !== "billed_cents")
98
+ return 0;
99
+ const conditions = [eq(costEvents.companyId, policy.companyId)];
100
+ if (policy.scopeType === "agent")
101
+ conditions.push(eq(costEvents.agentId, policy.scopeId));
102
+ if (policy.scopeType === "project")
103
+ conditions.push(eq(costEvents.projectId, policy.scopeId));
104
+ const { start, end } = resolveWindow(policy.windowKind);
105
+ if (policy.windowKind === "calendar_month_utc") {
106
+ conditions.push(gte(costEvents.occurredAt, start));
107
+ conditions.push(lt(costEvents.occurredAt, end));
108
+ }
109
+ const [row] = await db
110
+ .select({
111
+ total: sql `coalesce(sum(${costEvents.costCents}), 0)::int`,
112
+ })
113
+ .from(costEvents)
114
+ .where(and(...conditions));
115
+ return Number(row?.total ?? 0);
116
+ }
117
+ function buildApprovalPayload(input) {
118
+ return {
119
+ scopeType: input.policy.scopeType,
120
+ scopeId: input.policy.scopeId,
121
+ scopeName: input.scopeName,
122
+ metric: input.policy.metric,
123
+ windowKind: input.policy.windowKind,
124
+ thresholdType: input.thresholdType,
125
+ budgetAmount: input.policy.amount,
126
+ observedAmount: input.amountObserved,
127
+ warnPercent: input.policy.warnPercent,
128
+ windowStart: input.windowStart.toISOString(),
129
+ windowEnd: input.windowEnd.toISOString(),
130
+ policyId: input.policy.id,
131
+ guidance: "Raise the budget and resume the scope, or keep the scope paused.",
132
+ };
133
+ }
134
+ async function markApprovalStatus(db, approvalId, status, decisionNote, decidedByUserId) {
135
+ if (!approvalId)
136
+ return;
137
+ await db
138
+ .update(approvals)
139
+ .set({
140
+ status,
141
+ decisionNote: decisionNote ?? null,
142
+ decidedByUserId,
143
+ decidedAt: new Date(),
144
+ updatedAt: new Date(),
145
+ })
146
+ .where(eq(approvals.id, approvalId));
147
+ }
148
+ export function budgetService(db, hooks = {}) {
149
+ async function pauseScopeForBudget(policy) {
150
+ const now = new Date();
151
+ if (policy.scopeType === "agent") {
152
+ await db
153
+ .update(agents)
154
+ .set({
155
+ status: "paused",
156
+ pauseReason: "budget",
157
+ pausedAt: now,
158
+ updatedAt: now,
159
+ })
160
+ .where(and(eq(agents.id, policy.scopeId), inArray(agents.status, ["active", "idle", "running", "error"])));
161
+ return;
162
+ }
163
+ if (policy.scopeType === "project") {
164
+ await db
165
+ .update(projects)
166
+ .set({
167
+ pauseReason: "budget",
168
+ pausedAt: now,
169
+ updatedAt: now,
170
+ })
171
+ .where(eq(projects.id, policy.scopeId));
172
+ return;
173
+ }
174
+ await db
175
+ .update(companies)
176
+ .set({
177
+ status: "paused",
178
+ pauseReason: "budget",
179
+ pausedAt: now,
180
+ updatedAt: now,
181
+ })
182
+ .where(eq(companies.id, policy.scopeId));
183
+ }
184
+ async function pauseAndCancelScopeForBudget(policy) {
185
+ await pauseScopeForBudget(policy);
186
+ await hooks.cancelWorkForScope?.({
187
+ companyId: policy.companyId,
188
+ scopeType: policy.scopeType,
189
+ scopeId: policy.scopeId,
190
+ });
191
+ }
192
+ async function resumeScopeFromBudget(policy) {
193
+ const now = new Date();
194
+ if (policy.scopeType === "agent") {
195
+ await db
196
+ .update(agents)
197
+ .set({
198
+ status: "idle",
199
+ pauseReason: null,
200
+ pausedAt: null,
201
+ updatedAt: now,
202
+ })
203
+ .where(and(eq(agents.id, policy.scopeId), eq(agents.pauseReason, "budget")));
204
+ return;
205
+ }
206
+ if (policy.scopeType === "project") {
207
+ await db
208
+ .update(projects)
209
+ .set({
210
+ pauseReason: null,
211
+ pausedAt: null,
212
+ updatedAt: now,
213
+ })
214
+ .where(and(eq(projects.id, policy.scopeId), eq(projects.pauseReason, "budget")));
215
+ return;
216
+ }
217
+ await db
218
+ .update(companies)
219
+ .set({
220
+ status: "active",
221
+ pauseReason: null,
222
+ pausedAt: null,
223
+ updatedAt: now,
224
+ })
225
+ .where(and(eq(companies.id, policy.scopeId), eq(companies.pauseReason, "budget")));
226
+ }
227
+ async function getPolicyRow(policyId) {
228
+ const policy = await db
229
+ .select()
230
+ .from(budgetPolicies)
231
+ .where(eq(budgetPolicies.id, policyId))
232
+ .then((rows) => rows[0] ?? null);
233
+ if (!policy)
234
+ throw notFound("Budget policy not found");
235
+ return policy;
236
+ }
237
+ async function listPolicyRows(companyId) {
238
+ return db
239
+ .select()
240
+ .from(budgetPolicies)
241
+ .where(eq(budgetPolicies.companyId, companyId))
242
+ .orderBy(desc(budgetPolicies.updatedAt));
243
+ }
244
+ async function buildPolicySummary(policy) {
245
+ const scope = await resolveScopeRecord(db, policy.scopeType, policy.scopeId);
246
+ const observedAmount = await computeObservedAmount(db, policy);
247
+ const { start, end } = resolveWindow(policy.windowKind);
248
+ const amount = policy.isActive ? policy.amount : 0;
249
+ const utilizationPercent = amount > 0 ? Number(((observedAmount / amount) * 100).toFixed(2)) : 0;
250
+ return {
251
+ policyId: policy.id,
252
+ companyId: policy.companyId,
253
+ scopeType: policy.scopeType,
254
+ scopeId: policy.scopeId,
255
+ scopeName: normalizeScopeName(policy.scopeType, scope.name),
256
+ metric: policy.metric,
257
+ windowKind: policy.windowKind,
258
+ amount,
259
+ observedAmount,
260
+ remainingAmount: amount > 0 ? Math.max(0, amount - observedAmount) : 0,
261
+ utilizationPercent,
262
+ warnPercent: policy.warnPercent,
263
+ hardStopEnabled: policy.hardStopEnabled,
264
+ notifyEnabled: policy.notifyEnabled,
265
+ isActive: policy.isActive,
266
+ status: policy.isActive
267
+ ? budgetStatusFromObserved(observedAmount, amount, policy.warnPercent)
268
+ : "ok",
269
+ paused: scope.paused,
270
+ pauseReason: scope.pauseReason,
271
+ windowStart: start,
272
+ windowEnd: end,
273
+ };
274
+ }
275
+ async function createIncidentIfNeeded(policy, thresholdType, amountObserved) {
276
+ const { start, end } = resolveWindow(policy.windowKind);
277
+ const existing = await db
278
+ .select()
279
+ .from(budgetIncidents)
280
+ .where(and(eq(budgetIncidents.policyId, policy.id), eq(budgetIncidents.windowStart, start), eq(budgetIncidents.thresholdType, thresholdType), ne(budgetIncidents.status, "dismissed")))
281
+ .then((rows) => rows[0] ?? null);
282
+ if (existing)
283
+ return existing;
284
+ const scope = await resolveScopeRecord(db, policy.scopeType, policy.scopeId);
285
+ const payload = buildApprovalPayload({
286
+ policy,
287
+ scopeName: normalizeScopeName(policy.scopeType, scope.name),
288
+ thresholdType,
289
+ amountObserved,
290
+ windowStart: start,
291
+ windowEnd: end,
292
+ });
293
+ const approval = thresholdType === "hard"
294
+ ? await db
295
+ .insert(approvals)
296
+ .values({
297
+ companyId: policy.companyId,
298
+ type: "budget_override_required",
299
+ requestedByUserId: null,
300
+ requestedByAgentId: null,
301
+ status: "pending",
302
+ payload,
303
+ })
304
+ .returning()
305
+ .then((rows) => rows[0] ?? null)
306
+ : null;
307
+ return db
308
+ .insert(budgetIncidents)
309
+ .values({
310
+ companyId: policy.companyId,
311
+ policyId: policy.id,
312
+ scopeType: policy.scopeType,
313
+ scopeId: policy.scopeId,
314
+ metric: policy.metric,
315
+ windowKind: policy.windowKind,
316
+ windowStart: start,
317
+ windowEnd: end,
318
+ thresholdType,
319
+ amountLimit: policy.amount,
320
+ amountObserved,
321
+ status: "open",
322
+ approvalId: approval?.id ?? null,
323
+ })
324
+ .returning()
325
+ .then((rows) => rows[0] ?? null);
326
+ }
327
+ async function resolveOpenSoftIncidents(policyId) {
328
+ await db
329
+ .update(budgetIncidents)
330
+ .set({
331
+ status: "resolved",
332
+ resolvedAt: new Date(),
333
+ updatedAt: new Date(),
334
+ })
335
+ .where(and(eq(budgetIncidents.policyId, policyId), eq(budgetIncidents.thresholdType, "soft"), eq(budgetIncidents.status, "open")));
336
+ }
337
+ async function resolveOpenIncidentsForPolicy(policyId, approvalStatus, decidedByUserId) {
338
+ const openRows = await db
339
+ .select()
340
+ .from(budgetIncidents)
341
+ .where(and(eq(budgetIncidents.policyId, policyId), eq(budgetIncidents.status, "open")));
342
+ await db
343
+ .update(budgetIncidents)
344
+ .set({
345
+ status: "resolved",
346
+ resolvedAt: new Date(),
347
+ updatedAt: new Date(),
348
+ })
349
+ .where(and(eq(budgetIncidents.policyId, policyId), eq(budgetIncidents.status, "open")));
350
+ if (!approvalStatus || !decidedByUserId)
351
+ return;
352
+ for (const row of openRows) {
353
+ await markApprovalStatus(db, row.approvalId ?? null, approvalStatus, "Resolved via budget update", decidedByUserId);
354
+ }
355
+ }
356
+ async function hydrateIncidentRows(rows) {
357
+ const approvalIds = rows.map((row) => row.approvalId).filter((value) => Boolean(value));
358
+ const approvalRows = approvalIds.length > 0
359
+ ? await db
360
+ .select({ id: approvals.id, status: approvals.status })
361
+ .from(approvals)
362
+ .where(inArray(approvals.id, approvalIds))
363
+ : [];
364
+ const approvalStatusById = new Map(approvalRows.map((row) => [row.id, row.status]));
365
+ return Promise.all(rows.map(async (row) => {
366
+ const scope = await resolveScopeRecord(db, row.scopeType, row.scopeId);
367
+ return {
368
+ id: row.id,
369
+ companyId: row.companyId,
370
+ policyId: row.policyId,
371
+ scopeType: row.scopeType,
372
+ scopeId: row.scopeId,
373
+ scopeName: normalizeScopeName(row.scopeType, scope.name),
374
+ metric: row.metric,
375
+ windowKind: row.windowKind,
376
+ windowStart: row.windowStart,
377
+ windowEnd: row.windowEnd,
378
+ thresholdType: row.thresholdType,
379
+ amountLimit: row.amountLimit,
380
+ amountObserved: row.amountObserved,
381
+ status: row.status,
382
+ approvalId: row.approvalId ?? null,
383
+ approvalStatus: row.approvalId ? approvalStatusById.get(row.approvalId) ?? null : null,
384
+ resolvedAt: row.resolvedAt ?? null,
385
+ createdAt: row.createdAt,
386
+ updatedAt: row.updatedAt,
387
+ };
388
+ }));
389
+ }
390
+ return {
391
+ listPolicies: async (companyId) => {
392
+ const rows = await listPolicyRows(companyId);
393
+ return rows.map((row) => ({
394
+ ...row,
395
+ scopeType: row.scopeType,
396
+ metric: row.metric,
397
+ windowKind: row.windowKind,
398
+ }));
399
+ },
400
+ upsertPolicy: async (companyId, input, actorUserId) => {
401
+ const scope = await resolveScopeRecord(db, input.scopeType, input.scopeId);
402
+ if (scope.companyId !== companyId) {
403
+ throw unprocessable("Budget scope does not belong to company");
404
+ }
405
+ const metric = input.metric ?? "billed_cents";
406
+ const windowKind = input.windowKind ?? (input.scopeType === "project" ? "lifetime" : "calendar_month_utc");
407
+ const amount = Math.max(0, Math.floor(input.amount));
408
+ const nextIsActive = amount > 0 && (input.isActive ?? true);
409
+ const existing = await db
410
+ .select()
411
+ .from(budgetPolicies)
412
+ .where(and(eq(budgetPolicies.companyId, companyId), eq(budgetPolicies.scopeType, input.scopeType), eq(budgetPolicies.scopeId, input.scopeId), eq(budgetPolicies.metric, metric), eq(budgetPolicies.windowKind, windowKind)))
413
+ .then((rows) => rows[0] ?? null);
414
+ const now = new Date();
415
+ const row = existing
416
+ ? await db
417
+ .update(budgetPolicies)
418
+ .set({
419
+ amount,
420
+ warnPercent: input.warnPercent ?? existing.warnPercent,
421
+ hardStopEnabled: input.hardStopEnabled ?? existing.hardStopEnabled,
422
+ notifyEnabled: input.notifyEnabled ?? existing.notifyEnabled,
423
+ isActive: nextIsActive,
424
+ updatedByUserId: actorUserId,
425
+ updatedAt: now,
426
+ })
427
+ .where(eq(budgetPolicies.id, existing.id))
428
+ .returning()
429
+ .then((rows) => rows[0])
430
+ : await db
431
+ .insert(budgetPolicies)
432
+ .values({
433
+ companyId,
434
+ scopeType: input.scopeType,
435
+ scopeId: input.scopeId,
436
+ metric,
437
+ windowKind,
438
+ amount,
439
+ warnPercent: input.warnPercent ?? 80,
440
+ hardStopEnabled: input.hardStopEnabled ?? true,
441
+ notifyEnabled: input.notifyEnabled ?? true,
442
+ isActive: nextIsActive,
443
+ createdByUserId: actorUserId,
444
+ updatedByUserId: actorUserId,
445
+ })
446
+ .returning()
447
+ .then((rows) => rows[0]);
448
+ if (input.scopeType === "company" && windowKind === "calendar_month_utc") {
449
+ await db
450
+ .update(companies)
451
+ .set({
452
+ budgetMonthlyCents: amount,
453
+ updatedAt: now,
454
+ })
455
+ .where(eq(companies.id, input.scopeId));
456
+ }
457
+ if (input.scopeType === "agent" && windowKind === "calendar_month_utc") {
458
+ await db
459
+ .update(agents)
460
+ .set({
461
+ budgetMonthlyCents: amount,
462
+ updatedAt: now,
463
+ })
464
+ .where(eq(agents.id, input.scopeId));
465
+ }
466
+ if (amount > 0) {
467
+ const observedAmount = await computeObservedAmount(db, row);
468
+ if (observedAmount < amount) {
469
+ await resumeScopeFromBudget(row);
470
+ await resolveOpenIncidentsForPolicy(row.id, actorUserId ? "approved" : null, actorUserId);
471
+ }
472
+ else {
473
+ const softThreshold = Math.ceil((row.amount * row.warnPercent) / 100);
474
+ if (row.notifyEnabled && observedAmount >= softThreshold) {
475
+ await createIncidentIfNeeded(row, "soft", observedAmount);
476
+ }
477
+ if (row.hardStopEnabled && observedAmount >= row.amount) {
478
+ await resolveOpenSoftIncidents(row.id);
479
+ await createIncidentIfNeeded(row, "hard", observedAmount);
480
+ await pauseAndCancelScopeForBudget(row);
481
+ }
482
+ }
483
+ }
484
+ else {
485
+ await resumeScopeFromBudget(row);
486
+ await resolveOpenIncidentsForPolicy(row.id, actorUserId ? "approved" : null, actorUserId);
487
+ }
488
+ await logActivity(db, {
489
+ companyId,
490
+ actorType: "user",
491
+ actorId: actorUserId ?? "board",
492
+ action: "budget.policy_upserted",
493
+ entityType: "budget_policy",
494
+ entityId: row.id,
495
+ details: {
496
+ scopeType: row.scopeType,
497
+ scopeId: row.scopeId,
498
+ amount: row.amount,
499
+ windowKind: row.windowKind,
500
+ },
501
+ });
502
+ return buildPolicySummary(row);
503
+ },
504
+ overview: async (companyId) => {
505
+ const rows = await listPolicyRows(companyId);
506
+ const policies = await Promise.all(rows.map((row) => buildPolicySummary(row)));
507
+ const activeIncidentRows = await db
508
+ .select()
509
+ .from(budgetIncidents)
510
+ .where(and(eq(budgetIncidents.companyId, companyId), eq(budgetIncidents.status, "open")))
511
+ .orderBy(desc(budgetIncidents.createdAt));
512
+ const activeIncidents = await hydrateIncidentRows(activeIncidentRows);
513
+ return {
514
+ companyId,
515
+ policies,
516
+ activeIncidents,
517
+ pausedAgentCount: policies.filter((policy) => policy.scopeType === "agent" && policy.paused).length,
518
+ pausedProjectCount: policies.filter((policy) => policy.scopeType === "project" && policy.paused).length,
519
+ pendingApprovalCount: activeIncidents.filter((incident) => incident.approvalStatus === "pending").length,
520
+ };
521
+ },
522
+ evaluateCostEvent: async (event) => {
523
+ const candidatePolicies = await db
524
+ .select()
525
+ .from(budgetPolicies)
526
+ .where(and(eq(budgetPolicies.companyId, event.companyId), eq(budgetPolicies.isActive, true), inArray(budgetPolicies.scopeType, ["company", "agent", "project"])));
527
+ const relevantPolicies = candidatePolicies.filter((policy) => {
528
+ if (policy.scopeType === "company")
529
+ return policy.scopeId === event.companyId;
530
+ if (policy.scopeType === "agent")
531
+ return policy.scopeId === event.agentId;
532
+ if (policy.scopeType === "project")
533
+ return Boolean(event.projectId) && policy.scopeId === event.projectId;
534
+ return false;
535
+ });
536
+ for (const policy of relevantPolicies) {
537
+ if (policy.metric !== "billed_cents" || policy.amount <= 0)
538
+ continue;
539
+ const observedAmount = await computeObservedAmount(db, policy);
540
+ const softThreshold = Math.ceil((policy.amount * policy.warnPercent) / 100);
541
+ if (policy.notifyEnabled && observedAmount >= softThreshold) {
542
+ const softIncident = await createIncidentIfNeeded(policy, "soft", observedAmount);
543
+ if (softIncident) {
544
+ await logActivity(db, {
545
+ companyId: policy.companyId,
546
+ actorType: "system",
547
+ actorId: "budget_service",
548
+ action: "budget.soft_threshold_crossed",
549
+ entityType: "budget_incident",
550
+ entityId: softIncident.id,
551
+ details: {
552
+ scopeType: policy.scopeType,
553
+ scopeId: policy.scopeId,
554
+ amountObserved: observedAmount,
555
+ amountLimit: policy.amount,
556
+ },
557
+ });
558
+ }
559
+ }
560
+ if (policy.hardStopEnabled && observedAmount >= policy.amount) {
561
+ await resolveOpenSoftIncidents(policy.id);
562
+ const hardIncident = await createIncidentIfNeeded(policy, "hard", observedAmount);
563
+ await pauseAndCancelScopeForBudget(policy);
564
+ if (hardIncident) {
565
+ await logActivity(db, {
566
+ companyId: policy.companyId,
567
+ actorType: "system",
568
+ actorId: "budget_service",
569
+ action: "budget.hard_threshold_crossed",
570
+ entityType: "budget_incident",
571
+ entityId: hardIncident.id,
572
+ details: {
573
+ scopeType: policy.scopeType,
574
+ scopeId: policy.scopeId,
575
+ amountObserved: observedAmount,
576
+ amountLimit: policy.amount,
577
+ approvalId: hardIncident.approvalId ?? null,
578
+ },
579
+ });
580
+ }
581
+ }
582
+ }
583
+ },
584
+ getInvocationBlock: async (companyId, agentId, context) => {
585
+ const agent = await db
586
+ .select({
587
+ status: agents.status,
588
+ pauseReason: agents.pauseReason,
589
+ companyId: agents.companyId,
590
+ name: agents.name,
591
+ })
592
+ .from(agents)
593
+ .where(eq(agents.id, agentId))
594
+ .then((rows) => rows[0] ?? null);
595
+ if (!agent || agent.companyId !== companyId)
596
+ throw notFound("Agent not found");
597
+ const company = await db
598
+ .select({
599
+ status: companies.status,
600
+ pauseReason: companies.pauseReason,
601
+ name: companies.name,
602
+ })
603
+ .from(companies)
604
+ .where(eq(companies.id, companyId))
605
+ .then((rows) => rows[0] ?? null);
606
+ if (!company)
607
+ throw notFound("Company not found");
608
+ if (company.status === "paused") {
609
+ return {
610
+ scopeType: "company",
611
+ scopeId: companyId,
612
+ scopeName: company.name,
613
+ reason: company.pauseReason === "budget"
614
+ ? "Company is paused because its budget hard-stop was reached."
615
+ : "Company is paused and cannot start new work.",
616
+ };
617
+ }
618
+ const companyPolicy = await db
619
+ .select()
620
+ .from(budgetPolicies)
621
+ .where(and(eq(budgetPolicies.companyId, companyId), eq(budgetPolicies.scopeType, "company"), eq(budgetPolicies.scopeId, companyId), eq(budgetPolicies.isActive, true), eq(budgetPolicies.metric, "billed_cents")))
622
+ .then((rows) => rows[0] ?? null);
623
+ if (companyPolicy && companyPolicy.hardStopEnabled && companyPolicy.amount > 0) {
624
+ const observed = await computeObservedAmount(db, companyPolicy);
625
+ if (observed >= companyPolicy.amount) {
626
+ return {
627
+ scopeType: "company",
628
+ scopeId: companyId,
629
+ scopeName: company.name,
630
+ reason: "Company cannot start new work because its budget hard-stop is exceeded.",
631
+ };
632
+ }
633
+ }
634
+ if (agent.status === "paused" && agent.pauseReason === "budget") {
635
+ return {
636
+ scopeType: "agent",
637
+ scopeId: agentId,
638
+ scopeName: agent.name,
639
+ reason: "Agent is paused because its budget hard-stop was reached.",
640
+ };
641
+ }
642
+ const agentPolicy = await db
643
+ .select()
644
+ .from(budgetPolicies)
645
+ .where(and(eq(budgetPolicies.companyId, companyId), eq(budgetPolicies.scopeType, "agent"), eq(budgetPolicies.scopeId, agentId), eq(budgetPolicies.isActive, true), eq(budgetPolicies.metric, "billed_cents")))
646
+ .then((rows) => rows[0] ?? null);
647
+ if (agentPolicy && agentPolicy.hardStopEnabled && agentPolicy.amount > 0) {
648
+ const observed = await computeObservedAmount(db, agentPolicy);
649
+ if (observed >= agentPolicy.amount) {
650
+ return {
651
+ scopeType: "agent",
652
+ scopeId: agentId,
653
+ scopeName: agent.name,
654
+ reason: "Agent cannot start because its budget hard-stop is still exceeded.",
655
+ };
656
+ }
657
+ }
658
+ const candidateProjectId = context?.projectId ?? null;
659
+ if (!candidateProjectId)
660
+ return null;
661
+ const project = await db
662
+ .select({
663
+ id: projects.id,
664
+ name: projects.name,
665
+ companyId: projects.companyId,
666
+ pauseReason: projects.pauseReason,
667
+ pausedAt: projects.pausedAt,
668
+ })
669
+ .from(projects)
670
+ .where(eq(projects.id, candidateProjectId))
671
+ .then((rows) => rows[0] ?? null);
672
+ if (!project || project.companyId !== companyId)
673
+ return null;
674
+ const projectPolicy = await db
675
+ .select()
676
+ .from(budgetPolicies)
677
+ .where(and(eq(budgetPolicies.companyId, companyId), eq(budgetPolicies.scopeType, "project"), eq(budgetPolicies.scopeId, project.id), eq(budgetPolicies.isActive, true), eq(budgetPolicies.metric, "billed_cents")))
678
+ .then((rows) => rows[0] ?? null);
679
+ if (projectPolicy && projectPolicy.hardStopEnabled && projectPolicy.amount > 0) {
680
+ const observed = await computeObservedAmount(db, projectPolicy);
681
+ if (observed >= projectPolicy.amount) {
682
+ return {
683
+ scopeType: "project",
684
+ scopeId: project.id,
685
+ scopeName: project.name,
686
+ reason: "Project cannot start work because its budget hard-stop is still exceeded.",
687
+ };
688
+ }
689
+ }
690
+ if (!project.pausedAt || project.pauseReason !== "budget")
691
+ return null;
692
+ return {
693
+ scopeType: "project",
694
+ scopeId: project.id,
695
+ scopeName: project.name,
696
+ reason: "Project is paused because its budget hard-stop was reached.",
697
+ };
698
+ },
699
+ resolveIncident: async (companyId, incidentId, input, actorUserId) => {
700
+ const incident = await db
701
+ .select()
702
+ .from(budgetIncidents)
703
+ .where(eq(budgetIncidents.id, incidentId))
704
+ .then((rows) => rows[0] ?? null);
705
+ if (!incident)
706
+ throw notFound("Budget incident not found");
707
+ if (incident.companyId !== companyId)
708
+ throw notFound("Budget incident not found");
709
+ const policy = await getPolicyRow(incident.policyId);
710
+ if (input.action === "raise_budget_and_resume") {
711
+ const nextAmount = Math.max(0, Math.floor(input.amount ?? 0));
712
+ const currentObserved = await computeObservedAmount(db, policy);
713
+ if (nextAmount <= currentObserved) {
714
+ throw unprocessable("New budget must exceed current observed spend");
715
+ }
716
+ const now = new Date();
717
+ await db
718
+ .update(budgetPolicies)
719
+ .set({
720
+ amount: nextAmount,
721
+ isActive: true,
722
+ updatedByUserId: actorUserId,
723
+ updatedAt: now,
724
+ })
725
+ .where(eq(budgetPolicies.id, policy.id));
726
+ if (policy.scopeType === "company" && policy.windowKind === "calendar_month_utc") {
727
+ await db
728
+ .update(companies)
729
+ .set({ budgetMonthlyCents: nextAmount, updatedAt: now })
730
+ .where(eq(companies.id, policy.scopeId));
731
+ }
732
+ if (policy.scopeType === "agent" && policy.windowKind === "calendar_month_utc") {
733
+ await db
734
+ .update(agents)
735
+ .set({ budgetMonthlyCents: nextAmount, updatedAt: now })
736
+ .where(eq(agents.id, policy.scopeId));
737
+ }
738
+ await resumeScopeFromBudget(policy);
739
+ await db
740
+ .update(budgetIncidents)
741
+ .set({
742
+ status: "resolved",
743
+ resolvedAt: now,
744
+ updatedAt: now,
745
+ })
746
+ .where(and(eq(budgetIncidents.policyId, policy.id), eq(budgetIncidents.status, "open")));
747
+ await markApprovalStatus(db, incident.approvalId ?? null, "approved", input.decisionNote, actorUserId);
748
+ }
749
+ else {
750
+ await db
751
+ .update(budgetIncidents)
752
+ .set({
753
+ status: "dismissed",
754
+ resolvedAt: new Date(),
755
+ updatedAt: new Date(),
756
+ })
757
+ .where(eq(budgetIncidents.id, incident.id));
758
+ await markApprovalStatus(db, incident.approvalId ?? null, "rejected", input.decisionNote, actorUserId);
759
+ }
760
+ await logActivity(db, {
761
+ companyId: incident.companyId,
762
+ actorType: "user",
763
+ actorId: actorUserId,
764
+ action: "budget.incident_resolved",
765
+ entityType: "budget_incident",
766
+ entityId: incident.id,
767
+ details: {
768
+ action: input.action,
769
+ amount: input.amount ?? null,
770
+ scopeType: incident.scopeType,
771
+ scopeId: incident.scopeId,
772
+ },
773
+ });
774
+ const [updated] = await hydrateIncidentRows([{
775
+ ...incident,
776
+ status: input.action === "raise_budget_and_resume" ? "resolved" : "dismissed",
777
+ resolvedAt: new Date(),
778
+ updatedAt: new Date(),
779
+ }]);
780
+ return updated;
781
+ },
782
+ };
783
+ }
784
+ //# sourceMappingURL=budgets.js.map