patchrelay 0.8.8 → 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 +21 -54
  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,536 +0,0 @@
1
- import { isoNow } from "./shared.js";
2
- export class AuthoritativeLedgerStore {
3
- connection;
4
- constructor(connection) {
5
- this.connection = connection;
6
- }
7
- insertEventReceipt(params) {
8
- const inserted = this.connection
9
- .prepare(`
10
- INSERT OR IGNORE INTO event_receipts (
11
- source, external_id, event_type, received_at, acceptance_status, processing_status,
12
- project_id, linear_issue_id, headers_json, payload_json
13
- ) VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?)
14
- `)
15
- .run(params.source, params.externalId, params.eventType, params.receivedAt, params.acceptanceStatus, params.projectId ?? null, params.linearIssueId ?? null, params.headersJson ?? null, params.payloadJson ?? null);
16
- const row = this.connection
17
- .prepare("SELECT * FROM event_receipts WHERE source = ? AND external_id = ?")
18
- .get(params.source, params.externalId);
19
- if (!row) {
20
- throw new Error(`Failed to load event receipt for ${params.source}:${params.externalId}`);
21
- }
22
- if (!inserted.changes) {
23
- this.connection
24
- .prepare("UPDATE event_receipts SET acceptance_status = 'duplicate' WHERE id = ? AND acceptance_status = 'accepted'")
25
- .run(row.id);
26
- }
27
- return { id: Number(row.id), inserted: Boolean(inserted.changes) };
28
- }
29
- markEventReceiptProcessed(id, status) {
30
- this.connection.prepare("UPDATE event_receipts SET processing_status = ? WHERE id = ?").run(status, id);
31
- }
32
- assignEventReceiptContext(id, params) {
33
- this.connection
34
- .prepare(`
35
- UPDATE event_receipts
36
- SET project_id = COALESCE(?, project_id),
37
- linear_issue_id = COALESCE(?, linear_issue_id)
38
- WHERE id = ?
39
- `)
40
- .run(params.projectId ?? null, params.linearIssueId ?? null, id);
41
- }
42
- getEventReceipt(id) {
43
- const row = this.connection.prepare("SELECT * FROM event_receipts WHERE id = ?").get(id);
44
- return row ? mapEventReceipt(row) : undefined;
45
- }
46
- getEventReceiptBySourceExternalId(source, externalId) {
47
- const row = this.connection
48
- .prepare("SELECT * FROM event_receipts WHERE source = ? AND external_id = ?")
49
- .get(source, externalId);
50
- return row ? mapEventReceipt(row) : undefined;
51
- }
52
- upsertIssueControl(params) {
53
- const now = isoNow();
54
- this.connection
55
- .prepare(`
56
- INSERT INTO issue_control (
57
- project_id, linear_issue_id, selected_workflow_id, desired_stage, desired_receipt_id, active_workspace_ownership_id,
58
- active_run_lease_id, service_owned_comment_id, active_agent_session_id, lifecycle_status, updated_at
59
- ) VALUES (
60
- @projectId, @linearIssueId, @selectedWorkflowId, @desiredStage, @desiredReceiptId, @activeWorkspaceOwnershipId,
61
- @activeRunLeaseId, @serviceOwnedCommentId, @activeAgentSessionId, @lifecycleStatus, @updatedAt
62
- )
63
- ON CONFLICT(project_id, linear_issue_id) DO UPDATE SET
64
- selected_workflow_id = CASE WHEN @setSelectedWorkflowId = 1 THEN @selectedWorkflowId ELSE issue_control.selected_workflow_id END,
65
- desired_stage = CASE WHEN @setDesiredStage = 1 THEN @desiredStage ELSE issue_control.desired_stage END,
66
- desired_receipt_id = CASE WHEN @setDesiredReceiptId = 1 THEN @desiredReceiptId ELSE issue_control.desired_receipt_id END,
67
- active_workspace_ownership_id = CASE WHEN @setActiveWorkspaceOwnershipId = 1 THEN @activeWorkspaceOwnershipId ELSE issue_control.active_workspace_ownership_id END,
68
- active_run_lease_id = CASE WHEN @setActiveRunLeaseId = 1 THEN @activeRunLeaseId ELSE issue_control.active_run_lease_id END,
69
- service_owned_comment_id = CASE WHEN @setServiceOwnedCommentId = 1 THEN @serviceOwnedCommentId ELSE issue_control.service_owned_comment_id END,
70
- active_agent_session_id = CASE WHEN @setActiveAgentSessionId = 1 THEN @activeAgentSessionId ELSE issue_control.active_agent_session_id END,
71
- lifecycle_status = @lifecycleStatus,
72
- updated_at = @updatedAt
73
- `)
74
- .run({
75
- projectId: params.projectId,
76
- linearIssueId: params.linearIssueId,
77
- selectedWorkflowId: params.selectedWorkflowId ?? null,
78
- desiredStage: params.desiredStage ?? null,
79
- desiredReceiptId: params.desiredReceiptId ?? null,
80
- activeWorkspaceOwnershipId: params.activeWorkspaceOwnershipId ?? null,
81
- activeRunLeaseId: params.activeRunLeaseId ?? null,
82
- serviceOwnedCommentId: params.serviceOwnedCommentId ?? null,
83
- activeAgentSessionId: params.activeAgentSessionId ?? null,
84
- lifecycleStatus: params.lifecycleStatus,
85
- updatedAt: now,
86
- setSelectedWorkflowId: Number("selectedWorkflowId" in params),
87
- setDesiredStage: Number("desiredStage" in params),
88
- setDesiredReceiptId: Number("desiredReceiptId" in params),
89
- setActiveWorkspaceOwnershipId: Number("activeWorkspaceOwnershipId" in params),
90
- setActiveRunLeaseId: Number("activeRunLeaseId" in params),
91
- setServiceOwnedCommentId: Number("serviceOwnedCommentId" in params),
92
- setActiveAgentSessionId: Number("activeAgentSessionId" in params),
93
- });
94
- return this.getIssueControl(params.projectId, params.linearIssueId);
95
- }
96
- getIssueControl(projectId, linearIssueId) {
97
- const row = this.connection
98
- .prepare("SELECT * FROM issue_control WHERE project_id = ? AND linear_issue_id = ?")
99
- .get(projectId, linearIssueId);
100
- return row ? mapIssueControl(row) : undefined;
101
- }
102
- listIssueControlsReadyForLaunch() {
103
- const rows = this.connection
104
- .prepare("SELECT * FROM issue_control WHERE desired_stage IS NOT NULL AND active_run_lease_id IS NULL ORDER BY id")
105
- .all();
106
- return rows.map((row) => mapIssueControl(row));
107
- }
108
- upsertWorkspaceOwnership(params) {
109
- const now = isoNow();
110
- this.connection
111
- .prepare(`
112
- INSERT INTO workspace_ownership (
113
- project_id, linear_issue_id, branch_name, worktree_path, status, current_run_lease_id, created_at, updated_at
114
- ) VALUES (@projectId, @linearIssueId, @branchName, @worktreePath, @status, @currentRunLeaseId, @createdAt, @updatedAt)
115
- ON CONFLICT(project_id, linear_issue_id) DO UPDATE SET
116
- branch_name = @branchName,
117
- worktree_path = @worktreePath,
118
- status = @status,
119
- current_run_lease_id = CASE WHEN @setCurrentRunLeaseId = 1 THEN @currentRunLeaseId ELSE workspace_ownership.current_run_lease_id END,
120
- updated_at = @updatedAt
121
- `)
122
- .run({
123
- projectId: params.projectId,
124
- linearIssueId: params.linearIssueId,
125
- branchName: params.branchName,
126
- worktreePath: params.worktreePath,
127
- status: params.status,
128
- currentRunLeaseId: params.currentRunLeaseId ?? null,
129
- createdAt: now,
130
- updatedAt: now,
131
- setCurrentRunLeaseId: Number("currentRunLeaseId" in params),
132
- });
133
- return this.getWorkspaceOwnershipForIssue(params.projectId, params.linearIssueId);
134
- }
135
- getWorkspaceOwnership(id) {
136
- const row = this.connection
137
- .prepare("SELECT * FROM workspace_ownership WHERE id = ?")
138
- .get(id);
139
- return row ? mapWorkspaceOwnership(row) : undefined;
140
- }
141
- getWorkspaceOwnershipForIssue(projectId, linearIssueId) {
142
- const row = this.connection
143
- .prepare("SELECT * FROM workspace_ownership WHERE project_id = ? AND linear_issue_id = ?")
144
- .get(projectId, linearIssueId);
145
- return row ? mapWorkspaceOwnership(row) : undefined;
146
- }
147
- upsertIssueSession(params) {
148
- const now = isoNow();
149
- this.connection
150
- .prepare(`
151
- INSERT INTO issue_sessions (
152
- project_id, linear_issue_id, workspace_ownership_id, run_lease_id, thread_id, parent_thread_id,
153
- source, linked_agent_session_id, created_at, updated_at, last_opened_at
154
- ) VALUES (
155
- @projectId, @linearIssueId, @workspaceOwnershipId, @runLeaseId, @threadId, @parentThreadId,
156
- @source, @linkedAgentSessionId, @createdAt, @updatedAt, NULL
157
- )
158
- ON CONFLICT(thread_id) DO UPDATE SET
159
- project_id = @projectId,
160
- linear_issue_id = @linearIssueId,
161
- workspace_ownership_id = @workspaceOwnershipId,
162
- run_lease_id = CASE WHEN @setRunLeaseId = 1 THEN @runLeaseId ELSE issue_sessions.run_lease_id END,
163
- parent_thread_id = CASE WHEN @setParentThreadId = 1 THEN @parentThreadId ELSE issue_sessions.parent_thread_id END,
164
- source = @source,
165
- linked_agent_session_id = CASE
166
- WHEN @setLinkedAgentSessionId = 1 THEN @linkedAgentSessionId
167
- ELSE issue_sessions.linked_agent_session_id
168
- END,
169
- updated_at = @updatedAt
170
- `)
171
- .run({
172
- projectId: params.projectId,
173
- linearIssueId: params.linearIssueId,
174
- workspaceOwnershipId: params.workspaceOwnershipId,
175
- runLeaseId: params.runLeaseId ?? null,
176
- threadId: params.threadId,
177
- parentThreadId: params.parentThreadId ?? null,
178
- source: params.source,
179
- linkedAgentSessionId: params.linkedAgentSessionId ?? null,
180
- createdAt: now,
181
- updatedAt: now,
182
- setRunLeaseId: Number("runLeaseId" in params),
183
- setParentThreadId: Number("parentThreadId" in params),
184
- setLinkedAgentSessionId: Number("linkedAgentSessionId" in params),
185
- });
186
- return this.getIssueSessionByThreadId(params.threadId);
187
- }
188
- getIssueSessionByThreadId(threadId) {
189
- const row = this.connection
190
- .prepare("SELECT * FROM issue_sessions WHERE thread_id = ?")
191
- .get(threadId);
192
- return row ? mapIssueSession(row) : undefined;
193
- }
194
- listIssueSessionsForIssue(projectId, linearIssueId) {
195
- const rows = this.connection
196
- .prepare(`
197
- SELECT * FROM issue_sessions
198
- WHERE project_id = ? AND linear_issue_id = ?
199
- ORDER BY
200
- CASE WHEN last_opened_at IS NULL THEN 1 ELSE 0 END,
201
- last_opened_at DESC,
202
- id DESC
203
- `)
204
- .all(projectId, linearIssueId);
205
- return rows.map((row) => mapIssueSession(row));
206
- }
207
- touchIssueSession(threadId) {
208
- const now = isoNow();
209
- const result = this.connection
210
- .prepare(`
211
- UPDATE issue_sessions
212
- SET updated_at = @updatedAt,
213
- last_opened_at = @lastOpenedAt
214
- WHERE thread_id = @threadId
215
- `)
216
- .run({
217
- threadId,
218
- updatedAt: now,
219
- lastOpenedAt: now,
220
- });
221
- if (result.changes < 1) {
222
- return undefined;
223
- }
224
- return this.getIssueSessionByThreadId(threadId);
225
- }
226
- createRunLease(params) {
227
- const result = this.connection
228
- .prepare(`
229
- INSERT INTO run_leases (
230
- issue_control_id, project_id, linear_issue_id, workspace_ownership_id, stage, status, trigger_receipt_id, workflow_file, prompt_text, started_at
231
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
232
- `)
233
- .run(params.issueControlId, params.projectId, params.linearIssueId, params.workspaceOwnershipId, params.stage, params.status ?? "queued", params.triggerReceiptId ?? null, params.workflowFile ?? "", params.promptText ?? "", isoNow());
234
- return this.getRunLease(Number(result.lastInsertRowid));
235
- }
236
- getRunLease(id) {
237
- const row = this.connection.prepare("SELECT * FROM run_leases WHERE id = ?").get(id);
238
- return row ? mapRunLease(row) : undefined;
239
- }
240
- getRunLeaseByThreadId(threadId) {
241
- const row = this.connection
242
- .prepare("SELECT * FROM run_leases WHERE thread_id = ? ORDER BY id DESC LIMIT 1")
243
- .get(threadId);
244
- return row ? mapRunLease(row) : undefined;
245
- }
246
- listActiveRunLeases() {
247
- const rows = this.connection
248
- .prepare("SELECT * FROM run_leases WHERE status IN ('queued', 'running', 'paused') ORDER BY id")
249
- .all();
250
- return rows.map((row) => mapRunLease(row));
251
- }
252
- listRunLeasesForIssue(projectId, linearIssueId) {
253
- const rows = this.connection
254
- .prepare("SELECT * FROM run_leases WHERE project_id = ? AND linear_issue_id = ? ORDER BY id")
255
- .all(projectId, linearIssueId);
256
- return rows.map((row) => mapRunLease(row));
257
- }
258
- updateRunLeaseThread(params) {
259
- this.connection
260
- .prepare(`
261
- UPDATE run_leases
262
- SET thread_id = CASE WHEN @setThreadId = 1 THEN @threadId ELSE thread_id END,
263
- parent_thread_id = CASE WHEN @setParentThreadId = 1 THEN @parentThreadId ELSE parent_thread_id END,
264
- turn_id = CASE WHEN @setTurnId = 1 THEN @turnId ELSE turn_id END
265
- WHERE id = @runLeaseId
266
- `)
267
- .run({
268
- runLeaseId: params.runLeaseId,
269
- threadId: params.threadId ?? null,
270
- parentThreadId: params.parentThreadId ?? null,
271
- turnId: params.turnId ?? null,
272
- setThreadId: Number("threadId" in params),
273
- setParentThreadId: Number("parentThreadId" in params),
274
- setTurnId: Number("turnId" in params),
275
- });
276
- }
277
- finishRunLease(params) {
278
- const now = isoNow();
279
- this.connection
280
- .prepare(`
281
- UPDATE run_leases
282
- SET status = @status,
283
- thread_id = CASE WHEN @setThreadId = 1 THEN @threadId ELSE thread_id END,
284
- turn_id = CASE WHEN @setTurnId = 1 THEN @turnId ELSE turn_id END,
285
- failure_reason = CASE WHEN @setFailureReason = 1 THEN @failureReason ELSE failure_reason END,
286
- ended_at = CASE WHEN @status IN ('completed', 'failed', 'released') THEN @endedAt ELSE ended_at END
287
- WHERE id = @runLeaseId
288
- `)
289
- .run({
290
- runLeaseId: params.runLeaseId,
291
- status: params.status,
292
- threadId: params.threadId ?? null,
293
- turnId: params.turnId ?? null,
294
- failureReason: params.failureReason ?? null,
295
- endedAt: now,
296
- setThreadId: Number("threadId" in params),
297
- setTurnId: Number("turnId" in params),
298
- setFailureReason: Number("failureReason" in params),
299
- });
300
- }
301
- enqueueObligation(params) {
302
- const now = isoNow();
303
- const result = this.connection
304
- .prepare(`
305
- INSERT OR IGNORE INTO obligations (
306
- project_id, linear_issue_id, kind, status, source, payload_json, run_lease_id, thread_id, turn_id, dedupe_key, created_at, updated_at
307
- ) VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?, ?, ?)
308
- `)
309
- .run(params.projectId, params.linearIssueId, params.kind, params.source, params.payloadJson, params.runLeaseId ?? null, params.threadId ?? null, params.turnId ?? null, params.dedupeKey ?? null, now, now);
310
- if (result.changes) {
311
- return this.getObligation(Number(result.lastInsertRowid));
312
- }
313
- if (params.dedupeKey) {
314
- const existing = params.runLeaseId === undefined || params.runLeaseId === null
315
- ? undefined
316
- : this.getObligationByDedupeKey({
317
- runLeaseId: params.runLeaseId,
318
- kind: params.kind,
319
- dedupeKey: params.dedupeKey,
320
- });
321
- if (existing) {
322
- return existing;
323
- }
324
- }
325
- throw new Error(`Failed to persist obligation for ${params.projectId}:${params.linearIssueId}:${params.kind}`);
326
- }
327
- listPendingObligations(params) {
328
- const statuses = params?.includeInProgress ? ["pending", "in_progress"] : ["pending"];
329
- const clauses = [`status IN (${statuses.map(() => "?").join(", ")})`];
330
- const values = [...statuses];
331
- if (params?.runLeaseId !== undefined) {
332
- clauses.push("run_lease_id = ?");
333
- values.push(params.runLeaseId);
334
- }
335
- if (params?.kind) {
336
- clauses.push("kind = ?");
337
- values.push(params.kind);
338
- }
339
- const rows = this.connection
340
- .prepare(`SELECT * FROM obligations WHERE ${clauses.join(" AND ")} ORDER BY id`)
341
- .all(...values);
342
- return rows.map((row) => mapObligation(row));
343
- }
344
- claimPendingObligation(id, params) {
345
- const result = this.connection
346
- .prepare(`
347
- UPDATE obligations
348
- SET status = 'in_progress',
349
- run_lease_id = CASE WHEN @setRunLeaseId = 1 THEN @runLeaseId ELSE run_lease_id END,
350
- thread_id = CASE WHEN @setThreadId = 1 THEN @threadId ELSE thread_id END,
351
- turn_id = CASE WHEN @setTurnId = 1 THEN @turnId ELSE turn_id END,
352
- last_error = NULL,
353
- updated_at = @updatedAt
354
- WHERE id = @id
355
- AND status = 'pending'
356
- `)
357
- .run({
358
- id,
359
- runLeaseId: params?.runLeaseId ?? null,
360
- threadId: params?.threadId ?? null,
361
- turnId: params?.turnId ?? null,
362
- updatedAt: isoNow(),
363
- setRunLeaseId: Number("runLeaseId" in (params ?? {})),
364
- setThreadId: Number("threadId" in (params ?? {})),
365
- setTurnId: Number("turnId" in (params ?? {})),
366
- });
367
- return result.changes > 0;
368
- }
369
- updateObligationPayloadJson(id, payloadJson) {
370
- this.connection
371
- .prepare(`
372
- UPDATE obligations
373
- SET payload_json = ?,
374
- updated_at = ?
375
- WHERE id = ?
376
- `)
377
- .run(payloadJson, isoNow(), id);
378
- }
379
- updateObligationRouting(id, params) {
380
- this.connection
381
- .prepare(`
382
- UPDATE obligations
383
- SET run_lease_id = CASE WHEN @setRunLeaseId = 1 THEN @runLeaseId ELSE run_lease_id END,
384
- thread_id = CASE WHEN @setThreadId = 1 THEN @threadId ELSE thread_id END,
385
- turn_id = CASE WHEN @setTurnId = 1 THEN @turnId ELSE turn_id END,
386
- updated_at = @updatedAt
387
- WHERE id = @id
388
- `)
389
- .run({
390
- id,
391
- runLeaseId: params.runLeaseId ?? null,
392
- threadId: params.threadId ?? null,
393
- turnId: params.turnId ?? null,
394
- updatedAt: isoNow(),
395
- setRunLeaseId: Number("runLeaseId" in params),
396
- setThreadId: Number("threadId" in params),
397
- setTurnId: Number("turnId" in params),
398
- });
399
- }
400
- markObligationStatus(id, status, lastError) {
401
- const now = isoNow();
402
- this.connection
403
- .prepare(`
404
- UPDATE obligations
405
- SET status = @status,
406
- last_error = CASE WHEN @setLastError = 1 THEN @lastError ELSE last_error END,
407
- updated_at = @updatedAt,
408
- completed_at = CASE WHEN @status = 'completed' THEN @completedAt ELSE completed_at END
409
- WHERE id = @id
410
- `)
411
- .run({
412
- id,
413
- status,
414
- lastError: lastError ?? null,
415
- updatedAt: now,
416
- completedAt: now,
417
- setLastError: Number(lastError !== undefined),
418
- });
419
- }
420
- getObligation(id) {
421
- const row = this.connection.prepare("SELECT * FROM obligations WHERE id = ?").get(id);
422
- return row ? mapObligation(row) : undefined;
423
- }
424
- getObligationByDedupeKey(params) {
425
- const row = this.connection
426
- .prepare(`
427
- SELECT * FROM obligations
428
- WHERE run_lease_id IS ?
429
- AND kind = ?
430
- AND dedupe_key = ?
431
- ORDER BY id DESC
432
- LIMIT 1
433
- `)
434
- .get(params.runLeaseId, params.kind, params.dedupeKey);
435
- return row ? mapObligation(row) : undefined;
436
- }
437
- }
438
- function mapEventReceipt(row) {
439
- return {
440
- id: Number(row.id),
441
- source: String(row.source),
442
- externalId: String(row.external_id),
443
- eventType: String(row.event_type),
444
- receivedAt: String(row.received_at),
445
- acceptanceStatus: row.acceptance_status,
446
- processingStatus: row.processing_status,
447
- ...(row.project_id === null ? {} : { projectId: String(row.project_id) }),
448
- ...(row.linear_issue_id === null ? {} : { linearIssueId: String(row.linear_issue_id) }),
449
- ...(row.headers_json === null ? {} : { headersJson: String(row.headers_json) }),
450
- ...(row.payload_json === null ? {} : { payloadJson: String(row.payload_json) }),
451
- };
452
- }
453
- function mapIssueControl(row) {
454
- return {
455
- id: Number(row.id),
456
- projectId: String(row.project_id),
457
- linearIssueId: String(row.linear_issue_id),
458
- ...(row.selected_workflow_id === null ? {} : { selectedWorkflowId: String(row.selected_workflow_id) }),
459
- ...(row.desired_stage === null ? {} : { desiredStage: row.desired_stage }),
460
- ...(row.desired_receipt_id === null ? {} : { desiredReceiptId: Number(row.desired_receipt_id) }),
461
- ...(row.active_run_lease_id === null ? {} : { activeRunLeaseId: Number(row.active_run_lease_id) }),
462
- ...(row.active_workspace_ownership_id === null ? {} : { activeWorkspaceOwnershipId: Number(row.active_workspace_ownership_id) }),
463
- ...(row.service_owned_comment_id === null ? {} : { serviceOwnedCommentId: String(row.service_owned_comment_id) }),
464
- ...(row.active_agent_session_id === null ? {} : { activeAgentSessionId: String(row.active_agent_session_id) }),
465
- lifecycleStatus: row.lifecycle_status,
466
- updatedAt: String(row.updated_at),
467
- };
468
- }
469
- function mapWorkspaceOwnership(row) {
470
- return {
471
- id: Number(row.id),
472
- projectId: String(row.project_id),
473
- linearIssueId: String(row.linear_issue_id),
474
- branchName: String(row.branch_name),
475
- worktreePath: String(row.worktree_path),
476
- status: row.status,
477
- ...(row.current_run_lease_id === null ? {} : { currentRunLeaseId: Number(row.current_run_lease_id) }),
478
- createdAt: String(row.created_at),
479
- updatedAt: String(row.updated_at),
480
- };
481
- }
482
- function mapRunLease(row) {
483
- return {
484
- id: Number(row.id),
485
- issueControlId: Number(row.issue_control_id),
486
- projectId: String(row.project_id),
487
- linearIssueId: String(row.linear_issue_id),
488
- workspaceOwnershipId: Number(row.workspace_ownership_id),
489
- stage: row.stage,
490
- status: row.status,
491
- ...(row.trigger_receipt_id === null ? {} : { triggerReceiptId: Number(row.trigger_receipt_id) }),
492
- workflowFile: String(row.workflow_file ?? ""),
493
- promptText: String(row.prompt_text ?? ""),
494
- ...(row.thread_id === null ? {} : { threadId: String(row.thread_id) }),
495
- ...(row.parent_thread_id === null ? {} : { parentThreadId: String(row.parent_thread_id) }),
496
- ...(row.turn_id === null ? {} : { turnId: String(row.turn_id) }),
497
- startedAt: String(row.started_at),
498
- ...(row.ended_at === null ? {} : { endedAt: String(row.ended_at) }),
499
- ...(row.failure_reason === null ? {} : { failureReason: String(row.failure_reason) }),
500
- };
501
- }
502
- function mapIssueSession(row) {
503
- return {
504
- id: Number(row.id),
505
- projectId: String(row.project_id),
506
- linearIssueId: String(row.linear_issue_id),
507
- workspaceOwnershipId: Number(row.workspace_ownership_id),
508
- threadId: String(row.thread_id),
509
- source: row.source,
510
- ...(row.run_lease_id === null ? {} : { runLeaseId: Number(row.run_lease_id) }),
511
- ...(row.parent_thread_id === null ? {} : { parentThreadId: String(row.parent_thread_id) }),
512
- ...(row.linked_agent_session_id === null ? {} : { linkedAgentSessionId: String(row.linked_agent_session_id) }),
513
- createdAt: String(row.created_at),
514
- updatedAt: String(row.updated_at),
515
- ...(row.last_opened_at === null ? {} : { lastOpenedAt: String(row.last_opened_at) }),
516
- };
517
- }
518
- function mapObligation(row) {
519
- return {
520
- id: Number(row.id),
521
- projectId: String(row.project_id),
522
- linearIssueId: String(row.linear_issue_id),
523
- kind: String(row.kind),
524
- status: row.status,
525
- source: String(row.source),
526
- payloadJson: String(row.payload_json),
527
- ...(row.run_lease_id === null ? {} : { runLeaseId: Number(row.run_lease_id) }),
528
- ...(row.thread_id === null ? {} : { threadId: String(row.thread_id) }),
529
- ...(row.turn_id === null ? {} : { turnId: String(row.turn_id) }),
530
- ...(row.dedupe_key === null ? {} : { dedupeKey: String(row.dedupe_key) }),
531
- ...(row.last_error === null ? {} : { lastError: String(row.last_error) }),
532
- createdAt: String(row.created_at),
533
- updatedAt: String(row.updated_at),
534
- ...(row.completed_at === null ? {} : { completedAt: String(row.completed_at) }),
535
- };
536
- }
@@ -1,54 +0,0 @@
1
- import { isoNow } from "./shared.js";
2
- export class IssueProjectionStore {
3
- connection;
4
- constructor(connection) {
5
- this.connection = connection;
6
- }
7
- upsertIssueProjection(params) {
8
- this.connection
9
- .prepare(`
10
- INSERT INTO issue_projection (
11
- project_id, linear_issue_id, issue_key, title, issue_url, current_linear_state, last_webhook_at, updated_at
12
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
13
- ON CONFLICT(project_id, linear_issue_id) DO UPDATE SET
14
- issue_key = COALESCE(excluded.issue_key, issue_projection.issue_key),
15
- title = COALESCE(excluded.title, issue_projection.title),
16
- issue_url = COALESCE(excluded.issue_url, issue_projection.issue_url),
17
- current_linear_state = COALESCE(excluded.current_linear_state, issue_projection.current_linear_state),
18
- last_webhook_at = COALESCE(excluded.last_webhook_at, issue_projection.last_webhook_at),
19
- updated_at = excluded.updated_at
20
- `)
21
- .run(params.projectId, params.linearIssueId, params.issueKey ?? null, params.title ?? null, params.issueUrl ?? null, params.currentLinearState ?? null, params.lastWebhookAt ?? null, isoNow());
22
- }
23
- getIssueProjection(projectId, linearIssueId) {
24
- const row = this.connection
25
- .prepare("SELECT * FROM issue_projection WHERE project_id = ? AND linear_issue_id = ?")
26
- .get(projectId, linearIssueId);
27
- return row ? mapIssueProjection(row) : undefined;
28
- }
29
- getIssueProjectionByKey(issueKey) {
30
- const row = this.connection
31
- .prepare("SELECT * FROM issue_projection WHERE issue_key = ? ORDER BY updated_at DESC LIMIT 1")
32
- .get(issueKey);
33
- return row ? mapIssueProjection(row) : undefined;
34
- }
35
- getIssueProjectionByLinearIssueId(linearIssueId) {
36
- const row = this.connection
37
- .prepare("SELECT * FROM issue_projection WHERE linear_issue_id = ? ORDER BY updated_at DESC LIMIT 1")
38
- .get(linearIssueId);
39
- return row ? mapIssueProjection(row) : undefined;
40
- }
41
- }
42
- function mapIssueProjection(row) {
43
- return {
44
- id: Number(row.id),
45
- projectId: String(row.project_id),
46
- linearIssueId: String(row.linear_issue_id),
47
- ...(row.issue_key === null ? {} : { issueKey: String(row.issue_key) }),
48
- ...(row.title === null ? {} : { title: String(row.title) }),
49
- ...(row.issue_url === null ? {} : { issueUrl: String(row.issue_url) }),
50
- ...(row.current_linear_state === null ? {} : { currentLinearState: String(row.current_linear_state) }),
51
- ...(row.last_webhook_at === null ? {} : { lastWebhookAt: String(row.last_webhook_at) }),
52
- updatedAt: String(row.updated_at),
53
- };
54
- }