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.
- 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 +8 -0
- package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js +67 -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.module.js +14 -4
- 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 +138 -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 +12 -3
- package/dist/server/modules/epic-time/services/epic-time.service.js +155 -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 +29 -3
- package/dist/server/modules/epic-time/services/epic-time.store.js +524 -40
- 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/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-BG9SopK8.js} +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-BmyiM5IR.js → ReviewDetailPage-DNo7Qx3m.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-CQ87BMwT.js → ReviewsPage-Cay-IObR.js} +1 -1
- package/dist/server/ui/assets/index-C1Fdqua-.js +1197 -0
- package/dist/server/ui/assets/index-CV8ydm7w.css +32 -0
- package/dist/server/ui/assets/{useReviewSubscription-DjbEUzt4.js → useReviewSubscription-sAAfh4k9.js} +1 -1
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/teams-dev.json +311 -301
- package/package.json +19 -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;
|
|
@@ -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,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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 [];
|
|
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(`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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,
|
|
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 = ?,
|
|
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
|