crewly 1.20.186 → 1.20.188

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 (321) hide show
  1. package/config/skills/agent/core/project-tickets/SKILL.md +3 -2
  2. package/config/skills/agent/core/project-tickets/execute.sh +4 -4
  3. package/config/skills/agent/core/project-tickets/execute.test.sh +2 -2
  4. package/config/skills/agent/core/remember/SKILL.md +1 -0
  5. package/config/skills/agent/core/remember/execute.sh +12 -0
  6. package/config/skills/agent/core/report-status/SKILL.md +7 -1
  7. package/config/skills/agent/core/report-status/execute.sh +41 -0
  8. package/config/skills/agent/core/slack-post/SKILL.md +1 -0
  9. package/config/skills/agent/core/slack-post/execute.sh +9 -2
  10. package/config/skills/team-leader/_common/lib.sh +45 -0
  11. package/config/skills/team-leader/start-agent/SKILL.md +6 -6
  12. package/config/skills/team-leader/start-agent/execute.sh +3 -12
  13. package/config/skills/team-leader/stop-agent/SKILL.md +6 -6
  14. package/config/skills/team-leader/stop-agent/execute.sh +3 -12
  15. package/dist/backend/backend/src/constants.d.ts +114 -2
  16. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  17. package/dist/backend/backend/src/constants.js +114 -2
  18. package/dist/backend/backend/src/constants.js.map +1 -1
  19. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
  20. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +62 -0
  21. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
  22. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
  23. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
  24. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +5 -1
  25. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +1 -1
  27. package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +1 -1
  28. package/dist/backend/backend/src/controllers/request/open-items.controller.js +1 -1
  29. package/dist/backend/backend/src/controllers/request/open-items.controller.js.map +1 -1
  30. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  31. package/dist/backend/backend/src/controllers/slack/slack.controller.js +4 -1
  32. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  33. package/dist/backend/backend/src/controllers/standing/standing.controller.d.ts +18 -1
  34. package/dist/backend/backend/src/controllers/standing/standing.controller.d.ts.map +1 -1
  35. package/dist/backend/backend/src/controllers/standing/standing.controller.js +77 -4
  36. package/dist/backend/backend/src/controllers/standing/standing.controller.js.map +1 -1
  37. package/dist/backend/backend/src/controllers/system/system.controller.d.ts +12 -0
  38. package/dist/backend/backend/src/controllers/system/system.controller.d.ts.map +1 -1
  39. package/dist/backend/backend/src/controllers/system/system.controller.js +28 -3
  40. package/dist/backend/backend/src/controllers/system/system.controller.js.map +1 -1
  41. package/dist/backend/backend/src/controllers/system/usage.controller.d.ts +46 -0
  42. package/dist/backend/backend/src/controllers/system/usage.controller.d.ts.map +1 -0
  43. package/dist/backend/backend/src/controllers/system/usage.controller.js +181 -0
  44. package/dist/backend/backend/src/controllers/system/usage.controller.js.map +1 -0
  45. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +7 -0
  46. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  47. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +64 -1
  48. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  49. package/dist/backend/backend/src/index.d.ts +7 -0
  50. package/dist/backend/backend/src/index.d.ts.map +1 -1
  51. package/dist/backend/backend/src/index.js +115 -1
  52. package/dist/backend/backend/src/index.js.map +1 -1
  53. package/dist/backend/backend/src/routes/modules/system.routes.d.ts.map +1 -1
  54. package/dist/backend/backend/src/routes/modules/system.routes.js +3 -0
  55. package/dist/backend/backend/src/routes/modules/system.routes.js.map +1 -1
  56. package/dist/backend/backend/src/scripts/open-items-stale-followups.d.ts +51 -0
  57. package/dist/backend/backend/src/scripts/open-items-stale-followups.d.ts.map +1 -0
  58. package/dist/backend/backend/src/scripts/open-items-stale-followups.js +142 -0
  59. package/dist/backend/backend/src/scripts/open-items-stale-followups.js.map +1 -0
  60. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  61. package/dist/backend/backend/src/services/agent/agent-registration.service.js +32 -1
  62. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  63. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +28 -0
  64. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  65. package/dist/backend/backend/src/services/event-bus/event-bus.service.js +83 -2
  66. package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  67. package/dist/backend/backend/src/services/memory/memory.service.d.ts +14 -2
  68. package/dist/backend/backend/src/services/memory/memory.service.d.ts.map +1 -1
  69. package/dist/backend/backend/src/services/memory/memory.service.js +12 -1
  70. package/dist/backend/backend/src/services/memory/memory.service.js.map +1 -1
  71. package/dist/backend/backend/src/services/memory/project-memory.service.d.ts +18 -1
  72. package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  73. package/dist/backend/backend/src/services/memory/project-memory.service.js +38 -1
  74. package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
  75. package/dist/backend/backend/src/services/memory/standing-answers.service.d.ts +11 -0
  76. package/dist/backend/backend/src/services/memory/standing-answers.service.d.ts.map +1 -1
  77. package/dist/backend/backend/src/services/memory/standing-answers.service.js +40 -2
  78. package/dist/backend/backend/src/services/memory/standing-answers.service.js.map +1 -1
  79. package/dist/backend/backend/src/services/messaging/offline-agent-message.d.ts +70 -0
  80. package/dist/backend/backend/src/services/messaging/offline-agent-message.d.ts.map +1 -0
  81. package/dist/backend/backend/src/services/messaging/offline-agent-message.js +92 -0
  82. package/dist/backend/backend/src/services/messaging/offline-agent-message.js.map +1 -0
  83. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts +7 -1
  84. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
  85. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js +16 -2
  86. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
  87. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.d.ts.map +1 -1
  88. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js +2 -0
  89. package/dist/backend/backend/src/services/messaging/owner-message-watchdog.wiring.js.map +1 -1
  90. package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/messaging/queue-processor.service.js +9 -1
  92. package/dist/backend/backend/src/services/messaging/queue-processor.service.js.map +1 -1
  93. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +41 -1
  94. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +87 -2
  96. package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
  97. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts +30 -12
  98. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
  99. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js +87 -15
  100. package/dist/backend/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
  101. package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.d.ts +130 -0
  102. package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.d.ts.map +1 -0
  103. package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.js +274 -0
  104. package/dist/backend/backend/src/services/monitoring/antigravity-usage-sync.service.js.map +1 -0
  105. package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.d.ts +152 -0
  106. package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.d.ts.map +1 -0
  107. package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.js +311 -0
  108. package/dist/backend/backend/src/services/monitoring/codex-rollout-sync.service.js.map +1 -0
  109. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts +78 -1
  110. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  111. package/dist/backend/backend/src/services/monitoring/token-usage.service.js +96 -4
  112. package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  113. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts +10 -1
  114. package/dist/backend/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  115. package/dist/backend/backend/src/services/open-items/open-item-extractor.js +86 -5
  116. package/dist/backend/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  117. package/dist/backend/backend/src/services/open-items/open-items-backfill.d.ts +2 -1
  118. package/dist/backend/backend/src/services/open-items/open-items-backfill.d.ts.map +1 -1
  119. package/dist/backend/backend/src/services/open-items/open-items-backfill.js +17 -6
  120. package/dist/backend/backend/src/services/open-items/open-items-backfill.js.map +1 -1
  121. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts +62 -1
  122. package/dist/backend/backend/src/services/open-items/open-items.service.d.ts.map +1 -1
  123. package/dist/backend/backend/src/services/open-items/open-items.service.js +203 -9
  124. package/dist/backend/backend/src/services/open-items/open-items.service.js.map +1 -1
  125. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +4 -4
  126. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
  127. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +1 -1
  128. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
  129. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +4 -3
  130. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
  131. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +7 -4
  132. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
  133. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +40 -9
  134. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
  135. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +68 -20
  136. package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
  137. package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.d.ts +40 -0
  138. package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.d.ts.map +1 -0
  139. package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.js +82 -0
  140. package/dist/backend/backend/src/services/reconciler/idle-holder-surfacer.js.map +1 -0
  141. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +29 -0
  142. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  143. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +49 -0
  144. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  145. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  146. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +6 -11
  147. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  148. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +23 -0
  149. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  150. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +55 -1
  151. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  152. package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts.map +1 -1
  153. package/dist/backend/backend/src/services/slack/slack-agent-post.service.js +10 -4
  154. package/dist/backend/backend/src/services/slack/slack-agent-post.service.js.map +1 -1
  155. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +13 -2
  156. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  157. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +19 -5
  158. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  159. package/dist/backend/backend/src/services/spend/spend-cap-command.d.ts +129 -0
  160. package/dist/backend/backend/src/services/spend/spend-cap-command.d.ts.map +1 -0
  161. package/dist/backend/backend/src/services/spend/spend-cap-command.js +323 -0
  162. package/dist/backend/backend/src/services/spend/spend-cap-command.js.map +1 -0
  163. package/dist/backend/backend/src/services/spend/spend-cap.gate.d.ts +60 -0
  164. package/dist/backend/backend/src/services/spend/spend-cap.gate.d.ts.map +1 -0
  165. package/dist/backend/backend/src/services/spend/spend-cap.gate.js +66 -0
  166. package/dist/backend/backend/src/services/spend/spend-cap.gate.js.map +1 -0
  167. package/dist/backend/backend/src/services/spend/spend-cap.service.d.ts +340 -0
  168. package/dist/backend/backend/src/services/spend/spend-cap.service.d.ts.map +1 -0
  169. package/dist/backend/backend/src/services/spend/spend-cap.service.js +760 -0
  170. package/dist/backend/backend/src/services/spend/spend-cap.service.js.map +1 -0
  171. package/dist/backend/backend/src/services/spend/spend-cap.store.d.ts +126 -0
  172. package/dist/backend/backend/src/services/spend/spend-cap.store.d.ts.map +1 -0
  173. package/dist/backend/backend/src/services/spend/spend-cap.store.js +141 -0
  174. package/dist/backend/backend/src/services/spend/spend-cap.store.js.map +1 -0
  175. package/dist/backend/backend/src/services/spend/spend-cap.wiring.d.ts +52 -0
  176. package/dist/backend/backend/src/services/spend/spend-cap.wiring.d.ts.map +1 -0
  177. package/dist/backend/backend/src/services/spend/spend-cap.wiring.js +112 -0
  178. package/dist/backend/backend/src/services/spend/spend-cap.wiring.js.map +1 -0
  179. package/dist/backend/backend/src/services/spend/spend-ledger.service.d.ts +145 -0
  180. package/dist/backend/backend/src/services/spend/spend-ledger.service.d.ts.map +1 -0
  181. package/dist/backend/backend/src/services/spend/spend-ledger.service.js +214 -0
  182. package/dist/backend/backend/src/services/spend/spend-ledger.service.js.map +1 -0
  183. package/dist/backend/backend/src/services/task-pool/claim-target-waker.d.ts +51 -0
  184. package/dist/backend/backend/src/services/task-pool/claim-target-waker.d.ts.map +1 -0
  185. package/dist/backend/backend/src/services/task-pool/claim-target-waker.js +43 -0
  186. package/dist/backend/backend/src/services/task-pool/claim-target-waker.js.map +1 -0
  187. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +38 -0
  188. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  189. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +73 -1
  190. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  191. package/dist/backend/backend/src/services/task-pool/work-item-usage.d.ts +53 -0
  192. package/dist/backend/backend/src/services/task-pool/work-item-usage.d.ts.map +1 -0
  193. package/dist/backend/backend/src/services/task-pool/work-item-usage.js +39 -0
  194. package/dist/backend/backend/src/services/task-pool/work-item-usage.js.map +1 -0
  195. package/dist/backend/backend/src/services/usage/token-format.d.ts +49 -0
  196. package/dist/backend/backend/src/services/usage/token-format.d.ts.map +1 -0
  197. package/dist/backend/backend/src/services/usage/token-format.js +81 -0
  198. package/dist/backend/backend/src/services/usage/token-format.js.map +1 -0
  199. package/dist/backend/backend/src/services/usage/usage-stats.service.d.ts +132 -0
  200. package/dist/backend/backend/src/services/usage/usage-stats.service.d.ts.map +1 -0
  201. package/dist/backend/backend/src/services/usage/usage-stats.service.js +226 -0
  202. package/dist/backend/backend/src/services/usage/usage-stats.service.js.map +1 -0
  203. package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
  204. package/dist/backend/backend/src/services/v3/v3-data.service.js +11 -14
  205. package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
  206. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +25 -0
  207. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
  208. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +50 -0
  209. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
  210. package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts +9 -1
  211. package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -1
  212. package/dist/backend/backend/src/services/workflow/scheduler.service.js +16 -2
  213. package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
  214. package/dist/backend/backend/src/types/decision.types.d.ts +3 -1
  215. package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
  216. package/dist/backend/backend/src/types/event-bus.types.d.ts +9 -1
  217. package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
  218. package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
  219. package/dist/backend/backend/src/types/review-reason.types.d.ts +3 -1
  220. package/dist/backend/backend/src/types/review-reason.types.d.ts.map +1 -1
  221. package/dist/backend/backend/src/types/review-reason.types.js +1 -0
  222. package/dist/backend/backend/src/types/review-reason.types.js.map +1 -1
  223. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +24 -7
  224. package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
  225. package/dist/backend/backend/src/types/ticket-autopilot.types.js +29 -13
  226. package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -1
  227. package/dist/backend/backend/src/types/v2/open-item.types.d.ts +5 -1
  228. package/dist/backend/backend/src/types/v2/open-item.types.d.ts.map +1 -1
  229. package/dist/backend/backend/src/types/v2/open-item.types.js +1 -1
  230. package/dist/backend/backend/src/types/v2/open-item.types.js.map +1 -1
  231. package/dist/backend/build-info.json +2 -2
  232. package/dist/cli/backend/src/constants.d.ts +114 -2
  233. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  234. package/dist/cli/backend/src/constants.js +114 -2
  235. package/dist/cli/backend/src/constants.js.map +1 -1
  236. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  237. package/dist/cli/backend/src/services/agent/agent-registration.service.js +32 -1
  238. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  239. package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts +28 -0
  240. package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  241. package/dist/cli/backend/src/services/event-bus/event-bus.service.js +83 -2
  242. package/dist/cli/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  243. package/dist/cli/backend/src/services/memory/memory.service.d.ts +14 -2
  244. package/dist/cli/backend/src/services/memory/memory.service.d.ts.map +1 -1
  245. package/dist/cli/backend/src/services/memory/memory.service.js +12 -1
  246. package/dist/cli/backend/src/services/memory/memory.service.js.map +1 -1
  247. package/dist/cli/backend/src/services/memory/project-memory.service.d.ts +18 -1
  248. package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  249. package/dist/cli/backend/src/services/memory/project-memory.service.js +38 -1
  250. package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
  251. package/dist/cli/backend/src/services/memory/standing-answers.service.d.ts +11 -0
  252. package/dist/cli/backend/src/services/memory/standing-answers.service.d.ts.map +1 -1
  253. package/dist/cli/backend/src/services/memory/standing-answers.service.js +40 -2
  254. package/dist/cli/backend/src/services/memory/standing-answers.service.js.map +1 -1
  255. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts +7 -1
  256. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.d.ts.map +1 -1
  257. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js +16 -2
  258. package/dist/cli/backend/src/services/messaging/owner-message-watchdog.service.js.map +1 -1
  259. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
  260. package/dist/cli/backend/src/services/messaging/queue-processor.service.js +9 -1
  261. package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -1
  262. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +41 -1
  263. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
  264. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +87 -2
  265. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
  266. package/dist/cli/backend/src/services/monitoring/activity-monitor.service.d.ts +30 -12
  267. package/dist/cli/backend/src/services/monitoring/activity-monitor.service.d.ts.map +1 -1
  268. package/dist/cli/backend/src/services/monitoring/activity-monitor.service.js +87 -15
  269. package/dist/cli/backend/src/services/monitoring/activity-monitor.service.js.map +1 -1
  270. package/dist/cli/backend/src/services/monitoring/model-pricing.d.ts +97 -0
  271. package/dist/cli/backend/src/services/monitoring/model-pricing.d.ts.map +1 -0
  272. package/dist/cli/backend/src/services/monitoring/model-pricing.js +120 -0
  273. package/dist/cli/backend/src/services/monitoring/model-pricing.js.map +1 -0
  274. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts +78 -1
  275. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  276. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +96 -4
  277. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  278. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts +10 -1
  279. package/dist/cli/backend/src/services/open-items/open-item-extractor.d.ts.map +1 -1
  280. package/dist/cli/backend/src/services/open-items/open-item-extractor.js +86 -5
  281. package/dist/cli/backend/src/services/open-items/open-item-extractor.js.map +1 -1
  282. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +13 -2
  283. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  284. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +19 -5
  285. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  286. package/dist/cli/backend/src/services/spend/spend-cap.gate.d.ts +60 -0
  287. package/dist/cli/backend/src/services/spend/spend-cap.gate.d.ts.map +1 -0
  288. package/dist/cli/backend/src/services/spend/spend-cap.gate.js +66 -0
  289. package/dist/cli/backend/src/services/spend/spend-cap.gate.js.map +1 -0
  290. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +38 -0
  291. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  292. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +73 -1
  293. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  294. package/dist/cli/backend/src/services/task-pool/work-item-usage.d.ts +53 -0
  295. package/dist/cli/backend/src/services/task-pool/work-item-usage.d.ts.map +1 -0
  296. package/dist/cli/backend/src/services/task-pool/work-item-usage.js +39 -0
  297. package/dist/cli/backend/src/services/task-pool/work-item-usage.js.map +1 -0
  298. package/dist/cli/backend/src/services/usage/token-format.d.ts +49 -0
  299. package/dist/cli/backend/src/services/usage/token-format.d.ts.map +1 -0
  300. package/dist/cli/backend/src/services/usage/token-format.js +81 -0
  301. package/dist/cli/backend/src/services/usage/token-format.js.map +1 -0
  302. package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
  303. package/dist/cli/backend/src/services/v3/v3-data.service.js +11 -14
  304. package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -1
  305. package/dist/cli/backend/src/types/decision.types.d.ts +3 -1
  306. package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
  307. package/dist/cli/backend/src/types/event-bus.types.d.ts +9 -1
  308. package/dist/cli/backend/src/types/event-bus.types.d.ts.map +1 -1
  309. package/dist/cli/backend/src/types/event-bus.types.js.map +1 -1
  310. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +24 -7
  311. package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
  312. package/dist/cli/backend/src/types/ticket-autopilot.types.js +29 -13
  313. package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -1
  314. package/dist/cli/backend/src/types/v2/open-item.types.d.ts +5 -1
  315. package/dist/cli/backend/src/types/v2/open-item.types.d.ts.map +1 -1
  316. package/dist/cli/backend/src/types/v2/open-item.types.js +1 -1
  317. package/dist/cli/backend/src/types/v2/open-item.types.js.map +1 -1
  318. package/frontend/dist/assets/{index-a79c7852.css → index-20a67829.css} +1 -1
  319. package/frontend/dist/assets/{index-1ee8a9a2.js → index-49f50b45.js} +282 -282
  320. package/frontend/dist/index.html +2 -2
  321. package/package.json +1 -1
