openmausbot 0.1.85 → 0.1.87

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 (262) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  3. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  4. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  5. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  11. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  17. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  20. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  23. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  29. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  32. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  35. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  44. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  47. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  50. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  53. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  54. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  55. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  58. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  61. package/dist/assets/abnfDiagram-O67JEVCF-BTiuL79F.js +1 -0
  62. package/dist/assets/arc-C88Blc2v.js +1 -0
  63. package/dist/assets/architectureDiagram-NJMV4G6O-BZsJIZrb.js +36 -0
  64. package/dist/assets/blockDiagram-BEXU5L5S-CVeMe9Ah.js +140 -0
  65. package/dist/assets/c4Diagram-YGBWAQC7-Cedk3Bhx.js +38 -0
  66. package/dist/assets/channel-taCNRlUU.js +1 -0
  67. package/dist/assets/chunk-2Q5K7J3B-D-VRqzTB.js +1 -0
  68. package/dist/assets/chunk-3FUC2YCW-BChov01Y.js +2 -0
  69. package/dist/assets/chunk-5VM5RSS4-C-TfwvHy.js +15 -0
  70. package/dist/assets/chunk-GTNCS2PH-Db7CGj5Y.js +88 -0
  71. package/dist/assets/chunk-GWA4HPMP-CNvIfqKT.js +1 -0
  72. package/dist/assets/chunk-JWPE2WC7-C_bEp5tq.js +1 -0
  73. package/dist/assets/chunk-NETBCI7D-Coi4BOls.js +1 -0
  74. package/dist/assets/chunk-WEXAMYUT-CzylEuL3.js +1 -0
  75. package/dist/assets/chunk-XXDRQBXY-m7p7ztaD.js +1 -0
  76. package/dist/assets/classDiagram-v2-NBCMYWYE-BMy6o9x2.js +217 -0
  77. package/dist/assets/cose-bilkent-JH36ORCC-BDQ__0_J.js +1 -0
  78. package/dist/assets/cynefin-OW5HDTMX-Djv4ghUb.js +166 -0
  79. package/dist/assets/cynefinDiagram-VND7K2PF-BULrlGYH.js +62 -0
  80. package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
  81. package/dist/assets/dagre-6A5THRUB-D-jrT_hU.js +4 -0
  82. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  83. package/dist/assets/diagram-22UHCM2B-p2UhQmfL.js +200 -0
  84. package/dist/assets/diagram-3UASUU5V-DHvGWZ7q.js +24 -0
  85. package/dist/assets/diagram-ATOU4E4O-ChhKDot-.js +3 -0
  86. package/dist/assets/diagram-CDSNMT55-B-T7A43R.js +30 -0
  87. package/dist/assets/diagram-MLGK6HIB-CJm8ENNY.js +24 -0
  88. package/dist/assets/diagram-MPIPVDR6-510tlza8.js +41 -0
  89. package/dist/assets/ebnfDiagram-ZINNZB2B-PkvRPHel.js +1 -0
  90. package/dist/assets/elk-276RUBZZ-BVBVPoX3.js +27 -0
  91. package/dist/assets/erDiagram-OPXOYQCR-BgYMVm7L.js +99 -0
  92. package/dist/assets/flowDiagram-KWPJA3E3-BGqsnc_3.js +213 -0
  93. package/dist/assets/ganttDiagram-FUAMR5RP-D_-UeReH.js +292 -0
  94. package/dist/assets/gitGraphDiagram-X574FWY7-D0uooONx.js +106 -0
  95. package/dist/assets/graph-D8UmR1vK.js +1 -0
  96. package/dist/assets/index-BwdMAQ6p.css +1 -0
  97. package/dist/assets/index-ByJennr7.js +581 -0
  98. package/dist/assets/{index-CTNNoqSJ.js → index-zRsJERGJ.js} +1 -1
  99. package/dist/assets/infoDiagram-VRGFBTTK-B9e1z77f.js +2 -0
  100. package/dist/assets/init-Gi6I4Gst.js +1 -0
  101. package/dist/assets/ishikawaDiagram-OU5B5YK6-BB4vNwvD.js +70 -0
  102. package/dist/assets/journeyDiagram-ZHPQQLJL-DpkosOTB.js +139 -0
  103. package/dist/assets/kanban-definition-PNTS6WVX-DbDP4kUu.js +89 -0
  104. package/dist/assets/linear-DBJGPmjT.js +1 -0
  105. package/dist/assets/mermaid.core-DWPRi-ue.js +311 -0
  106. package/dist/assets/mindmap-definition-NLK3R4M7-nszBbJmx.js +96 -0
  107. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  108. package/dist/assets/pegDiagram-GJSIUBJH-CWykJWu_.js +1 -0
  109. package/dist/assets/pieDiagram-5QR66LMP-g3Da-Vnv.js +39 -0
  110. package/dist/assets/quadrantDiagram-O4NWA36T-Wzrlpzgh.js +7 -0
  111. package/dist/assets/railroadDiagram-XR7U4H2S-moHTFMC0.js +1 -0
  112. package/dist/assets/requirementDiagram-PLB6GJNP-CwfXdFH8.js +84 -0
  113. package/dist/assets/sankeyDiagram-IPEJSGJF-3ZROYvnq.js +40 -0
  114. package/dist/assets/sequenceDiagram-PO4LG4MO-E1TBebP8.js +169 -0
  115. package/dist/assets/sizeCapture-INFHLROL-DHKEU5pu.js +1 -0
  116. package/dist/assets/stateDiagram-v2-GCMORJYK-BguEj09D.js +291 -0
  117. package/dist/assets/swimlanes-2SLR337P-C7IDYnhd.js +1 -0
  118. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
  119. package/dist/assets/timeline-definition-EJHVYXUP-DCW-F5PW.js +120 -0
  120. package/dist/assets/usecaseDiagram-POWQR4AR-xBMFtWYV.js +370 -0
  121. package/dist/assets/vennDiagram-UO4OBE2U-B5nIkuH8.js +34 -0
  122. package/dist/assets/wardleyDiagram-VNRHLVJA-DYFAhdbr.js +78 -0
  123. package/dist/assets/xychartDiagram-PMCCYNJV-DeXwX6Cz.js +7 -0
  124. package/dist/index.html +2 -2
  125. package/dist-server/browser-proxy.js +2 -1
  126. package/dist-server/container-mcp.js +1047 -173
  127. package/dist-server/drivers/agents-proxy.js +16121 -1504
  128. package/dist-server/drivers/phone-proxy.js +33 -2
  129. package/dist-server/enterprise/LICENSE +32 -0
  130. package/dist-server/enterprise/server/index.js +5 -5
  131. package/dist-server/index.js +16134 -7446
  132. package/dist-server/local-computer-proxy.js +3 -3
  133. package/dist-server/local-computer.js +53 -17
  134. package/dist-server/mcp-server.js +3 -1
  135. package/dist-server/openmausbot.js +9353 -6124
  136. package/dist-server/pair-cli.js +9353 -6124
  137. package/dist-server/permission-proxy.js +29 -9
  138. package/dist-server/server/admin-activity.js +410 -0
  139. package/dist-server/server/attachments.js +55 -0
  140. package/dist-server/server/auto-approve.js +4 -3
  141. package/dist-server/server/auto-vm-claims.js +1 -0
  142. package/dist-server/server/bot-package.js +18 -292
  143. package/dist-server/server/bot-profile.js +5 -17
  144. package/dist-server/server/bot-visibility.js +462 -0
  145. package/dist-server/server/box-create-idempotency.js +3 -3
  146. package/dist-server/server/box.js +59 -56
  147. package/dist-server/server/browser-proxy.js +3 -2
  148. package/dist-server/server/browser-runtime.js +38 -2
  149. package/dist-server/server/channel-queue.js +2 -0
  150. package/dist-server/server/cli-prompts.js +24 -2
  151. package/dist-server/server/cli.js +81 -12
  152. package/dist-server/server/command-allowlist.js +154 -0
  153. package/dist-server/server/composio.js +49 -11
  154. package/dist-server/server/computer-wait.js +22 -9
  155. package/dist-server/server/config.js +133 -17
  156. package/dist-server/server/connector-verdict.js +141 -0
  157. package/dist-server/server/container-computer.js +1 -1
  158. package/dist-server/server/decision-log.js +209 -48
  159. package/dist-server/server/default-model-selection.js +29 -3
  160. package/dist-server/server/delegations.js +97 -44
  161. package/dist-server/server/drivers/acp/core.js +163 -33
  162. package/dist-server/server/drivers/acp/grok.js +4 -3
  163. package/dist-server/server/drivers/agents-call.js +1162 -0
  164. package/dist-server/server/drivers/agents-catalog.js +689 -0
  165. package/dist-server/server/drivers/agents-client.js +28 -0
  166. package/dist-server/server/drivers/agents-proxy.js +18 -1722
  167. package/dist-server/server/drivers/boxagent.js +174 -36
  168. package/dist-server/server/drivers/builtIn.js +2 -0
  169. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  170. package/dist-server/server/drivers/chat-images.js +54 -0
  171. package/dist-server/server/drivers/chat-mcp-tools.js +55 -10
  172. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  173. package/dist-server/server/drivers/claude.js +28 -8
  174. package/dist-server/server/drivers/codex-instructions.js +48 -6
  175. package/dist-server/server/drivers/codex-mcp-names.js +44 -0
  176. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  177. package/dist-server/server/drivers/codex.js +121 -34
  178. package/dist-server/server/drivers/grok.js +2 -1
  179. package/dist-server/server/drivers/mistral.js +115 -0
  180. package/dist-server/server/drivers/openai-chat.js +155 -32
  181. package/dist-server/server/drivers/openai-compat.js +16 -8
  182. package/dist-server/server/drivers/permission-command.js +35 -0
  183. package/dist-server/server/drivers/phone-proxy.js +47 -2
  184. package/dist-server/server/drivers/pi.js +75 -4
  185. package/dist-server/server/drivers/prompt-split.js +87 -0
  186. package/dist-server/server/enterprise.js +97 -16
  187. package/dist-server/server/external-runtime.js +83 -0
  188. package/dist-server/server/fleet.js +1 -1
  189. package/dist-server/server/group-thread-usage.js +103 -0
  190. package/dist-server/server/guarded-requests.js +60 -0
  191. package/dist-server/server/harness/http.js +61 -0
  192. package/dist-server/server/harness/registry.js +1 -0
  193. package/dist-server/server/hosted-models.js +151 -0
  194. package/dist-server/server/hosted-slack.js +11 -0
  195. package/dist-server/server/index.js +3091 -658
  196. package/dist-server/server/local-computer.js +72 -23
  197. package/dist-server/server/managed-desktop.js +79 -14
  198. package/dist-server/server/managed-policy.js +153 -0
  199. package/dist-server/server/mcp-bridge.js +6 -6
  200. package/dist-server/server/message-db.js +28 -0
  201. package/dist-server/server/model-context-window.js +7 -0
  202. package/dist-server/server/model-prices.js +121 -0
  203. package/dist-server/server/new-bot-defaults.js +115 -0
  204. package/dist-server/server/notify.js +10 -1
  205. package/dist-server/server/org-library.js +1051 -0
  206. package/dist-server/server/package-export.js +627 -39
  207. package/dist-server/server/package-import.js +458 -0
  208. package/dist-server/server/package-parts.js +87 -0
  209. package/dist-server/server/peer-approval.js +16 -7
  210. package/dist-server/server/peer-roster.js +23 -2
  211. package/dist-server/server/presets.js +492 -0
  212. package/dist-server/server/provider-key-check.js +3 -2
  213. package/dist-server/server/published-teams.js +80 -0
  214. package/dist-server/server/recent-work.js +15 -0
  215. package/dist-server/server/request-auth.js +96 -0
  216. package/dist-server/server/room-handoffs.js +44 -14
  217. package/dist-server/server/routes/bot-presets.js +25 -0
  218. package/dist-server/server/routes/hosted-slack.js +20 -0
  219. package/dist-server/server/routes/table.js +14 -0
  220. package/dist-server/server/routine-requests.js +6 -3
  221. package/dist-server/server/routines.js +63 -1
  222. package/dist-server/server/section-context.js +10 -0
  223. package/dist-server/server/skills.js +34 -61
  224. package/dist-server/server/spend.js +111 -16
  225. package/dist-server/server/steer-queue.js +9 -3
  226. package/dist-server/server/store.js +550 -35
  227. package/dist-server/server/surface.js +10 -4
  228. package/dist-server/server/system-prompt.js +12 -4
  229. package/dist-server/server/team-backup.js +16 -3
  230. package/dist-server/server/team-computers.js +10 -0
  231. package/dist-server/server/team-library.js +6 -2
  232. package/dist-server/server/team-manifest.js +4 -3
  233. package/dist-server/server/team-setup-requests.js +16 -5
  234. package/dist-server/server/thread-events.js +17 -1
  235. package/dist-server/server/thread-starters.js +53 -0
  236. package/dist-server/server/turn-context.js +2 -0
  237. package/dist-server/server/turn-watchdog.js +12 -3
  238. package/dist-server/server/usage-ledger.js +43 -9
  239. package/dist-server/server/vps-computer.js +139 -51
  240. package/dist-server/server/vps-container-mcp.js +7 -0
  241. package/dist-server/server/vps-ssh.js +36 -7
  242. package/dist-server/server/workspace-backup-policy.js +21 -2
  243. package/dist-server/server/workspace-backup.js +7 -4
  244. package/dist-server/server/workspace.js +1 -1
  245. package/dist-server/shared/approval-mode.js +1 -1
  246. package/dist-server/shared/ask-question.js +188 -12
  247. package/dist-server/shared/bot-profile.js +15 -0
  248. package/dist-server/shared/command-allowlist.js +1 -0
  249. package/dist-server/shared/credential-request.js +1 -1
  250. package/dist-server/shared/mention-boundary.js +15 -0
  251. package/dist-server/shared/package-format.js +967 -0
  252. package/dist-server/shared/routines.js +7 -1
  253. package/dist-server/shared/skill-md.js +64 -0
  254. package/dist-server/shared/team-backup.js +9 -0
  255. package/dist-server/shared/wire.js +4 -0
  256. package/dist-server/src/lib/composer-attachments.js +16 -0
  257. package/dist-server/vps-container-mcp.js +1147 -179
  258. package/enterprise/README.md +33 -12
  259. package/enterprise/server/index.js +5 -5
  260. package/package.json +1 -1
  261. package/dist/assets/index-Ba9G44HI.js +0 -310
  262. package/dist/assets/index-CkRNp7wX.css +0 -1
