devchain-cli 0.22.0 → 0.23.0

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 (241) hide show
  1. package/dist/drizzle/0077_familiar_morbius.sql +6 -0
  2. package/dist/drizzle/0078_preserve_legacy_connection_ownership.sql +180 -0
  3. package/dist/drizzle/0079_premium_forgotten_one.sql +18 -0
  4. package/dist/drizzle/0080_lethal_thunderbolt_ross.sql +49 -0
  5. package/dist/drizzle/0081_early_kronos.sql +58 -0
  6. package/dist/drizzle/0082_huge_black_queen.sql +14 -0
  7. package/dist/drizzle/meta/0077_snapshot.json +6957 -0
  8. package/dist/drizzle/meta/0078_snapshot.json +6957 -0
  9. package/dist/drizzle/meta/0079_snapshot.json +7093 -0
  10. package/dist/drizzle/meta/0080_snapshot.json +7241 -0
  11. package/dist/drizzle/meta/0081_snapshot.json +7482 -0
  12. package/dist/drizzle/meta/0082_snapshot.json +7571 -0
  13. package/dist/drizzle/meta/_journal.json +42 -0
  14. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  15. package/dist/server/common/errors/error-types.d.ts +7 -0
  16. package/dist/server/common/errors/error-types.js +7 -1
  17. package/dist/server/common/errors/error-types.js.map +1 -1
  18. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.d.ts +8 -0
  19. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js +67 -0
  20. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js.map +1 -0
  21. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.d.ts +12 -0
  22. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js +322 -0
  23. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js.map +1 -0
  24. package/dist/server/modules/epic-time/epic-time.module.js +14 -4
  25. package/dist/server/modules/epic-time/epic-time.module.js.map +1 -1
  26. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.d.ts +30 -0
  27. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js +125 -0
  28. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js.map +1 -0
  29. package/dist/server/modules/epic-time/models/epic-time-local-day.d.ts +9 -0
  30. package/dist/server/modules/epic-time/models/epic-time-local-day.js +164 -0
  31. package/dist/server/modules/epic-time/models/epic-time-local-day.js.map +1 -0
  32. package/dist/server/modules/epic-time/models/epic-time.models.d.ts +138 -0
  33. package/dist/server/modules/epic-time/models/epic-time.models.js +2 -0
  34. package/dist/server/modules/epic-time/models/epic-time.models.js.map +1 -1
  35. package/dist/server/modules/epic-time/services/agent-time-accounting.service.d.ts +1 -0
  36. package/dist/server/modules/epic-time/services/agent-time-accounting.service.js +31 -8
  37. package/dist/server/modules/epic-time/services/agent-time-accounting.service.js.map +1 -1
  38. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.d.ts +37 -0
  39. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js +716 -0
  40. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js.map +1 -0
  41. package/dist/server/modules/epic-time/services/epic-time.service.d.ts +12 -3
  42. package/dist/server/modules/epic-time/services/epic-time.service.js +155 -69
  43. package/dist/server/modules/epic-time/services/epic-time.service.js.map +1 -1
  44. package/dist/server/modules/epic-time/services/epic-time.store.d.ts +29 -3
  45. package/dist/server/modules/epic-time/services/epic-time.store.js +524 -40
  46. package/dist/server/modules/epic-time/services/epic-time.store.js.map +1 -1
  47. package/dist/server/modules/epics/controllers/epics.controller.d.ts +10 -1
  48. package/dist/server/modules/epics/controllers/epics.controller.js +108 -2
  49. package/dist/server/modules/epics/controllers/epics.controller.js.map +1 -1
  50. package/dist/server/modules/epics/epics.module.js +8 -2
  51. package/dist/server/modules/epics/epics.module.js.map +1 -1
  52. package/dist/server/modules/epics/models/epic-relations.models.d.ts +34 -0
  53. package/dist/server/modules/epics/models/epic-relations.models.js +16 -0
  54. package/dist/server/modules/epics/models/epic-relations.models.js.map +1 -0
  55. package/dist/server/modules/epics/services/epic-relation-target-resolver.d.ts +8 -0
  56. package/dist/server/modules/epics/services/epic-relation-target-resolver.js +37 -0
  57. package/dist/server/modules/epics/services/epic-relation-target-resolver.js.map +1 -0
  58. package/dist/server/modules/epics/services/epic-relations.service.d.ts +52 -0
  59. package/dist/server/modules/epics/services/epic-relations.service.js +135 -0
  60. package/dist/server/modules/epics/services/epic-relations.service.js.map +1 -0
  61. package/dist/server/modules/epics/services/epics.service.d.ts +9 -2
  62. package/dist/server/modules/epics/services/epics.service.js +115 -11
  63. package/dist/server/modules/epics/services/epics.service.js.map +1 -1
  64. package/dist/server/modules/events/catalog/broadcast-registry.js +16 -0
  65. package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
  66. package/dist/server/modules/events/catalog/epic.relations.invalidated.d.ts +12 -0
  67. package/dist/server/modules/events/catalog/epic.relations.invalidated.js +13 -0
  68. package/dist/server/modules/events/catalog/epic.relations.invalidated.js.map +1 -0
  69. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.d.ts +12 -0
  70. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js +13 -0
  71. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js.map +1 -0
  72. package/dist/server/modules/events/catalog/index.d.ts +27 -4
  73. package/dist/server/modules/events/catalog/index.js +6 -0
  74. package/dist/server/modules/events/catalog/index.js.map +1 -1
  75. package/dist/server/modules/events/catalog/integration.connection.created.d.ts +4 -1
  76. package/dist/server/modules/events/catalog/integration.connection.created.js +5 -2
  77. package/dist/server/modules/events/catalog/integration.connection.created.js.map +1 -1
  78. package/dist/server/modules/events/catalog/integration.connection.deleted.d.ts +4 -1
  79. package/dist/server/modules/events/catalog/integration.connection.deleted.js +5 -2
  80. package/dist/server/modules/events/catalog/integration.connection.deleted.js.map +1 -1
  81. package/dist/server/modules/events/catalog/integration.connection.legacy.d.ts +85 -0
  82. package/dist/server/modules/events/catalog/integration.connection.legacy.js +41 -0
  83. package/dist/server/modules/events/catalog/integration.connection.legacy.js.map +1 -0
  84. package/dist/server/modules/events/catalog/integration.connection.updated.d.ts +4 -1
  85. package/dist/server/modules/events/catalog/integration.connection.updated.js +5 -2
  86. package/dist/server/modules/events/catalog/integration.connection.updated.js.map +1 -1
  87. package/dist/server/modules/events/services/committed-event-replay.d.ts +10 -0
  88. package/dist/server/modules/events/services/committed-event-replay.js +30 -0
  89. package/dist/server/modules/events/services/committed-event-replay.js.map +1 -0
  90. package/dist/server/modules/events/services/committed-event.store.d.ts +1 -0
  91. package/dist/server/modules/events/services/committed-event.store.js +12 -7
  92. package/dist/server/modules/events/services/committed-event.store.js.map +1 -1
  93. package/dist/server/modules/events/services/durable-event-registry.service.d.ts +9 -2
  94. package/dist/server/modules/events/services/durable-event-registry.service.js.map +1 -1
  95. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.d.ts +7 -0
  96. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js +158 -50
  97. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js.map +1 -1
  98. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.d.ts +4 -0
  99. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js +73 -33
  100. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js.map +1 -1
  101. package/dist/server/modules/external-integrations/connections/integration-connections.controller.d.ts +6 -3
  102. package/dist/server/modules/external-integrations/connections/integration-connections.controller.js +67 -12
  103. package/dist/server/modules/external-integrations/connections/integration-connections.controller.js.map +1 -1
  104. package/dist/server/modules/external-integrations/connections/integration-connections.service.d.ts +35 -3
  105. package/dist/server/modules/external-integrations/connections/integration-connections.service.js +178 -17
  106. package/dist/server/modules/external-integrations/connections/integration-connections.service.js.map +1 -1
  107. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.d.ts +6 -3
  108. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js +51 -11
  109. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js.map +1 -1
  110. package/dist/server/modules/external-integrations/external-integrations.module.js +6 -1
  111. package/dist/server/modules/external-integrations/external-integrations.module.js.map +1 -1
  112. package/dist/server/modules/external-integrations/models/external-edit-session.models.d.ts +1 -0
  113. package/dist/server/modules/external-integrations/models/external-provider.models.d.ts +6 -2
  114. package/dist/server/modules/external-integrations/models/external-provider.models.js +2 -3
  115. package/dist/server/modules/external-integrations/models/external-provider.models.js.map +1 -1
  116. package/dist/server/modules/external-integrations/models/external-time-mutation.models.d.ts +33 -2
  117. package/dist/server/modules/external-integrations/models/external-time-mutation.models.js +14 -0
  118. package/dist/server/modules/external-integrations/models/external-time-mutation.models.js.map +1 -1
  119. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.d.ts +10 -9
  120. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js +81 -46
  121. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js.map +1 -1
  122. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.d.ts +22 -20
  123. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js +136 -64
  124. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js.map +1 -1
  125. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.d.ts +7 -0
  126. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js +289 -4
  127. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js.map +1 -1
  128. package/dist/server/modules/external-integrations/my-work/external-my-work.service.d.ts +12 -7
  129. package/dist/server/modules/external-integrations/my-work/external-my-work.service.js +114 -54
  130. package/dist/server/modules/external-integrations/my-work/external-my-work.service.js.map +1 -1
  131. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.d.ts +19 -6
  132. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js +346 -46
  133. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js.map +1 -1
  134. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.d.ts +2 -1
  135. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js +3 -2
  136. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js.map +1 -1
  137. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.d.ts +6 -1
  138. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js +19 -0
  139. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js.map +1 -1
  140. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.d.ts +15 -2
  141. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js +33 -6
  142. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js.map +1 -1
  143. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.d.ts +13 -6
  144. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js +212 -75
  145. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js.map +1 -1
  146. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.d.ts +13 -4
  147. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js +98 -0
  148. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js.map +1 -1
  149. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.d.ts +10 -4
  150. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js +57 -18
  151. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js.map +1 -1
  152. package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +139 -0
  153. package/dist/server/modules/mcp/dtos/mcp.dto.js +41 -1
  154. package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
  155. package/dist/server/modules/mcp/services/handlers/epic-context.d.ts +2 -0
  156. package/dist/server/modules/mcp/services/handlers/epic-tools.d.ts +4 -0
  157. package/dist/server/modules/mcp/services/handlers/epic-tools.js +275 -0
  158. package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -1
  159. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.d.ts +2 -1
  160. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js +13 -8
  161. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js.map +1 -1
  162. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.d.ts +1 -1
  163. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js +1 -1
  164. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js.map +1 -1
  165. package/dist/server/modules/mcp/tool-descriptors/binding-types.d.ts +2 -0
  166. package/dist/server/modules/mcp/tool-descriptors/binding-types.js.map +1 -1
  167. package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js +6 -0
  168. package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js.map +1 -1
  169. package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js +132 -0
  170. package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js.map +1 -1
  171. package/dist/server/modules/projects/controllers/projects.controller.js +1 -4
  172. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  173. package/dist/server/modules/projects/projects.module.js +2 -0
  174. package/dist/server/modules/projects/projects.module.js.map +1 -1
  175. package/dist/server/modules/projects/services/projects.service.d.ts +4 -1
  176. package/dist/server/modules/projects/services/projects.service.js +23 -2
  177. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  178. package/dist/server/modules/providers/adapters/claude.adapter.d.ts +4 -2
  179. package/dist/server/modules/providers/adapters/claude.adapter.js +31 -0
  180. package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
  181. package/dist/server/modules/providers/services/provider-mcp-ensure.service.d.ts +7 -0
  182. package/dist/server/modules/providers/services/provider-mcp-ensure.service.js +69 -4
  183. package/dist/server/modules/providers/services/provider-mcp-ensure.service.js.map +1 -1
  184. package/dist/server/modules/session-reader/data/pricing.json +452 -56
  185. package/dist/server/modules/session-reader/services/session-reader.service.d.ts +2 -0
  186. package/dist/server/modules/session-reader/services/session-reader.service.js +85 -9
  187. package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
  188. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js +8 -2
  189. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js.map +1 -1
  190. package/dist/server/modules/session-reader/services/transcript-watcher.service.js +44 -17
  191. package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
  192. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +1 -0
  193. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +1 -0
  194. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
  195. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +11 -1
  196. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
  197. package/dist/server/modules/sessions/utils/claude-config.d.ts +4 -1
  198. package/dist/server/modules/sessions/utils/claude-config.js +265 -60
  199. package/dist/server/modules/sessions/utils/claude-config.js.map +1 -1
  200. package/dist/server/modules/storage/db/schema.d.ts +1109 -98
  201. package/dist/server/modules/storage/db/schema.js +141 -3
  202. package/dist/server/modules/storage/db/schema.js.map +1 -1
  203. package/dist/server/modules/storage/db/sqlite-json.d.ts +1 -0
  204. package/dist/server/modules/storage/db/sqlite-json.js +4 -0
  205. package/dist/server/modules/storage/db/sqlite-json.js.map +1 -1
  206. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +58 -10
  207. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  208. package/dist/server/modules/storage/local/delegates/epic.delegate.d.ts +30 -5
  209. package/dist/server/modules/storage/local/delegates/epic.delegate.js +718 -14
  210. package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -1
  211. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.d.ts +52 -0
  212. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js +659 -0
  213. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js.map +1 -0
  214. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.d.ts +2 -1
  215. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js +17 -4
  216. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js.map +1 -1
  217. package/dist/server/modules/storage/local/delegates/integration.delegate.d.ts +23 -8
  218. package/dist/server/modules/storage/local/delegates/integration.delegate.js +238 -52
  219. package/dist/server/modules/storage/local/delegates/integration.delegate.js.map +1 -1
  220. package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +2 -0
  221. package/dist/server/modules/storage/local/delegates/project.delegate.js +180 -130
  222. package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -1
  223. package/dist/server/modules/storage/local/local-storage.service.d.ts +45 -10
  224. package/dist/server/modules/storage/local/local-storage.service.js +99 -15
  225. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  226. package/dist/server/modules/storage/models/domain.models.d.ts +170 -0
  227. package/dist/server/modules/storage/models/domain.models.js +9 -0
  228. package/dist/server/modules/storage/models/domain.models.js.map +1 -1
  229. package/dist/server/templates/teams-dev.json +311 -301
  230. package/dist/server/tsconfig.tsbuildinfo +1 -1
  231. package/dist/server/ui/assets/{ExternalRichEditor-DMtnZIMZ.js → ExternalRichEditor-BG9SopK8.js} +1 -1
  232. package/dist/server/ui/assets/{ReviewDetailPage-BmyiM5IR.js → ReviewDetailPage-DNo7Qx3m.js} +1 -1
  233. package/dist/server/ui/assets/{ReviewsPage-CQ87BMwT.js → ReviewsPage-Cay-IObR.js} +1 -1
  234. package/dist/server/ui/assets/index-C1Fdqua-.js +1197 -0
  235. package/dist/server/ui/assets/index-CV8ydm7w.css +32 -0
  236. package/dist/server/ui/assets/{useReviewSubscription-DjbEUzt4.js → useReviewSubscription-sAAfh4k9.js} +1 -1
  237. package/dist/server/ui/index.html +2 -2
  238. package/dist/templates/teams-dev.json +311 -301
  239. package/package.json +19 -2
  240. package/dist/server/ui/assets/index-BX6Yze-A.js +0 -1177
  241. package/dist/server/ui/assets/index-qoQEv_u4.css +0 -32