@@ -0,0 +1,760 @@
1
+ /**
2
+ * Daily token caps with a hard stop: per agent, per team and all agents
3
+ * together, plus temporary boosts.
4
+ *
5
+ * - The unit is tokens (input incl. cached + output, see `eventTokens`).
6
+ * - Caps are the owner's own limits (unlike the runtime fallback, which
7
+ * reacts to a provider's usage limit). All are OFF until the owner sets
8
+ * one: a default per-agent cap, per-agent overrides, per-team caps, and an
9
+ * optional all-agents total.
10
+ * - At {@link SPEND_CAP_CONSTANTS.WARN_FRACTION} of a cap the owner gets ONE
11
+ * heads-up.
12
+ * - At 100% the agent is hard-stopped: {@link stopOf} answers the delivery
13
+ * and wake gates, so no new turn starts (the current one may finish) and
14
+ * messages stay queued. The owner gets ONE decision card: "Boost +X
15
+ * today" / "Unlimited today" / "Keep stopped".
16
+ * - A boost covers an agent, a team (its members) or everyone. For every
17
+ * agent it covers, it adds `extraTokens` to EVERY cap checked for that
18
+ * agent (its own, its team's, the total), or lifts them all
19
+ * (`unlimited`). It ends at `until` — by default the next local midnight.
20
+ * - Local midnight resets the day; when a stop lifts (midnight, boost, cap
21
+ * change) the queued messages are released.
22
+ *
23
+ * specs/2026-10-02-spend-cap.md
24
+ *
25
+ * @module services/spend/spend-cap.service
26
+ */
27
+ import { randomUUID } from 'crypto';
28
+ import { ORCHESTRATOR_SESSION_NAME, SPEND_CAP_CONSTANTS as C } from '../../constants.js';
29
+ import { localDateKey, localMidnight } from '../project-tickets/ticket-autopilot-decision.js';
30
+ import { compactTokens, formatTokens, parseTokenAmount } from '../usage/token-format.js';
31
+ import { spendCapReason } from './spend-cap.gate.js';
32
+ import { emptyDay, emptySpendCapFile } from './spend-cap.store.js';
33
+ /** A rejected change. */
34
+ export class SpendCapError extends Error {
35
+ status;
36
+ /**
37
+ * @param status - HTTP status
38
+ * @param message - Owner-facing reason
39
+ */
40
+ constructor(status, message) {
41
+ super(message);
42
+ this.status = status;
43
+ this.name = 'SpendCapError';
44
+ }
45
+ }
46
+ /**
47
+ * Whether a cap value is usable.
48
+ *
49
+ * @param v - Value
50
+ * @returns True for a finite positive number
51
+ */
52
+ function isCap(v) {
53
+ return typeof v === 'number' && Number.isFinite(v) && v > 0;
54
+ }
55
+ /**
56
+ * Validate a cap from the API.
57
+ *
58
+ * @param v - Value (number or "20M")
59
+ * @param what - Field name for the error
60
+ * @returns The cap (whole tokens) or null
61
+ * @throws SpendCapError
62
+ */
63
+ function parseCap(v, what) {
64
+ if (v === null || v === '')
65
+ return null;
66
+ const n = parseTokenAmount(v);
67
+ if (n === null)
68
+ throw new SpendCapError(400, `${what} must be a positive number of tokens (e.g. 5000000 or "5M"), or null to turn it off`);
69
+ return n;
70
+ }
71
+ /**
72
+ * Round up to a whole million (at least 1M).
73
+ *
74
+ * @param tokens - Tokens
75
+ * @returns Rounded tokens
76
+ */
77
+ export function roundUpMillions(tokens) {
78
+ return Math.max(1_000_000, Math.ceil(tokens / 1_000_000) * 1_000_000);
79
+ }
80
+ /**
81
+ * The "Boost +X today" amount on a cap card: the cap times BOOST_FACTOR,
82
+ * rounded up to a whole million.
83
+ *
84
+ * @param capTokens - Cap in force
85
+ * @returns Tokens
86
+ */
87
+ export function suggestedBoost(capTokens) {
88
+ return roundUpMillions(capTokens * C.BOOST_FACTOR);
89
+ }
90
+ /**
91
+ * Read the amount out of a "Boost +X tokens today" option label.
92
+ *
93
+ * @param label - Option label
94
+ * @returns Tokens, `unlimited`, or null when the label is not a boost
95
+ */
96
+ export function boostOfLabel(label) {
97
+ const text = (label ?? '').trim();
98
+ if (text.toLowerCase().startsWith(C.OPTIONS.UNLIMITED.toLowerCase()))
99
+ return 'unlimited';
100
+ const m = /^Boost \+(\S+?)(?: tokens)? today/i.exec(text);
101
+ return m ? parseTokenAmount(m[1]) : null;
102
+ }
103
+ /** Team target key. */
104
+ export function teamTarget(teamId) {
105
+ return `${C.TEAM_TARGET_PREFIX}${teamId}`;
106
+ }
107
+ /**
108
+ * Daily token cap service.
109
+ */
110
+ export class SpendCapService {
111
+ deps;
112
+ file;
113
+ now;
114
+ logger;
115
+ timer = null;
116
+ /** Sessions stopped at the last evaluation (to release them when the stop lifts) */
117
+ lastStopped = new Set();
118
+ chain = Promise.resolve();
119
+ queuedPass = null;
120
+ /** Team index, refreshed on every evaluation (the gate is synchronous) */
121
+ teamList = [];
122
+ teamsOfSession = new Map();
123
+ /**
124
+ * @param deps - Collaborators
125
+ */
126
+ constructor(deps) {
127
+ this.deps = deps;
128
+ this.now = deps.now ?? (() => new Date());
129
+ this.logger = deps.logger;
130
+ this.file = deps.store.read() ?? emptySpendCapFile(localDateKey(this.now()));
131
+ }
132
+ /** Start the enforcement tick. */
133
+ start() {
134
+ if (this.timer)
135
+ return;
136
+ this.timer = setInterval(() => void this.evaluate(), C.TICK_MS);
137
+ this.timer.unref?.();
138
+ void this.evaluate();
139
+ }
140
+ /** Stop the tick. */
141
+ stop() {
142
+ if (this.timer)
143
+ clearInterval(this.timer);
144
+ this.timer = null;
145
+ }
146
+ /** Re-read the team list now (tests; the tick does it too). */
147
+ async refreshTeams() {
148
+ const teams = (await this.deps.teams?.().catch(() => [])) ?? [];
149
+ this.teamList = teams;
150
+ const index = new Map();
151
+ for (const t of teams) {
152
+ for (const s of t.members)
153
+ index.set(s, [...(index.get(s) ?? []), t.id]);
154
+ }
155
+ this.teamsOfSession = index;
156
+ }
157
+ // ---------------------------------------------------------------- caps
158
+ /** @returns The owner's caps (a copy) */
159
+ getConfig() {
160
+ return JSON.parse(JSON.stringify(this.file.config));
161
+ }
162
+ /**
163
+ * Change caps. Takes effect at once (a stop can lift or start).
164
+ *
165
+ * @param patch - Changes
166
+ * @returns The new caps
167
+ * @throws SpendCapError on a bad value
168
+ */
169
+ async setCaps(patch) {
170
+ if (!patch || typeof patch !== 'object')
171
+ throw new SpendCapError(400, 'Send the caps to change as JSON');
172
+ const next = this.getConfig();
173
+ if ('defaultAgentCapTokens' in patch)
174
+ next.defaultAgentCapTokens = parseCap(patch.defaultAgentCapTokens, 'defaultAgentCapTokens');
175
+ if ('totalCapTokens' in patch)
176
+ next.totalCapTokens = parseCap(patch.totalCapTokens, 'totalCapTokens');
177
+ if (patch.agents !== undefined) {
178
+ if (!patch.agents || typeof patch.agents !== 'object')
179
+ throw new SpendCapError(400, 'agents must be an object: { "<session>": 5000000 | null | "default" }');
180
+ for (const [session, value] of Object.entries(patch.agents)) {
181
+ if (!session.trim())
182
+ throw new SpendCapError(400, 'agents keys must be agent session names');
183
+ if (value === 'default')
184
+ delete next.agentCapsTokens[session];
185
+ else
186
+ next.agentCapsTokens[session] = parseCap(value, `agents.${session}`);
187
+ }
188
+ }
189
+ if (patch.teams !== undefined) {
190
+ if (!patch.teams || typeof patch.teams !== 'object')
191
+ throw new SpendCapError(400, 'teams must be an object: { "<teamId>": 50000000 | null }');
192
+ for (const [teamId, value] of Object.entries(patch.teams)) {
193
+ if (!teamId.trim())
194
+ throw new SpendCapError(400, 'teams keys must be team ids');
195
+ const cap = parseCap(value, `teams.${teamId}`);
196
+ if (cap === null)
197
+ delete next.teamCapsTokens[teamId];
198
+ else
199
+ next.teamCapsTokens[teamId] = cap;
200
+ }
201
+ }
202
+ next.updatedAt = this.now().toISOString();
203
+ this.file.config = next;
204
+ this.persist();
205
+ this.logger?.info('Token caps changed', { caps: next });
206
+ await this.evaluate();
207
+ return this.getConfig();
208
+ }
209
+ // ---------------------------------------------------------------- boosts
210
+ /** @returns Boosts in force (expired ones are dropped) */
211
+ activeBoosts() {
212
+ this.pruneBoosts();
213
+ return this.file.boosts.map((b) => ({ ...b }));
214
+ }
215
+ /**
216
+ * Add a temporary boost.
217
+ *
218
+ * @param input - Scope, target, extra tokens or unlimited, optional end
219
+ * @returns The boost
220
+ * @throws SpendCapError on a bad request
221
+ */
222
+ async boost(input) {
223
+ if (!input || typeof input !== 'object')
224
+ throw new SpendCapError(400, 'Send the boost as JSON');
225
+ const target = await this.resolveBoostTarget(input.scope, input.id);
226
+ const unlimited = input.unlimited === true;
227
+ const extra = unlimited ? null : parseTokenAmount(input.extraTokens);
228
+ if (!unlimited && extra === null)
229
+ throw new SpendCapError(400, 'Send extraTokens (a positive number of tokens, e.g. 20000000 or "20M") or unlimited: true');
230
+ const now = this.now();
231
+ let until = this.nextMidnight();
232
+ if (input.until !== undefined && input.until !== null && input.until !== '') {
233
+ const t = new Date(String(input.until));
234
+ if (Number.isNaN(t.getTime()) || t.getTime() <= now.getTime())
235
+ throw new SpendCapError(400, 'until must be a future time (ISO 8601)');
236
+ until = t;
237
+ }
238
+ const boost = {
239
+ id: randomUUID(),
240
+ target,
241
+ ...(unlimited ? { unlimited: true } : { extraTokens: extra }),
242
+ until: until.toISOString(),
243
+ createdAt: now.toISOString(),
244
+ ...(input.by ? { by: input.by } : {}),
245
+ };
246
+ this.pruneBoosts();
247
+ this.file.boosts.push(boost);
248
+ this.persist();
249
+ this.logger?.info('Usage boost added', { target, extraTokens: boost.extraTokens, unlimited: boost.unlimited, until: boost.until });
250
+ await this.evaluate();
251
+ return { ...boost };
252
+ }
253
+ /**
254
+ * End a boost early.
255
+ *
256
+ * @param id - Boost id
257
+ * @returns True when it existed
258
+ */
259
+ async removeBoost(id) {
260
+ const before = this.file.boosts.length;
261
+ this.file.boosts = this.file.boosts.filter((b) => b.id !== id);
262
+ if (this.file.boosts.length === before)
263
+ return false;
264
+ this.persist();
265
+ await this.evaluate();
266
+ return true;
267
+ }
268
+ /**
269
+ * Owner-facing description of a boost target.
270
+ *
271
+ * @param target - Boost target
272
+ * @returns e.g. `everyone`, `team CE`, `Ella`
273
+ */
274
+ describeTarget(target) {
275
+ if (target === C.TOTAL_TARGET)
276
+ return 'everyone';
277
+ if (target.startsWith(C.TEAM_TARGET_PREFIX)) {
278
+ const id = target.slice(C.TEAM_TARGET_PREFIX.length);
279
+ return `team ${this.teamList.find((t) => t.id === id)?.name ?? id}`;
280
+ }
281
+ return this.nameOf(target);
282
+ }
283
+ /**
284
+ * Extra tokens / unlimited a boost set gives for one target set.
285
+ *
286
+ * @param covers - Which boost targets apply
287
+ * @returns Sum of extras and whether any is unlimited
288
+ */
289
+ boostFor(covers) {
290
+ let extra = 0;
291
+ let unlimited = false;
292
+ for (const b of this.activeBoostsRaw()) {
293
+ if (!covers(b.target))
294
+ continue;
295
+ if (b.unlimited)
296
+ unlimited = true;
297
+ else
298
+ extra += b.extraTokens ?? 0;
299
+ }
300
+ return { extra, unlimited };
301
+ }
302
+ /** Boosts covering an agent: its own, its teams', everyone's. */
303
+ boostForSession(session) {
304
+ const teams = new Set((this.teamsOfSession.get(session) ?? []).map(teamTarget));
305
+ return this.boostFor((t) => t === C.TOTAL_TARGET || t === session || teams.has(t));
306
+ }
307
+ /**
308
+ * Boosts covering a team (team and everyone boosts) — what the ticket
309
+ * autopilot budget of the team's projects honours.
310
+ *
311
+ * @param teamId - Team id
312
+ * @returns Extra tokens and unlimited
313
+ */
314
+ boostForTeam(teamId) {
315
+ return this.boostForTeams([teamId]);
316
+ }
317
+ /**
318
+ * Boosts covering any of several teams, each boost counted once (a
319
+ * project worked on by two teams).
320
+ *
321
+ * @param teamIds - Team ids
322
+ * @returns Extra tokens and unlimited
323
+ */
324
+ boostForTeams(teamIds) {
325
+ const keys = new Set(teamIds.map(teamTarget));
326
+ return this.boostFor((t) => t === C.TOTAL_TARGET || keys.has(t));
327
+ }
328
+ // ---------------------------------------------------------------- gate
329
+ /**
330
+ * The stop in force for a session (the delivery / wake gate). Synchronous
331
+ * and live: it reads today's usage now, not at the last tick.
332
+ *
333
+ * @param session - Agent session
334
+ * @returns Stop, or null when the agent may run
335
+ */
336
+ stopOf(session) {
337
+ this.rollDay();
338
+ const stop = this.computeStop(session);
339
+ if (stop && !this.file.day.stopped.includes(this.stopKey(stop)))
340
+ this.scheduleEvaluate();
341
+ return stop;
342
+ }
343
+ /**
344
+ * Owner-facing name.
345
+ *
346
+ * @param session - Agent session
347
+ * @returns Name
348
+ */
349
+ displayNameOf(session) {
350
+ return this.nameOf(session);
351
+ }
352
+ /**
353
+ * An agent's own cap before boosts, and where it comes from.
354
+ *
355
+ * @param session - Agent session
356
+ * @returns Cap (null = none) and source
357
+ */
358
+ ownCapOf(session) {
359
+ const cfg = this.file.config;
360
+ if (Object.prototype.hasOwnProperty.call(cfg.agentCapsTokens, session)) {
361
+ const v = cfg.agentCapsTokens[session];
362
+ return isCap(v) ? { capTokens: v, source: 'override' } : { capTokens: null, source: 'exempt' };
363
+ }
364
+ return isCap(cfg.defaultAgentCapTokens) ? { capTokens: cfg.defaultAgentCapTokens, source: 'default' } : { capTokens: null, source: 'none' };
365
+ }
366
+ // ---------------------------------------------------------------- view
367
+ /**
368
+ * Usage plus caps for the API / Settings panel.
369
+ *
370
+ * @param days - Window
371
+ * @returns View
372
+ */
373
+ async view(days = C.DEFAULT_DAYS) {
374
+ this.rollDay();
375
+ await this.refreshTeams();
376
+ const summary = this.deps.ledger.summarize(days);
377
+ const known = (await this.deps.knownSessions?.().catch(() => [])) ?? [];
378
+ const rows = new Map(summary.agents.map((a) => [a.session, a]));
379
+ for (const s of [...known, ...this.teamList.flatMap((t) => t.members)]) {
380
+ if (!rows.has(s))
381
+ rows.set(s, { session: s, runtimes: [], todayTokens: 0, windowTokens: 0, windowCachedTokens: 0, daily: summary.days.map(() => 0) });
382
+ }
383
+ const agents = [...rows.values()].map((a) => {
384
+ const own = this.ownCapOf(a.session);
385
+ const b = this.boostForSession(a.session);
386
+ const stop = this.computeStop(a.session);
387
+ return {
388
+ session: a.session,
389
+ name: this.nameOf(a.session),
390
+ ...(this.teamsOfSession.get(a.session)?.[0] ? { teamId: this.teamsOfSession.get(a.session)?.[0] } : {}),
391
+ runtimes: a.runtimes,
392
+ todayTokens: a.todayTokens,
393
+ windowTokens: a.windowTokens,
394
+ daily: a.daily,
395
+ baseCapTokens: own.capTokens,
396
+ capTokens: own.capTokens === null || b.unlimited ? null : own.capTokens + b.extra,
397
+ capSource: own.source,
398
+ boosted: b.unlimited || b.extra > 0,
399
+ unlimited: b.unlimited,
400
+ stopped: !!stop,
401
+ ...(stop ? { stopReason: spendCapReason(stop, this.nameOf(a.session)) } : {}),
402
+ };
403
+ });
404
+ const teams = this.teamList.map((t) => {
405
+ const base = this.file.config.teamCapsTokens[t.id];
406
+ const b = this.boostForTeam(t.id);
407
+ const cap = isCap(base) && !b.unlimited ? base + b.extra : null;
408
+ const used = this.deps.ledger.groupToday(t.members);
409
+ return {
410
+ teamId: t.id,
411
+ name: t.name,
412
+ members: t.members,
413
+ todayTokens: used,
414
+ baseCapTokens: isCap(base) ? base : null,
415
+ capTokens: cap,
416
+ extraTokens: b.extra,
417
+ unlimited: b.unlimited,
418
+ boosts: this.activeBoostsRaw().filter((x) => x.target === teamTarget(t.id)),
419
+ stopped: cap !== null && used >= cap,
420
+ };
421
+ });
422
+ const totalBoost = this.boostFor((t) => t === C.TOTAL_TARGET);
423
+ const total = isCap(this.file.config.totalCapTokens) && !totalBoost.unlimited ? this.file.config.totalCapTokens + totalBoost.extra : null;
424
+ const { agents: _omit, ...rest } = summary;
425
+ void _omit;
426
+ return {
427
+ ...rest,
428
+ agents,
429
+ teams,
430
+ caps: this.getConfig(),
431
+ boosts: this.activeBoosts(),
432
+ totalCapTodayTokens: total,
433
+ suggestedAgentCapTokens: summary.p90AgentDayTokens > 0 ? roundUpMillions(summary.p90AgentDayTokens) : null,
434
+ totalStopped: total !== null && this.deps.ledger.totalToday() >= total,
435
+ };
436
+ }
437
+ // ---------------------------------------------------------------- enforcement
438
+ /**
439
+ * One enforcement pass: day roll, boost expiry, 80% notices, stop
440
+ * announcements (one card per stop), and releasing agents whose stop
441
+ * lifted. Passes run one at a time; a call during a pass gets another
442
+ * pass after it.
443
+ */
444
+ evaluate() {
445
+ if (this.queuedPass)
446
+ return this.queuedPass;
447
+ const pass = this.chain.then(async () => {
448
+ this.queuedPass = null;
449
+ try {
450
+ await this.evaluateOnce();
451
+ }
452
+ catch (err) {
453
+ this.logger?.warn('Token cap evaluation failed', { error: err instanceof Error ? err.message : String(err) });
454
+ }
455
+ });
456
+ this.queuedPass = pass;
457
+ this.chain = pass;
458
+ return pass;
459
+ }
460
+ /**
461
+ * React to a settled "cap reached" card (decision kind `spend_cap`).
462
+ *
463
+ * @param d - The settled decision
464
+ * @returns Always null (nobody else is told)
465
+ */
466
+ async onSettled(d) {
467
+ if (d.kind !== C.DECISION_KIND || !d.system)
468
+ return null;
469
+ const target = d.system.key;
470
+ this.rollDay();
471
+ if (this.file.day.cards[target] !== d.id) {
472
+ if (d.status === 'resolved')
473
+ await this.reply(d.id, 'This card is from an earlier day; the cap has already reset at midnight.');
474
+ return null;
475
+ }
476
+ const who = this.describeTarget(target);
477
+ if (d.status !== 'resolved')
478
+ return null;
479
+ const label = d.options.find((o) => o.key === d.chosenKey)?.label;
480
+ const boost = boostOfLabel(label);
481
+ if (boost === null) {
482
+ await this.reply(d.id, `OK, ${who} stays stopped until midnight. Messages stay queued.`);
483
+ return null;
484
+ }
485
+ try {
486
+ const scope = target === C.TOTAL_TARGET ? 'all' : target.startsWith(C.TEAM_TARGET_PREFIX) ? 'team' : 'agent';
487
+ const id = scope === 'team' ? target.slice(C.TEAM_TARGET_PREFIX.length) : scope === 'agent' ? target : undefined;
488
+ await this.boost(boost === 'unlimited' ? { scope, id, unlimited: true, by: 'card' } : { scope, id, extraTokens: boost, by: 'card' });
489
+ const what = boost === 'unlimited' ? 'no cap' : `+${formatTokens(boost)}`;
490
+ await this.reply(d.id, `Done: ${what} for ${who} until midnight. Queued messages are being delivered.`);
491
+ }
492
+ catch (err) {
493
+ await this.reply(d.id, `Could not boost: ${err instanceof Error ? err.message : String(err)}`);
494
+ }
495
+ return null;
496
+ }
497
+ // ---------------------------------------------------------------- internals
498
+ async evaluateOnce() {
499
+ this.rollDay();
500
+ const hadBoosts = this.file.boosts.length;
501
+ this.pruneBoosts();
502
+ if (hadBoosts !== this.file.boosts.length)
503
+ this.persist();
504
+ if (!this.anyCap() && this.lastStopped.size === 0)
505
+ return;
506
+ await this.refreshTeams();
507
+ this.deps.ledger.invalidate();
508
+ const summary = this.deps.ledger.summarize(1);
509
+ const known = (await this.deps.knownSessions?.().catch(() => [])) ?? [];
510
+ const sessions = new Set([...known, ...summary.agents.map((a) => a.session), ...this.teamList.flatMap((t) => t.members)]);
511
+ for (const s of this.lastStopped)
512
+ sessions.add(s);
513
+ // The all-agents total first: its notice names the whole crew once.
514
+ const totalCap = this.capForTarget(C.TOTAL_TARGET);
515
+ if (totalCap !== null)
516
+ await this.checkThresholds(C.TOTAL_TARGET, totalCap, this.deps.ledger.totalToday());
517
+ for (const t of this.teamList) {
518
+ const cap = this.capForTarget(teamTarget(t.id));
519
+ if (cap !== null)
520
+ await this.checkThresholds(teamTarget(t.id), cap, this.deps.ledger.groupToday(t.members));
521
+ }
522
+ const stoppedNow = new Set();
523
+ for (const session of sessions) {
524
+ const cap = this.capForTarget(session);
525
+ if (cap !== null)
526
+ await this.checkThresholds(session, cap, this.deps.ledger.usedToday(session));
527
+ if (this.computeStop(session))
528
+ stoppedNow.add(session);
529
+ }
530
+ const released = [...this.lastStopped].filter((s) => !stoppedNow.has(s));
531
+ this.lastStopped = stoppedNow;
532
+ if (released.length > 0) {
533
+ this.logger?.info('Token cap stop lifted — releasing queued messages', { sessions: released });
534
+ await this.deps.onReleased?.(released).catch((err) => this.logger?.warn('Releasing queued messages failed', { sessions: released, error: err instanceof Error ? err.message : String(err) }));
535
+ }
536
+ }
537
+ /**
538
+ * The cap in force for a cap target, with the boosts that cover the
539
+ * target itself (for notices and cards).
540
+ */
541
+ capForTarget(target) {
542
+ const cfg = this.file.config;
543
+ if (target === C.TOTAL_TARGET) {
544
+ const b = this.boostFor((t) => t === C.TOTAL_TARGET);
545
+ return isCap(cfg.totalCapTokens) && !b.unlimited ? cfg.totalCapTokens + b.extra : null;
546
+ }
547
+ if (target.startsWith(C.TEAM_TARGET_PREFIX)) {
548
+ const id = target.slice(C.TEAM_TARGET_PREFIX.length);
549
+ const base = cfg.teamCapsTokens[id];
550
+ const b = this.boostForTeam(id);
551
+ return isCap(base) && !b.unlimited ? base + b.extra : null;
552
+ }
553
+ const own = this.ownCapOf(target).capTokens;
554
+ const b = this.boostForSession(target);
555
+ return own !== null && !b.unlimited ? own + b.extra : null;
556
+ }
557
+ async checkThresholds(target, cap, used) {
558
+ const key = `${target}@${cap}`;
559
+ const day = this.file.day;
560
+ const who = this.describeTarget(target);
561
+ if (used >= cap) {
562
+ if (day.stopped.includes(key))
563
+ return;
564
+ day.stopped.push(key);
565
+ if (!day.warned.includes(key))
566
+ day.warned.push(key);
567
+ this.persist();
568
+ this.logger?.warn('Daily token cap reached — hard stop (no new turns until midnight)', { target, capTokens: cap, usedTokens: used });
569
+ await this.announceStop(target, who, cap, used);
570
+ return;
571
+ }
572
+ if (used >= cap * C.WARN_FRACTION && !day.warned.includes(key)) {
573
+ day.warned.push(key);
574
+ this.persist();
575
+ const pct = Math.floor((used / cap) * 100);
576
+ const subject = target === C.TOTAL_TARGET ? 'All agents together have' : target.startsWith(C.TEAM_TARGET_PREFIX) ? `Team ${who.replace(/^team /, '')} has` : `${who} has`;
577
+ const stops = target === C.TOTAL_TARGET ? 'every agent stops' : target.startsWith(C.TEAM_TARGET_PREFIX) ? 'its members stop' : 'it stops';
578
+ await this.notify(`Heads-up: ${subject} used ${formatTokens(used)} of the ${formatTokens(cap)} daily token cap today (${pct}%). At ${compactTokens(cap)} ${stops} taking new turns until midnight.`);
579
+ }
580
+ }
581
+ async announceStop(target, who, cap, used) {
582
+ const extra = suggestedBoost(cap);
583
+ const dmWho = target === C.TOTAL_TARGET ? 'everyone' : target === ORCHESTRATOR_SESSION_NAME ? 'orc' : who.replace(/^team /, '');
584
+ const headline = target === C.TOTAL_TARGET
585
+ ? `All agents together hit the daily token cap (${formatTokens(cap)}); every agent is stopped until midnight.`
586
+ : target.startsWith(C.TEAM_TARGET_PREFIX)
587
+ ? `Team ${who.replace(/^team /, '')} hit its daily token cap (${formatTokens(cap)}); its members are stopped until midnight.`
588
+ : `${who} hit its daily token cap (${formatTokens(cap)}) and is stopped until midnight.`;
589
+ const decisions = this.deps.decisions?.() ?? null;
590
+ if (decisions) {
591
+ try {
592
+ const d = await decisions.askSystem({
593
+ kind: C.DECISION_KIND,
594
+ system: { key: target, defaultIsDecline: true },
595
+ title: 'Daily token cap reached',
596
+ question: `${headline} Boost it for today?`,
597
+ body: [
598
+ `Used today: ${formatTokens(used)}. The current turn was allowed to finish; no new turns start. Messages are kept and delivered when it runs again.`,
599
+ ],
600
+ options: [
601
+ `Boost +${compactTokens(extra)} tokens today — runs again until midnight`,
602
+ `${C.OPTIONS.UNLIMITED} — no cap until midnight`,
603
+ `${C.OPTIONS.KEEP} — resets at midnight`,
604
+ ],
605
+ default: C.OPTIONS.KEEP,
606
+ deadline: this.nextMidnight(),
607
+ sensitive: 'spend',
608
+ });
609
+ this.file.day.cards[target] = d.id;
610
+ this.persist();
611
+ return;
612
+ }
613
+ catch (err) {
614
+ this.logger?.warn('Could not post the token cap card — sending a notice instead', { target, error: err instanceof Error ? err.message : String(err) });
615
+ }
616
+ }
617
+ await this.notify(`${headline} Messages are queued. Reply \`boost ${dmWho} by ${compactTokens(extra)} today\` or \`unlimited today for ${dmWho}\`.`);
618
+ }
619
+ anyCap() {
620
+ const cfg = this.file.config;
621
+ return (isCap(cfg.defaultAgentCapTokens) ||
622
+ isCap(cfg.totalCapTokens) ||
623
+ Object.values(cfg.agentCapsTokens).some(isCap) ||
624
+ Object.values(cfg.teamCapsTokens).some(isCap));
625
+ }
626
+ computeStop(session) {
627
+ const b = this.boostForSession(session);
628
+ if (b.unlimited)
629
+ return null;
630
+ const cfg = this.file.config;
631
+ const own = this.ownCapOf(session).capTokens;
632
+ if (own !== null) {
633
+ const used = this.deps.ledger.usedToday(session);
634
+ if (used >= own + b.extra)
635
+ return { session, scope: 'agent', capTokens: own + b.extra, usedTokens: used };
636
+ }
637
+ for (const teamId of this.teamsOfSession.get(session) ?? []) {
638
+ const base = cfg.teamCapsTokens[teamId];
639
+ if (!isCap(base))
640
+ continue;
641
+ const team = this.teamList.find((t) => t.id === teamId);
642
+ const used = this.deps.ledger.groupToday(team?.members ?? [session]);
643
+ if (used >= base + b.extra)
644
+ return { session, scope: 'team', capTokens: base + b.extra, usedTokens: used, teamId, teamName: team?.name ?? teamId };
645
+ }
646
+ if (isCap(cfg.totalCapTokens)) {
647
+ const used = this.deps.ledger.totalToday();
648
+ if (used >= cfg.totalCapTokens + b.extra)
649
+ return { session, scope: 'total', capTokens: cfg.totalCapTokens + b.extra, usedTokens: used };
650
+ }
651
+ return null;
652
+ }
653
+ stopKey(stop) {
654
+ const target = stop.scope === 'total' ? C.TOTAL_TARGET : stop.scope === 'team' ? teamTarget(stop.teamId ?? '') : stop.session;
655
+ return `${target}@${stop.capTokens}`;
656
+ }
657
+ scheduleEvaluate() {
658
+ void this.evaluate();
659
+ }
660
+ activeBoostsRaw() {
661
+ const now = this.now().getTime();
662
+ return this.file.boosts.filter((b) => new Date(b.until).getTime() > now);
663
+ }
664
+ pruneBoosts() {
665
+ const live = this.activeBoostsRaw();
666
+ if (live.length !== this.file.boosts.length) {
667
+ const ended = this.file.boosts.filter((b) => !live.includes(b));
668
+ this.file.boosts = live;
669
+ this.logger?.info('Usage boosts ended', { boosts: ended.map((b) => ({ target: b.target, until: b.until })) });
670
+ }
671
+ }
672
+ async resolveBoostTarget(scope, id) {
673
+ if (scope === 'all')
674
+ return C.TOTAL_TARGET;
675
+ const raw = typeof id === 'string' ? id.trim() : '';
676
+ if (scope === 'team') {
677
+ if (!raw)
678
+ throw new SpendCapError(400, 'id is required for a team boost (team id or name)');
679
+ await this.refreshTeams();
680
+ const w = raw.toLowerCase();
681
+ const hit = this.teamList.find((t) => t.id === raw) ?? this.teamList.filter((t) => t.name.toLowerCase() === w).at(0);
682
+ if (!hit)
683
+ throw new SpendCapError(404, `No team called "${raw}"`);
684
+ return teamTarget(hit.id);
685
+ }
686
+ if (scope === 'agent') {
687
+ if (!raw)
688
+ throw new SpendCapError(400, 'id is required for an agent boost (session or name)');
689
+ await this.refreshTeams();
690
+ const known = new Set([...((await this.deps.knownSessions?.().catch(() => [])) ?? []), ...this.teamList.flatMap((t) => t.members)]);
691
+ if (known.has(raw))
692
+ return raw;
693
+ const byName = [...known].filter((s) => this.nameOf(s).toLowerCase() === raw.toLowerCase());
694
+ if (byName.length === 1)
695
+ return byName[0];
696
+ if (byName.length > 1)
697
+ throw new SpendCapError(400, `More than one agent is called "${raw}"; use its session name`);
698
+ return raw;
699
+ }
700
+ throw new SpendCapError(400, 'scope must be team, agent or all');
701
+ }
702
+ /** Reset today's bookkeeping when the local day changed. */
703
+ rollDay() {
704
+ const today = localDateKey(this.now());
705
+ if (this.file.day.date === today)
706
+ return;
707
+ const had = this.file.day.stopped.length;
708
+ this.file.day = emptyDay(today);
709
+ this.pruneBoosts();
710
+ this.deps.ledger.invalidate();
711
+ this.persist();
712
+ if (had > 0)
713
+ this.logger?.info('Token caps reset for the new day', { date: today });
714
+ }
715
+ nextMidnight() {
716
+ const next = localMidnight(this.now());
717
+ next.setDate(next.getDate() + 1);
718
+ // A card must have a future deadline even in the last minute of the day.
719
+ return next.getTime() - this.now().getTime() < 60_000 ? new Date(this.now().getTime() + 60_000) : next;
720
+ }
721
+ nameOf(target) {
722
+ if (target === C.TOTAL_TARGET)
723
+ return 'All agents';
724
+ try {
725
+ return this.deps.displayNameOf?.(target) || target;
726
+ }
727
+ catch {
728
+ return target;
729
+ }
730
+ }
731
+ async notify(text) {
732
+ try {
733
+ await this.deps.notifyOwner(text);
734
+ }
735
+ catch (err) {
736
+ this.logger?.warn('Could not send the token cap notice', { error: err instanceof Error ? err.message : String(err) });
737
+ }
738
+ }
739
+ async reply(decisionId, text) {
740
+ await this.deps.decisions?.()?.replyInThread(decisionId, text).catch(() => undefined);
741
+ }
742
+ persist() {
743
+ try {
744
+ this.deps.store.write(this.file);
745
+ }
746
+ catch (err) {
747
+ this.logger?.warn('Could not save token caps', { error: err instanceof Error ? err.message : String(err) });
748
+ }
749
+ }
750
+ }
751
+ let instance = null;
752
+ /** @returns The backend's service, or null before it is wired */
753
+ export function getSpendCapService() {
754
+ return instance;
755
+ }
756
+ /** @param service - Instance (null clears) */
757
+ export function setSpendCapService(service) {
758
+ instance = service;
759
+ }
760
+ //# sourceMappingURL=spend-cap.service.js.map