@@ -1,33 +1,76 @@
1
+ // Spend limits over the usage ledger: what this workspace has spent this
2
+ // month against its cap, the refusal a turn gets once the cap is reached,
3
+ // and the one-per-month notices when the month crosses the warning
4
+ // threshold and the cap. The figure is every cost in the ledger: what
5
+ // engines reported (on a workspace of personal subscriptions, their
6
+ // equivalents too) plus the estimates booked for engines that report tokens
7
+ // but no price (server/model-prices.ts). A turn on an unpriced model counts
8
+ // nothing. Enforced only with the `budgets` entitlement; without it the
9
+ // setting is inert.
10
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
1
12
  import { entitled } from "./enterprise.js";
2
- import { readUsage } from "./usage-ledger.js";
13
+ import { readUsage, usageRowKey } from "./usage-ledger.js";
3
14
  const DEFAULT_WARN_AT_PERCENT = 80;
4
15
  const CACHE_MS = 15_000;
5
16
  // Every turn start asks; reading the month file each time would be silly.
17
+ // A read keeps the keys of the rows it summed, so a booked row can be told
18
+ // apart from the same row read back later.
6
19
  const cache = new Map();
20
+ // Booked turns whose ledger row may not be on disk yet: dataDir → row key →
21
+ // cost. The append is asynchronous, and a turn that ran longer than CACHE_MS
22
+ // would otherwise be re-read out of the month before its own row landed.
23
+ const unwritten = new Map();
7
24
  function monthOf(now) {
8
25
  return now.toISOString().slice(0, 7);
9
26
  }
