devchain-cli 0.22.1 → 0.23.1
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.
- package/dist/drizzle/0077_familiar_morbius.sql +6 -0
- package/dist/drizzle/0078_preserve_legacy_connection_ownership.sql +180 -0
- package/dist/drizzle/0079_premium_forgotten_one.sql +18 -0
- package/dist/drizzle/0080_lethal_thunderbolt_ross.sql +49 -0
- package/dist/drizzle/0081_early_kronos.sql +58 -0
- package/dist/drizzle/0082_huge_black_queen.sql +14 -0
- package/dist/drizzle/meta/0077_snapshot.json +6957 -0
- package/dist/drizzle/meta/0078_snapshot.json +6957 -0
- package/dist/drizzle/meta/0079_snapshot.json +7093 -0
- package/dist/drizzle/meta/0080_snapshot.json +7241 -0
- package/dist/drizzle/meta/0081_snapshot.json +7482 -0
- package/dist/drizzle/meta/0082_snapshot.json +7571 -0
- package/dist/drizzle/meta/_journal.json +42 -0
- package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
- package/dist/server/common/errors/error-types.d.ts +7 -0
- package/dist/server/common/errors/error-types.js +7 -1
- package/dist/server/common/errors/error-types.js.map +1 -1
- package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.d.ts +9 -0
- package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js +90 -0
- package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js.map +1 -0
- package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.d.ts +12 -0
- package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js +322 -0
- package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js.map +1 -0
- package/dist/server/modules/epic-time/epic-time-store.module.d.ts +2 -0
- package/dist/server/modules/epic-time/epic-time-store.module.js +23 -0
- package/dist/server/modules/epic-time/epic-time-store.module.js.map +1 -0
- package/dist/server/modules/epic-time/epic-time.module.js +10 -6
- package/dist/server/modules/epic-time/epic-time.module.js.map +1 -1
- package/dist/server/modules/epic-time/models/epic-time-daily-allocator.d.ts +30 -0
- package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js +125 -0
- package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js.map +1 -0
- package/dist/server/modules/epic-time/models/epic-time-local-day.d.ts +9 -0
- package/dist/server/modules/epic-time/models/epic-time-local-day.js +164 -0
- package/dist/server/modules/epic-time/models/epic-time-local-day.js.map +1 -0
- package/dist/server/modules/epic-time/models/epic-time.models.d.ts +147 -0
- package/dist/server/modules/epic-time/models/epic-time.models.js +2 -0
- package/dist/server/modules/epic-time/models/epic-time.models.js.map +1 -1
- package/dist/server/modules/epic-time/services/agent-time-accounting.service.d.ts +1 -0
- package/dist/server/modules/epic-time/services/agent-time-accounting.service.js +31 -8
- package/dist/server/modules/epic-time/services/agent-time-accounting.service.js.map +1 -1
- package/dist/server/modules/epic-time/services/epic-estimate-logging.service.d.ts +37 -0
- package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js +716 -0
- package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js.map +1 -0
- package/dist/server/modules/epic-time/services/epic-time.service.d.ts +13 -3
- package/dist/server/modules/epic-time/services/epic-time.service.js +162 -69
- package/dist/server/modules/epic-time/services/epic-time.service.js.map +1 -1
- package/dist/server/modules/epic-time/services/epic-time.store.d.ts +50 -3
- package/dist/server/modules/epic-time/services/epic-time.store.js +705 -146
- package/dist/server/modules/epic-time/services/epic-time.store.js.map +1 -1
- package/dist/server/modules/epics/controllers/epics.controller.d.ts +10 -1
- package/dist/server/modules/epics/controllers/epics.controller.js +108 -2
- package/dist/server/modules/epics/controllers/epics.controller.js.map +1 -1
- package/dist/server/modules/epics/epics.module.js +8 -2
- package/dist/server/modules/epics/epics.module.js.map +1 -1
- package/dist/server/modules/epics/models/epic-relations.models.d.ts +34 -0
- package/dist/server/modules/epics/models/epic-relations.models.js +16 -0
- package/dist/server/modules/epics/models/epic-relations.models.js.map +1 -0
- package/dist/server/modules/epics/services/epic-relation-target-resolver.d.ts +8 -0
- package/dist/server/modules/epics/services/epic-relation-target-resolver.js +37 -0
- package/dist/server/modules/epics/services/epic-relation-target-resolver.js.map +1 -0
- package/dist/server/modules/epics/services/epic-relations.service.d.ts +52 -0
- package/dist/server/modules/epics/services/epic-relations.service.js +135 -0
- package/dist/server/modules/epics/services/epic-relations.service.js.map +1 -0
- package/dist/server/modules/epics/services/epics.service.d.ts +9 -2
- package/dist/server/modules/epics/services/epics.service.js +115 -11
- package/dist/server/modules/epics/services/epics.service.js.map +1 -1
- package/dist/server/modules/events/catalog/broadcast-registry.js +16 -0
- package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
- package/dist/server/modules/events/catalog/epic.relations.invalidated.d.ts +12 -0
- package/dist/server/modules/events/catalog/epic.relations.invalidated.js +13 -0
- package/dist/server/modules/events/catalog/epic.relations.invalidated.js.map +1 -0
- package/dist/server/modules/events/catalog/epic.time.scope.invalidated.d.ts +12 -0
- package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js +13 -0
- package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js.map +1 -0
- package/dist/server/modules/events/catalog/index.d.ts +27 -4
- package/dist/server/modules/events/catalog/index.js +6 -0
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/integration.connection.created.d.ts +4 -1
- package/dist/server/modules/events/catalog/integration.connection.created.js +5 -2
- package/dist/server/modules/events/catalog/integration.connection.created.js.map +1 -1
- package/dist/server/modules/events/catalog/integration.connection.deleted.d.ts +4 -1
- package/dist/server/modules/events/catalog/integration.connection.deleted.js +5 -2
- package/dist/server/modules/events/catalog/integration.connection.deleted.js.map +1 -1
- package/dist/server/modules/events/catalog/integration.connection.legacy.d.ts +85 -0
- package/dist/server/modules/events/catalog/integration.connection.legacy.js +41 -0
- package/dist/server/modules/events/catalog/integration.connection.legacy.js.map +1 -0
- package/dist/server/modules/events/catalog/integration.connection.updated.d.ts +4 -1
- package/dist/server/modules/events/catalog/integration.connection.updated.js +5 -2
- package/dist/server/modules/events/catalog/integration.connection.updated.js.map +1 -1
- package/dist/server/modules/events/services/committed-event-replay.d.ts +10 -0
- package/dist/server/modules/events/services/committed-event-replay.js +30 -0
- package/dist/server/modules/events/services/committed-event-replay.js.map +1 -0
- package/dist/server/modules/events/services/committed-event.store.d.ts +1 -0
- package/dist/server/modules/events/services/committed-event.store.js +12 -7
- package/dist/server/modules/events/services/committed-event.store.js.map +1 -1
- package/dist/server/modules/events/services/durable-event-registry.service.d.ts +9 -2
- package/dist/server/modules/events/services/durable-event-registry.service.js.map +1 -1
- package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.d.ts +7 -0
- package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js +158 -50
- package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js.map +1 -1
- package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.d.ts +4 -0
- package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js +73 -33
- package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js.map +1 -1
- package/dist/server/modules/external-integrations/connections/integration-connections.controller.d.ts +6 -3
- package/dist/server/modules/external-integrations/connections/integration-connections.controller.js +67 -12
- package/dist/server/modules/external-integrations/connections/integration-connections.controller.js.map +1 -1
- package/dist/server/modules/external-integrations/connections/integration-connections.service.d.ts +35 -3
- package/dist/server/modules/external-integrations/connections/integration-connections.service.js +178 -17
- package/dist/server/modules/external-integrations/connections/integration-connections.service.js.map +1 -1
- package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.d.ts +6 -3
- package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js +51 -11
- package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js.map +1 -1
- package/dist/server/modules/external-integrations/external-integrations.module.js +6 -1
- package/dist/server/modules/external-integrations/external-integrations.module.js.map +1 -1
- package/dist/server/modules/external-integrations/models/external-edit-session.models.d.ts +1 -0
- package/dist/server/modules/external-integrations/models/external-provider.models.d.ts +6 -2
- package/dist/server/modules/external-integrations/models/external-provider.models.js +2 -3
- package/dist/server/modules/external-integrations/models/external-provider.models.js.map +1 -1
- package/dist/server/modules/external-integrations/models/external-time-mutation.models.d.ts +33 -2
- package/dist/server/modules/external-integrations/models/external-time-mutation.models.js +14 -0
- package/dist/server/modules/external-integrations/models/external-time-mutation.models.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-edit-session.service.d.ts +10 -9
- package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js +81 -46
- package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.controller.d.ts +22 -20
- package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js +136 -64
- package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.d.ts +7 -0
- package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js +289 -4
- package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-my-work.service.d.ts +12 -7
- package/dist/server/modules/external-integrations/my-work/external-my-work.service.js +114 -54
- package/dist/server/modules/external-integrations/my-work/external-my-work.service.js.map +1 -1
- package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.d.ts +19 -6
- package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js +346 -46
- package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js.map +1 -1
- package/dist/server/modules/external-integrations/sessions/external-edit-session.store.d.ts +2 -1
- package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js +3 -2
- package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js.map +1 -1
- package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.d.ts +6 -1
- package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js +19 -0
- package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js.map +1 -1
- package/dist/server/modules/external-integrations/sessions/provider-operation-gate.d.ts +15 -2
- package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js +33 -6
- package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js.map +1 -1
- package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.d.ts +13 -6
- package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js +212 -75
- package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js.map +1 -1
- package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.d.ts +13 -4
- package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js +98 -0
- package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js.map +1 -1
- package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.d.ts +10 -4
- package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js +57 -18
- package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +139 -0
- package/dist/server/modules/mcp/dtos/mcp.dto.js +41 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/services/handlers/epic-context.d.ts +2 -0
- package/dist/server/modules/mcp/services/handlers/epic-tools.d.ts +4 -0
- package/dist/server/modules/mcp/services/handlers/epic-tools.js +275 -0
- package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp-tool-binding.registry.d.ts +2 -1
- package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js +13 -8
- package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js.map +1 -1
- package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.d.ts +1 -1
- package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js +1 -1
- package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/binding-types.d.ts +2 -0
- package/dist/server/modules/mcp/tool-descriptors/binding-types.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js +6 -0
- package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js.map +1 -1
- package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js +132 -0
- package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js.map +1 -1
- package/dist/server/modules/projects/controllers/projects.controller.js +1 -4
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/projects.module.js +2 -0
- package/dist/server/modules/projects/projects.module.js.map +1 -1
- package/dist/server/modules/projects/services/projects.service.d.ts +4 -1
- package/dist/server/modules/projects/services/projects.service.js +23 -2
- package/dist/server/modules/projects/services/projects.service.js.map +1 -1
- package/dist/server/modules/providers/adapters/claude.adapter.d.ts +4 -2
- package/dist/server/modules/providers/adapters/claude.adapter.js +31 -0
- package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
- package/dist/server/modules/providers/services/provider-mcp-ensure.service.d.ts +7 -0
- package/dist/server/modules/providers/services/provider-mcp-ensure.service.js +69 -4
- package/dist/server/modules/providers/services/provider-mcp-ensure.service.js.map +1 -1
- package/dist/server/modules/session-reader/data/pricing.json +452 -56
- package/dist/server/modules/session-reader/services/session-reader.service.d.ts +2 -0
- package/dist/server/modules/session-reader/services/session-reader.service.js +85 -9
- package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
- package/dist/server/modules/session-reader/services/transcript-path-validator.service.js +8 -2
- package/dist/server/modules/session-reader/services/transcript-path-validator.service.js.map +1 -1
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js +44 -17
- package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +1 -0
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +1 -0
- package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
- package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +11 -1
- package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions.service.d.ts +3 -1
- package/dist/server/modules/sessions/services/sessions.service.js +36 -6
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/sessions.module.js +2 -0
- package/dist/server/modules/sessions/sessions.module.js.map +1 -1
- package/dist/server/modules/sessions/utils/claude-config.d.ts +4 -1
- package/dist/server/modules/sessions/utils/claude-config.js +265 -60
- package/dist/server/modules/sessions/utils/claude-config.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +1109 -98
- package/dist/server/modules/storage/db/schema.js +141 -3
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/db/sqlite-json.d.ts +1 -0
- package/dist/server/modules/storage/db/sqlite-json.js +4 -0
- package/dist/server/modules/storage/db/sqlite-json.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +58 -10
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/epic.delegate.d.ts +30 -5
- package/dist/server/modules/storage/local/delegates/epic.delegate.js +718 -14
- package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.d.ts +52 -0
- package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js +659 -0
- package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js.map +1 -0
- package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.d.ts +2 -1
- package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js +17 -4
- package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/integration.delegate.d.ts +23 -8
- package/dist/server/modules/storage/local/delegates/integration.delegate.js +238 -52
- package/dist/server/modules/storage/local/delegates/integration.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +2 -0
- package/dist/server/modules/storage/local/delegates/project.delegate.js +180 -130
- package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -1
- package/dist/server/modules/storage/local/local-storage.service.d.ts +45 -10
- package/dist/server/modules/storage/local/local-storage.service.js +99 -15
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +170 -0
- package/dist/server/modules/storage/models/domain.models.js +9 -0
- package/dist/server/modules/storage/models/domain.models.js.map +1 -1
- package/dist/server/templates/teams-dev.json +311 -301
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ExternalRichEditor-DMtnZIMZ.js → ExternalRichEditor-FwCAalXO.js} +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-BmyiM5IR.js → ReviewDetailPage-D8DVpsJm.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-CQ87BMwT.js → ReviewsPage-DcWpRFbm.js} +1 -1
- package/dist/server/ui/assets/index-CV8ydm7w.css +32 -0
- package/dist/server/ui/assets/index-Cwe7LxcW.js +1197 -0
- package/dist/server/ui/assets/{useReviewSubscription-DjbEUzt4.js → useReviewSubscription-CYoSg-88.js} +1 -1
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/teams-dev.json +311 -301
- package/package.json +20 -2
- package/dist/server/ui/assets/index-BX6Yze-A.js +0 -1177
- 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;
|
|
@@ -51,6 +96,12 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
51
96
|
};
|
|
52
97
|
});
|
|
53
98
|
}
|
|
99
|
+
readActivationSettings() {
|
|
100
|
+
return {
|
|
101
|
+
trackingStartedAt: this.readSetting(TRACKING_STARTED_AT_KEY),
|
|
102
|
+
idleTimeoutMs: this.readIdleTimeoutMs(),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
54
105
|
listReconciliationSessionIds(trackingStartedAt) {
|
|
55
106
|
return this.rawClient
|
|
56
107
|
.prepare(`SELECT session_id
|
|
@@ -70,130 +121,194 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
70
121
|
.all(trackingStartedAt).map((row) => row.session_id);
|
|
71
122
|
}
|
|
72
123
|
async reconcileSession(sessionId, trackingStartedAt, idleTimeoutMs, now = new Date(), options = {}) {
|
|
124
|
+
return this.transactionRunner.runImmediateQueued(() => this.reconcileSessionCore(sessionId, trackingStartedAt, idleTimeoutMs, now, options));
|
|
125
|
+
}
|
|
126
|
+
reconcileSessionCore(sessionId, trackingStartedAt, idleTimeoutMs, now, options) {
|
|
73
127
|
const nowIso = now.toISOString();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
80
|
-
return {
|
|
81
|
-
sessionId,
|
|
82
|
-
action: 'closed',
|
|
83
|
-
watermark: null,
|
|
84
|
-
segmentId: openSegment.id,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return { sessionId, action: 'noop', watermark: null, segmentId: null };
|
|
88
|
-
}
|
|
89
|
-
const watermark = this.loadWatermark(sessionId);
|
|
90
|
-
const activityAt = session.last_activity_at;
|
|
91
|
-
const hasNewActivity = activityAt !== null &&
|
|
92
|
-
this.isAfter(activityAt, trackingStartedAt) &&
|
|
93
|
-
(!watermark || this.isAfter(activityAt, watermark.last_activity_at));
|
|
94
|
-
if (!session.agent_id || !session.project_id || !session.agent_name) {
|
|
95
|
-
if (openSegment) {
|
|
96
|
-
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
97
|
-
}
|
|
98
|
-
let action = 'noop';
|
|
99
|
-
if (hasNewActivity) {
|
|
100
|
-
action = 'discarded';
|
|
101
|
-
}
|
|
102
|
-
else if (openSegment) {
|
|
103
|
-
action = 'closed';
|
|
104
|
-
}
|
|
105
|
-
const watermarkProjectId = session.project_id ?? session.epic_project_id ?? openSegment?.project_id;
|
|
106
|
-
if (hasNewActivity && activityAt && watermarkProjectId) {
|
|
107
|
-
this.upsertWatermark(session.id, watermarkProjectId, activityAt, nowIso);
|
|
108
|
-
}
|
|
128
|
+
let openSegment = this.loadOpenSegment(sessionId);
|
|
129
|
+
const session = this.loadSession(sessionId);
|
|
130
|
+
if (!session) {
|
|
131
|
+
if (openSegment) {
|
|
132
|
+
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
109
133
|
return {
|
|
110
134
|
sessionId,
|
|
111
|
-
action,
|
|
112
|
-
watermark:
|
|
113
|
-
segmentId: openSegment
|
|
135
|
+
action: 'closed',
|
|
136
|
+
watermark: null,
|
|
137
|
+
segmentId: openSegment.id,
|
|
114
138
|
};
|
|
115
139
|
}
|
|
140
|
+
return { sessionId, action: 'noop', watermark: null, segmentId: null };
|
|
141
|
+
}
|
|
142
|
+
const watermark = this.loadWatermark(sessionId);
|
|
143
|
+
const activityAt = session.last_activity_at;
|
|
144
|
+
const hasNewActivity = activityAt !== null &&
|
|
145
|
+
this.isAfter(activityAt, trackingStartedAt) &&
|
|
146
|
+
(!watermark || this.isAfter(activityAt, watermark.last_activity_at));
|
|
147
|
+
if (!session.agent_id || !session.project_id || !session.agent_name) {
|
|
148
|
+
if (openSegment) {
|
|
149
|
+
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
150
|
+
}
|
|
116
151
|
let action = 'noop';
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const hadOpenSegment = openSegment !== null;
|
|
120
|
-
const epicId = this.resolveAttributionEpic(session);
|
|
121
|
-
const priorActivityAt = openSegment?.last_activity_at ?? watermark?.last_activity_at ?? null;
|
|
122
|
-
const busyStart = session.activity_state === 'busy' && session.busy_since ? session.busy_since : activityAt;
|
|
123
|
-
const continuityStart = priorActivityAt ?? busyStart;
|
|
124
|
-
const continuityGapMs = this.elapsedMs(continuityStart, activityAt);
|
|
125
|
-
const continuous = continuityGapMs >= 0 && continuityGapMs <= idleTimeoutMs;
|
|
126
|
-
const sameSegment = openSegment !== null &&
|
|
127
|
-
openSegment.project_id === session.project_id &&
|
|
128
|
-
openSegment.agent_id_snapshot === session.agent_id &&
|
|
129
|
-
openSegment.epic_id === epicId &&
|
|
130
|
-
continuous;
|
|
131
|
-
if (sameSegment && openSegment) {
|
|
132
|
-
const deltaMs = this.elapsedMs(openSegment.last_activity_at, activityAt);
|
|
133
|
-
this.rawClient
|
|
134
|
-
.prepare(`UPDATE epic_time_segments
|
|
135
|
-
SET last_activity_at = ?, duration_ms = duration_ms + ?, updated_at = ?
|
|
136
|
-
WHERE id = ? AND closed_at IS NULL`)
|
|
137
|
-
.run(activityAt, deltaMs, nowIso, openSegment.id);
|
|
138
|
-
action = 'advanced';
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
if (openSegment) {
|
|
142
|
-
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
143
|
-
openSegment = null;
|
|
144
|
-
}
|
|
145
|
-
const lowerBound = this.latestTimestamp([
|
|
146
|
-
trackingStartedAt,
|
|
147
|
-
session.started_at,
|
|
148
|
-
watermark?.last_activity_at ?? null,
|
|
149
|
-
]);
|
|
150
|
-
const busyStartProvesNewWindow = !watermark || this.isAfter(busyStart, watermark.last_activity_at);
|
|
151
|
-
const startedAt = continuous && (hadOpenSegment || busyStartProvesNewWindow)
|
|
152
|
-
? this.latestTimestamp([busyStart, lowerBound])
|
|
153
|
-
: activityAt;
|
|
154
|
-
const durationMs = Math.max(0, this.elapsedMs(startedAt, activityAt));
|
|
155
|
-
segmentId = (0, node_crypto_1.randomUUID)();
|
|
156
|
-
this.rawClient
|
|
157
|
-
.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);
|
|
163
|
-
action = 'created';
|
|
164
|
-
if (epicId === null) {
|
|
165
|
-
const pendingClaim = this.applyPendingBufferClaims(session.project_id, session.agent_id, nowIso);
|
|
166
|
-
if (pendingClaim.discardedSegmentIds.has(segmentId)) {
|
|
167
|
-
action = 'discarded';
|
|
168
|
-
segmentId = null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
this.upsertWatermark(session.id, session.project_id, activityAt, nowIso);
|
|
173
|
-
openSegment = this.loadOpenSegment(sessionId);
|
|
152
|
+
if (hasNewActivity) {
|
|
153
|
+
action = 'discarded';
|
|
174
154
|
}
|
|
175
|
-
if (openSegment
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
else if (action === 'advanced') {
|
|
183
|
-
action = 'advanced_closed';
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
action = 'closed';
|
|
187
|
-
}
|
|
188
|
-
segmentId = openSegment.id;
|
|
155
|
+
else if (openSegment) {
|
|
156
|
+
action = 'closed';
|
|
157
|
+
}
|
|
158
|
+
const watermarkProjectId = session.project_id ?? session.epic_project_id ?? openSegment?.project_id;
|
|
159
|
+
if (hasNewActivity && activityAt && watermarkProjectId) {
|
|
160
|
+
this.upsertWatermark(session.id, watermarkProjectId, activityAt, nowIso);
|
|
189
161
|
}
|
|
190
162
|
return {
|
|
191
163
|
sessionId,
|
|
192
164
|
action,
|
|
193
165
|
watermark: hasNewActivity ? activityAt : (watermark?.last_activity_at ?? null),
|
|
194
|
-
segmentId,
|
|
166
|
+
segmentId: openSegment?.id ?? null,
|
|
195
167
|
};
|
|
196
|
-
}
|
|
168
|
+
}
|
|
169
|
+
let action = 'noop';
|
|
170
|
+
let segmentId = openSegment?.id ?? null;
|
|
171
|
+
if (hasNewActivity && activityAt) {
|
|
172
|
+
const hadOpenSegment = openSegment !== null;
|
|
173
|
+
const epicId = this.resolveAttributionEpic(session);
|
|
174
|
+
const eligibleTeam = epicId === null ? this.resolveEligibleTeam(session) : null;
|
|
175
|
+
const openTeamBatch = eligibleTeam
|
|
176
|
+
? this.loadOpenTeamBatch(session.project_id, eligibleTeam.team_id)
|
|
177
|
+
: null;
|
|
178
|
+
const priorActivityAt = openSegment?.last_activity_at ?? watermark?.last_activity_at ?? null;
|
|
179
|
+
const busyStart = session.activity_state === 'busy' && session.busy_since ? session.busy_since : activityAt;
|
|
180
|
+
const continuityStart = priorActivityAt ?? busyStart;
|
|
181
|
+
const continuityGapMs = this.elapsedMs(continuityStart, activityAt);
|
|
182
|
+
const continuous = continuityGapMs >= 0 && continuityGapMs <= idleTimeoutMs;
|
|
183
|
+
const sameSegment = openSegment !== null &&
|
|
184
|
+
openSegment.project_id === session.project_id &&
|
|
185
|
+
openSegment.agent_id_snapshot === session.agent_id &&
|
|
186
|
+
openSegment.epic_id === epicId &&
|
|
187
|
+
(eligibleTeam
|
|
188
|
+
? openTeamBatch !== null && openSegment.team_batch_id === openTeamBatch.id
|
|
189
|
+
: openSegment.team_batch_id === null) &&
|
|
190
|
+
continuous;
|
|
191
|
+
if (sameSegment && openSegment) {
|
|
192
|
+
const deltaMs = this.elapsedMs(openSegment.last_activity_at, activityAt);
|
|
193
|
+
this.rawClient
|
|
194
|
+
.prepare(`UPDATE epic_time_segments
|
|
195
|
+
SET last_activity_at = ?, duration_ms = duration_ms + ?, updated_at = ?
|
|
196
|
+
WHERE id = ? AND closed_at IS NULL`)
|
|
197
|
+
.run(activityAt, deltaMs, nowIso, openSegment.id);
|
|
198
|
+
action = 'advanced';
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
if (openSegment) {
|
|
202
|
+
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
203
|
+
openSegment = null;
|
|
204
|
+
}
|
|
205
|
+
const lowerBound = this.latestTimestamp([
|
|
206
|
+
trackingStartedAt,
|
|
207
|
+
session.started_at,
|
|
208
|
+
watermark?.last_activity_at ?? null,
|
|
209
|
+
]);
|
|
210
|
+
const busyStartProvesNewWindow = !watermark || this.isAfter(busyStart, watermark.last_activity_at);
|
|
211
|
+
const startedAt = continuous && (hadOpenSegment || busyStartProvesNewWindow)
|
|
212
|
+
? this.latestTimestamp([busyStart, lowerBound])
|
|
213
|
+
: activityAt;
|
|
214
|
+
const durationMs = Math.max(0, this.elapsedMs(startedAt, activityAt));
|
|
215
|
+
const teamBatch = eligibleTeam
|
|
216
|
+
? (openTeamBatch ??
|
|
217
|
+
this.createOrLoadOpenTeamBatch(session.project_id, eligibleTeam, startedAt, nowIso))
|
|
218
|
+
: null;
|
|
219
|
+
segmentId = (0, node_crypto_1.randomUUID)();
|
|
220
|
+
this.rawClient
|
|
221
|
+
.prepare(`INSERT INTO epic_time_segments
|
|
222
|
+
(id, project_id, epic_id, team_batch_id, attribution_source,
|
|
223
|
+
team_id_snapshot, team_name_snapshot, session_id_snapshot,
|
|
224
|
+
agent_id_snapshot, agent_name_snapshot, started_at, last_activity_at,
|
|
225
|
+
closed_at, duration_ms, created_at, updated_at)
|
|
226
|
+
VALUES (?, ?, ?, ?, 'direct', ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`)
|
|
227
|
+
.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);
|
|
228
|
+
action = 'created';
|
|
229
|
+
if (epicId === null) {
|
|
230
|
+
const pendingClaim = this.applyPendingBufferClaims(session.project_id, session.agent_id, nowIso);
|
|
231
|
+
if (pendingClaim.discardedSegmentIds.has(segmentId)) {
|
|
232
|
+
action = 'discarded';
|
|
233
|
+
segmentId = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this.upsertWatermark(session.id, session.project_id, activityAt, nowIso);
|
|
238
|
+
openSegment = this.loadOpenSegment(sessionId);
|
|
239
|
+
}
|
|
240
|
+
if (openSegment &&
|
|
241
|
+
(options.forceCloseOpenSegment ||
|
|
242
|
+
this.shouldClose(session, openSegment.last_activity_at, idleTimeoutMs, now))) {
|
|
243
|
+
this.closeSegment(openSegment.id, openSegment.last_activity_at, nowIso);
|
|
244
|
+
if (action === 'created') {
|
|
245
|
+
action = 'created_closed';
|
|
246
|
+
}
|
|
247
|
+
else if (action === 'advanced') {
|
|
248
|
+
action = 'advanced_closed';
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
action = 'closed';
|
|
252
|
+
}
|
|
253
|
+
segmentId = openSegment.id;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
sessionId,
|
|
257
|
+
action,
|
|
258
|
+
watermark: hasNewActivity ? activityAt : (watermark?.last_activity_at ?? null),
|
|
259
|
+
segmentId,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async processTeamBatches(deliveryKey, idleTimeoutMs, now = new Date()) {
|
|
263
|
+
return this.transactionRunner.runImmediateQueued(() => this.processTeamBatchesCore(deliveryKey, idleTimeoutMs, now));
|
|
264
|
+
}
|
|
265
|
+
processTeamBatchesCore(deliveryKey, idleTimeoutMs, now) {
|
|
266
|
+
const nowIso = now.toISOString();
|
|
267
|
+
const activeAfter = new Date(now.getTime() - idleTimeoutMs).toISOString();
|
|
268
|
+
let sealedBatches = 0;
|
|
269
|
+
let finalizedBatches = 0;
|
|
270
|
+
let cancelledBatches = 0;
|
|
271
|
+
const openBatches = this.loadTeamBatches(false);
|
|
272
|
+
for (const batch of openBatches) {
|
|
273
|
+
if (this.hasActiveEligibleMember(batch, activeAfter)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
this.rawClient
|
|
277
|
+
.prepare(`UPDATE epic_time_segments
|
|
278
|
+
SET closed_at = COALESCE(closed_at, last_activity_at), updated_at = ?
|
|
279
|
+
WHERE team_batch_id = ?`)
|
|
280
|
+
.run(nowIso, batch.id);
|
|
281
|
+
this.rawClient
|
|
282
|
+
.prepare(`UPDATE epic_time_team_batches
|
|
283
|
+
SET sealed_at = ?, updated_at = ?
|
|
284
|
+
WHERE id = ? AND sealed_at IS NULL`)
|
|
285
|
+
.run(nowIso, nowIso, batch.id);
|
|
286
|
+
this.rawClient
|
|
287
|
+
.prepare(`INSERT OR IGNORE INTO epic_time_team_batch_event_barriers
|
|
288
|
+
(team_batch_id, committed_event_id, created_at)
|
|
289
|
+
SELECT ?, eh.event_id, ?
|
|
290
|
+
FROM event_handlers eh
|
|
291
|
+
INNER JOIN events e ON e.id = eh.event_id
|
|
292
|
+
WHERE eh.delivery_key = ?
|
|
293
|
+
AND eh.status IN ('pending', 'running', 'retry')
|
|
294
|
+
AND e.published_at <= ?`)
|
|
295
|
+
.run(batch.id, nowIso, deliveryKey, nowIso);
|
|
296
|
+
sealedBatches += 1;
|
|
297
|
+
}
|
|
298
|
+
const sealed = this.loadTeamBatches(true);
|
|
299
|
+
for (const batch of sealed) {
|
|
300
|
+
if (this.batchHasPendingBarrier(batch.id, deliveryKey)) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (!this.isTeamBatchIdentityCurrent(batch)) {
|
|
304
|
+
this.cancelTeamBatch(batch.id, nowIso);
|
|
305
|
+
cancelledBatches += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
this.finalizeTeamBatch(batch, nowIso);
|
|
309
|
+
finalizedBatches += 1;
|
|
310
|
+
}
|
|
311
|
+
return { sealedBatches, finalizedBatches, cancelledBatches };
|
|
197
312
|
}
|
|
198
313
|
async recordTaskTouch(touch, now = new Date()) {
|
|
199
314
|
const nowIso = now.toISOString();
|
|
@@ -223,6 +338,150 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
223
338
|
};
|
|
224
339
|
});
|
|
225
340
|
}
|
|
341
|
+
listAgentTimeBuffers(projectId) {
|
|
342
|
+
const rows = this.listEligibleBufferRows(projectId, null);
|
|
343
|
+
if (rows.length === 0) {
|
|
344
|
+
return { capturedAt: null, items: [] };
|
|
345
|
+
}
|
|
346
|
+
let capturedAt = rows[0].updated_at;
|
|
347
|
+
let capturedAtMs = this.timestampMs(capturedAt);
|
|
348
|
+
const rowsByAgent = new Map();
|
|
349
|
+
for (const row of rows) {
|
|
350
|
+
const updatedAtMs = this.timestampMs(row.updated_at);
|
|
351
|
+
if (updatedAtMs > capturedAtMs) {
|
|
352
|
+
capturedAt = row.updated_at;
|
|
353
|
+
capturedAtMs = updatedAtMs;
|
|
354
|
+
}
|
|
355
|
+
const agentRows = rowsByAgent.get(row.agent_id_snapshot) ?? [];
|
|
356
|
+
agentRows.push(row);
|
|
357
|
+
rowsByAgent.set(row.agent_id_snapshot, agentRows);
|
|
358
|
+
}
|
|
359
|
+
const items = [];
|
|
360
|
+
for (const [agentId, agentRows] of rowsByAgent) {
|
|
361
|
+
const durationMs = agentRows.reduce((total, row) => total + row.duration_ms, 0);
|
|
362
|
+
let oldest = agentRows[0].last_activity_at;
|
|
363
|
+
let newest = agentRows[0].last_activity_at;
|
|
364
|
+
for (const row of agentRows.slice(1)) {
|
|
365
|
+
if (this.timestampMs(row.last_activity_at) < this.timestampMs(oldest)) {
|
|
366
|
+
oldest = row.last_activity_at;
|
|
367
|
+
}
|
|
368
|
+
if (this.timestampMs(row.last_activity_at) > this.timestampMs(newest)) {
|
|
369
|
+
newest = row.last_activity_at;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
items.push({
|
|
373
|
+
agentId,
|
|
374
|
+
snapshotToken: buildBufferSnapshotToken(projectId, agentId, agentRows),
|
|
375
|
+
minutes: Math.floor(durationMs / MILLIS_PER_MINUTE),
|
|
376
|
+
durationMs,
|
|
377
|
+
segmentCount: agentRows.length,
|
|
378
|
+
oldestActivityAt: oldest,
|
|
379
|
+
newestActivityAt: newest,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
items.sort((left, right) => left.agentId.localeCompare(right.agentId));
|
|
383
|
+
return { capturedAt, items };
|
|
384
|
+
}
|
|
385
|
+
async assignAgentTimeBuffer(input, now = new Date()) {
|
|
386
|
+
const nowIso = now.toISOString();
|
|
387
|
+
return this.transactionRunner.runImmediateQueued(() => {
|
|
388
|
+
const project = this.rawClient
|
|
389
|
+
.prepare(`SELECT workspace_id FROM projects WHERE id = ?`)
|
|
390
|
+
.get(input.projectId);
|
|
391
|
+
if (!project) {
|
|
392
|
+
throw new error_types_1.NotFoundError('Project', input.projectId);
|
|
393
|
+
}
|
|
394
|
+
const agent = this.rawClient
|
|
395
|
+
.prepare(`SELECT id FROM agents WHERE id = ? AND project_id = ?`)
|
|
396
|
+
.get(input.agentId, input.projectId);
|
|
397
|
+
if (!agent) {
|
|
398
|
+
throw new error_types_1.NotFoundError('Agent', input.agentId);
|
|
399
|
+
}
|
|
400
|
+
const epic = this.rawClient
|
|
401
|
+
.prepare(`SELECT id FROM epics WHERE id = ? AND project_id = ?`)
|
|
402
|
+
.get(input.targetEpicId, input.projectId);
|
|
403
|
+
if (!epic) {
|
|
404
|
+
throw new error_types_1.NotFoundError('Epic', input.targetEpicId);
|
|
405
|
+
}
|
|
406
|
+
const rows = this.requireCapturedBufferRows(input);
|
|
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
|
+
}
|
|
421
|
+
async resetAgentTimeBuffer(input) {
|
|
422
|
+
return this.transactionRunner.runImmediateQueued(() => {
|
|
423
|
+
const project = this.rawClient
|
|
424
|
+
.prepare(`SELECT workspace_id FROM projects WHERE id = ?`)
|
|
425
|
+
.get(input.projectId);
|
|
426
|
+
if (!project) {
|
|
427
|
+
throw new error_types_1.NotFoundError('Project', input.projectId);
|
|
428
|
+
}
|
|
429
|
+
const agent = this.rawClient
|
|
430
|
+
.prepare(`SELECT id FROM agents WHERE id = ? AND project_id = ?`)
|
|
431
|
+
.get(input.agentId, input.projectId);
|
|
432
|
+
if (!agent) {
|
|
433
|
+
throw new error_types_1.NotFoundError('Agent', input.agentId);
|
|
434
|
+
}
|
|
435
|
+
const rows = this.requireCapturedBufferRows(input);
|
|
436
|
+
const deleteRow = this.rawClient.prepare(`DELETE FROM epic_time_segments
|
|
437
|
+
WHERE id = ?
|
|
438
|
+
AND epic_id IS NULL
|
|
439
|
+
AND team_batch_id IS NULL
|
|
440
|
+
AND closed_at IS NOT NULL
|
|
441
|
+
AND duration_ms > 0`);
|
|
442
|
+
for (const row of rows) {
|
|
443
|
+
if (deleteRow.run(row.id).changes !== 1) {
|
|
444
|
+
throw new error_types_1.ConflictError(STALE_SNAPSHOT_MESSAGE);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return { workspaceId: project.workspace_id };
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
runTerminationResetSync(input) {
|
|
451
|
+
const now = input.now;
|
|
452
|
+
this.reconcileSessionCore(input.sessionId, input.trackingStartedAt, input.idleTimeoutMs, now, {});
|
|
453
|
+
this.processTeamBatchesCore(input.deliveryKey, input.idleTimeoutMs, now);
|
|
454
|
+
if (!input.agentId) {
|
|
455
|
+
return { workspaceId: null, deletedSegments: 0 };
|
|
456
|
+
}
|
|
457
|
+
const deleted = this.rawClient
|
|
458
|
+
.prepare(`DELETE FROM epic_time_segments
|
|
459
|
+
WHERE id IN (
|
|
460
|
+
SELECT seg.id
|
|
461
|
+
FROM epic_time_segments seg
|
|
462
|
+
INNER JOIN agents a
|
|
463
|
+
ON a.id = seg.agent_id_snapshot AND a.project_id = seg.project_id
|
|
464
|
+
WHERE seg.agent_id_snapshot = ?
|
|
465
|
+
AND seg.epic_id IS NULL
|
|
466
|
+
AND seg.team_batch_id IS NULL
|
|
467
|
+
AND seg.closed_at IS NOT NULL
|
|
468
|
+
AND seg.duration_ms > 0
|
|
469
|
+
)`)
|
|
470
|
+
.run(input.agentId);
|
|
471
|
+
if (deleted.changes === 0) {
|
|
472
|
+
return { workspaceId: null, deletedSegments: 0 };
|
|
473
|
+
}
|
|
474
|
+
const agentProject = this.rawClient
|
|
475
|
+
.prepare(`SELECT p.workspace_id AS workspace_id
|
|
476
|
+
FROM agents a
|
|
477
|
+
INNER JOIN projects p ON p.id = a.project_id
|
|
478
|
+
WHERE a.id = ?`)
|
|
479
|
+
.get(input.agentId);
|
|
480
|
+
return {
|
|
481
|
+
workspaceId: agentProject?.workspace_id ?? null,
|
|
482
|
+
deletedSegments: deleted.changes,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
226
485
|
getEpicTimeScope(epicId) {
|
|
227
486
|
const row = this.rawClient
|
|
228
487
|
.prepare(`SELECT id, parent_id FROM epics WHERE id = ?`)
|
|
@@ -238,40 +497,274 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
238
497
|
.prepare(`SELECT id, parent_id FROM epics WHERE id IN (${placeholders})`)
|
|
239
498
|
.all(...epicIds).map((row) => ({ id: row.id, parentId: row.parent_id }));
|
|
240
499
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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 [];
|
|
500
|
+
listResolvedScope(focalEpicIds) {
|
|
501
|
+
if (focalEpicIds.length === 0) {
|
|
502
|
+
return { segments: [], routedRootIdsByFocal: new Map() };
|
|
257
503
|
}
|
|
258
|
-
const placeholders = rootEpicIds.map(() => '?').join(', ');
|
|
259
504
|
const rows = this.rawClient
|
|
260
|
-
.prepare(`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
505
|
+
.prepare(`WITH RECURSIVE
|
|
506
|
+
${RESOLVED_SCOPE_CTE},
|
|
507
|
+
scope(focal_id, epic_id, is_direct, group_epic_id) AS (
|
|
508
|
+
SELECT f.focal_id, e.id, 1, f.focal_id
|
|
509
|
+
FROM focal f
|
|
510
|
+
JOIN epics e ON e.id = f.focal_id
|
|
511
|
+
UNION
|
|
512
|
+
SELECT f.focal_id, child.id, 0, f.focal_id
|
|
513
|
+
FROM focal f
|
|
514
|
+
JOIN epics e ON e.id = f.focal_id AND e.parent_id IS NULL
|
|
515
|
+
JOIN epics child ON child.parent_id = e.id AND child.project_id = e.project_id
|
|
516
|
+
UNION
|
|
517
|
+
SELECT rc.focal_id, rc.epic_id, 0, rc.epic_id
|
|
518
|
+
FROM route_chain rc
|
|
519
|
+
WHERE rc.epic_id <> rc.focal_id
|
|
520
|
+
UNION
|
|
521
|
+
SELECT rc.focal_id, child.id, 0, rc.epic_id
|
|
522
|
+
FROM route_chain rc
|
|
523
|
+
JOIN epics routed ON routed.id = rc.epic_id
|
|
524
|
+
JOIN epics child ON child.parent_id = routed.id AND child.project_id = routed.project_id
|
|
525
|
+
WHERE rc.epic_id <> rc.focal_id
|
|
526
|
+
AND NOT EXISTS (
|
|
527
|
+
SELECT 1 FROM external_task_links link WHERE link.epic_id = child.id
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
SELECT DISTINCT scope.focal_id AS root_epic_id,
|
|
531
|
+
seg.id AS segment_id,
|
|
532
|
+
scoped.id AS epic_id, scoped.title AS epic_title,
|
|
533
|
+
seg.agent_id_snapshot, seg.agent_name_snapshot, seg.attribution_source,
|
|
534
|
+
seg.team_id_snapshot, seg.team_name_snapshot,
|
|
535
|
+
seg.duration_ms, seg.last_activity_at, seg.updated_at,
|
|
536
|
+
scope.is_direct AS is_direct,
|
|
537
|
+
scope.group_epic_id AS group_epic_id,
|
|
538
|
+
group_epic.title AS group_epic_title,
|
|
539
|
+
NULL AS routed_epic_id
|
|
540
|
+
FROM scope
|
|
541
|
+
JOIN epics scoped ON scoped.id = scope.epic_id
|
|
542
|
+
LEFT JOIN epics group_epic ON group_epic.id = scope.group_epic_id
|
|
543
|
+
JOIN epic_time_segments seg
|
|
544
|
+
ON seg.epic_id = scoped.id
|
|
545
|
+
AND seg.project_id = scoped.project_id
|
|
546
|
+
AND seg.closed_at IS NOT NULL
|
|
547
|
+
UNION ALL
|
|
548
|
+
SELECT DISTINCT rc.focal_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
549
|
+
NULL, NULL, NULL, NULL, NULL, NULL, NULL, rc.epic_id
|
|
550
|
+
FROM route_chain rc
|
|
551
|
+
WHERE rc.epic_id <> rc.focal_id
|
|
552
|
+
ORDER BY root_epic_id, last_activity_at, segment_id`)
|
|
553
|
+
.all(JSON.stringify(focalEpicIds));
|
|
554
|
+
const segments = [];
|
|
555
|
+
const routedRootIdsByFocal = new Map();
|
|
556
|
+
for (const row of rows) {
|
|
557
|
+
if (row.routed_epic_id !== null) {
|
|
558
|
+
const routed = routedRootIdsByFocal.get(row.root_epic_id) ?? [];
|
|
559
|
+
routed.push(row.routed_epic_id);
|
|
560
|
+
routedRootIdsByFocal.set(row.root_epic_id, routed);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
segments.push(this.mapSummarySegment({
|
|
564
|
+
id: row.segment_id,
|
|
565
|
+
epic_id: row.epic_id,
|
|
566
|
+
epic_title: row.epic_title,
|
|
567
|
+
agent_id_snapshot: row.agent_id_snapshot,
|
|
568
|
+
agent_name_snapshot: row.agent_name_snapshot,
|
|
569
|
+
attribution_source: row.attribution_source,
|
|
570
|
+
team_id_snapshot: row.team_id_snapshot,
|
|
571
|
+
team_name_snapshot: row.team_name_snapshot,
|
|
572
|
+
duration_ms: row.duration_ms,
|
|
573
|
+
last_activity_at: row.last_activity_at,
|
|
574
|
+
updated_at: row.updated_at,
|
|
575
|
+
}, row.root_epic_id, row.is_direct === 1, row.group_epic_id, row.group_epic_title));
|
|
576
|
+
}
|
|
577
|
+
for (const routed of routedRootIdsByFocal.values()) {
|
|
578
|
+
routed.sort();
|
|
579
|
+
}
|
|
580
|
+
for (const focalId of focalEpicIds) {
|
|
581
|
+
if (!routedRootIdsByFocal.has(focalId)) {
|
|
582
|
+
routedRootIdsByFocal.set(focalId, []);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return { segments, routedRootIdsByFocal };
|
|
586
|
+
}
|
|
587
|
+
resolveEligibleTeam(session) {
|
|
588
|
+
if (!session.agent_id || !session.project_id) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
return (this.rawClient
|
|
592
|
+
.prepare(`SELECT t.id AS team_id, t.name AS team_name,
|
|
593
|
+
lead.id AS lead_agent_id, lead.name AS lead_agent_name
|
|
594
|
+
FROM team_members tm
|
|
595
|
+
INNER JOIN teams t ON t.id = tm.team_id AND t.project_id = ?
|
|
596
|
+
INNER JOIN agents lead
|
|
597
|
+
ON lead.id = t.team_lead_agent_id AND lead.project_id = t.project_id
|
|
598
|
+
WHERE tm.agent_id = ?
|
|
599
|
+
AND t.team_lead_agent_id <> tm.agent_id
|
|
600
|
+
AND (
|
|
601
|
+
SELECT COUNT(*)
|
|
602
|
+
FROM team_members memberships
|
|
603
|
+
INNER JOIN teams member_teams
|
|
604
|
+
ON member_teams.id = memberships.team_id
|
|
605
|
+
AND member_teams.project_id = ?
|
|
606
|
+
WHERE memberships.agent_id = tm.agent_id
|
|
607
|
+
) = 1
|
|
608
|
+
LIMIT 1`)
|
|
609
|
+
.get(session.project_id, session.agent_id, session.project_id) ?? null);
|
|
610
|
+
}
|
|
611
|
+
loadOpenTeamBatch(projectId, teamId) {
|
|
612
|
+
return (this.rawClient
|
|
613
|
+
.prepare(`SELECT id, project_id, team_id_snapshot, team_name_snapshot,
|
|
614
|
+
lead_agent_id_snapshot, lead_agent_name_snapshot, started_at, sealed_at
|
|
615
|
+
FROM epic_time_team_batches
|
|
616
|
+
WHERE project_id = ? AND team_id_snapshot = ? AND sealed_at IS NULL`)
|
|
617
|
+
.get(projectId, teamId) ?? null);
|
|
618
|
+
}
|
|
619
|
+
createOrLoadOpenTeamBatch(projectId, team, startedAt, nowIso) {
|
|
620
|
+
this.rawClient
|
|
621
|
+
.prepare(`INSERT INTO epic_time_team_batches
|
|
622
|
+
(id, project_id, team_id_snapshot, team_name_snapshot,
|
|
623
|
+
lead_agent_id_snapshot, lead_agent_name_snapshot, started_at,
|
|
624
|
+
sealed_at, created_at, updated_at)
|
|
625
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
626
|
+
ON CONFLICT DO NOTHING`)
|
|
627
|
+
.run((0, node_crypto_1.randomUUID)(), projectId, team.team_id, team.team_name, team.lead_agent_id, team.lead_agent_name, startedAt, nowIso, nowIso);
|
|
628
|
+
const batch = this.loadOpenTeamBatch(projectId, team.team_id);
|
|
629
|
+
if (!batch) {
|
|
630
|
+
throw new Error('Unable to create or load the open Epic-time team batch.');
|
|
631
|
+
}
|
|
632
|
+
return batch;
|
|
633
|
+
}
|
|
634
|
+
loadTeamBatches(sealed) {
|
|
635
|
+
return this.rawClient
|
|
636
|
+
.prepare(`SELECT id, project_id, team_id_snapshot, team_name_snapshot,
|
|
637
|
+
lead_agent_id_snapshot, lead_agent_name_snapshot, started_at, sealed_at
|
|
638
|
+
FROM epic_time_team_batches
|
|
639
|
+
WHERE sealed_at IS ${sealed ? 'NOT NULL' : 'NULL'}
|
|
640
|
+
ORDER BY started_at, id`)
|
|
641
|
+
.all();
|
|
642
|
+
}
|
|
643
|
+
hasActiveEligibleMember(batch, activeAfter) {
|
|
644
|
+
return Boolean(this.rawClient
|
|
645
|
+
.prepare(`SELECT 1
|
|
646
|
+
FROM sessions s
|
|
647
|
+
INNER JOIN agents a
|
|
648
|
+
ON a.id = s.agent_id AND a.project_id = ?
|
|
649
|
+
INNER JOIN team_members tm
|
|
650
|
+
ON tm.agent_id = a.id AND tm.team_id = ?
|
|
651
|
+
INNER JOIN teams t
|
|
652
|
+
ON t.id = tm.team_id
|
|
653
|
+
AND t.project_id = ?
|
|
654
|
+
AND t.team_lead_agent_id = ?
|
|
655
|
+
INNER JOIN agents lead
|
|
656
|
+
ON lead.id = t.team_lead_agent_id AND lead.project_id = t.project_id
|
|
657
|
+
WHERE s.status = 'running'
|
|
658
|
+
AND (s.activity_state IS NULL OR s.activity_state <> 'idle')
|
|
659
|
+
AND s.last_activity_at > ?
|
|
660
|
+
AND a.id <> t.team_lead_agent_id
|
|
661
|
+
AND (
|
|
662
|
+
SELECT COUNT(*)
|
|
663
|
+
FROM team_members memberships
|
|
664
|
+
INNER JOIN teams member_teams
|
|
665
|
+
ON member_teams.id = memberships.team_id
|
|
666
|
+
AND member_teams.project_id = ?
|
|
667
|
+
WHERE memberships.agent_id = a.id
|
|
668
|
+
) = 1
|
|
669
|
+
AND NOT EXISTS (
|
|
670
|
+
SELECT 1 FROM epics bound
|
|
671
|
+
WHERE bound.id = s.epic_id AND bound.project_id = ?
|
|
672
|
+
)
|
|
673
|
+
AND NOT EXISTS (
|
|
674
|
+
SELECT 1 FROM epics assigned
|
|
675
|
+
WHERE assigned.project_id = ? AND assigned.agent_id = a.id
|
|
676
|
+
)
|
|
677
|
+
LIMIT 1`)
|
|
678
|
+
.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));
|
|
679
|
+
}
|
|
680
|
+
batchHasPendingBarrier(batchId, deliveryKey) {
|
|
681
|
+
return Boolean(this.rawClient
|
|
682
|
+
.prepare(`SELECT 1
|
|
683
|
+
FROM epic_time_team_batch_event_barriers barrier
|
|
684
|
+
INNER JOIN event_handlers delivery
|
|
685
|
+
ON delivery.event_id = barrier.committed_event_id
|
|
686
|
+
AND delivery.delivery_key = ?
|
|
687
|
+
WHERE barrier.team_batch_id = ?
|
|
688
|
+
AND delivery.status IN ('pending', 'running', 'retry')
|
|
689
|
+
LIMIT 1`)
|
|
690
|
+
.get(deliveryKey, batchId));
|
|
691
|
+
}
|
|
692
|
+
isTeamBatchIdentityCurrent(batch) {
|
|
693
|
+
const teamStillMatches = Boolean(this.rawClient
|
|
694
|
+
.prepare(`SELECT 1
|
|
695
|
+
FROM teams t
|
|
696
|
+
INNER JOIN agents lead
|
|
697
|
+
ON lead.id = t.team_lead_agent_id AND lead.project_id = t.project_id
|
|
698
|
+
WHERE t.id = ? AND t.project_id = ? AND t.team_lead_agent_id = ?`)
|
|
699
|
+
.get(batch.team_id_snapshot, batch.project_id, batch.lead_agent_id_snapshot));
|
|
700
|
+
if (!teamStillMatches) {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
return !this.rawClient
|
|
704
|
+
.prepare(`SELECT 1
|
|
705
|
+
FROM epic_time_segments segment
|
|
706
|
+
LEFT JOIN agents original
|
|
707
|
+
ON original.id = segment.agent_id_snapshot AND original.project_id = segment.project_id
|
|
708
|
+
WHERE segment.team_batch_id = ?
|
|
709
|
+
AND segment.epic_id IS NULL
|
|
710
|
+
AND (
|
|
711
|
+
original.id IS NULL
|
|
712
|
+
OR NOT EXISTS (
|
|
713
|
+
SELECT 1 FROM team_members exact_membership
|
|
714
|
+
WHERE exact_membership.team_id = ?
|
|
715
|
+
AND exact_membership.agent_id = segment.agent_id_snapshot
|
|
716
|
+
)
|
|
717
|
+
OR (
|
|
718
|
+
SELECT COUNT(*)
|
|
719
|
+
FROM team_members memberships
|
|
720
|
+
INNER JOIN teams member_teams
|
|
721
|
+
ON member_teams.id = memberships.team_id
|
|
722
|
+
AND member_teams.project_id = segment.project_id
|
|
723
|
+
WHERE memberships.agent_id = segment.agent_id_snapshot
|
|
724
|
+
) <> 1
|
|
725
|
+
)
|
|
726
|
+
LIMIT 1`)
|
|
727
|
+
.get(batch.id, batch.team_id_snapshot);
|
|
728
|
+
}
|
|
729
|
+
cancelTeamBatch(batchId, nowIso) {
|
|
730
|
+
this.rawClient
|
|
731
|
+
.prepare(`UPDATE epic_time_segments
|
|
732
|
+
SET team_batch_id = NULL, attribution_source = 'direct',
|
|
733
|
+
team_id_snapshot = NULL, team_name_snapshot = NULL, updated_at = ?
|
|
734
|
+
WHERE team_batch_id = ? AND epic_id IS NULL`)
|
|
735
|
+
.run(nowIso, batchId);
|
|
736
|
+
this.rawClient.prepare(`DELETE FROM epic_time_team_batches WHERE id = ?`).run(batchId);
|
|
737
|
+
}
|
|
738
|
+
finalizeTeamBatch(batch, nowIso) {
|
|
739
|
+
const winner = this.rawClient
|
|
740
|
+
.prepare(`SELECT agent_id_snapshot, SUM(duration_ms) AS duration_ms,
|
|
741
|
+
MIN(started_at) AS earliest_started_at
|
|
742
|
+
FROM epic_time_segments
|
|
743
|
+
WHERE team_batch_id = ? AND epic_id IS NULL
|
|
744
|
+
GROUP BY agent_id_snapshot
|
|
745
|
+
ORDER BY duration_ms DESC, earliest_started_at, agent_id_snapshot
|
|
746
|
+
LIMIT 1`)
|
|
747
|
+
.get(batch.id);
|
|
748
|
+
if (winner) {
|
|
749
|
+
const target = this.rawClient
|
|
750
|
+
.prepare(`SELECT id
|
|
751
|
+
FROM epics
|
|
752
|
+
WHERE project_id = ? AND agent_id = ?
|
|
753
|
+
ORDER BY updated_at DESC, id DESC
|
|
754
|
+
LIMIT 1`)
|
|
755
|
+
.get(batch.project_id, batch.lead_agent_id_snapshot);
|
|
756
|
+
this.rawClient
|
|
757
|
+
.prepare(`DELETE FROM epic_time_segments
|
|
758
|
+
WHERE team_batch_id = ? AND epic_id IS NULL AND agent_id_snapshot <> ?`)
|
|
759
|
+
.run(batch.id, winner.agent_id_snapshot);
|
|
760
|
+
this.rawClient
|
|
761
|
+
.prepare(`UPDATE epic_time_segments
|
|
762
|
+
SET epic_id = ?, agent_id_snapshot = ?, agent_name_snapshot = ?,
|
|
763
|
+
attribution_source = 'team', updated_at = ?
|
|
764
|
+
WHERE team_batch_id = ? AND epic_id IS NULL AND agent_id_snapshot = ?`)
|
|
765
|
+
.run(target?.id ?? null, batch.lead_agent_id_snapshot, batch.lead_agent_name_snapshot, nowIso, batch.id, winner.agent_id_snapshot);
|
|
766
|
+
}
|
|
767
|
+
this.rawClient.prepare(`DELETE FROM epic_time_team_batches WHERE id = ?`).run(batch.id);
|
|
275
768
|
}
|
|
276
769
|
loadSession(sessionId) {
|
|
277
770
|
return (this.rawClient
|
|
@@ -284,15 +777,23 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
284
777
|
WHERE s.id = ?`)
|
|
285
778
|
.get(sessionId) ?? null);
|
|
286
779
|
}
|
|
287
|
-
mapSummarySegment(row, rootEpicId, isDirect) {
|
|
780
|
+
mapSummarySegment(row, rootEpicId, isDirect, groupEpicId, groupEpicTitle) {
|
|
781
|
+
const attributionSource = row.attribution_source === 'team' && row.team_id_snapshot && row.team_name_snapshot
|
|
782
|
+
? 'team'
|
|
783
|
+
: 'direct';
|
|
288
784
|
return {
|
|
289
785
|
id: row.id,
|
|
290
786
|
rootEpicId,
|
|
291
787
|
epicId: row.epic_id,
|
|
292
788
|
epicTitle: row.epic_title,
|
|
789
|
+
groupEpicId,
|
|
790
|
+
groupEpicTitle,
|
|
293
791
|
isDirect,
|
|
294
792
|
agentId: row.agent_id_snapshot,
|
|
295
793
|
agentName: row.agent_name_snapshot,
|
|
794
|
+
attributionSource,
|
|
795
|
+
teamId: attributionSource === 'team' ? row.team_id_snapshot : null,
|
|
796
|
+
teamName: attributionSource === 'team' ? row.team_name_snapshot : null,
|
|
296
797
|
durationMs: row.duration_ms,
|
|
297
798
|
lastActivityAt: row.last_activity_at,
|
|
298
799
|
updatedAt: row.updated_at,
|
|
@@ -307,7 +808,9 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
307
808
|
}
|
|
308
809
|
loadOpenSegment(sessionId) {
|
|
309
810
|
return (this.rawClient
|
|
310
|
-
.prepare(`SELECT id, project_id, epic_id,
|
|
811
|
+
.prepare(`SELECT id, project_id, epic_id, team_batch_id, attribution_source,
|
|
812
|
+
team_id_snapshot, team_name_snapshot, agent_id_snapshot,
|
|
813
|
+
last_activity_at, duration_ms
|
|
311
814
|
FROM epic_time_segments
|
|
312
815
|
WHERE session_id_snapshot = ? AND closed_at IS NULL`)
|
|
313
816
|
.get(sessionId) ?? null);
|
|
@@ -356,6 +859,21 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
356
859
|
WHERE earliest.project_id = s.project_id
|
|
357
860
|
AND earliest.agent_id_snapshot = s.agent_id_snapshot
|
|
358
861
|
AND earliest.published_at >= s.started_at
|
|
862
|
+
AND (
|
|
863
|
+
s.team_batch_id IS NULL
|
|
864
|
+
OR EXISTS (
|
|
865
|
+
SELECT 1
|
|
866
|
+
FROM epic_time_team_batches open_batch
|
|
867
|
+
WHERE open_batch.id = s.team_batch_id
|
|
868
|
+
AND open_batch.sealed_at IS NULL
|
|
869
|
+
)
|
|
870
|
+
OR EXISTS (
|
|
871
|
+
SELECT 1
|
|
872
|
+
FROM epic_time_team_batch_event_barriers barrier
|
|
873
|
+
WHERE barrier.team_batch_id = s.team_batch_id
|
|
874
|
+
AND barrier.committed_event_id = earliest.committed_event_id
|
|
875
|
+
)
|
|
876
|
+
)
|
|
359
877
|
ORDER BY earliest.published_at, earliest.claim_sequence
|
|
360
878
|
LIMIT 1
|
|
361
879
|
)
|
|
@@ -368,7 +886,21 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
368
886
|
const discardedSegmentIds = new Set();
|
|
369
887
|
const targetExists = new Map();
|
|
370
888
|
const updateSegment = this.rawClient.prepare(`UPDATE epic_time_segments
|
|
371
|
-
SET epic_id = ?,
|
|
889
|
+
SET epic_id = ?,
|
|
890
|
+
attribution_source = CASE
|
|
891
|
+
WHEN team_batch_id IS NOT NULL THEN 'direct'
|
|
892
|
+
ELSE attribution_source
|
|
893
|
+
END,
|
|
894
|
+
team_id_snapshot = CASE
|
|
895
|
+
WHEN team_batch_id IS NOT NULL THEN NULL
|
|
896
|
+
ELSE team_id_snapshot
|
|
897
|
+
END,
|
|
898
|
+
team_name_snapshot = CASE
|
|
899
|
+
WHEN team_batch_id IS NOT NULL THEN NULL
|
|
900
|
+
ELSE team_name_snapshot
|
|
901
|
+
END,
|
|
902
|
+
team_batch_id = NULL,
|
|
903
|
+
updated_at = ?
|
|
372
904
|
WHERE id = ? AND epic_id IS NULL`);
|
|
373
905
|
const deleteSegment = this.rawClient.prepare(`DELETE FROM epic_time_segments
|
|
374
906
|
WHERE id = ? AND epic_id IS NULL`);
|
|
@@ -395,6 +927,33 @@ let EpicTimeStore = class EpicTimeStore {
|
|
|
395
927
|
}
|
|
396
928
|
return { claimedSegmentIds, discardedSegmentIds };
|
|
397
929
|
}
|
|
930
|
+
requireCapturedBufferRows(input) {
|
|
931
|
+
const rows = this.listEligibleBufferRows(input.projectId, input.agentId);
|
|
932
|
+
const capturedAtMs = this.timestampMs(input.capturedAt);
|
|
933
|
+
if (rows.length === 0 || rows.some((row) => this.timestampMs(row.updated_at) > capturedAtMs)) {
|
|
934
|
+
throw new error_types_1.ConflictError(STALE_SNAPSHOT_MESSAGE);
|
|
935
|
+
}
|
|
936
|
+
if (buildBufferSnapshotToken(input.projectId, input.agentId, rows) !== input.snapshotToken) {
|
|
937
|
+
throw new error_types_1.ConflictError(STALE_SNAPSHOT_MESSAGE);
|
|
938
|
+
}
|
|
939
|
+
return rows;
|
|
940
|
+
}
|
|
941
|
+
listEligibleBufferRows(projectId, agentId) {
|
|
942
|
+
return this.rawClient
|
|
943
|
+
.prepare(`SELECT seg.id, seg.agent_id_snapshot, seg.duration_ms, seg.started_at,
|
|
944
|
+
seg.last_activity_at, seg.closed_at, seg.updated_at
|
|
945
|
+
FROM epic_time_segments seg
|
|
946
|
+
INNER JOIN agents a
|
|
947
|
+
ON a.id = seg.agent_id_snapshot AND a.project_id = seg.project_id
|
|
948
|
+
WHERE seg.project_id = ?
|
|
949
|
+
AND (? IS NULL OR seg.agent_id_snapshot = ?)
|
|
950
|
+
AND seg.epic_id IS NULL
|
|
951
|
+
AND seg.team_batch_id IS NULL
|
|
952
|
+
AND seg.closed_at IS NOT NULL
|
|
953
|
+
AND seg.duration_ms > 0
|
|
954
|
+
ORDER BY seg.agent_id_snapshot, seg.started_at, seg.id`)
|
|
955
|
+
.all(projectId, agentId, agentId);
|
|
956
|
+
}
|
|
398
957
|
closeSegment(segmentId, closedAt, nowIso) {
|
|
399
958
|
this.rawClient
|
|
400
959
|
.prepare(`UPDATE epic_time_segments
|