patchrelay 0.8.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +64 -62
  2. package/dist/agent-session-plan.js +17 -17
  3. package/dist/build-info.json +3 -3
  4. package/dist/cli/commands/issues.js +12 -12
  5. package/dist/cli/data.js +109 -298
  6. package/dist/cli/formatters/text.js +22 -28
  7. package/dist/config.js +13 -166
  8. package/dist/db/migrations.js +46 -154
  9. package/dist/db.js +369 -45
  10. package/dist/factory-state.js +55 -0
  11. package/dist/github-webhook-handler.js +199 -0
  12. package/dist/github-webhooks.js +166 -0
  13. package/dist/hook-runner.js +28 -0
  14. package/dist/http.js +48 -22
  15. package/dist/issue-query-service.js +33 -38
  16. package/dist/linear-workflow.js +5 -118
  17. package/dist/preflight.js +1 -6
  18. package/dist/project-resolution.js +12 -1
  19. package/dist/run-orchestrator.js +446 -0
  20. package/dist/{stage-reporting.js → run-reporting.js} +11 -13
  21. package/dist/service-runtime.js +12 -61
  22. package/dist/service-webhooks.js +7 -52
  23. package/dist/service.js +39 -61
  24. package/dist/webhook-handler.js +387 -0
  25. package/dist/webhook-installation-handler.js +3 -8
  26. package/package.json +2 -1
  27. package/dist/db/authoritative-ledger-store.js +0 -536
  28. package/dist/db/issue-projection-store.js +0 -54
  29. package/dist/db/issue-workflow-coordinator.js +0 -320
  30. package/dist/db/issue-workflow-store.js +0 -194
  31. package/dist/db/run-report-store.js +0 -33
  32. package/dist/db/stage-event-store.js +0 -33
  33. package/dist/db/webhook-event-store.js +0 -59
  34. package/dist/db-ports.js +0 -5
  35. package/dist/ledger-ports.js +0 -1
  36. package/dist/reconciliation-action-applier.js +0 -68
  37. package/dist/reconciliation-actions.js +0 -1
  38. package/dist/reconciliation-engine.js +0 -350
  39. package/dist/reconciliation-snapshot-builder.js +0 -135
  40. package/dist/reconciliation-types.js +0 -1
  41. package/dist/service-stage-finalizer.js +0 -753
  42. package/dist/service-stage-runner.js +0 -336
  43. package/dist/service-webhook-processor.js +0 -411
  44. package/dist/stage-agent-activity-publisher.js +0 -59
  45. package/dist/stage-event-ports.js +0 -1
  46. package/dist/stage-failure.js +0 -92
  47. package/dist/stage-handoff.js +0 -107
  48. package/dist/stage-launch.js +0 -84
  49. package/dist/stage-lifecycle-publisher.js +0 -284
  50. package/dist/stage-turn-input-dispatcher.js +0 -104
  51. package/dist/webhook-agent-session-handler.js +0 -228
  52. package/dist/webhook-comment-handler.js +0 -141
  53. package/dist/webhook-desired-stage-recorder.js +0 -122
  54. package/dist/webhook-event-ports.js +0 -1
  55. package/dist/workflow-policy.js +0 -149
  56. package/dist/workflow-ports.js +0 -1
  57. /package/dist/{installation-ports.js → github-types.js} +0 -0