10
- /** Reported cost this month so far, from the ledger, cached briefly. */
27
+ const costOf = (value) => (typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0);
28
+ /** Reported and estimated cost this month so far: the ledger file (read at
29
+ * most every CACHE_MS) plus turns booked since whose rows have not landed. */
11
30
  export function monthToDateSpend(dataDir, now = new Date()) {
12
31
  const month = monthOf(now);
13
- const hit = cache.get(dataDir);
14
- if (hit && hit.month === month && now.getTime() - hit.at < CACHE_MS)
15
- return hit.spentUsd;
16
- const from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
17
- const spentUsd = readUsage(dataDir, { from, to: now }).reduce((sum, row) => sum + (typeof row.costUsd === "number" && Number.isFinite(row.costUsd) ? row.costUsd : 0), 0);
18
- cache.set(dataDir, { at: now.getTime(), month, spentUsd });
19
- return spentUsd;
20
- }
21
- /** Called right after a turn is booked, so the next check sees it without
22
- * waiting for the ledger's append to land or the cache to expire. */
23
- export function noteSpend(dataDir, costUsd, now = new Date()) {
24
- const hit = cache.get(dataDir);
25
- if (hit && hit.month === monthOf(now) && typeof costUsd === "number" && Number.isFinite(costUsd) && costUsd > 0) {
26
- hit.spentUsd += costUsd;
32
+ let read = cache.get(dataDir);
33
+ if (!read || read.month !== month || now.getTime() - read.at >= CACHE_MS) {
34
+ const from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
35
+ const rows = readUsage(dataDir, { from, to: now });
36
+ read = { at: now.getTime(), month, fileUsd: rows.reduce((sum, row) => sum + costOf(row.costUsd), 0), keys: new Set(rows.map(usageRowKey)) };
37
+ cache.set(dataDir, read);
27
38
  }
39
+ let pending = 0;
40
+ for (const [key, entry] of unwritten.get(dataDir) ?? []) {
41
+ if (entry.month === month && !read.keys.has(key))
42
+ pending += entry.costUsd;
43
+ }
44
+ return read.fileUsd + pending;
45
+ }
46
+ /** Called as a turn is booked, with the ledger's write for its row
47
+ * (appendUsage's promise). The cost counts from memory at once; when the
48
+ * row lands it moves into the cached read, and a re-read that already sees
49
+ * the row never counts it twice. A write that failed stops counting, as
50
+ * the ledger has no row for it. */
51
+ export function noteSpend(dataDir, row, written) {
52
+ const costUsd = costOf(row.costUsd);
53
+ if (!costUsd)
54
+ return;
55
+ const key = usageRowKey(row);
56
+ const month = row.at.slice(0, 7);
57
+ const entries = unwritten.get(dataDir) ?? new Map();
58
+ unwritten.set(dataDir, entries);
59
+ entries.set(key, { month, costUsd });
60
+ void written.then((landed) => {
61
+ entries.delete(key);
62
+ if (entries.size === 0 && unwritten.get(dataDir) === entries)
63
+ unwritten.delete(dataDir);
64
+ const read = cache.get(dataDir);
65
+ if (landed && read && read.month === month && !read.keys.has(key)) {
66
+ read.fileUsd += costUsd;
67
+ read.keys.add(key);
68
+ }
69
+ });
28
70
  }
29
71
  export function resetSpendCacheForTests() {
30
72
  cache.clear();
73
+ unwritten.clear();
31
74
  }
32
75
  /** The cap and where the month stands against it; null when there is no
33
76
  * enforceable cap (no entitlement, or none set). */
@@ -59,3 +102,55 @@ export function assertWithinBudget(cfg, dataDir, now = new Date(), isEntitled =
59
102
  return;
60
103
  throw Object.assign(new Error(`this workspace has reached its monthly spend limit of $${usd(state.monthlyUsd)} — an admin can raise it under Settings → Usage`), { status: 409, code: "spend_cap" });
61
104
  }
105
+ const ALERTS_FILE = "alerts.json";
106
+ function readMarks(dataDir) {
107
+ try {
108
+ const value = JSON.parse(readFileSync(join(dataDir, "usage", ALERTS_FILE), "utf8"));
109
+ if (typeof value !== "object" || value === null)
110
+ return null;
111
+ const marks = value;
112
+ return typeof marks.month === "string" && typeof marks.monthlyUsd === "number" ? marks : null;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ /** The notice this booking should raise, if any: the cap the first time the
119
+ * month reaches it, the warning the first time it crosses the threshold, and
120
+ * nothing again for either that month. A new month, or a different cap, is a
121
+ * new budget and starts over. Marks live beside the ledger so a restart does
122
+ * not repeat them; if they cannot be written the notice is still sent once
123
+ * for this run. */
124
+ export function takeSpendAlert(dataDir, state) {
125
+ if (!state || (!state.warn && !state.exceeded))
126
+ return null;
127
+ const saved = readMarks(dataDir) ?? memoryMarks.get(dataDir) ?? null;
128
+ const marks = saved && saved.month === state.month && saved.monthlyUsd === state.monthlyUsd
129
+ ? saved
130
+ : { month: state.month, monthlyUsd: state.monthlyUsd };
131
+ const alert = state.exceeded && !marks.cap ? "cap" : state.warn && !marks.warn && !marks.cap ? "warn" : null;
132
+ if (!alert)
133
+ return null;
134
+ // Reaching the cap also answers the warning: one notice, the stronger one.
135
+ const next = { ...marks, warn: true, ...(alert === "cap" ? { cap: true } : {}) };
136
+ memoryMarks.set(dataDir, next);
137
+ try {
138
+ mkdirSync(join(dataDir, "usage"), { recursive: true, mode: 0o700 });
139
+ writeFileSync(join(dataDir, "usage", ALERTS_FILE), JSON.stringify(next), { mode: 0o600 });
140
+ }
141
+ catch {
142
+ /* bookkeeping must never take down the turn */
143
+ }
144
+ return alert;
145
+ }
146
+ const memoryMarks = new Map();
147
+ export function resetSpendAlertsForTests() {
148
+ memoryMarks.clear();
149
+ }
150
+ /** The notice's words. English like the server's other notification titles. */
151
+ export function spendAlertText(alert, state) {
152
+ const spent = `$${state.spentUsd.toFixed(2)} of $${usd(state.monthlyUsd)} spent this month (${state.month})`;
153
+ return alert === "cap"
154
+ ? { title: "Monthly spend limit reached", body: `${spent}. New turns are refused until an admin raises the limit under Settings → Usage.` }
155
+ : { title: `Spend is at ${state.percent}% of the monthly limit`, body: `${spent}. New turns stop when the limit is reached.` };
156
+ }
@@ -72,6 +72,8 @@ export function queueSteeredMessage(botId, threadId, text, options = {}) {
72
72
  reason: options.reason,
73
73
  unattended: options.unattended,
74
74
  peerAsk: options.peerAsk,
75
+ sender: options.sender,
76
+ trigger: options.trigger,
75
77
  };
76
78
  saveChatFollowup({ id, kind: "bot", ownerId: botId, threadId, payload: item });
77
79
  entry.items.push(item);
@@ -79,14 +81,15 @@ export function queueSteeredMessage(botId, threadId, text, options = {}) {
79
81
  changed();
80
82
  return { id };
81
83
  }
82
- /** Where a thread stands among this bot's threads waiting for a free slot:
84
+ /** Where a thread stands among this bot's threads waiting for the bot to
85
+ * become available (a full slot list or an active room turn):
83
86
  * 1 for the next to start. The drain visits queues in insertion order, so
84
87
  * insertion order is the line. Null when nothing of this bot's is waiting
85
88
  * on that thread. */
86
89
  export function queuedThreadPosition(botId, threadId) {
87
90
  let position = 0;
88
91
  for (const [candidate, entry] of queues) {
89
- if (entry.botId !== botId || !entry.items.some((item) => item.reason === "capacity"))
92
+ if (entry.botId !== botId || !entry.items.some((item) => item.reason === "capacity" || item.reason === "group-turn"))
90
93
  continue;
91
94
  position += 1;
92
95
  if (candidate === threadId)
@@ -140,6 +143,7 @@ export function drainSteeredMessages(store, run, isBlocked) {
140
143
  sendId: item.sendId,
141
144
  queueId: item.messageId,
142
145
  peerAsk: item.peerAsk,
146
+ sender: item.sender,
143
147
  }));
144
148
  }
145
149
  const last = appended.at(-1);
@@ -153,7 +157,9 @@ export function drainSteeredMessages(store, run, isBlocked) {
153
157
  const running = run(entry.botId, threadId, prompt, last, appended.map((message) => message.id),
154
158
  // one unattended line makes the whole drained turn unattended: a
155
159
  // person's words in the same queue cannot re-attend a bot's own
156
- entry.items.some((item) => item.unattended === true));
160
+ entry.items.some((item) => item.unattended === true),
161
+ // the first waiting line is the one that starts this turn
162
+ { trigger: entry.items[0].trigger, sender: entry.items[0].sender, peerAsk: entry.items[0].peerAsk });
157
163
  void Promise.resolve(running).then(() => settleChatFollowups(ids, null), () => settleChatFollowups(ids, "interrupted")).catch((error) => console.warn("steer-queue: could not settle durable follow-up", error));
158
164
  }
159
165
  }