crewly 1.20.301 → 1.20.303

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 (203) hide show
  1. package/config/roles/developer/prompt.md +7 -0
  2. package/config/roles/fullstack-dev/prompt.md +7 -0
  3. package/config/roles/product-manager/prompt.md +7 -0
  4. package/config/skills/agent/core/receive-file/SKILL.md +4 -0
  5. package/config/skills/agent/core/request-file/SKILL.md +74 -0
  6. package/config/skills/agent/core/request-file/execute.sh +89 -0
  7. package/config/skills/agent/core/request-file/execute.test.sh +77 -0
  8. package/config/skills/agent/core/slack-file/SKILL.md +1 -1
  9. package/config/skills/orchestrator/usage-categories/SKILL.md +59 -0
  10. package/config/skills/orchestrator/usage-categories/execute.sh +81 -0
  11. package/config/skills/orchestrator/usage-categories/execute.test.sh +45 -0
  12. package/config/skills/orchestrator/usage-report/SKILL.md +71 -0
  13. package/config/skills/orchestrator/usage-report/execute.sh +84 -0
  14. package/config/skills/orchestrator/usage-report/execute.test.sh +39 -0
  15. package/config/skills/team-leader/usage-report/SKILL.md +71 -0
  16. package/config/skills/team-leader/usage-report/execute.sh +84 -0
  17. package/config/skills/team-leader/usage-report/execute.test.sh +39 -0
  18. package/dist/backend/backend/src/constants.d.ts +24 -4
  19. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  20. package/dist/backend/backend/src/constants.js +29 -6
  21. package/dist/backend/backend/src/constants.js.map +1 -1
  22. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +9 -0
  23. package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
  24. package/dist/backend/backend/src/controllers/apps/apps.controller.js +21 -0
  25. package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
  26. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +2 -0
  27. package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
  28. package/dist/backend/backend/src/controllers/apps/apps.routes.js +7 -1
  29. package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
  30. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts +36 -0
  31. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts.map +1 -0
  32. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js +158 -0
  33. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js.map +1 -0
  34. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts +15 -0
  35. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts.map +1 -0
  36. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js +26 -0
  37. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js.map +1 -0
  38. package/dist/backend/backend/src/index.d.ts +10 -0
  39. package/dist/backend/backend/src/index.d.ts.map +1 -1
  40. package/dist/backend/backend/src/index.js +75 -17
  41. package/dist/backend/backend/src/index.js.map +1 -1
  42. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  43. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  44. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  45. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts +32 -0
  46. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/agent/follow-through.wiring.js +42 -1
  48. package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
  49. package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts +26 -6
  50. package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/agent/resource-mode.service.js +59 -5
  52. package/dist/backend/backend/src/services/agent/resource-mode.service.js.map +1 -1
  53. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  55. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  56. package/dist/backend/backend/src/services/apps/apps.wiring.d.ts.map +1 -1
  57. package/dist/backend/backend/src/services/apps/apps.wiring.js +14 -0
  58. package/dist/backend/backend/src/services/apps/apps.wiring.js.map +1 -1
  59. package/dist/backend/backend/src/services/apps/file-request-watch.service.d.ts +88 -0
  60. package/dist/backend/backend/src/services/apps/file-request-watch.service.d.ts.map +1 -0
  61. package/dist/backend/backend/src/services/apps/file-request-watch.service.js +170 -0
  62. package/dist/backend/backend/src/services/apps/file-request-watch.service.js.map +1 -0
  63. package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
  64. package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
  65. package/dist/backend/backend/src/services/monitoring/agent-turn-state.js +101 -0
  66. package/dist/backend/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
  67. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts +58 -0
  68. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts.map +1 -0
  69. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js +100 -0
  70. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js.map +1 -0
  71. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/monitoring/token-usage.service.js +3 -0
  73. package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  74. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +7 -0
  75. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +15 -0
  77. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  78. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +51 -33
  80. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  81. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
  82. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
  84. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  85. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts +42 -2
  86. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/trace/trace-context.service.js +66 -5
  88. package/dist/backend/backend/src/services/trace/trace-context.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/trace/trace-recorder.js +11 -0
  91. package/dist/backend/backend/src/services/trace/trace-recorder.js.map +1 -1
  92. package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts +28 -0
  93. package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts.map +1 -0
  94. package/dist/backend/backend/src/services/usage-ledger/categories-param.js +67 -0
  95. package/dist/backend/backend/src/services/usage-ledger/categories-param.js.map +1 -0
  96. package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts +37 -0
  97. package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts.map +1 -0
  98. package/dist/backend/backend/src/services/usage-ledger/excerpt.js +49 -0
  99. package/dist/backend/backend/src/services/usage-ledger/excerpt.js.map +1 -0
  100. package/dist/backend/backend/src/services/usage-ledger/index.d.ts +14 -0
  101. package/dist/backend/backend/src/services/usage-ledger/index.d.ts.map +1 -0
  102. package/dist/backend/backend/src/services/usage-ledger/index.js +14 -0
  103. package/dist/backend/backend/src/services/usage-ledger/index.js.map +1 -0
  104. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
  105. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
  106. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
  107. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
  108. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts +100 -0
  109. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts.map +1 -0
  110. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js +189 -0
  111. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js.map +1 -0
  112. package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts +41 -0
  113. package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts.map +1 -0
  114. package/dist/backend/backend/src/services/usage-ledger/owner-spans.js +71 -0
  115. package/dist/backend/backend/src/services/usage-ledger/owner-spans.js.map +1 -0
  116. package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts +47 -0
  117. package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts.map +1 -0
  118. package/dist/backend/backend/src/services/usage-ledger/team-directory.js +106 -0
  119. package/dist/backend/backend/src/services/usage-ledger/team-directory.js.map +1 -0
  120. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts +101 -0
  121. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts.map +1 -0
  122. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js +263 -0
  123. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js.map +1 -0
  124. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts +96 -0
  125. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts.map +1 -0
  126. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js +166 -0
  127. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js.map +1 -0
  128. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts +90 -0
  129. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts.map +1 -0
  130. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js +294 -0
  131. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js.map +1 -0
  132. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts +102 -0
  133. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts.map +1 -0
  134. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js +314 -0
  135. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js.map +1 -0
  136. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts +62 -0
  137. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts.map +1 -0
  138. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js +106 -0
  139. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js.map +1 -0
  140. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts +52 -0
  141. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts.map +1 -0
  142. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js +95 -0
  143. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js.map +1 -0
  144. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts +127 -0
  145. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts.map +1 -0
  146. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js +19 -0
  147. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js.map +1 -0
  148. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts +124 -0
  149. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts.map +1 -0
  150. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js +372 -0
  151. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js.map +1 -0
  152. package/dist/backend/backend/src/types/slack.types.d.ts +8 -0
  153. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  154. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  155. package/dist/backend/build-info.json +2 -2
  156. package/dist/cli/backend/src/constants.d.ts +24 -4
  157. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  158. package/dist/cli/backend/src/constants.js +29 -6
  159. package/dist/cli/backend/src/constants.js.map +1 -1
  160. package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts +26 -6
  161. package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
  162. package/dist/cli/backend/src/services/agent/resource-mode.service.js +59 -5
  163. package/dist/cli/backend/src/services/agent/resource-mode.service.js.map +1 -1
  164. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  165. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  166. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  167. package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
  168. package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
  169. package/dist/cli/backend/src/services/monitoring/agent-turn-state.js +101 -0
  170. package/dist/cli/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
  171. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  172. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +3 -0
  173. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  174. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  175. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +51 -33
  176. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  177. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
  178. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  179. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
  180. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  181. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts +42 -2
  182. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  183. package/dist/cli/backend/src/services/trace/trace-context.service.js +66 -5
  184. package/dist/cli/backend/src/services/trace/trace-context.service.js.map +1 -1
  185. package/dist/cli/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
  186. package/dist/cli/backend/src/services/trace/trace-recorder.js +11 -0
  187. package/dist/cli/backend/src/services/trace/trace-recorder.js.map +1 -1
  188. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
  189. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
  190. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
  191. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
  192. package/dist/cli/backend/src/services/worktree/low-disk-guard.d.ts +109 -0
  193. package/dist/cli/backend/src/services/worktree/low-disk-guard.d.ts.map +1 -0
  194. package/dist/cli/backend/src/services/worktree/low-disk-guard.js +148 -0
  195. package/dist/cli/backend/src/services/worktree/low-disk-guard.js.map +1 -0
  196. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.d.ts +224 -0
  197. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.d.ts.map +1 -0
  198. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.js +449 -0
  199. package/dist/cli/backend/src/services/worktree/worktree-janitor.git.js.map +1 -0
  200. package/dist/cli/backend/src/types/slack.types.d.ts +8 -0
  201. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  202. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  203. package/package.json +1 -1
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Writes the usage ledger as work happens.
3
+ *
4
+ * Attribution comes from the trace context (which ticket an agent's turn is
5
+ * about, set when the harness delivers a message). The recorder listens to
6
+ * trace events and to token-usage events and appends facts; usage and busy
7
+ * time are summed per (unit, agent, model, hour) in memory and written as one
8
+ * delta fact per bucket on a timer, so the ledger stays small.
9
+ *
10
+ * Rules (specs/2026-10-11-usage-ledger.md):
11
+ * - A usage event belongs to the ticket(s) the agent's turn was about at the
12
+ * event's time. One ticket: all of it. A delivery naming n tickets: 1/n
13
+ * each. No ticket: a per-agent "(no ticket)" unit.
14
+ * - Busy time is recorded when a turn ends, in the ticket the turn started on
15
+ * and the one it is on now, split evenly.
16
+ * - Owner messages delivered to an agent on a ticket are owner-message facts;
17
+ * active spans are computed from them at report time.
18
+ *
19
+ * @module services/usage-ledger/usage-ledger.recorder
20
+ */
21
+ import { eventCostUsd, eventTokens } from '../monitoring/token-usage.service.js';
22
+ import { getTraceContext } from '../trace/trace-context.service.js';
23
+ import { setRequestObserver, setUsageObserver } from './ledger-hooks.js';
24
+ import { cleanText, cut, EXCERPT_MAX, TITLE_MAX, titleFromSummary } from './excerpt.js';
25
+ import { LEDGER_VERSION, UNTICKETED_PREFIX, } from './usage-ledger.types.js';
26
+ /** Statuses that close a ticket / request. */
27
+ const TERMINAL_STATUSES = new Set(['done', 'cancelled']);
28
+ /** Owner messages closer than this on one unit are the same message delivered twice. */
29
+ const OWNER_DEDUPE_MS = 2_000;
30
+ /** Default flush interval of the in-memory buckets. */
31
+ const FLUSH_INTERVAL_MS = 60_000;
32
+ /**
33
+ * Start of the UTC hour of a time.
34
+ *
35
+ * @param ms - Epoch ms
36
+ * @returns ISO string of the hour start
37
+ */
38
+ export function hourStart(ms) {
39
+ return new Date(Math.floor(ms / 3_600_000) * 3_600_000).toISOString();
40
+ }
41
+ /**
42
+ * Appends ledger facts from live events.
43
+ */
44
+ export class UsageLedgerRecorder {
45
+ deps;
46
+ context;
47
+ now;
48
+ opened = new Set();
49
+ agentsSeen = new Set();
50
+ closed = new Set();
51
+ lastOwner = new Map();
52
+ usage = new Map();
53
+ busy = new Map();
54
+ detach = null;
55
+ timer = null;
56
+ constructor(deps) {
57
+ this.deps = deps;
58
+ this.context = deps.context ?? getTraceContext;
59
+ this.now = deps.now ?? (() => Date.now());
60
+ }
61
+ /**
62
+ * Start listening (trace events, usage events, new requests) and flushing.
63
+ */
64
+ start() {
65
+ if (this.detach)
66
+ return;
67
+ const removeListener = this.context().addListener((event, meta) => this.onTraceEvent(event, meta.share));
68
+ setUsageObserver((session, event) => this.noteUsage(session, event));
69
+ setRequestObserver((meta) => this.noteRequest(meta));
70
+ this.detach = () => {
71
+ removeListener();
72
+ setUsageObserver(null);
73
+ setRequestObserver(null);
74
+ };
75
+ this.timer = setInterval(() => void this.flush(), this.deps.flushIntervalMs ?? FLUSH_INTERVAL_MS);
76
+ this.timer.unref?.();
77
+ }
78
+ /**
79
+ * Stop listening and write what is buffered.
80
+ */
81
+ async stop() {
82
+ this.detach?.();
83
+ this.detach = null;
84
+ if (this.timer)
85
+ clearInterval(this.timer);
86
+ this.timer = null;
87
+ await this.flush();
88
+ }
89
+ /**
90
+ * Write the buffered usage / busy buckets and wait for the files.
91
+ */
92
+ async flush() {
93
+ const facts = [...this.usage.values(), ...this.busy.values()];
94
+ this.usage.clear();
95
+ this.busy.clear();
96
+ this.deps.store.appendMany(facts);
97
+ await this.deps.store.flush();
98
+ }
99
+ /**
100
+ * A token-usage event was recorded: attribute it to the ticket(s) its
101
+ * session was working on at that time.
102
+ *
103
+ * @param session - Agent session
104
+ * @param event - The usage event
105
+ */
106
+ noteUsage(session, event) {
107
+ const at = Date.parse(event.timestamp);
108
+ const when = Number.isFinite(at) ? at : this.now();
109
+ const ctx = this.context();
110
+ let units = ctx.tracesAt(session, when);
111
+ if (event.traceId && units[0] !== event.traceId)
112
+ units = [event.traceId];
113
+ if (units.length === 0)
114
+ units = [`${UNTICKETED_PREFIX}${session}`];
115
+ const share = 1 / units.length;
116
+ const tokens = eventTokens(event);
117
+ const costUsd = eventCostUsd(event);
118
+ const info = this.deps.directory.lookup(session);
119
+ const hour = hourStart(when);
120
+ for (const unitId of units) {
121
+ this.ensureUnit(unitId, when);
122
+ this.noteAgent(unitId, session, when);
123
+ const key = [unitId, session, event.model, hour, share].join('|');
124
+ const fact = this.usage.get(key) ??
125
+ {
126
+ v: LEDGER_VERSION,
127
+ ts: hour,
128
+ kind: 'usage',
129
+ unitId,
130
+ agent: session,
131
+ ...(info?.team ? { team: info.team } : {}),
132
+ model: event.model,
133
+ ...(event.runtime ? { runtime: event.runtime } : {}),
134
+ events: 0,
135
+ share,
136
+ input: 0,
137
+ cachedInput: 0,
138
+ output: 0,
139
+ cacheWrite: 0,
140
+ budget: 0,
141
+ costUsd: 0,
142
+ attribution: 'recorded',
143
+ };
144
+ fact.events += 1;
145
+ fact.input += tokens.input;
146
+ fact.cachedInput += tokens.cachedInput;
147
+ fact.output += tokens.output;
148
+ fact.cacheWrite += tokens.cacheWrite;
149
+ fact.budget += tokens.budget;
150
+ fact.costUsd += costUsd;
151
+ this.usage.set(key, fact);
152
+ }
153
+ }
154
+ /**
155
+ * A request got its trace: record what the ticket is about.
156
+ *
157
+ * @param meta - Request facts
158
+ */
159
+ noteRequest(meta) {
160
+ this.ensureUnit(meta.traceId);
161
+ this.deps.store.append({
162
+ v: LEDGER_VERSION,
163
+ ts: meta.createdAt ?? new Date(this.now()).toISOString(),
164
+ kind: 'unit.meta',
165
+ unitId: meta.traceId,
166
+ ...(meta.ticketId ? { ticketId: meta.ticketId } : {}),
167
+ requestId: meta.requestId,
168
+ title: cut(cleanText(meta.title), TITLE_MAX),
169
+ ...(meta.description ? { excerpt: cut(cleanText(meta.description), EXCERPT_MAX) } : {}),
170
+ ...(meta.channel ? { channel: meta.channel } : {}),
171
+ ...(meta.threadRef ? { threadRef: meta.threadRef } : {}),
172
+ attribution: 'recorded',
173
+ });
174
+ }
175
+ /**
176
+ * Fold one trace event into facts.
177
+ *
178
+ * @param event - The trace event
179
+ * @param share - Fraction of a shared fact this trace carries
180
+ */
181
+ onTraceEvent(event, share) {
182
+ const unitId = event.traceId;
183
+ const at = Date.parse(event.ts);
184
+ const when = Number.isFinite(at) ? at : this.now();
185
+ switch (event.type) {
186
+ case 'turn.delivered': {
187
+ this.ensureUnit(unitId, when);
188
+ if (event.refs.session)
189
+ this.noteAgent(unitId, event.refs.session, when);
190
+ if (event.actor.kind === 'owner' && event.data?.kind === 'owner_message')
191
+ this.noteOwnerMessage(unitId, when);
192
+ break;
193
+ }
194
+ case 'turn.ended': {
195
+ const busyMs = Number(event.data?.busyMs);
196
+ const session = event.refs.session ?? event.actor.session;
197
+ if (!session || !Number.isFinite(busyMs) || busyMs <= 0)
198
+ break;
199
+ this.ensureUnit(unitId, when);
200
+ this.noteAgent(unitId, session, when);
201
+ this.addBusy(unitId, session, when, busyMs, share);
202
+ break;
203
+ }
204
+ case 'request.status':
205
+ case 'ticket.status': {
206
+ const to = typeof event.data?.to === 'string' ? event.data.to : '';
207
+ this.ensureUnit(unitId, when);
208
+ const projectId = typeof event.data?.projectId === 'string' ? event.data.projectId : '';
209
+ const project = projectId ? this.deps.directory.projectName(projectId) : undefined;
210
+ if (project) {
211
+ this.deps.store.append({ v: LEDGER_VERSION, ts: event.ts, kind: 'unit.meta', unitId, project, attribution: 'recorded' });
212
+ }
213
+ if (TERMINAL_STATUSES.has(to)) {
214
+ const key = `${unitId}|${to}`;
215
+ if (!this.closed.has(key)) {
216
+ this.closed.add(key);
217
+ this.deps.store.append({ v: LEDGER_VERSION, ts: event.ts, kind: 'unit.closed', unitId, status: to, attribution: 'recorded' });
218
+ }
219
+ }
220
+ break;
221
+ }
222
+ default:
223
+ break;
224
+ }
225
+ }
226
+ addBusy(unitId, agent, when, busyMs, share) {
227
+ const hour = hourStart(when);
228
+ const key = [unitId, agent, hour, share].join('|');
229
+ const info = this.deps.directory.lookup(agent);
230
+ const fact = this.busy.get(key) ??
231
+ {
232
+ v: LEDGER_VERSION,
233
+ ts: hour,
234
+ kind: 'busy',
235
+ unitId,
236
+ agent,
237
+ ...(info?.team ? { team: info.team } : {}),
238
+ turns: 0,
239
+ busyMs: 0,
240
+ share,
241
+ attribution: 'recorded',
242
+ };
243
+ fact.turns += 1;
244
+ fact.busyMs += busyMs;
245
+ this.busy.set(key, fact);
246
+ }
247
+ noteOwnerMessage(unitId, when) {
248
+ const last = this.lastOwner.get(unitId);
249
+ if (last !== undefined && Math.abs(when - last) < OWNER_DEDUPE_MS)
250
+ return;
251
+ this.lastOwner.set(unitId, when);
252
+ this.deps.store.append({ v: LEDGER_VERSION, ts: new Date(when).toISOString(), kind: 'owner.message', unitId, attribution: 'recorded' });
253
+ }
254
+ noteAgent(unitId, agent, when) {
255
+ const key = `${unitId}|${agent}`;
256
+ if (this.agentsSeen.has(key))
257
+ return;
258
+ this.agentsSeen.add(key);
259
+ const info = this.deps.directory.lookup(agent);
260
+ this.deps.store.append({
261
+ v: LEDGER_VERSION,
262
+ ts: new Date(when).toISOString(),
263
+ kind: 'unit.agent',
264
+ unitId,
265
+ agent,
266
+ ...(info?.team ? { team: info.team } : {}),
267
+ ...(info?.project ? { project: info.project } : {}),
268
+ attribution: 'recorded',
269
+ });
270
+ }
271
+ /**
272
+ * Write a unit's descriptive fact the first time the unit is seen (this
273
+ * process): ticket id, title, excerpt, kind, start. Later facts (the
274
+ * request's description, thread, project) fill the gaps.
275
+ */
276
+ ensureUnit(unitId, when = this.now()) {
277
+ if (this.opened.has(unitId))
278
+ return;
279
+ this.opened.add(unitId);
280
+ const ts = new Date(when).toISOString();
281
+ if (unitId.startsWith(UNTICKETED_PREFIX)) {
282
+ this.deps.store.append({
283
+ v: LEDGER_VERSION,
284
+ ts,
285
+ kind: 'unit.meta',
286
+ unitId,
287
+ rootKind: 'none',
288
+ title: `(no ticket) ${unitId.slice(UNTICKETED_PREFIX.length)}`,
289
+ attribution: 'recorded',
290
+ });
291
+ return;
292
+ }
293
+ let root;
294
+ try {
295
+ root = this.context().store.getEntry(unitId)?.root;
296
+ }
297
+ catch {
298
+ root = undefined;
299
+ }
300
+ const clean = root ? cleanText(root.summary) : '';
301
+ this.deps.store.append({
302
+ v: LEDGER_VERSION,
303
+ ts,
304
+ kind: 'unit.meta',
305
+ unitId,
306
+ ...(root?.refs.ticketId ? { ticketId: root.refs.ticketId } : {}),
307
+ ...(root?.refs.requestId ? { requestId: root.refs.requestId } : {}),
308
+ ...(root ? { rootKind: root.kind, openedAt: root.createdAt } : {}),
309
+ ...(root ? { title: titleFromSummary(root.summary), excerpt: cut(clean, EXCERPT_MAX) } : {}),
310
+ attribution: 'recorded',
311
+ });
312
+ }
313
+ }
314
+ //# sourceMappingURL=usage-ledger.recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-ledger.recorder.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/usage-ledger.recorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Wires the usage ledger: store, recorder, rollup, categories, report.
3
+ * One instance per process (see {@link getUsageLedger}).
4
+ *
5
+ * @module services/usage-ledger/usage-ledger.runtime
6
+ */
7
+ import { LedgerRollup } from './ledger-rollup.js';
8
+ import { type TeamLookup } from './team-directory.js';
9
+ import { UsageCategoriesStore } from './usage-categories.store.js';
10
+ import { type BackfillResult } from './usage-ledger.backfill.js';
11
+ import { UsageLedgerRecorder } from './usage-ledger.recorder.js';
12
+ import { UsageLedgerStore } from './usage-ledger.store.js';
13
+ import { type CategoryLlm } from './unit-classifier.js';
14
+ import { UsageReportService } from './usage-report.service.js';
15
+ /** Overrides for tests. */
16
+ export interface UsageLedgerOptions {
17
+ /** Crewly home (default CREWLY_HOME) */
18
+ home?: string;
19
+ directory?: TeamLookup;
20
+ llm?: CategoryLlm | null;
21
+ }
22
+ /**
23
+ * The assembled ledger.
24
+ */
25
+ export declare class UsageLedger {
26
+ readonly store: UsageLedgerStore;
27
+ readonly rollup: LedgerRollup;
28
+ readonly categories: UsageCategoriesStore;
29
+ readonly directory: TeamLookup;
30
+ readonly recorder: UsageLedgerRecorder;
31
+ readonly reports: UsageReportService;
32
+ private readonly home;
33
+ private backfillTimer;
34
+ constructor(options?: UsageLedgerOptions);
35
+ /**
36
+ * Start recording and schedule the one-time history rebuild.
37
+ */
38
+ start(): Promise<void>;
39
+ /**
40
+ * Stop recording and write what is buffered.
41
+ */
42
+ stop(): Promise<void>;
43
+ /**
44
+ * Rebuild history (once; later calls do nothing).
45
+ *
46
+ * @returns What happened
47
+ */
48
+ backfill(): Promise<BackfillResult>;
49
+ }
50
+ /**
51
+ * The process-wide ledger.
52
+ *
53
+ * @returns The ledger (created on first use)
54
+ */
55
+ export declare function getUsageLedger(): UsageLedger;
56
+ /**
57
+ * Replace the process-wide ledger (tests).
58
+ *
59
+ * @param ledger - Ledger, or null to rebuild lazily
60
+ */
61
+ export declare function setUsageLedgerForTesting(ledger: UsageLedger | null): void;
62
+ //# sourceMappingURL=usage-ledger.runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-ledger.runtime.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/usage-ledger.runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAmB,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAK/D,2BAA2B;AAC3B,MAAM,WAAW,kBAAkB;IAClC,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,qBAAa,WAAW;IACvB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,MAAM,eAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,aAAa,CAA+B;gBAExC,OAAO,GAAE,kBAAuB;IAoB5C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;CAazC;AAID;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEzE"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Wires the usage ledger: store, recorder, rollup, categories, report.
3
+ * One instance per process (see {@link getUsageLedger}).
4
+ *
5
+ * @module services/usage-ledger/usage-ledger.runtime
6
+ */
7
+ import * as path from 'path';
8
+ import { TRACE_CONSTANTS } from '../../constants.js';
9
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
10
+ import { LoggerService } from '../core/logger.service.js';
11
+ import { TokenUsageService } from '../monitoring/token-usage.service.js';
12
+ import { LedgerRollup } from './ledger-rollup.js';
13
+ import { TeamDirectory } from './team-directory.js';
14
+ import { UsageCategoriesStore } from './usage-categories.store.js';
15
+ import { ensureLiveSince, runBackfill } from './usage-ledger.backfill.js';
16
+ import { UsageLedgerRecorder } from './usage-ledger.recorder.js';
17
+ import { UsageLedgerStore, LEDGER_DIR_NAME } from './usage-ledger.store.js';
18
+ import { defaultCategoryLlm, UnitClassifier } from './unit-classifier.js';
19
+ import { UsageReportService } from './usage-report.service.js';
20
+ /** Delay before the one-time history rebuild, so startup is not slowed. */
21
+ const BACKFILL_DELAY_MS = 120_000;
22
+ /**
23
+ * The assembled ledger.
24
+ */
25
+ export class UsageLedger {
26
+ store;
27
+ rollup = new LedgerRollup();
28
+ categories;
29
+ directory;
30
+ recorder;
31
+ reports;
32
+ home;
33
+ backfillTimer = null;
34
+ constructor(options = {}) {
35
+ this.home = options.home ?? getCrewlyHomePath();
36
+ const dir = path.join(this.home, LEDGER_DIR_NAME);
37
+ this.store = new UsageLedgerStore(dir);
38
+ this.categories = new UsageCategoriesStore(dir);
39
+ this.directory = options.directory ?? new TeamDirectory(this.home);
40
+ this.recorder = new UsageLedgerRecorder({ store: this.store, directory: this.directory });
41
+ const classifier = new UnitClassifier(dir, options.llm === undefined ? defaultCategoryLlm() : options.llm);
42
+ this.reports = new UsageReportService({
43
+ rollup: this.rollup,
44
+ directory: this.directory,
45
+ classifier,
46
+ categories: async () => (await this.categories.list()).categories,
47
+ sync: async () => {
48
+ await this.recorder.flush();
49
+ await this.rollup.refresh(this.store);
50
+ },
51
+ });
52
+ }
53
+ /**
54
+ * Start recording and schedule the one-time history rebuild.
55
+ */
56
+ async start() {
57
+ await ensureLiveSince(this.store.dir);
58
+ this.recorder.start();
59
+ this.backfillTimer = setTimeout(() => {
60
+ void this.backfill().catch((err) => LoggerService.getInstance().createComponentLogger('UsageLedger').warn('Ledger backfill failed', { error: err instanceof Error ? err.message : String(err) }));
61
+ }, BACKFILL_DELAY_MS);
62
+ this.backfillTimer.unref?.();
63
+ }
64
+ /**
65
+ * Stop recording and write what is buffered.
66
+ */
67
+ async stop() {
68
+ if (this.backfillTimer)
69
+ clearTimeout(this.backfillTimer);
70
+ await this.recorder.stop();
71
+ }
72
+ /**
73
+ * Rebuild history (once; later calls do nothing).
74
+ *
75
+ * @returns What happened
76
+ */
77
+ async backfill() {
78
+ await this.directory.ready();
79
+ const liveSince = await ensureLiveSince(this.store.dir);
80
+ return runBackfill(this.store, {
81
+ forEachUsage: (visit) => TokenUsageService.getInstance().forEachEvent(visit),
82
+ traceDir: path.join(this.home, TRACE_CONSTANTS.DIR_NAME),
83
+ directory: this.directory,
84
+ }, { cutoffMs: Date.parse(liveSince) });
85
+ }
86
+ }
87
+ let instance = null;
88
+ /**
89
+ * The process-wide ledger.
90
+ *
91
+ * @returns The ledger (created on first use)
92
+ */
93
+ export function getUsageLedger() {
94
+ if (!instance)
95
+ instance = new UsageLedger();
96
+ return instance;
97
+ }
98
+ /**
99
+ * Replace the process-wide ledger (tests).
100
+ *
101
+ * @param ledger - Ledger, or null to rebuild lazily
102
+ */
103
+ export function setUsageLedgerForTesting(ledger) {
104
+ instance = ledger;
105
+ }
106
+ //# sourceMappingURL=usage-ledger.runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-ledger.runtime.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/usage-ledger.runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Append-only storage of ledger facts: one JSON object per line, one file per
3
+ * UTC month (`2026-10.jsonl`), plus `backfill.jsonl` for facts rebuilt from
4
+ * history. There is no update and no delete: a fact is written once.
5
+ *
6
+ * @module services/usage-ledger/usage-ledger.store
7
+ */
8
+ import type { LedgerFact, LedgerFile } from './usage-ledger.types.js';
9
+ /** Directory under CREWLY_HOME that holds the ledger. */
10
+ export declare const LEDGER_DIR_NAME = "usage-ledger";
11
+ /** File of facts rebuilt from history. */
12
+ export declare const BACKFILL_FILE = "backfill.jsonl";
13
+ /** Month file name of an ISO time (UTC). */
14
+ export declare function monthFileFor(ts: string): string;
15
+ /**
16
+ * The ledger's files. Writes are queued and applied in order.
17
+ */
18
+ export declare class UsageLedgerStore {
19
+ readonly dir: string;
20
+ private chain;
21
+ private dirReady;
22
+ /**
23
+ * @param dir - Ledger directory (default `~/.crewly/usage-ledger`)
24
+ */
25
+ constructor(dir?: string);
26
+ /**
27
+ * Queue a fact. Never throws; a failed write is dropped (accounting must not
28
+ * break the harness).
29
+ *
30
+ * @param fact - The fact
31
+ * @param file - `live` (month file of the fact) or `backfill`
32
+ */
33
+ append(fact: LedgerFact, file?: LedgerFile): void;
34
+ /**
35
+ * Queue several facts as one write.
36
+ *
37
+ * @param facts - The facts
38
+ * @param file - Target
39
+ */
40
+ appendMany(facts: LedgerFact[], file?: LedgerFile): void;
41
+ /**
42
+ * Wait for every queued write.
43
+ */
44
+ flush(): Promise<void>;
45
+ /**
46
+ * The ledger's fact files.
47
+ *
48
+ * @returns File names (month files oldest first, then the backfill file)
49
+ */
50
+ listFiles(): Promise<string[]>;
51
+ }
52
+ //# sourceMappingURL=usage-ledger.store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-ledger.store.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/usage-ledger.store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEtE,yDAAyD;AACzD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAC9C,0CAA0C;AAC1C,eAAO,MAAM,aAAa,mBAAmB,CAAC;AAE9C,4CAA4C;AAC5C,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAOhB,QAAQ,CAAC,GAAG,EAAE,MAAM;IANhC,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;gBACkB,GAAG,GAAE,MAAwD;IAElF;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAE,UAAmB,GAAG,IAAI;IAIzD;;;;;OAKG;IACH,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,IAAI,GAAE,UAAmB,GAAG,IAAI;IAwBhE;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CASpC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Append-only storage of ledger facts: one JSON object per line, one file per
3
+ * UTC month (`2026-10.jsonl`), plus `backfill.jsonl` for facts rebuilt from
4
+ * history. There is no update and no delete: a fact is written once.
5
+ *
6
+ * @module services/usage-ledger/usage-ledger.store
7
+ */
8
+ import { promises as fs } from 'fs';
9
+ import * as path from 'path';
10
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
11
+ /** Directory under CREWLY_HOME that holds the ledger. */
12
+ export const LEDGER_DIR_NAME = 'usage-ledger';
13
+ /** File of facts rebuilt from history. */
14
+ export const BACKFILL_FILE = 'backfill.jsonl';
15
+ /** Month file name of an ISO time (UTC). */
16
+ export function monthFileFor(ts) {
17
+ return `${/^\d{4}-\d{2}/.test(ts) ? ts.slice(0, 7) : new Date().toISOString().slice(0, 7)}.jsonl`;
18
+ }
19
+ /**
20
+ * The ledger's files. Writes are queued and applied in order.
21
+ */
22
+ export class UsageLedgerStore {
23
+ dir;
24
+ chain = Promise.resolve();
25
+ dirReady = false;
26
+ /**
27
+ * @param dir - Ledger directory (default `~/.crewly/usage-ledger`)
28
+ */
29
+ constructor(dir = path.join(getCrewlyHomePath(), LEDGER_DIR_NAME)) {
30
+ this.dir = dir;
31
+ }
32
+ /**
33
+ * Queue a fact. Never throws; a failed write is dropped (accounting must not
34
+ * break the harness).
35
+ *
36
+ * @param fact - The fact
37
+ * @param file - `live` (month file of the fact) or `backfill`
38
+ */
39
+ append(fact, file = 'live') {
40
+ this.appendMany([fact], file);
41
+ }
42
+ /**
43
+ * Queue several facts as one write.
44
+ *
45
+ * @param facts - The facts
46
+ * @param file - Target
47
+ */
48
+ appendMany(facts, file = 'live') {
49
+ if (facts.length === 0)
50
+ return;
51
+ const byFile = new Map();
52
+ for (const fact of facts) {
53
+ const name = file === 'backfill' ? BACKFILL_FILE : monthFileFor(fact.ts);
54
+ const lines = byFile.get(name) ?? [];
55
+ lines.push(JSON.stringify(fact));
56
+ byFile.set(name, lines);
57
+ }
58
+ this.chain = this.chain.then(async () => {
59
+ try {
60
+ if (!this.dirReady) {
61
+ await fs.mkdir(this.dir, { recursive: true });
62
+ this.dirReady = true;
63
+ }
64
+ for (const [name, lines] of byFile) {
65
+ await fs.appendFile(path.join(this.dir, name), `${lines.join('\n')}\n`, 'utf-8');
66
+ }
67
+ }
68
+ catch {
69
+ // dropped
70
+ }
71
+ });
72
+ }
73
+ /**
74
+ * Wait for every queued write.
75
+ */
76
+ async flush() {
77
+ await this.chain;
78
+ }
79
+ /**
80
+ * The ledger's fact files.
81
+ *
82
+ * @returns File names (month files oldest first, then the backfill file)
83
+ */
84
+ async listFiles() {
85
+ try {
86
+ const names = await fs.readdir(this.dir);
87
+ const months = names.filter((n) => /^\d{4}-\d{2}\.jsonl$/.test(n)).sort();
88
+ return names.includes(BACKFILL_FILE) ? [...months, BACKFILL_FILE] : months;
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ }
94
+ }
95
+ //# sourceMappingURL=usage-ledger.store.js.map