@@ -1,320 +0,0 @@
1
- import { isoNow } from "./shared.js";
2
- export class IssueWorkflowCoordinator {
3
- connection;
4
- authoritativeLedger;
5
- issueProjections;
6
- issueWorkflows;
7
- runReports;
8
- constructor(dependencies) {
9
- this.connection = dependencies.connection;
10
- this.authoritativeLedger = dependencies.authoritativeLedger;
11
- this.issueProjections = dependencies.issueProjections;
12
- this.issueWorkflows = dependencies.issueWorkflows;
13
- this.runReports = dependencies.runReports;
14
- }
15
- upsertTrackedIssue(params) {
16
- this.issueProjections.upsertIssueProjection({
17
- projectId: params.projectId,
18
- linearIssueId: params.linearIssueId,
19
- ...(params.issueKey ? { issueKey: params.issueKey } : {}),
20
- ...(params.title ? { title: params.title } : {}),
21
- ...(params.issueUrl ? { issueUrl: params.issueUrl } : {}),
22
- ...(params.currentLinearState ? { currentLinearState: params.currentLinearState } : {}),
23
- ...(params.lastWebhookAt ? { lastWebhookAt: params.lastWebhookAt } : {}),
24
- });
25
- const desiredReceiptId = this.resolveDesiredReceiptId({
26
- projectId: params.projectId,
27
- linearIssueId: params.linearIssueId,
28
- ...(params.desiredWebhookId !== undefined ? { desiredWebhookId: params.desiredWebhookId } : {}),
29
- ...(params.desiredReceiptId !== undefined ? { desiredReceiptId: params.desiredReceiptId } : {}),
30
- });
31
- this.authoritativeLedger.upsertIssueControl({
32
- projectId: params.projectId,
33
- linearIssueId: params.linearIssueId,
34
- ...(params.selectedWorkflowId !== undefined ? { selectedWorkflowId: params.selectedWorkflowId } : {}),
35
- ...(params.desiredStage !== undefined ? { desiredStage: params.desiredStage } : {}),
36
- ...(desiredReceiptId !== undefined ? { desiredReceiptId } : {}),
37
- ...(params.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: params.activeWorkspaceId } : {}),
38
- ...(params.activeStageRunId !== undefined ? { activeRunLeaseId: params.activeStageRunId } : {}),
39
- ...(params.statusCommentId !== undefined ? { serviceOwnedCommentId: params.statusCommentId } : {}),
40
- ...(params.activeAgentSessionId !== undefined ? { activeAgentSessionId: params.activeAgentSessionId } : {}),
41
- lifecycleStatus: params.lifecycleStatus,
42
- });
43
- return this.issueWorkflows.getTrackedIssue(params.projectId, params.linearIssueId);
44
- }
45
- recordDesiredStage(params) {
46
- const existing = this.issueWorkflows.getTrackedIssue(params.projectId, params.linearIssueId);
47
- this.issueProjections.upsertIssueProjection({
48
- projectId: params.projectId,
49
- linearIssueId: params.linearIssueId,
50
- ...(params.issueKey ? { issueKey: params.issueKey } : existing?.issueKey ? { issueKey: existing.issueKey } : {}),
51
- ...(params.title ? { title: params.title } : existing?.title ? { title: existing.title } : {}),
52
- ...(params.issueUrl ? { issueUrl: params.issueUrl } : existing?.issueUrl ? { issueUrl: existing.issueUrl } : {}),
53
- ...(params.currentLinearState
54
- ? { currentLinearState: params.currentLinearState }
55
- : existing?.currentLinearState
56
- ? { currentLinearState: existing.currentLinearState }
57
- : {}),
58
- lastWebhookAt: params.lastWebhookAt,
59
- });
60
- const existingIssueControl = this.authoritativeLedger.getIssueControl(params.projectId, params.linearIssueId);
61
- const lifecycleStatus = existingIssueControl?.activeRunLeaseId || params.desiredStage
62
- ? existing?.lifecycleStatus ?? "queued"
63
- : existing?.lifecycleStatus ?? "idle";
64
- const desiredReceiptId = this.resolveDesiredReceiptId({
65
- projectId: params.projectId,
66
- linearIssueId: params.linearIssueId,
67
- ...(params.desiredWebhookId !== undefined ? { desiredWebhookId: params.desiredWebhookId } : {}),
68
- ...(params.desiredReceiptId !== undefined ? { desiredReceiptId: params.desiredReceiptId } : {}),
69
- });
70
- this.authoritativeLedger.upsertIssueControl({
71
- projectId: params.projectId,
72
- linearIssueId: params.linearIssueId,
73
- ...(params.selectedWorkflowId !== undefined
74
- ? { selectedWorkflowId: params.selectedWorkflowId }
75
- : existing?.selectedWorkflowId
76
- ? { selectedWorkflowId: existing.selectedWorkflowId }
77
- : {}),
78
- ...(params.desiredStage !== undefined ? { desiredStage: params.desiredStage } : {}),
79
- ...(desiredReceiptId !== undefined ? { desiredReceiptId } : {}),
80
- lifecycleStatus,
81
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
82
- ...(params.activeAgentSessionId !== undefined
83
- ? { activeAgentSessionId: params.activeAgentSessionId }
84
- : existing?.activeAgentSessionId
85
- ? { activeAgentSessionId: existing.activeAgentSessionId }
86
- : {}),
87
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
88
- ...(existingIssueControl?.activeRunLeaseId !== undefined ? { activeRunLeaseId: existingIssueControl.activeRunLeaseId } : {}),
89
- });
90
- return this.issueWorkflows.getTrackedIssue(params.projectId, params.linearIssueId);
91
- }
92
- claimStageRun(params) {
93
- const transaction = this.connection.transaction(() => {
94
- const issue = this.issueWorkflows.getTrackedIssue(params.projectId, params.linearIssueId);
95
- const issueControl = this.authoritativeLedger.getIssueControl(params.projectId, params.linearIssueId);
96
- if (!issue ||
97
- !issueControl ||
98
- issueControl.activeRunLeaseId !== undefined ||
99
- issue.desiredStage !== params.stage ||
100
- issue.desiredWebhookId !== params.triggerWebhookId) {
101
- return undefined;
102
- }
103
- const workspaceOwnership = this.authoritativeLedger.upsertWorkspaceOwnership({
104
- projectId: params.projectId,
105
- linearIssueId: params.linearIssueId,
106
- branchName: params.branchName,
107
- worktreePath: params.worktreePath,
108
- status: "active",
109
- });
110
- const runLease = this.authoritativeLedger.createRunLease({
111
- issueControlId: issueControl.id,
112
- projectId: params.projectId,
113
- linearIssueId: params.linearIssueId,
114
- workspaceOwnershipId: workspaceOwnership.id,
115
- stage: params.stage,
116
- status: "running",
117
- workflowFile: params.workflowFile,
118
- promptText: params.promptText,
119
- triggerReceiptId: issueControl.desiredReceiptId ?? null,
120
- });
121
- this.authoritativeLedger.upsertWorkspaceOwnership({
122
- projectId: params.projectId,
123
- linearIssueId: params.linearIssueId,
124
- branchName: params.branchName,
125
- worktreePath: params.worktreePath,
126
- status: "active",
127
- currentRunLeaseId: runLease.id,
128
- });
129
- this.authoritativeLedger.upsertIssueControl({
130
- projectId: params.projectId,
131
- linearIssueId: params.linearIssueId,
132
- ...(issue.selectedWorkflowId ? { selectedWorkflowId: issue.selectedWorkflowId } : {}),
133
- desiredStage: null,
134
- desiredReceiptId: null,
135
- activeWorkspaceOwnershipId: workspaceOwnership.id,
136
- activeRunLeaseId: runLease.id,
137
- lifecycleStatus: "running",
138
- ...(issue.statusCommentId ? { serviceOwnedCommentId: issue.statusCommentId } : {}),
139
- ...(issue.activeAgentSessionId ? { activeAgentSessionId: issue.activeAgentSessionId } : {}),
140
- });
141
- const refreshedIssue = this.issueWorkflows.getTrackedIssue(params.projectId, params.linearIssueId);
142
- const workspace = this.issueWorkflows.getWorkspace(workspaceOwnership.id);
143
- const stageRun = this.issueWorkflows.getStageRun(runLease.id);
144
- const pipeline = this.issueWorkflows.getPipelineRun(runLease.id);
145
- return { issue: refreshedIssue, workspace, pipeline, stageRun };
146
- });
147
- return transaction();
148
- }
149
- updateStageRunThread(params) {
150
- this.authoritativeLedger.updateRunLeaseThread({
151
- runLeaseId: params.stageRunId,
152
- threadId: params.threadId,
153
- ...(params.parentThreadId !== undefined ? { parentThreadId: params.parentThreadId } : {}),
154
- ...(params.turnId !== undefined ? { turnId: params.turnId } : {}),
155
- });
156
- const stageRun = this.issueWorkflows.getStageRun(params.stageRunId);
157
- if (!stageRun) {
158
- return;
159
- }
160
- const issue = this.issueWorkflows.getTrackedIssue(stageRun.projectId, stageRun.linearIssueId);
161
- this.authoritativeLedger.upsertIssueSession({
162
- projectId: stageRun.projectId,
163
- linearIssueId: stageRun.linearIssueId,
164
- workspaceOwnershipId: stageRun.workspaceId,
165
- runLeaseId: stageRun.id,
166
- threadId: params.threadId,
167
- ...(params.parentThreadId !== undefined ? { parentThreadId: params.parentThreadId } : {}),
168
- ...(issue?.activeAgentSessionId ? { linkedAgentSessionId: issue.activeAgentSessionId } : {}),
169
- source: "stage_run",
170
- });
171
- }
172
- finishStageRun(params) {
173
- const stageRun = this.issueWorkflows.getStageRun(params.stageRunId);
174
- if (!stageRun) {
175
- return;
176
- }
177
- this.runReports.saveRunReport({
178
- runLeaseId: params.stageRunId,
179
- ...(params.summaryJson !== undefined ? { summaryJson: params.summaryJson } : {}),
180
- ...(params.reportJson !== undefined ? { reportJson: params.reportJson } : {}),
181
- });
182
- this.authoritativeLedger.finishRunLease({
183
- runLeaseId: params.stageRunId,
184
- status: params.status === "failed" ? "failed" : "completed",
185
- threadId: params.threadId,
186
- ...(params.turnId !== undefined ? { turnId: params.turnId } : {}),
187
- });
188
- const runLease = this.authoritativeLedger.getRunLease(params.stageRunId);
189
- if (runLease?.workspaceOwnershipId !== undefined) {
190
- const issue = this.issueWorkflows.getTrackedIssue(stageRun.projectId, stageRun.linearIssueId);
191
- this.authoritativeLedger.upsertIssueSession({
192
- projectId: stageRun.projectId,
193
- linearIssueId: stageRun.linearIssueId,
194
- workspaceOwnershipId: runLease.workspaceOwnershipId,
195
- runLeaseId: params.stageRunId,
196
- threadId: params.threadId,
197
- ...(runLease.parentThreadId ? { parentThreadId: runLease.parentThreadId } : {}),
198
- ...(issue?.activeAgentSessionId ? { linkedAgentSessionId: issue.activeAgentSessionId } : {}),
199
- source: "stage_run",
200
- });
201
- }
202
- const workspace = this.authoritativeLedger.getWorkspaceOwnership(stageRun.workspaceId);
203
- if (workspace && workspace.currentRunLeaseId === params.stageRunId) {
204
- this.authoritativeLedger.upsertWorkspaceOwnership({
205
- projectId: stageRun.projectId,
206
- linearIssueId: stageRun.linearIssueId,
207
- branchName: workspace.branchName,
208
- worktreePath: workspace.worktreePath,
209
- status: params.status === "completed" ? "active" : "paused",
210
- currentRunLeaseId: null,
211
- });
212
- }
213
- }
214
- setIssueDesiredStage(projectId, linearIssueId, desiredStage, options) {
215
- const existing = this.issueWorkflows.getTrackedIssue(projectId, linearIssueId);
216
- const existingIssueControl = this.authoritativeLedger.getIssueControl(projectId, linearIssueId);
217
- const desiredReceiptId = this.resolveDesiredReceiptId({
218
- projectId,
219
- linearIssueId,
220
- ...(options?.desiredWebhookId !== undefined ? { desiredWebhookId: options.desiredWebhookId } : {}),
221
- ...(options?.desiredReceiptId !== undefined ? { desiredReceiptId: options.desiredReceiptId } : {}),
222
- });
223
- this.authoritativeLedger.upsertIssueControl({
224
- projectId,
225
- linearIssueId,
226
- ...(existing?.selectedWorkflowId ? { selectedWorkflowId: existing.selectedWorkflowId } : {}),
227
- ...(desiredStage !== undefined ? { desiredStage } : { desiredStage: null }),
228
- ...(desiredReceiptId !== undefined
229
- ? { desiredReceiptId }
230
- : desiredStage === undefined
231
- ? { desiredReceiptId: null }
232
- : {}),
233
- lifecycleStatus: options?.lifecycleStatus ??
234
- (desiredStage ? "queued" : existingIssueControl?.activeRunLeaseId ? (existing?.lifecycleStatus ?? "idle") : "idle"),
235
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
236
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
237
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
238
- ...(existingIssueControl?.activeRunLeaseId !== undefined ? { activeRunLeaseId: existingIssueControl.activeRunLeaseId } : {}),
239
- });
240
- }
241
- setIssueLifecycleStatus(projectId, linearIssueId, lifecycleStatus) {
242
- const existing = this.issueWorkflows.getTrackedIssue(projectId, linearIssueId);
243
- const existingIssueControl = this.authoritativeLedger.getIssueControl(projectId, linearIssueId);
244
- this.authoritativeLedger.upsertIssueControl({
245
- projectId,
246
- linearIssueId,
247
- ...(existing?.selectedWorkflowId ? { selectedWorkflowId: existing.selectedWorkflowId } : {}),
248
- lifecycleStatus,
249
- ...(existing?.desiredStage ? { desiredStage: existing.desiredStage } : {}),
250
- ...(existingIssueControl?.desiredReceiptId !== undefined ? { desiredReceiptId: existingIssueControl.desiredReceiptId } : {}),
251
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
252
- ...(existingIssueControl?.activeRunLeaseId !== undefined ? { activeRunLeaseId: existingIssueControl.activeRunLeaseId } : {}),
253
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
254
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
255
- });
256
- }
257
- setIssueStatusComment(projectId, linearIssueId, statusCommentId) {
258
- const existing = this.issueWorkflows.getTrackedIssue(projectId, linearIssueId);
259
- const existingIssueControl = this.authoritativeLedger.getIssueControl(projectId, linearIssueId);
260
- this.authoritativeLedger.upsertIssueControl({
261
- projectId,
262
- linearIssueId,
263
- ...(existing?.selectedWorkflowId ? { selectedWorkflowId: existing.selectedWorkflowId } : {}),
264
- lifecycleStatus: existing?.lifecycleStatus ?? "idle",
265
- ...(existing?.desiredStage ? { desiredStage: existing.desiredStage } : {}),
266
- ...(existingIssueControl?.desiredReceiptId !== undefined ? { desiredReceiptId: existingIssueControl.desiredReceiptId } : {}),
267
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
268
- ...(existingIssueControl?.activeRunLeaseId !== undefined ? { activeRunLeaseId: existingIssueControl.activeRunLeaseId } : {}),
269
- serviceOwnedCommentId: statusCommentId ?? null,
270
- ...(existing?.activeAgentSessionId ? { activeAgentSessionId: existing.activeAgentSessionId } : {}),
271
- });
272
- }
273
- setIssueActiveAgentSession(projectId, linearIssueId, agentSessionId) {
274
- const existing = this.issueWorkflows.getTrackedIssue(projectId, linearIssueId);
275
- const existingIssueControl = this.authoritativeLedger.getIssueControl(projectId, linearIssueId);
276
- this.authoritativeLedger.upsertIssueControl({
277
- projectId,
278
- linearIssueId,
279
- ...(existing?.selectedWorkflowId ? { selectedWorkflowId: existing.selectedWorkflowId } : {}),
280
- lifecycleStatus: existing?.lifecycleStatus ?? "idle",
281
- ...(existing?.desiredStage ? { desiredStage: existing.desiredStage } : {}),
282
- ...(existingIssueControl?.desiredReceiptId !== undefined ? { desiredReceiptId: existingIssueControl.desiredReceiptId } : {}),
283
- ...(existing?.activeWorkspaceId !== undefined ? { activeWorkspaceOwnershipId: existing.activeWorkspaceId } : {}),
284
- ...(existingIssueControl?.activeRunLeaseId !== undefined ? { activeRunLeaseId: existingIssueControl.activeRunLeaseId } : {}),
285
- ...(existing?.statusCommentId ? { serviceOwnedCommentId: existing.statusCommentId } : {}),
286
- activeAgentSessionId: agentSessionId ?? null,
287
- });
288
- }
289
- resolveDesiredReceiptId(params) {
290
- if (params.desiredReceiptId !== undefined) {
291
- return params.desiredReceiptId;
292
- }
293
- if (params.desiredWebhookId === undefined) {
294
- return undefined;
295
- }
296
- if (params.desiredWebhookId === null) {
297
- return null;
298
- }
299
- return this.ensureDesiredReceipt(params.projectId, params.linearIssueId, params.desiredWebhookId);
300
- }
301
- ensureDesiredReceipt(projectId, linearIssueId, webhookId) {
302
- const existing = this.connection
303
- .prepare("SELECT id FROM event_receipts WHERE external_id = ? ORDER BY id DESC LIMIT 1")
304
- .get(webhookId);
305
- if (existing) {
306
- return Number(existing.id);
307
- }
308
- const receipt = this.authoritativeLedger.insertEventReceipt({
309
- source: "patchrelay-desired-stage",
310
- externalId: webhookId,
311
- eventType: "desired_stage",
312
- receivedAt: isoNow(),
313
- acceptanceStatus: "accepted",
314
- projectId,
315
- linearIssueId,
316
- });
317
- this.authoritativeLedger.markEventReceiptProcessed(receipt.id, "processed");
318
- return receipt.id;
319
- }
320
- }
@@ -1,194 +0,0 @@
1
- import { isoNow } from "./shared.js";
2
- export class IssueWorkflowStore {
3
- authoritativeLedger;
4
- issueProjections;
5
- runReports;
6
- constructor(dependencies) {
7
- this.authoritativeLedger = dependencies.authoritativeLedger;
8
- this.issueProjections = dependencies.issueProjections;
9
- this.runReports = dependencies.runReports;
10
- }
11
- getTrackedIssue(projectId, linearIssueId) {
12
- const issueControl = this.authoritativeLedger.getIssueControl(projectId, linearIssueId);
13
- const projection = this.issueProjections.getIssueProjection(projectId, linearIssueId);
14
- if (!issueControl && !projection) {
15
- return undefined;
16
- }
17
- return this.buildTrackedIssue(issueControl, projection);
18
- }
19
- getTrackedIssueByKey(issueKey) {
20
- const projection = this.issueProjections.getIssueProjectionByKey(issueKey);
21
- if (!projection) {
22
- return undefined;
23
- }
24
- return this.getTrackedIssue(projection.projectId, projection.linearIssueId);
25
- }
26
- getTrackedIssueByLinearIssueId(linearIssueId) {
27
- const projection = this.issueProjections.getIssueProjectionByLinearIssueId(linearIssueId);
28
- if (!projection) {
29
- return undefined;
30
- }
31
- return this.getTrackedIssue(projection.projectId, linearIssueId);
32
- }
33
- listActiveStageRuns() {
34
- return this.authoritativeLedger
35
- .listActiveRunLeases()
36
- .filter((runLease) => runLease.status === "running")
37
- .map((runLease) => this.buildStageRun(runLease));
38
- }
39
- getWorkspace(id) {
40
- const workspaceOwnership = this.authoritativeLedger.getWorkspaceOwnership(id);
41
- return workspaceOwnership ? this.buildWorkspace(workspaceOwnership) : undefined;
42
- }
43
- getActiveWorkspaceForIssue(projectId, linearIssueId) {
44
- const workspaceOwnership = this.authoritativeLedger.getWorkspaceOwnershipForIssue(projectId, linearIssueId);
45
- return workspaceOwnership ? this.buildWorkspace(workspaceOwnership) : undefined;
46
- }
47
- getPipelineRun(id) {
48
- const runLease = this.authoritativeLedger.getRunLease(id);
49
- if (!runLease) {
50
- return undefined;
51
- }
52
- const issueControl = this.authoritativeLedger.getIssueControl(runLease.projectId, runLease.linearIssueId);
53
- const status = resolvePipelineStatus(runLease.status, issueControl?.lifecycleStatus);
54
- return {
55
- id: runLease.id,
56
- projectId: runLease.projectId,
57
- linearIssueId: runLease.linearIssueId,
58
- workspaceId: runLease.workspaceOwnershipId,
59
- status,
60
- currentStage: runLease.stage,
61
- startedAt: runLease.startedAt,
62
- ...(runLease.endedAt ? { endedAt: runLease.endedAt } : {}),
63
- };
64
- }
65
- getStageRun(id) {
66
- const runLease = this.authoritativeLedger.getRunLease(id);
67
- return runLease ? this.buildStageRun(runLease) : undefined;
68
- }
69
- getStageRunByThreadId(threadId) {
70
- const runLease = this.authoritativeLedger.getRunLeaseByThreadId(threadId);
71
- return runLease ? this.buildStageRun(runLease) : undefined;
72
- }
73
- listStageRunsForIssue(projectId, linearIssueId) {
74
- return this.authoritativeLedger.listRunLeasesForIssue(projectId, linearIssueId).map((runLease) => this.buildStageRun(runLease));
75
- }
76
- getLatestStageRunForIssue(projectId, linearIssueId) {
77
- const latestRunLease = this.getLatestRunLeaseForIssue(projectId, linearIssueId);
78
- return latestRunLease ? this.buildStageRun(latestRunLease) : undefined;
79
- }
80
- getIssueOverview(issueKey) {
81
- const issue = this.getTrackedIssueByKey(issueKey);
82
- if (!issue) {
83
- return undefined;
84
- }
85
- const issueControl = this.authoritativeLedger.getIssueControl(issue.projectId, issue.linearIssueId);
86
- const activeWorkspaceOwnershipId = issueControl?.activeWorkspaceOwnershipId;
87
- const activeRunLeaseId = issueControl?.activeRunLeaseId;
88
- const workspace = activeWorkspaceOwnershipId ? this.getWorkspace(activeWorkspaceOwnershipId) : this.getActiveWorkspaceForIssue(issue.projectId, issue.linearIssueId);
89
- const pipeline = activeRunLeaseId ? this.getPipelineRun(activeRunLeaseId) : issue.activePipelineRunId ? this.getPipelineRun(issue.activePipelineRunId) : undefined;
90
- const activeStageRun = activeRunLeaseId === undefined ? undefined : this.getStageRun(activeRunLeaseId);
91
- return {
92
- issue,
93
- ...(workspace ? { workspace } : {}),
94
- ...(pipeline ? { pipeline } : {}),
95
- ...(activeStageRun ? { activeStageRun } : {}),
96
- };
97
- }
98
- buildTrackedIssue(issueControl, projection) {
99
- const projectId = issueControl?.projectId ?? projection?.projectId;
100
- const linearIssueId = issueControl?.linearIssueId ?? projection?.linearIssueId;
101
- if (!projectId || !linearIssueId) {
102
- throw new Error("Cannot synthesize tracked issue without an issue identity");
103
- }
104
- const latestRun = this.getLatestRunLeaseForIssue(projectId, linearIssueId);
105
- const activeRun = issueControl?.activeRunLeaseId ? this.getStageRun(issueControl.activeRunLeaseId) : undefined;
106
- return {
107
- id: issueControl?.id ?? projection?.id ?? 0,
108
- projectId,
109
- linearIssueId,
110
- ...(issueControl?.selectedWorkflowId ? { selectedWorkflowId: issueControl.selectedWorkflowId } : {}),
111
- ...(projection?.issueKey ? { issueKey: projection.issueKey } : {}),
112
- ...(projection?.title ? { title: projection.title } : {}),
113
- ...(projection?.issueUrl ? { issueUrl: projection.issueUrl } : {}),
114
- ...(projection?.currentLinearState ? { currentLinearState: projection.currentLinearState } : {}),
115
- ...(issueControl?.desiredStage ? { desiredStage: issueControl.desiredStage } : {}),
116
- ...(() => {
117
- if (!issueControl?.desiredReceiptId) {
118
- return {};
119
- }
120
- const receipt = this.authoritativeLedger.getEventReceipt(issueControl.desiredReceiptId);
121
- return receipt?.externalId ? { desiredWebhookId: receipt.externalId } : {};
122
- })(),
123
- ...(issueControl?.activeWorkspaceOwnershipId !== undefined ? { activeWorkspaceId: issueControl.activeWorkspaceOwnershipId } : {}),
124
- ...(latestRun ? { activePipelineRunId: latestRun.id } : {}),
125
- ...(issueControl?.activeRunLeaseId !== undefined ? { activeStageRunId: issueControl.activeRunLeaseId } : {}),
126
- ...(activeRun?.threadId ? { latestThreadId: activeRun.threadId } : latestRun?.threadId ? { latestThreadId: latestRun.threadId } : {}),
127
- ...(issueControl?.serviceOwnedCommentId ? { statusCommentId: issueControl.serviceOwnedCommentId } : {}),
128
- ...(issueControl?.activeAgentSessionId ? { activeAgentSessionId: issueControl.activeAgentSessionId } : {}),
129
- lifecycleStatus: issueControl?.lifecycleStatus ?? "idle",
130
- ...(projection?.lastWebhookAt ? { lastWebhookAt: projection.lastWebhookAt } : {}),
131
- updatedAt: issueControl?.updatedAt ?? projection?.updatedAt ?? isoNow(),
132
- };
133
- }
134
- buildWorkspace(workspaceOwnership) {
135
- const stageRuns = this.listStageRunsForIssue(workspaceOwnership.projectId, workspaceOwnership.linearIssueId);
136
- const latestStageRun = stageRuns.findLast((stageRun) => stageRun.status !== "running") ?? stageRuns.at(-1);
137
- return {
138
- id: workspaceOwnership.id,
139
- projectId: workspaceOwnership.projectId,
140
- linearIssueId: workspaceOwnership.linearIssueId,
141
- branchName: workspaceOwnership.branchName,
142
- worktreePath: workspaceOwnership.worktreePath,
143
- status: workspaceOwnership.status === "released" ? "closed" : workspaceOwnership.status,
144
- ...(latestStageRun ? { lastStage: latestStageRun.stage } : {}),
145
- ...(latestStageRun?.threadId ? { lastThreadId: latestStageRun.threadId } : {}),
146
- createdAt: workspaceOwnership.createdAt,
147
- updatedAt: workspaceOwnership.updatedAt,
148
- };
149
- }
150
- buildStageRun(runLease) {
151
- const report = this.runReports.getRunReport(runLease.id);
152
- const triggerWebhookId = runLease.triggerReceiptId
153
- ? this.authoritativeLedger.getEventReceipt(runLease.triggerReceiptId)?.externalId ?? `run-lease:${runLease.id}`
154
- : `run-lease:${runLease.id}`;
155
- return {
156
- id: runLease.id,
157
- pipelineRunId: runLease.id,
158
- projectId: runLease.projectId,
159
- linearIssueId: runLease.linearIssueId,
160
- workspaceId: runLease.workspaceOwnershipId,
161
- stage: runLease.stage,
162
- status: runLease.status === "failed"
163
- ? "failed"
164
- : runLease.status === "completed" || runLease.status === "released" || runLease.status === "paused"
165
- ? "completed"
166
- : "running",
167
- triggerWebhookId,
168
- workflowFile: runLease.workflowFile,
169
- promptText: runLease.promptText,
170
- ...(runLease.threadId ? { threadId: runLease.threadId } : {}),
171
- ...(runLease.parentThreadId ? { parentThreadId: runLease.parentThreadId } : {}),
172
- ...(runLease.turnId ? { turnId: runLease.turnId } : {}),
173
- ...(report?.summaryJson ? { summaryJson: report.summaryJson } : {}),
174
- ...(report?.reportJson ? { reportJson: report.reportJson } : {}),
175
- startedAt: runLease.startedAt,
176
- ...(runLease.endedAt ? { endedAt: runLease.endedAt } : {}),
177
- };
178
- }
179
- getLatestRunLeaseForIssue(projectId, linearIssueId) {
180
- return this.authoritativeLedger.listRunLeasesForIssue(projectId, linearIssueId).at(-1);
181
- }
182
- }
183
- function resolvePipelineStatus(runStatus, lifecycleStatus) {
184
- if (lifecycleStatus === "paused") {
185
- return "paused";
186
- }
187
- if (runStatus === "failed" || lifecycleStatus === "failed") {
188
- return "failed";
189
- }
190
- if (runStatus === "completed" || runStatus === "released" || lifecycleStatus === "completed") {
191
- return "completed";
192
- }
193
- return "active";
194
- }
@@ -1,33 +0,0 @@
1
- import { isoNow } from "./shared.js";
2
- export class RunReportStore {
3
- connection;
4
- constructor(connection) {
5
- this.connection = connection;
6
- }
7
- saveRunReport(params) {
8
- const now = isoNow();
9
- this.connection
10
- .prepare(`
11
- INSERT INTO run_reports (run_lease_id, summary_json, report_json, created_at, updated_at)
12
- VALUES (?, ?, ?, ?, ?)
13
- ON CONFLICT(run_lease_id) DO UPDATE SET
14
- summary_json = excluded.summary_json,
15
- report_json = excluded.report_json,
16
- updated_at = excluded.updated_at
17
- `)
18
- .run(params.runLeaseId, params.summaryJson ?? null, params.reportJson ?? null, now, now);
19
- }
20
- getRunReport(runLeaseId) {
21
- const row = this.connection.prepare("SELECT * FROM run_reports WHERE run_lease_id = ?").get(runLeaseId);
22
- return row ? mapRunReport(row) : undefined;
23
- }
24
- }
25
- function mapRunReport(row) {
26
- return {
27
- runLeaseId: Number(row.run_lease_id),
28
- ...(row.summary_json === null ? {} : { summaryJson: String(row.summary_json) }),
29
- ...(row.report_json === null ? {} : { reportJson: String(row.report_json) }),
30
- createdAt: String(row.created_at),
31
- updatedAt: String(row.updated_at),
32
- };
33
- }
@@ -1,33 +0,0 @@
1
- import { isoNow } from "./shared.js";
2
- export class StageEventStore {
3
- connection;
4
- constructor(connection) {
5
- this.connection = connection;
6
- }
7
- saveThreadEvent(params) {
8
- const result = this.connection
9
- .prepare(`
10
- INSERT INTO run_thread_events (run_lease_id, thread_id, turn_id, method, event_json, created_at)
11
- VALUES (?, ?, ?, ?, ?, ?)
12
- `)
13
- .run(params.stageRunId, params.threadId, params.turnId ?? null, params.method, params.eventJson, isoNow());
14
- return Number(result.lastInsertRowid);
15
- }
16
- listThreadEvents(stageRunId) {
17
- const rows = this.connection
18
- .prepare("SELECT * FROM run_thread_events WHERE run_lease_id = ? ORDER BY id")
19
- .all(stageRunId);
20
- return rows.map((row) => mapThreadEvent(row));
21
- }
22
- }
23
- function mapThreadEvent(row) {
24
- return {
25
- id: Number(row.id),
26
- stageRunId: Number(row.run_lease_id),
27
- threadId: String(row.thread_id),
28
- ...(row.turn_id === null ? {} : { turnId: String(row.turn_id) }),
29
- method: String(row.method),
30
- eventJson: String(row.event_json),
31
- createdAt: String(row.created_at),
32
- };
33
- }
@@ -1,59 +0,0 @@
1
- export class WebhookEventStore {
2
- connection;
3
- constructor(connection) {
4
- this.connection = connection;
5
- }
6
- insertWebhookEvent(params) {
7
- const existing = this.connection.prepare("SELECT id FROM webhook_events WHERE webhook_id = ?").get(params.webhookId);
8
- if (existing) {
9
- this.connection.prepare("UPDATE webhook_events SET dedupe_status = 'duplicate' WHERE id = ?").run(existing.id);
10
- return { id: existing.id, inserted: false };
11
- }
12
- const result = this.connection
13
- .prepare(`
14
- INSERT INTO webhook_events (
15
- webhook_id, received_at, event_type, issue_id, project_id, headers_json, payload_json, signature_valid, dedupe_status
16
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
17
- `)
18
- .run(params.webhookId, params.receivedAt, params.eventType, params.issueId ?? null, params.projectId ?? null, params.headersJson, params.payloadJson, params.signatureValid ? 1 : 0, params.dedupeStatus);
19
- return { id: Number(result.lastInsertRowid), inserted: true };
20
- }
21
- markWebhookProcessed(id, status) {
22
- this.connection.prepare("UPDATE webhook_events SET processing_status = ? WHERE id = ?").run(status, id);
23
- }
24
- assignWebhookProject(id, projectId) {
25
- this.connection.prepare("UPDATE webhook_events SET project_id = ? WHERE id = ?").run(projectId, id);
26
- }
27
- getWebhookEvent(id) {
28
- const row = this.connection.prepare("SELECT * FROM webhook_events WHERE id = ?").get(id);
29
- return row ? mapWebhookEvent(row) : undefined;
30
- }
31
- listWebhookEventsForIssueSince(issueId, receivedAfter) {
32
- const rows = this.connection
33
- .prepare(`
34
- SELECT *
35
- FROM webhook_events
36
- WHERE issue_id = ?
37
- AND dedupe_status = 'accepted'
38
- AND received_at > ?
39
- ORDER BY received_at ASC, id ASC
40
- `)
41
- .all(issueId, receivedAfter);
42
- return rows.map((row) => mapWebhookEvent(row));
43
- }
44
- }
45
- function mapWebhookEvent(row) {
46
- return {
47
- id: Number(row.id),
48
- webhookId: String(row.webhook_id),
49
- receivedAt: String(row.received_at),
50
- eventType: String(row.event_type),
51
- ...(row.issue_id === null ? {} : { issueId: String(row.issue_id) }),
52
- ...(row.project_id === null ? {} : { projectId: String(row.project_id) }),
53
- headersJson: String(row.headers_json),
54
- payloadJson: String(row.payload_json),
55
- signatureValid: Number(row.signature_valid) === 1,
56
- dedupeStatus: row.dedupe_status,
57
- processingStatus: row.processing_status,
58
- };
59
- }
package/dist/db-ports.js DELETED
@@ -1,5 +0,0 @@
1
- export * from "./webhook-event-ports.js";
2
- export * from "./installation-ports.js";
3
- export * from "./ledger-ports.js";
4
- export * from "./stage-event-ports.js";
5
- export * from "./workflow-ports.js";
@@ -1 +0,0 @@
1
- export {};