crewly 1.20.302 → 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 (157) 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/orchestrator/usage-categories/SKILL.md +59 -0
  5. package/config/skills/orchestrator/usage-categories/execute.sh +81 -0
  6. package/config/skills/orchestrator/usage-categories/execute.test.sh +45 -0
  7. package/config/skills/orchestrator/usage-report/SKILL.md +71 -0
  8. package/config/skills/orchestrator/usage-report/execute.sh +84 -0
  9. package/config/skills/orchestrator/usage-report/execute.test.sh +39 -0
  10. package/config/skills/team-leader/usage-report/SKILL.md +71 -0
  11. package/config/skills/team-leader/usage-report/execute.sh +84 -0
  12. package/config/skills/team-leader/usage-report/execute.test.sh +39 -0
  13. package/dist/backend/backend/src/constants.d.ts +11 -0
  14. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  15. package/dist/backend/backend/src/constants.js +11 -0
  16. package/dist/backend/backend/src/constants.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts +36 -0
  18. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts.map +1 -0
  19. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js +158 -0
  20. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js.map +1 -0
  21. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts +15 -0
  22. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts.map +1 -0
  23. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js +26 -0
  24. package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js.map +1 -0
  25. package/dist/backend/backend/src/index.d.ts +10 -0
  26. package/dist/backend/backend/src/index.d.ts.map +1 -1
  27. package/dist/backend/backend/src/index.js +75 -17
  28. package/dist/backend/backend/src/index.js.map +1 -1
  29. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  30. package/dist/backend/backend/src/routes/api.routes.js +3 -0
  31. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  32. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts +32 -0
  33. package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
  34. package/dist/backend/backend/src/services/agent/follow-through.wiring.js +42 -1
  35. package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
  36. package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts +26 -6
  37. package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
  38. package/dist/backend/backend/src/services/agent/resource-mode.service.js +59 -5
  39. package/dist/backend/backend/src/services/agent/resource-mode.service.js.map +1 -1
  40. package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
  41. package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
  42. package/dist/backend/backend/src/services/monitoring/agent-turn-state.js +101 -0
  43. package/dist/backend/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
  44. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts +58 -0
  45. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js +100 -0
  47. package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js.map +1 -0
  48. package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  49. package/dist/backend/backend/src/services/monitoring/token-usage.service.js +3 -0
  50. package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  51. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +7 -0
  52. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  53. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +15 -0
  54. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  55. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
  56. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  57. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
  58. package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  59. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts +42 -2
  60. package/dist/backend/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  61. package/dist/backend/backend/src/services/trace/trace-context.service.js +66 -5
  62. package/dist/backend/backend/src/services/trace/trace-context.service.js.map +1 -1
  63. package/dist/backend/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
  64. package/dist/backend/backend/src/services/trace/trace-recorder.js +11 -0
  65. package/dist/backend/backend/src/services/trace/trace-recorder.js.map +1 -1
  66. package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts +28 -0
  67. package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts.map +1 -0
  68. package/dist/backend/backend/src/services/usage-ledger/categories-param.js +67 -0
  69. package/dist/backend/backend/src/services/usage-ledger/categories-param.js.map +1 -0
  70. package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts +37 -0
  71. package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts.map +1 -0
  72. package/dist/backend/backend/src/services/usage-ledger/excerpt.js +49 -0
  73. package/dist/backend/backend/src/services/usage-ledger/excerpt.js.map +1 -0
  74. package/dist/backend/backend/src/services/usage-ledger/index.d.ts +14 -0
  75. package/dist/backend/backend/src/services/usage-ledger/index.d.ts.map +1 -0
  76. package/dist/backend/backend/src/services/usage-ledger/index.js +14 -0
  77. package/dist/backend/backend/src/services/usage-ledger/index.js.map +1 -0
  78. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
  79. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
  80. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
  81. package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
  82. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts +100 -0
  83. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts.map +1 -0
  84. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js +189 -0
  85. package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js.map +1 -0
  86. package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts +41 -0
  87. package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts.map +1 -0
  88. package/dist/backend/backend/src/services/usage-ledger/owner-spans.js +71 -0
  89. package/dist/backend/backend/src/services/usage-ledger/owner-spans.js.map +1 -0
  90. package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts +47 -0
  91. package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts.map +1 -0
  92. package/dist/backend/backend/src/services/usage-ledger/team-directory.js +106 -0
  93. package/dist/backend/backend/src/services/usage-ledger/team-directory.js.map +1 -0
  94. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts +101 -0
  95. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts.map +1 -0
  96. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js +263 -0
  97. package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js.map +1 -0
  98. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts +96 -0
  99. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts.map +1 -0
  100. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js +166 -0
  101. package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js.map +1 -0
  102. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts +90 -0
  103. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts.map +1 -0
  104. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js +294 -0
  105. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js.map +1 -0
  106. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts +102 -0
  107. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts.map +1 -0
  108. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js +314 -0
  109. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js.map +1 -0
  110. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts +62 -0
  111. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts.map +1 -0
  112. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js +106 -0
  113. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js.map +1 -0
  114. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts +52 -0
  115. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts.map +1 -0
  116. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js +95 -0
  117. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js.map +1 -0
  118. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts +127 -0
  119. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts.map +1 -0
  120. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js +19 -0
  121. package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js.map +1 -0
  122. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts +124 -0
  123. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts.map +1 -0
  124. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js +372 -0
  125. package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js.map +1 -0
  126. package/dist/backend/build-info.json +2 -2
  127. package/dist/cli/backend/src/constants.d.ts +11 -0
  128. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  129. package/dist/cli/backend/src/constants.js +11 -0
  130. package/dist/cli/backend/src/constants.js.map +1 -1
  131. package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts +26 -6
  132. package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
  133. package/dist/cli/backend/src/services/agent/resource-mode.service.js +59 -5
  134. package/dist/cli/backend/src/services/agent/resource-mode.service.js.map +1 -1
  135. package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
  136. package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
  137. package/dist/cli/backend/src/services/monitoring/agent-turn-state.js +101 -0
  138. package/dist/cli/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
  139. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
  140. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +3 -0
  141. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
  142. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
  143. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
  144. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
  145. package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
  146. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts +42 -2
  147. package/dist/cli/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
  148. package/dist/cli/backend/src/services/trace/trace-context.service.js +66 -5
  149. package/dist/cli/backend/src/services/trace/trace-context.service.js.map +1 -1
  150. package/dist/cli/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
  151. package/dist/cli/backend/src/services/trace/trace-recorder.js +11 -0
  152. package/dist/cli/backend/src/services/trace/trace-recorder.js.map +1 -1
  153. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
  154. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
  155. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
  156. package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
  157. package/package.json +1 -1
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Late-bound hooks so low-level services (token usage, trace recorder) can
3
+ * feed the usage ledger without importing it (the ledger imports them).
4
+ *
5
+ * @module services/usage-ledger/ledger-hooks
6
+ */
7
+ import type { TokenUsageEvent } from '../monitoring/token-usage.service.js';
8
+ /** What the ledger needs to know about a new request. */
9
+ export interface RequestMeta {
10
+ traceId: string;
11
+ ticketId?: string;
12
+ requestId: string;
13
+ title: string;
14
+ description?: string;
15
+ channel?: string;
16
+ threadRef?: string;
17
+ createdAt?: string;
18
+ }
19
+ /**
20
+ * Register the ledger's usage observer.
21
+ *
22
+ * @param fn - Called after every recorded usage event; null removes it
23
+ */
24
+ export declare function setUsageObserver(fn: ((session: string, event: TokenUsageEvent) => void) | null): void;
25
+ /**
26
+ * Register the ledger's request observer.
27
+ *
28
+ * @param fn - Called when a request gets its trace; null removes it
29
+ */
30
+ export declare function setRequestObserver(fn: ((meta: RequestMeta) => void) | null): void;
31
+ /**
32
+ * A usage event was recorded. Never throws.
33
+ *
34
+ * @param session - Agent session
35
+ * @param event - The event (trace id already stamped)
36
+ */
37
+ export declare function notifyUsage(session: string, event: TokenUsageEvent): void;
38
+ /**
39
+ * A request was given its trace. Never throws.
40
+ *
41
+ * @param meta - Request facts
42
+ */
43
+ export declare function notifyRequest(meta: RequestMeta): void;
44
+ //# sourceMappingURL=ledger-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-hooks.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/ledger-hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAE5E,yDAAyD;AACzD,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAErG;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAMzE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAMrD"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Late-bound hooks so low-level services (token usage, trace recorder) can
3
+ * feed the usage ledger without importing it (the ledger imports them).
4
+ *
5
+ * @module services/usage-ledger/ledger-hooks
6
+ */
7
+ let usageObserver = null;
8
+ let requestObserver = null;
9
+ /**
10
+ * Register the ledger's usage observer.
11
+ *
12
+ * @param fn - Called after every recorded usage event; null removes it
13
+ */
14
+ export function setUsageObserver(fn) {
15
+ usageObserver = fn;
16
+ }
17
+ /**
18
+ * Register the ledger's request observer.
19
+ *
20
+ * @param fn - Called when a request gets its trace; null removes it
21
+ */
22
+ export function setRequestObserver(fn) {
23
+ requestObserver = fn;
24
+ }
25
+ /**
26
+ * A usage event was recorded. Never throws.
27
+ *
28
+ * @param session - Agent session
29
+ * @param event - The event (trace id already stamped)
30
+ */
31
+ export function notifyUsage(session, event) {
32
+ try {
33
+ usageObserver?.(session, event);
34
+ }
35
+ catch {
36
+ // accounting must never break a turn
37
+ }
38
+ }
39
+ /**
40
+ * A request was given its trace. Never throws.
41
+ *
42
+ * @param meta - Request facts
43
+ */
44
+ export function notifyRequest(meta) {
45
+ try {
46
+ requestObserver?.(meta);
47
+ }
48
+ catch {
49
+ // accounting must never break a request
50
+ }
51
+ }
52
+ //# sourceMappingURL=ledger-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-hooks.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/ledger-hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,IAAI,aAAa,GAA+D,IAAI,CAAC;AACrF,IAAI,eAAe,GAAyC,IAAI,CAAC;AAEjE;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAA8D;IAC9F,aAAa,GAAG,EAAE,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAwC;IAC1E,eAAe,GAAG,EAAE,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAsB;IAClE,IAAI,CAAC;QACJ,aAAa,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACR,qCAAqC;IACtC,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAiB;IAC9C,IAAI,CAAC;QACJ,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,wCAAwC;IACzC,CAAC;AACF,CAAC"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * In-memory fold of the ledger files into per-unit rollups.
3
+ *
4
+ * Reads are incremental: each file's byte offset is remembered, so a refresh
5
+ * only parses what was appended since the last one (the first refresh after a
6
+ * restart reads everything once). The rollup is derived data; the files stay
7
+ * the source of truth.
8
+ *
9
+ * @module services/usage-ledger/ledger-rollup
10
+ */
11
+ import type { UsageLedgerStore } from './usage-ledger.store.js';
12
+ import { type Attribution, type LedgerFact } from './usage-ledger.types.js';
13
+ /** Usage of one agent / model in one hour, on one unit. */
14
+ export interface UsageEntry {
15
+ hourMs: number;
16
+ agent: string;
17
+ team?: string;
18
+ model: string;
19
+ /** Fraction of the raw figures that belongs to this unit */
20
+ share: number;
21
+ input: number;
22
+ cachedInput: number;
23
+ output: number;
24
+ cacheWrite: number;
25
+ budget: number;
26
+ costUsd: number;
27
+ attribution: Attribution;
28
+ }
29
+ /** Busy time of one agent in one hour, on one unit. */
30
+ export interface BusyEntry {
31
+ hourMs: number;
32
+ agent: string;
33
+ team?: string;
34
+ busyMs: number;
35
+ share: number;
36
+ attribution: Attribution;
37
+ }
38
+ /** Everything the ledger knows about one unit. */
39
+ export interface UnitRollup {
40
+ unitId: string;
41
+ /** Holds usage no ticket claimed (not a real ticket) */
42
+ synthetic: boolean;
43
+ ticketId?: string;
44
+ requestId?: string;
45
+ rootKind?: string;
46
+ title?: string;
47
+ excerpt?: string;
48
+ project?: string;
49
+ team?: string;
50
+ channel?: string;
51
+ threadRef?: string;
52
+ openedAt?: string;
53
+ /** First / last fact time (ms) */
54
+ firstMs: number;
55
+ lastMs: number;
56
+ closedAt?: string;
57
+ closedStatus?: string;
58
+ agents: Map<string, {
59
+ team?: string;
60
+ project?: string;
61
+ }>;
62
+ usage: UsageEntry[];
63
+ busy: BusyEntry[];
64
+ /** Owner message times (ms), in file order */
65
+ ownerMessages: number[];
66
+ /** Any fact of the unit was inferred rather than recorded */
67
+ inferred: boolean;
68
+ }
69
+ /**
70
+ * Per-unit rollups of the ledger.
71
+ */
72
+ export declare class LedgerRollup {
73
+ readonly units: Map<string, UnitRollup>;
74
+ private readonly offsets;
75
+ private running;
76
+ /** Facts folded in so far */
77
+ factCount: number;
78
+ /**
79
+ * Fold in whatever was appended since the last refresh. Calls are serialised.
80
+ *
81
+ * @param store - The ledger store
82
+ */
83
+ refresh(store: UsageLedgerStore): Promise<void>;
84
+ private run;
85
+ /**
86
+ * Read complete lines from `from` to `size`.
87
+ *
88
+ * @returns Bytes consumed (up to and including the last newline)
89
+ */
90
+ private readNew;
91
+ private foldLine;
92
+ private unit;
93
+ /**
94
+ * Fold one fact in.
95
+ *
96
+ * @param fact - A ledger fact
97
+ */
98
+ fold(fact: LedgerFact): void;
99
+ }
100
+ //# sourceMappingURL=ledger-rollup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-rollup.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/ledger-rollup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE/F,2DAA2D;AAC3D,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;CACzB;AAED,uDAAuD;AACvD,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,WAAW,CAAC;CACzB;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6DAA6D;IAC7D,QAAQ,EAAE,OAAO,CAAC;CAClB;AAID;;GAEG;AACH,qBAAa,YAAY;IACxB,QAAQ,CAAC,KAAK,0BAAiC;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,OAAO,CAAoC;IACnD,6BAA6B;IAC7B,SAAS,SAAK;IAEd;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAKjC,GAAG;IAwBjB;;;;OAIG;YACW,OAAO;IAyBrB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,IAAI;IAuBZ;;;;OAIG;IACH,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;CAwD5B"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * In-memory fold of the ledger files into per-unit rollups.
3
+ *
4
+ * Reads are incremental: each file's byte offset is remembered, so a refresh
5
+ * only parses what was appended since the last one (the first refresh after a
6
+ * restart reads everything once). The rollup is derived data; the files stay
7
+ * the source of truth.
8
+ *
9
+ * @module services/usage-ledger/ledger-rollup
10
+ */
11
+ import { promises as fs } from 'fs';
12
+ import * as path from 'path';
13
+ import { UNTICKETED_PREFIX } from './usage-ledger.types.js';
14
+ const READ_CHUNK = 1 << 20;
15
+ /**
16
+ * Per-unit rollups of the ledger.
17
+ */
18
+ export class LedgerRollup {
19
+ units = new Map();
20
+ offsets = new Map();
21
+ running = Promise.resolve();
22
+ /** Facts folded in so far */
23
+ factCount = 0;
24
+ /**
25
+ * Fold in whatever was appended since the last refresh. Calls are serialised.
26
+ *
27
+ * @param store - The ledger store
28
+ */
29
+ refresh(store) {
30
+ this.running = this.running.then(() => this.run(store)).catch(() => undefined);
31
+ return this.running;
32
+ }
33
+ async run(store) {
34
+ const files = await store.listFiles();
35
+ for (const name of files) {
36
+ const file = path.join(store.dir, name);
37
+ let size;
38
+ try {
39
+ size = (await fs.stat(file)).size;
40
+ }
41
+ catch {
42
+ continue;
43
+ }
44
+ const known = this.offsets.get(name) ?? 0;
45
+ if (size < known) {
46
+ // A file got shorter: the ledger was edited from outside. Start over.
47
+ this.units.clear();
48
+ this.offsets.clear();
49
+ this.factCount = 0;
50
+ await this.run(store);
51
+ return;
52
+ }
53
+ if (size === known)
54
+ continue;
55
+ this.offsets.set(name, known + (await this.readNew(file, known, size)));
56
+ }
57
+ }
58
+ /**
59
+ * Read complete lines from `from` to `size`.
60
+ *
61
+ * @returns Bytes consumed (up to and including the last newline)
62
+ */
63
+ async readNew(file, from, size) {
64
+ const handle = await fs.open(file, 'r');
65
+ try {
66
+ let consumed = 0;
67
+ let pending = Buffer.alloc(0);
68
+ let position = from;
69
+ while (position < size) {
70
+ const buf = Buffer.alloc(Math.min(READ_CHUNK, size - position));
71
+ const { bytesRead } = await handle.read(buf, 0, buf.length, position);
72
+ if (bytesRead === 0)
73
+ break;
74
+ position += bytesRead;
75
+ pending = Buffer.concat([pending, buf.subarray(0, bytesRead)]);
76
+ const lastNewline = pending.lastIndexOf(0x0a);
77
+ if (lastNewline < 0)
78
+ continue;
79
+ const complete = pending.subarray(0, lastNewline + 1).toString('utf-8');
80
+ pending = pending.subarray(lastNewline + 1);
81
+ consumed += Buffer.byteLength(complete);
82
+ for (const line of complete.split('\n'))
83
+ this.foldLine(line);
84
+ }
85
+ return consumed;
86
+ }
87
+ finally {
88
+ await handle.close();
89
+ }
90
+ }
91
+ foldLine(line) {
92
+ if (!line.trim())
93
+ return;
94
+ try {
95
+ const fact = JSON.parse(line);
96
+ if (fact && typeof fact === 'object' && typeof fact.unitId === 'string')
97
+ this.fold(fact);
98
+ }
99
+ catch {
100
+ // a torn or foreign line
101
+ }
102
+ }
103
+ unit(unitId, ms) {
104
+ let u = this.units.get(unitId);
105
+ if (!u) {
106
+ u = {
107
+ unitId,
108
+ synthetic: unitId.startsWith(UNTICKETED_PREFIX),
109
+ firstMs: ms,
110
+ lastMs: ms,
111
+ agents: new Map(),
112
+ usage: [],
113
+ busy: [],
114
+ ownerMessages: [],
115
+ inferred: false,
116
+ };
117
+ this.units.set(unitId, u);
118
+ }
119
+ if (Number.isFinite(ms)) {
120
+ u.firstMs = Math.min(u.firstMs, ms);
121
+ u.lastMs = Math.max(u.lastMs, ms);
122
+ }
123
+ return u;
124
+ }
125
+ /**
126
+ * Fold one fact in.
127
+ *
128
+ * @param fact - A ledger fact
129
+ */
130
+ fold(fact) {
131
+ const ms = Date.parse(fact.ts);
132
+ const u = this.unit(fact.unitId, ms);
133
+ this.factCount += 1;
134
+ if (fact.attribution === 'inferred')
135
+ u.inferred = true;
136
+ switch (fact.kind) {
137
+ case 'unit.meta': {
138
+ // First value wins; later facts only fill gaps.
139
+ u.ticketId ??= fact.ticketId;
140
+ u.requestId ??= fact.requestId;
141
+ u.rootKind ??= fact.rootKind;
142
+ u.title ||= fact.title;
143
+ u.excerpt ||= fact.excerpt;
144
+ u.project ??= fact.project;
145
+ u.team ??= fact.team;
146
+ u.channel ??= fact.channel;
147
+ u.threadRef ??= fact.threadRef;
148
+ u.openedAt ??= fact.openedAt;
149
+ break;
150
+ }
151
+ case 'unit.agent': {
152
+ const known = u.agents.get(fact.agent);
153
+ u.agents.set(fact.agent, { team: known?.team ?? fact.team, project: known?.project ?? fact.project });
154
+ break;
155
+ }
156
+ case 'unit.closed': {
157
+ if (!u.closedAt || fact.ts > u.closedAt) {
158
+ u.closedAt = fact.ts;
159
+ u.closedStatus = fact.status;
160
+ }
161
+ break;
162
+ }
163
+ case 'usage':
164
+ u.usage.push({
165
+ hourMs: ms,
166
+ agent: fact.agent,
167
+ team: fact.team,
168
+ model: fact.model,
169
+ share: fact.share,
170
+ input: fact.input,
171
+ cachedInput: fact.cachedInput,
172
+ output: fact.output,
173
+ cacheWrite: fact.cacheWrite,
174
+ budget: fact.budget,
175
+ costUsd: fact.costUsd,
176
+ attribution: fact.attribution,
177
+ });
178
+ break;
179
+ case 'busy':
180
+ u.busy.push({ hourMs: ms, agent: fact.agent, team: fact.team, busyMs: fact.busyMs, share: fact.share, attribution: fact.attribution });
181
+ break;
182
+ case 'owner.message':
183
+ if (Number.isFinite(ms))
184
+ u.ownerMessages.push(ms);
185
+ break;
186
+ }
187
+ }
188
+ }
189
+ //# sourceMappingURL=ledger-rollup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-rollup.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/ledger-rollup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Owner time per ticket, from owner message times.
3
+ *
4
+ * Heuristic (specs/2026-10-11-usage-ledger.md): owner messages on one ticket
5
+ * that follow each other within {@link OWNER_SPAN_GAP_MS} form one active
6
+ * span. A span runs from its first message to its last message plus
7
+ * {@link OWNER_MESSAGE_CREDIT_MS} (the time spent writing that last message),
8
+ * so a single message counts for one minute. When spans of different tickets
9
+ * overlap in time, the overlap is split evenly between them, so the owner's
10
+ * minutes are never counted twice.
11
+ *
12
+ * @module services/usage-ledger/owner-spans
13
+ */
14
+ /** Messages at most this far apart belong to the same span. */
15
+ export declare const OWNER_SPAN_GAP_MS: number;
16
+ /** Time credited to the last (or only) message of a span. */
17
+ export declare const OWNER_MESSAGE_CREDIT_MS = 60000;
18
+ /** One stretch of owner activity on a ticket. */
19
+ export interface OwnerSpan {
20
+ unitId: string;
21
+ startMs: number;
22
+ endMs: number;
23
+ }
24
+ /**
25
+ * Group one ticket's owner message times into spans.
26
+ *
27
+ * @param unitId - Ticket (unit) id
28
+ * @param times - Message times (ms), any order, duplicates allowed
29
+ * @returns Spans, oldest first
30
+ */
31
+ export declare function buildOwnerSpans(unitId: string, times: number[]): OwnerSpan[];
32
+ /**
33
+ * Owner minutes per ticket inside a time window, with overlaps split evenly.
34
+ *
35
+ * @param spans - Spans of any tickets
36
+ * @param fromMs - Window start (ms)
37
+ * @param toMs - Window end (ms)
38
+ * @returns Ticket id → minutes
39
+ */
40
+ export declare function ownerMinutesByUnit(spans: OwnerSpan[], fromMs: number, toMs: number): Map<string, number>;
41
+ //# sourceMappingURL=owner-spans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-spans.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/owner-spans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,QAAc,CAAC;AAC7C,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAgB5E;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBxG"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Owner time per ticket, from owner message times.
3
+ *
4
+ * Heuristic (specs/2026-10-11-usage-ledger.md): owner messages on one ticket
5
+ * that follow each other within {@link OWNER_SPAN_GAP_MS} form one active
6
+ * span. A span runs from its first message to its last message plus
7
+ * {@link OWNER_MESSAGE_CREDIT_MS} (the time spent writing that last message),
8
+ * so a single message counts for one minute. When spans of different tickets
9
+ * overlap in time, the overlap is split evenly between them, so the owner's
10
+ * minutes are never counted twice.
11
+ *
12
+ * @module services/usage-ledger/owner-spans
13
+ */
14
+ /** Messages at most this far apart belong to the same span. */
15
+ export const OWNER_SPAN_GAP_MS = 10 * 60_000;
16
+ /** Time credited to the last (or only) message of a span. */
17
+ export const OWNER_MESSAGE_CREDIT_MS = 60_000;
18
+ /**
19
+ * Group one ticket's owner message times into spans.
20
+ *
21
+ * @param unitId - Ticket (unit) id
22
+ * @param times - Message times (ms), any order, duplicates allowed
23
+ * @returns Spans, oldest first
24
+ */
25
+ export function buildOwnerSpans(unitId, times) {
26
+ const sorted = [...new Set(times.filter((t) => Number.isFinite(t)))].sort((a, b) => a - b);
27
+ const spans = [];
28
+ let start = null;
29
+ let last = 0;
30
+ for (const t of sorted) {
31
+ if (start !== null && t - last <= OWNER_SPAN_GAP_MS) {
32
+ last = t;
33
+ continue;
34
+ }
35
+ if (start !== null)
36
+ spans.push({ unitId, startMs: start, endMs: last + OWNER_MESSAGE_CREDIT_MS });
37
+ start = t;
38
+ last = t;
39
+ }
40
+ if (start !== null)
41
+ spans.push({ unitId, startMs: start, endMs: last + OWNER_MESSAGE_CREDIT_MS });
42
+ return spans;
43
+ }
44
+ /**
45
+ * Owner minutes per ticket inside a time window, with overlaps split evenly.
46
+ *
47
+ * @param spans - Spans of any tickets
48
+ * @param fromMs - Window start (ms)
49
+ * @param toMs - Window end (ms)
50
+ * @returns Ticket id → minutes
51
+ */
52
+ export function ownerMinutesByUnit(spans, fromMs, toMs) {
53
+ const clipped = spans
54
+ .map((s) => ({ unitId: s.unitId, startMs: Math.max(s.startMs, fromMs), endMs: Math.min(s.endMs, toMs) }))
55
+ .filter((s) => s.endMs > s.startMs);
56
+ const points = [...new Set(clipped.flatMap((s) => [s.startMs, s.endMs]))].sort((a, b) => a - b);
57
+ const minutes = new Map();
58
+ for (let i = 0; i < points.length - 1; i++) {
59
+ const a = points[i];
60
+ const b = points[i + 1];
61
+ const active = clipped.filter((s) => s.startMs <= a && s.endMs >= b);
62
+ if (active.length === 0)
63
+ continue;
64
+ const units = [...new Set(active.map((s) => s.unitId))];
65
+ const each = (b - a) / 60_000 / units.length;
66
+ for (const u of units)
67
+ minutes.set(u, (minutes.get(u) ?? 0) + each);
68
+ }
69
+ return minutes;
70
+ }
71
+ //# sourceMappingURL=owner-spans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-spans.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/owner-spans.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Which team and project an agent session belongs to, read from the team
3
+ * configs under CREWLY_HOME (`teams/<id>/config.json`, `projects.json`).
4
+ * Cached; a stale cache is refreshed in the background so hot paths stay
5
+ * synchronous and cheap.
6
+ *
7
+ * @module services/usage-ledger/team-directory
8
+ */
9
+ /** Team and project of a session. */
10
+ export interface TeamInfo {
11
+ team?: string;
12
+ project?: string;
13
+ }
14
+ /** What the ledger asks of a directory. */
15
+ export interface TeamLookup {
16
+ /** Team / project of an agent session (sync, from cache) */
17
+ lookup(session: string): TeamInfo | null;
18
+ /** Project name for a project id */
19
+ projectName(projectId: string): string | undefined;
20
+ /** Wait until the cache has been loaded at least once */
21
+ ready(): Promise<void>;
22
+ }
23
+ /**
24
+ * Reads team and project names from disk.
25
+ */
26
+ export declare class TeamDirectory implements TeamLookup {
27
+ private readonly home;
28
+ private readonly now;
29
+ private sessions;
30
+ private projects;
31
+ private loadedAt;
32
+ private loading;
33
+ /**
34
+ * @param home - Crewly home (default CREWLY_HOME)
35
+ * @param now - Clock (ms)
36
+ */
37
+ constructor(home?: string, now?: () => number);
38
+ lookup(session: string): TeamInfo | null;
39
+ projectName(projectId: string): string | undefined;
40
+ ready(): Promise<void>;
41
+ /**
42
+ * Reload the cache (one load at a time).
43
+ */
44
+ refresh(): Promise<void>;
45
+ private load;
46
+ }
47
+ //# sourceMappingURL=team-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-directory.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/team-directory.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IAC1B,4DAA4D;IAC5D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;IACzC,oCAAoC;IACpC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD,yDAAyD;IACzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAmBD;;GAEG;AACH,qBAAa,aAAc,YAAW,UAAU;IAW9C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAXrB,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,OAAO,CAA8B;IAE7C;;;OAGG;gBAEe,IAAI,GAAE,MAA4B,EAClC,GAAG,GAAE,MAAM,MAAyB;IAGtD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAMxC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAWV,IAAI;CAiClB"}