@@ -15,12 +15,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.EpicTimeStore = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const node_crypto_1 = require("node:crypto");
18
+ const error_types_1 = require("../../../common/errors/error-types");
18
19
  const db_provider_1 = require("../../storage/db/db.provider");
19
20
  const sqlite_raw_1 = require("../../storage/db/sqlite-raw");
20
21
  const transaction_runner_1 = require("../../storage/db/transaction-runner");
21
22
  const TRACKING_STARTED_AT_KEY = 'epicTime.trackingStartedAt';
22
23
  const ACTIVITY_IDLE_TIMEOUT_KEY = 'activity.idleTimeoutMs';
23
24
  const DEFAULT_IDLE_TIMEOUT_MS = 30_000;
25
+ const MILLIS_PER_MINUTE = 60_000;
26
+ const STALE_SNAPSHOT_MESSAGE = 'The captured agent time snapshot changed. Refresh the buffered time and try again.';
27
+ const RESOLVED_SCOPE_CTE = `
28
+ focal(focal_id) AS (
29
+ SELECT CAST(json_each.value AS TEXT) FROM json_each(?)
30
+ ),
31
+ route_chain(focal_id, epic_id) AS (
32
+ SELECT f.focal_id, e.id
33
+ FROM focal f
34
+ JOIN epics e ON e.id = f.focal_id AND e.parent_id IS NULL
35
+ UNION
36
+ SELECT rc.focal_id, source.id
37
+ FROM route_chain rc
38
+ JOIN epics target ON target.id = rc.epic_id
39
+ JOIN epics source
40
+ ON source.parent_id IS NULL AND source.project_id = target.project_id
41
+ JOIN epic_relations rel
42
+ ON rel.type = 'related'
43
+ AND (
44
+ (rel.left_epic_id = source.id AND rel.right_epic_id = target.id
45
+ AND rel.direction = 'left_to_right')
46
+ OR (rel.left_epic_id = target.id AND rel.right_epic_id = source.id
47
+ AND rel.direction = 'right_to_left')
48
+ )
49
+ WHERE NOT EXISTS (
50
+ SELECT 1 FROM external_task_links link WHERE link.epic_id = source.id
51
+ )
52
+ )`;
53
+ function buildBufferSnapshotToken(projectId, agentId, rows) {
54
+ return (0, node_crypto_1.createHash)('sha256')
55
+ .update(JSON.stringify({
56
+ agentId,
57
+ projectId,
58
+ rows: rows.map((row) => ({
59
+ closedAt: row.closed_at,
60
+ durationMs: row.duration_ms,
61
+ id: row.id,
62
+ lastActivityAt: row.last_activity_at,
63
+ startedAt: row.started_at,
64
+ updatedAt: row.updated_at,
65
+ })),
66
+ }))
67
+ .digest('hex');
68
+ }
24
69
  let EpicTimeStore = class EpicTimeStore {
25
70
  rawClient;
26
71
  transactionRunner;
@@ -118,6 +163,10 @@ let EpicTimeStore = class EpicTimeStore {
118
163
  if (hasNewActivity && activityAt) {
119
164
  const hadOpenSegment = openSegment !== null;
120
165
  const epicId = this.resolveAttributionEpic(session);
166
+ const eligibleTeam = epicId === null ? this.resolveEligibleTeam(session) : null;
167
+ const openTeamBatch = eligibleTeam
168
+ ? this.loadOpenTeamBatch(session.project_id, eligibleTeam.team_id)
169
+ : null;
121
170
  const priorActivityAt = openSegment?.last_activity_at ?? watermark?.last_activity_at ?? null;
122
171
  const busyStart = session.activity_state === 'busy' && session.busy_since ? session.busy_since : activityAt;
123
172
  const continuityStart = priorActivityAt ?? busyStart;
@@ -127,6 +176,9 @@ let EpicTimeStore = class EpicTimeStore {
127
176
  openSegment.project_id === session.project_id &&
128
177
  openSegment.agent_id_snapshot === session.agent_id &&
129
178
  openSegment.epic_id === epicId &&
179
+ (eligibleTeam
180
+ ? openTeamBatch !== null && openSegment.team_batch_id === openTeamBatch.id
181
+ : openSegment.team_batch_id === null) &&
130
182
  continuous;
131
183
  if (sameSegment && openSegment) {
132
184
  const deltaMs = this.elapsedMs(openSegment.last_activity_at, activityAt);
@@ -152,14 +204,19 @@ let EpicTimeStore = class EpicTimeStore {
152
204
  ? this.latestTimestamp([busyStart, lowerBound])
153
205
  : activityAt;
154
206
  const durationMs = Math.max(0, this.elapsedMs(startedAt, activityAt));
207
+ const teamBatch = eligibleTeam
208
+ ? (openTeamBatch ??
209
+ this.createOrLoadOpenTeamBatch(session.project_id, eligibleTeam, startedAt, nowIso))
210
+ : null;
155
211
  segmentId = (0, node_crypto_1.randomUUID)();
156
212
  this.rawClient
157
213
  .prepare(`INSERT INTO epic_time_segments
158
- (id, project_id, epic_id, session_id_snapshot, agent_id_snapshot,
159
- agent_name_snapshot, started_at, last_activity_at, closed_at,
160
- duration_ms, created_at, updated_at)
161
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`)
162
- .run(segmentId, session.project_id, epicId, session.id, session.agent_id, session.agent_name, startedAt, activityAt, durationMs, nowIso, nowIso);
214
+ (id, project_id, epic_id, team_batch_id, attribution_source,
215
+ team_id_snapshot, team_name_snapshot, session_id_snapshot,
216
+ agent_id_snapshot, agent_name_snapshot, started_at, last_activity_at,
217
+ closed_at, duration_ms, created_at, updated_at)
218
+ VALUES (?, ?, ?, ?, 'direct', ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`)
219
+ .run(segmentId, session.project_id, epicId, teamBatch?.id ?? null, eligibleTeam?.team_id ?? null, eligibleTeam?.team_name ?? null, session.id, session.agent_id, session.agent_name, startedAt, activityAt, durationMs, nowIso, nowIso);
163
220
  action = 'created';
164
221
  if (epicId === null) {
165
222
  const pendingClaim = this.applyPendingBufferClaims(session.project_id, session.agent_id, nowIso);
@@ -195,6 +252,56 @@ let EpicTimeStore = class EpicTimeStore {
195
252
  };
196
253
  });
197
254
  }
255
+ async processTeamBatches(deliveryKey, idleTimeoutMs, now = new Date()) {
256
+ const nowIso = now.toISOString();
257
+ const activeAfter = new Date(now.getTime() - idleTimeoutMs).toISOString();
258
+ return this.transactionRunner.runImmediateQueued(() => {
259
+ let sealedBatches = 0;
260
+ let finalizedBatches = 0;
261
+ let cancelledBatches = 0;
262
+ const openBatches = this.loadTeamBatches(false);
263
+ for (const batch of openBatches) {
264
+ if (this.hasActiveEligibleMember(batch, activeAfter)) {
265
+ continue;
266
+ }
267
+ this.rawClient
268
+ .prepare(`UPDATE epic_time_segments
269
+ SET closed_at = COALESCE(closed_at, last_activity_at), updated_at = ?
270
+ WHERE team_batch_id = ?`)
271
+ .run(nowIso, batch.id);
272
+ this.rawClient
273
+ .prepare(`UPDATE epic_time_team_batches
274
+ SET sealed_at = ?, updated_at = ?
275
+ WHERE id = ? AND sealed_at IS NULL`)
276
+ .run(nowIso, nowIso, batch.id);
277
+ this.rawClient
278
+ .prepare(`INSERT OR IGNORE INTO epic_time_team_batch_event_barriers
279
+ (team_batch_id, committed_event_id, created_at)
280
+ SELECT ?, eh.event_id, ?
281
+ FROM event_handlers eh
282
+ INNER JOIN events e ON e.id = eh.event_id
283
+ WHERE eh.delivery_key = ?
284
+ AND eh.status IN ('pending', 'running', 'retry')
285
+ AND e.published_at <= ?`)
286
+ .run(batch.id, nowIso, deliveryKey, nowIso);
287
+ sealedBatches += 1;
288
+ }
289
+ const sealed = this.loadTeamBatches(true);
290
+ for (const batch of sealed) {
291
+ if (this.batchHasPendingBarrier(batch.id, deliveryKey)) {
292
+ continue;
293
+ }
294
+ if (!this.isTeamBatchIdentityCurrent(batch)) {
295
+ this.cancelTeamBatch(batch.id, nowIso);
296
+ cancelledBatches += 1;
297
+ continue;
298
+ }
299
+ this.finalizeTeamBatch(batch, nowIso);
300
+ finalizedBatches += 1;
301
+ }
302
+ return { sealedBatches, finalizedBatches, cancelledBatches };
303
+ });
304
+ }
198
305
  async recordTaskTouch(touch, now = new Date()) {
199
306
  const nowIso = now.toISOString();
200
307
  return this.transactionRunner.runImmediateQueued(() => {
@@ -223,6 +330,94 @@ let EpicTimeStore = class EpicTimeStore {
223
330
  };
224
331
  });
225
332
  }
333
+ listAgentTimeBuffers(projectId) {
334
+ const rows = this.listEligibleBufferRows(projectId, null);
335
+ if (rows.length === 0) {
336
+ return { capturedAt: null, items: [] };
337
+ }
338
+ let capturedAt = rows[0].updated_at;
339
+ let capturedAtMs = this.timestampMs(capturedAt);
340
+ const rowsByAgent = new Map();
341
+ for (const row of rows) {
342
+ const updatedAtMs = this.timestampMs(row.updated_at);
343
+ if (updatedAtMs > capturedAtMs) {
344
+ capturedAt = row.updated_at;
345
+ capturedAtMs = updatedAtMs;
346
+ }
347
+ const agentRows = rowsByAgent.get(row.agent_id_snapshot) ?? [];
348
+ agentRows.push(row);
349
+ rowsByAgent.set(row.agent_id_snapshot, agentRows);
350
+ }
351
+ const items = [];
352
+ for (const [agentId, agentRows] of rowsByAgent) {
353
+ const durationMs = agentRows.reduce((total, row) => total + row.duration_ms, 0);
354
+ let oldest = agentRows[0].last_activity_at;
355
+ let newest = agentRows[0].last_activity_at;
356
+ for (const row of agentRows.slice(1)) {
357
+ if (this.timestampMs(row.last_activity_at) < this.timestampMs(oldest)) {
358
+ oldest = row.last_activity_at;
359
+ }
360
+ if (this.timestampMs(row.last_activity_at) > this.timestampMs(newest)) {
361
+ newest = row.last_activity_at;
362
+ }
363
+ }
364
+ items.push({
365
+ agentId,
366
+ snapshotToken: buildBufferSnapshotToken(projectId, agentId, agentRows),
367
+ minutes: Math.floor(durationMs / MILLIS_PER_MINUTE),
368
+ durationMs,
369
+ segmentCount: agentRows.length,
370
+ oldestActivityAt: oldest,
371
+ newestActivityAt: newest,
372
+ });
373
+ }
374
+ items.sort((left, right) => left.agentId.localeCompare(right.agentId));
375
+ return { capturedAt, items };
376
+ }
377
+ async assignAgentTimeBuffer(input, now = new Date()) {
378
+ const nowIso = now.toISOString();
379
+ return this.transactionRunner.runImmediateQueued(() => {
380
+ const project = this.rawClient
381
+ .prepare(`SELECT workspace_id FROM projects WHERE id = ?`)
382
+ .get(input.projectId);
383
+ if (!project) {
384
+ throw new error_types_1.NotFoundError('Project', input.projectId);
385
+ }
386
+ const agent = this.rawClient
387
+ .prepare(`SELECT id FROM agents WHERE id = ? AND project_id = ?`)
388
+ .get(input.agentId, input.projectId);
389
+ if (!agent) {
390
+ throw new error_types_1.NotFoundError('Agent', input.agentId);
391
+ }
392
+ const epic = this.rawClient
393
+ .prepare(`SELECT id FROM epics WHERE id = ? AND project_id = ?`)
394
+ .get(input.targetEpicId, input.projectId);
395
+ if (!epic) {
396
+ throw new error_types_1.NotFoundError('Epic', input.targetEpicId);
397
+ }
398
+ const rows = this.listEligibleBufferRows(input.projectId, input.agentId);
399
+ const capturedAtMs = this.timestampMs(input.capturedAt);
400
+ if (rows.length === 0 ||
401
+ rows.some((row) => this.timestampMs(row.updated_at) > capturedAtMs)) {
402
+ throw new error_types_1.ConflictError(STALE_SNAPSHOT_MESSAGE);
403
+ }
404
+ if (buildBufferSnapshotToken(input.projectId, input.agentId, rows) !== input.snapshotToken) {
405
+ throw new error_types_1.ConflictError(STALE_SNAPSHOT_MESSAGE);
406
+ }
407
+ const assignRow = this.rawClient.prepare(`UPDATE epic_time_segments
408
+ SET epic_id = ?, updated_at = ?
409
+ WHERE id = ?
410
+ AND epic_id IS NULL
411
+ AND team_batch_id IS NULL
412
+ AND closed_at IS NOT NULL`);
413
+ for (const row of rows) {
414
+ if (assignRow.run(input.targetEpicId, nowIso, row.id).changes !== 1) {
415
+ throw new error_types_1.ConflictError(STALE_SNAPSHOT_MESSAGE);
416
+ }
417
+ }
418
+ return { workspaceId: project.workspace_id };
419
+ });
420
+ }
226
421
  getEpicTimeScope(epicId) {
227
422
  const row = this.rawClient
228
423
  .prepare(`SELECT id, parent_id FROM epics WHERE id = ?`)
@@ -238,40 +433,274 @@ let EpicTimeStore = class EpicTimeStore {
238
433
  .prepare(`SELECT id, parent_id FROM epics WHERE id IN (${placeholders})`)
239
434
  .all(...epicIds).map((row) => ({ id: row.id, parentId: row.parent_id }));
240
435
  }
241
- listClosedSegmentsForEpic(epicId, includeDirectChildren) {
242
- const rows = this.rawClient
243
- .prepare(`SELECT s.id, scoped.id AS epic_id, scoped.title AS epic_title,
244
- s.agent_id_snapshot, s.agent_name_snapshot,
245
- s.duration_ms, s.last_activity_at, s.updated_at
246
- FROM epic_time_segments s
247
- INNER JOIN epics scoped ON scoped.id = s.epic_id AND scoped.project_id = s.project_id
248
- WHERE s.closed_at IS NOT NULL
249
- AND (scoped.id = ? OR (? = 1 AND scoped.parent_id = ?))
250
- ORDER BY s.last_activity_at, s.id`)
251
- .all(epicId, includeDirectChildren ? 1 : 0, epicId);
252
- return rows.map((row) => this.mapSummarySegment(row, null, row.epic_id === epicId));
253
- }
254
- listClosedSegmentsForRoots(rootEpicIds) {
255
- if (rootEpicIds.length === 0) {
256
- return [];
436
+ listResolvedScope(focalEpicIds) {
437
+ if (focalEpicIds.length === 0) {
438
+ return { segments: [], routedRootIdsByFocal: new Map() };
257
439
  }
258
- const placeholders = rootEpicIds.map(() => '?').join(', ');
259
440
  const rows = this.rawClient
260
- .prepare(`SELECT roots.id AS root_epic_id, s.id, scoped.id AS epic_id,
261
- scoped.title AS epic_title, s.agent_id_snapshot,
262
- s.agent_name_snapshot, s.duration_ms, s.last_activity_at, s.updated_at
263
- FROM epics roots
264
- INNER JOIN epics scoped
265
- ON (scoped.id = roots.id OR scoped.parent_id = roots.id)
266
- AND scoped.project_id = roots.project_id
267
- INNER JOIN epic_time_segments s
268
- ON s.project_id = roots.project_id
269
- AND s.epic_id = scoped.id
270
- AND s.closed_at IS NOT NULL
271
- WHERE roots.id IN (${placeholders})
272
- ORDER BY roots.id, s.last_activity_at, s.id`)
273
- .all(...rootEpicIds);
274
- return rows.map((row) => this.mapSummarySegment(row, row.root_epic_id, row.epic_id === row.root_epic_id));
441
+ .prepare(`WITH RECURSIVE
442
+ ${RESOLVED_SCOPE_CTE},
443
+ scope(focal_id, epic_id, is_direct, group_epic_id) AS (
444
+ SELECT f.focal_id, e.id, 1, f.focal_id
445
+ FROM focal f
446
+ JOIN epics e ON e.id = f.focal_id
447
+ UNION
448
+ SELECT f.focal_id, child.id, 0, f.focal_id
449
+ FROM focal f
450
+ JOIN epics e ON e.id = f.focal_id AND e.parent_id IS NULL
451
+ JOIN epics child ON child.parent_id = e.id AND child.project_id = e.project_id
452
+ UNION
453
+ SELECT rc.focal_id, rc.epic_id, 0, rc.epic_id
454
+ FROM route_chain rc
455
+ WHERE rc.epic_id <> rc.focal_id
456
+ UNION
457
+ SELECT rc.focal_id, child.id, 0, rc.epic_id
458
+ FROM route_chain rc
459
+ JOIN epics routed ON routed.id = rc.epic_id
460
+ JOIN epics child ON child.parent_id = routed.id AND child.project_id = routed.project_id
461
+ WHERE rc.epic_id <> rc.focal_id
462
+ AND NOT EXISTS (
463
+ SELECT 1 FROM external_task_links link WHERE link.epic_id = child.id
464
+ )
465
+ )
466
+ SELECT DISTINCT scope.focal_id AS root_epic_id,
467
+ seg.id AS segment_id,
468
+ scoped.id AS epic_id, scoped.title AS epic_title,
469
+ seg.agent_id_snapshot, seg.agent_name_snapshot, seg.attribution_source,
470
+ seg.team_id_snapshot, seg.team_name_snapshot,
471
+ seg.duration_ms, seg.last_activity_at, seg.updated_at,
472
+ scope.is_direct AS is_direct,
473
+ scope.group_epic_id AS group_epic_id,
474
+ group_epic.title AS group_epic_title,
475
+ NULL AS routed_epic_id
476
+ FROM scope
477
+ JOIN epics scoped ON scoped.id = scope.epic_id
478
+ LEFT JOIN epics group_epic ON group_epic.id = scope.group_epic_id
479
+ JOIN epic_time_segments seg
480
+ ON seg.epic_id = scoped.id
481
+ AND seg.project_id = scoped.project_id
482
+ AND seg.closed_at IS NOT NULL
483
+ UNION ALL
484
+ SELECT DISTINCT rc.focal_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
485
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, rc.epic_id
486
+ FROM route_chain rc
487
+ WHERE rc.epic_id <> rc.focal_id
488
+ ORDER BY root_epic_id, last_activity_at, segment_id`)
489
+ .all(JSON.stringify(focalEpicIds));
490
+ const segments = [];
491
+ const routedRootIdsByFocal = new Map();
492
+ for (const row of rows) {
493
+ if (row.routed_epic_id !== null) {
494
+ const routed = routedRootIdsByFocal.get(row.root_epic_id) ?? [];
495
+ routed.push(row.routed_epic_id);
496
+ routedRootIdsByFocal.set(row.root_epic_id, routed);
497
+ continue;
498
+ }
499
+ segments.push(this.mapSummarySegment({
500
+ id: row.segment_id,
501
+ epic_id: row.epic_id,
502
+ epic_title: row.epic_title,
503
+ agent_id_snapshot: row.agent_id_snapshot,
504
+ agent_name_snapshot: row.agent_name_snapshot,
505
+ attribution_source: row.attribution_source,
506
+ team_id_snapshot: row.team_id_snapshot,
507
+ team_name_snapshot: row.team_name_snapshot,
508
+ duration_ms: row.duration_ms,
509
+ last_activity_at: row.last_activity_at,
510
+ updated_at: row.updated_at,
511
+ }, row.root_epic_id, row.is_direct === 1, row.group_epic_id, row.group_epic_title));
512
+ }
513
+ for (const routed of routedRootIdsByFocal.values()) {
514
+ routed.sort();
515
+ }
516
+ for (const focalId of focalEpicIds) {
517
+ if (!routedRootIdsByFocal.has(focalId)) {
518
+ routedRootIdsByFocal.set(focalId, []);
519
+ }
520
+ }
521
+ return { segments, routedRootIdsByFocal };
522
+ }
523
+ resolveEligibleTeam(session) {
524
+ if (!session.agent_id || !session.project_id) {
525
+ return null;
526
+ }
527
+ return (this.rawClient
528
+ .prepare(`SELECT t.id AS team_id, t.name AS team_name,
529
+ lead.id AS lead_agent_id, lead.name AS lead_agent_name
530
+ FROM team_members tm
531
+ INNER JOIN teams t ON t.id = tm.team_id AND t.project_id = ?
532
+ INNER JOIN agents lead
533
+ ON lead.id = t.team_lead_agent_id AND lead.project_id = t.project_id
534
+ WHERE tm.agent_id = ?
535
+ AND t.team_lead_agent_id <> tm.agent_id
536
+ AND (
537
+ SELECT COUNT(*)
538
+ FROM team_members memberships
539
+ INNER JOIN teams member_teams
540
+ ON member_teams.id = memberships.team_id
541
+ AND member_teams.project_id = ?
542
+ WHERE memberships.agent_id = tm.agent_id
543
+ ) = 1
544
+ LIMIT 1`)
545
+ .get(session.project_id, session.agent_id, session.project_id) ?? null);
546
+ }
547
+ loadOpenTeamBatch(projectId, teamId) {
548
+ return (this.rawClient
549
+ .prepare(`SELECT id, project_id, team_id_snapshot, team_name_snapshot,
550
+ lead_agent_id_snapshot, lead_agent_name_snapshot, started_at, sealed_at
551
+ FROM epic_time_team_batches
552
+ WHERE project_id = ? AND team_id_snapshot = ? AND sealed_at IS NULL`)
553
+ .get(projectId, teamId) ?? null);
554
+ }
555
+ createOrLoadOpenTeamBatch(projectId, team, startedAt, nowIso) {
556
+ this.rawClient
557
+ .prepare(`INSERT INTO epic_time_team_batches
558
+ (id, project_id, team_id_snapshot, team_name_snapshot,
559
+ lead_agent_id_snapshot, lead_agent_name_snapshot, started_at,
560
+ sealed_at, created_at, updated_at)
561
+ VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
562
+ ON CONFLICT DO NOTHING`)
563
+ .run((0, node_crypto_1.randomUUID)(), projectId, team.team_id, team.team_name, team.lead_agent_id, team.lead_agent_name, startedAt, nowIso, nowIso);
564
+ const batch = this.loadOpenTeamBatch(projectId, team.team_id);
565
+ if (!batch) {
566
+ throw new Error('Unable to create or load the open Epic-time team batch.');
567
+ }
568
+ return batch;
569
+ }
570
+ loadTeamBatches(sealed) {
571
+ return this.rawClient
572
+ .prepare(`SELECT id, project_id, team_id_snapshot, team_name_snapshot,
573
+ lead_agent_id_snapshot, lead_agent_name_snapshot, started_at, sealed_at
574
+ FROM epic_time_team_batches
575
+ WHERE sealed_at IS ${sealed ? 'NOT NULL' : 'NULL'}
576
+ ORDER BY started_at, id`)
577
+ .all();
578
+ }
579
+ hasActiveEligibleMember(batch, activeAfter) {
580
+ return Boolean(this.rawClient
581
+ .prepare(`SELECT 1
582
+ FROM sessions s
583
+ INNER JOIN agents a
584
+ ON a.id = s.agent_id AND a.project_id = ?
585
+ INNER JOIN team_members tm
586
+ ON tm.agent_id = a.id AND tm.team_id = ?
587
+ INNER JOIN teams t
588
+ ON t.id = tm.team_id
589
+ AND t.project_id = ?
590
+ AND t.team_lead_agent_id = ?
591
+ INNER JOIN agents lead
592
+ ON lead.id = t.team_lead_agent_id AND lead.project_id = t.project_id
593
+ WHERE s.status = 'running'
594
+ AND (s.activity_state IS NULL OR s.activity_state <> 'idle')
595
+ AND s.last_activity_at > ?
596
+ AND a.id <> t.team_lead_agent_id
597
+ AND (
598
+ SELECT COUNT(*)
599
+ FROM team_members memberships
600
+ INNER JOIN teams member_teams
601
+ ON member_teams.id = memberships.team_id
602
+ AND member_teams.project_id = ?
603
+ WHERE memberships.agent_id = a.id
604
+ ) = 1
605
+ AND NOT EXISTS (
606
+ SELECT 1 FROM epics bound
607
+ WHERE bound.id = s.epic_id AND bound.project_id = ?
608
+ )
609
+ AND NOT EXISTS (
610
+ SELECT 1 FROM epics assigned
611
+ WHERE assigned.project_id = ? AND assigned.agent_id = a.id
612
+ )
613
+ LIMIT 1`)
614
+ .get(batch.project_id, batch.team_id_snapshot, batch.project_id, batch.lead_agent_id_snapshot, activeAfter, batch.project_id, batch.project_id, batch.project_id));
615
+ }
616
+ batchHasPendingBarrier(batchId, deliveryKey) {
617
+ return Boolean(this.rawClient
618
+ .prepare(`SELECT 1
619
+ FROM epic_time_team_batch_event_barriers barrier
620
+ INNER JOIN event_handlers delivery
621
+ ON delivery.event_id = barrier.committed_event_id
622
+ AND delivery.delivery_key = ?
623
+ WHERE barrier.team_batch_id = ?
624
+ AND delivery.status IN ('pending', 'running', 'retry')
625
+ LIMIT 1`)
626
+ .get(deliveryKey, batchId));
627
+ }
628
+ isTeamBatchIdentityCurrent(batch) {
629
+ const teamStillMatches = Boolean(this.rawClient
630
+ .prepare(`SELECT 1
631
+ FROM teams t
632
+ INNER JOIN agents lead
633
+ ON lead.id = t.team_lead_agent_id AND lead.project_id = t.project_id
634
+ WHERE t.id = ? AND t.project_id = ? AND t.team_lead_agent_id = ?`)
635
+ .get(batch.team_id_snapshot, batch.project_id, batch.lead_agent_id_snapshot));
636
+ if (!teamStillMatches) {
637
+ return false;
638
+ }
639
+ return !this.rawClient
640
+ .prepare(`SELECT 1
641
+ FROM epic_time_segments segment
642
+ LEFT JOIN agents original
643
+ ON original.id = segment.agent_id_snapshot AND original.project_id = segment.project_id
644
+ WHERE segment.team_batch_id = ?
645
+ AND segment.epic_id IS NULL
646
+ AND (
647
+ original.id IS NULL
648
+ OR NOT EXISTS (
649
+ SELECT 1 FROM team_members exact_membership
650
+ WHERE exact_membership.team_id = ?
651
+ AND exact_membership.agent_id = segment.agent_id_snapshot
652
+ )
653
+ OR (
654
+ SELECT COUNT(*)
655
+ FROM team_members memberships
656
+ INNER JOIN teams member_teams
657
+ ON member_teams.id = memberships.team_id
658
+ AND member_teams.project_id = segment.project_id
659
+ WHERE memberships.agent_id = segment.agent_id_snapshot
660
+ ) <> 1
661
+ )
662
+ LIMIT 1`)
663
+ .get(batch.id, batch.team_id_snapshot);
664
+ }
665
+ cancelTeamBatch(batchId, nowIso) {
666
+ this.rawClient
667
+ .prepare(`UPDATE epic_time_segments
668
+ SET team_batch_id = NULL, attribution_source = 'direct',
669
+ team_id_snapshot = NULL, team_name_snapshot = NULL, updated_at = ?
670
+ WHERE team_batch_id = ? AND epic_id IS NULL`)
671
+ .run(nowIso, batchId);
672
+ this.rawClient.prepare(`DELETE FROM epic_time_team_batches WHERE id = ?`).run(batchId);
673
+ }
674
+ finalizeTeamBatch(batch, nowIso) {
675
+ const winner = this.rawClient
676
+ .prepare(`SELECT agent_id_snapshot, SUM(duration_ms) AS duration_ms,
677
+ MIN(started_at) AS earliest_started_at
678
+ FROM epic_time_segments
679
+ WHERE team_batch_id = ? AND epic_id IS NULL
680
+ GROUP BY agent_id_snapshot
681
+ ORDER BY duration_ms DESC, earliest_started_at, agent_id_snapshot
682
+ LIMIT 1`)
683
+ .get(batch.id);
684
+ if (winner) {
685
+ const target = this.rawClient
686
+ .prepare(`SELECT id
687
+ FROM epics
688
+ WHERE project_id = ? AND agent_id = ?
689
+ ORDER BY updated_at DESC, id DESC
690
+ LIMIT 1`)
691
+ .get(batch.project_id, batch.lead_agent_id_snapshot);
692
+ this.rawClient
693
+ .prepare(`DELETE FROM epic_time_segments
694
+ WHERE team_batch_id = ? AND epic_id IS NULL AND agent_id_snapshot <> ?`)
695
+ .run(batch.id, winner.agent_id_snapshot);
696
+ this.rawClient
697
+ .prepare(`UPDATE epic_time_segments
698
+ SET epic_id = ?, agent_id_snapshot = ?, agent_name_snapshot = ?,
699
+ attribution_source = 'team', updated_at = ?
700
+ WHERE team_batch_id = ? AND epic_id IS NULL AND agent_id_snapshot = ?`)
701
+ .run(target?.id ?? null, batch.lead_agent_id_snapshot, batch.lead_agent_name_snapshot, nowIso, batch.id, winner.agent_id_snapshot);
702
+ }
703
+ this.rawClient.prepare(`DELETE FROM epic_time_team_batches WHERE id = ?`).run(batch.id);
275
704
  }
276
705
  loadSession(sessionId) {
277
706
  return (this.rawClient
@@ -284,15 +713,23 @@ let EpicTimeStore = class EpicTimeStore {
284
713
  WHERE s.id = ?`)
285
714
  .get(sessionId) ?? null);
286
715
  }
287
- mapSummarySegment(row, rootEpicId, isDirect) {
716
+ mapSummarySegment(row, rootEpicId, isDirect, groupEpicId, groupEpicTitle) {
717
+ const attributionSource = row.attribution_source === 'team' && row.team_id_snapshot && row.team_name_snapshot
718
+ ? 'team'
719
+ : 'direct';
288
720
  return {
289
721
  id: row.id,
290
722
  rootEpicId,
291
723
  epicId: row.epic_id,
292
724
  epicTitle: row.epic_title,
725
+ groupEpicId,
726
+ groupEpicTitle,
293
727
  isDirect,
294
728
  agentId: row.agent_id_snapshot,
295
729
  agentName: row.agent_name_snapshot,
730
+ attributionSource,
731
+ teamId: attributionSource === 'team' ? row.team_id_snapshot : null,
732
+ teamName: attributionSource === 'team' ? row.team_name_snapshot : null,
296
733
  durationMs: row.duration_ms,
297
734
  lastActivityAt: row.last_activity_at,
298
735
  updatedAt: row.updated_at,
@@ -307,7 +744,9 @@ let EpicTimeStore = class EpicTimeStore {
307
744
  }
308
745
  loadOpenSegment(sessionId) {
309
746
  return (this.rawClient
310
- .prepare(`SELECT id, project_id, epic_id, agent_id_snapshot, last_activity_at, duration_ms
747
+ .prepare(`SELECT id, project_id, epic_id, team_batch_id, attribution_source,
748
+ team_id_snapshot, team_name_snapshot, agent_id_snapshot,
749
+ last_activity_at, duration_ms
311
750
  FROM epic_time_segments
312
751
  WHERE session_id_snapshot = ? AND closed_at IS NULL`)
313
752
  .get(sessionId) ?? null);
@@ -356,6 +795,21 @@ let EpicTimeStore = class EpicTimeStore {
356
795
  WHERE earliest.project_id = s.project_id
357
796
  AND earliest.agent_id_snapshot = s.agent_id_snapshot
358
797
  AND earliest.published_at >= s.started_at
798
+ AND (
799
+ s.team_batch_id IS NULL
800
+ OR EXISTS (
801
+ SELECT 1
802
+ FROM epic_time_team_batches open_batch
803
+ WHERE open_batch.id = s.team_batch_id
804
+ AND open_batch.sealed_at IS NULL
805
+ )
806
+ OR EXISTS (
807
+ SELECT 1
808
+ FROM epic_time_team_batch_event_barriers barrier
809
+ WHERE barrier.team_batch_id = s.team_batch_id
810
+ AND barrier.committed_event_id = earliest.committed_event_id
811
+ )
812
+ )
359
813
  ORDER BY earliest.published_at, earliest.claim_sequence
360
814
  LIMIT 1
361
815
  )
@@ -368,7 +822,21 @@ let EpicTimeStore = class EpicTimeStore {
368
822
  const discardedSegmentIds = new Set();
369
823
  const targetExists = new Map();
370
824
  const updateSegment = this.rawClient.prepare(`UPDATE epic_time_segments
371
- SET epic_id = ?, updated_at = ?
825
+ SET epic_id = ?,
826
+ attribution_source = CASE
827
+ WHEN team_batch_id IS NOT NULL THEN 'direct'
828
+ ELSE attribution_source
829
+ END,
830
+ team_id_snapshot = CASE
831
+ WHEN team_batch_id IS NOT NULL THEN NULL
832
+ ELSE team_id_snapshot
833
+ END,
834
+ team_name_snapshot = CASE
835
+ WHEN team_batch_id IS NOT NULL THEN NULL
836
+ ELSE team_name_snapshot
837
+ END,
838
+ team_batch_id = NULL,
839
+ updated_at = ?
372
840
  WHERE id = ? AND epic_id IS NULL`);
373
841
  const deleteSegment = this.rawClient.prepare(`DELETE FROM epic_time_segments
374
842
  WHERE id = ? AND epic_id IS NULL`);
@@ -395,6 +863,22 @@ let EpicTimeStore = class EpicTimeStore {
395
863
  }
396
864
  return { claimedSegmentIds, discardedSegmentIds };
397
865
  }
866
+ listEligibleBufferRows(projectId, agentId) {
867
+ return this.rawClient
868
+ .prepare(`SELECT seg.id, seg.agent_id_snapshot, seg.duration_ms, seg.started_at,
869
+ seg.last_activity_at, seg.closed_at, seg.updated_at
870
+ FROM epic_time_segments seg
871
+ INNER JOIN agents a
872
+ ON a.id = seg.agent_id_snapshot AND a.project_id = seg.project_id
873
+ WHERE seg.project_id = ?
874
+ AND (? IS NULL OR seg.agent_id_snapshot = ?)
875
+ AND seg.epic_id IS NULL
876
+ AND seg.team_batch_id IS NULL
877
+ AND seg.closed_at IS NOT NULL
878
+ AND seg.duration_ms > 0
879
+ ORDER BY seg.agent_id_snapshot, seg.started_at, seg.id`)
880
+ .all(projectId, agentId, agentId);
881
+ }
398
882
  closeSegment(segmentId, closedAt, nowIso) {
399
883
  this.rawClient
400
884
  .prepare(`UPDATE epic_time_segments