patchrelay 0.8.9 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -62
- package/dist/agent-session-plan.js +17 -17
- package/dist/build-info.json +3 -3
- package/dist/cli/args.js +1 -1
- package/dist/cli/commands/issues.js +18 -18
- package/dist/cli/data.js +109 -298
- package/dist/cli/formatters/text.js +22 -28
- package/dist/cli/help.js +7 -7
- package/dist/cli/index.js +3 -3
- package/dist/config.js +13 -166
- package/dist/db/migrations.js +46 -154
- package/dist/db.js +369 -45
- package/dist/factory-state.js +55 -0
- package/dist/github-webhook-handler.js +199 -0
- package/dist/github-webhooks.js +166 -0
- package/dist/hook-runner.js +28 -0
- package/dist/http.js +48 -22
- package/dist/issue-query-service.js +33 -38
- package/dist/linear-workflow.js +5 -118
- package/dist/preflight.js +1 -6
- package/dist/project-resolution.js +12 -1
- package/dist/run-orchestrator.js +446 -0
- package/dist/{stage-reporting.js → run-reporting.js} +11 -13
- package/dist/service-runtime.js +12 -61
- package/dist/service-webhooks.js +7 -52
- package/dist/service.js +39 -61
- package/dist/webhook-handler.js +387 -0
- package/dist/webhook-installation-handler.js +3 -8
- package/package.json +2 -1
- package/dist/db/authoritative-ledger-store.js +0 -536
- package/dist/db/issue-projection-store.js +0 -54
- package/dist/db/issue-workflow-coordinator.js +0 -320
- package/dist/db/issue-workflow-store.js +0 -194
- package/dist/db/run-report-store.js +0 -33
- package/dist/db/stage-event-store.js +0 -33
- package/dist/db/webhook-event-store.js +0 -59
- package/dist/db-ports.js +0 -5
- package/dist/ledger-ports.js +0 -1
- package/dist/reconciliation-action-applier.js +0 -68
- package/dist/reconciliation-actions.js +0 -1
- package/dist/reconciliation-engine.js +0 -350
- package/dist/reconciliation-snapshot-builder.js +0 -135
- package/dist/reconciliation-types.js +0 -1
- package/dist/service-stage-finalizer.js +0 -753
- package/dist/service-stage-runner.js +0 -336
- package/dist/service-webhook-processor.js +0 -411
- package/dist/stage-agent-activity-publisher.js +0 -59
- package/dist/stage-event-ports.js +0 -1
- package/dist/stage-failure.js +0 -92
- package/dist/stage-handoff.js +0 -107
- package/dist/stage-launch.js +0 -84
- package/dist/stage-lifecycle-publisher.js +0 -284
- package/dist/stage-turn-input-dispatcher.js +0 -104
- package/dist/webhook-agent-session-handler.js +0 -228
- package/dist/webhook-comment-handler.js +0 -141
- package/dist/webhook-desired-stage-recorder.js +0 -122
- package/dist/webhook-event-ports.js +0 -1
- package/dist/workflow-policy.js +0 -149
- package/dist/workflow-ports.js +0 -1
- /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
|
-
}
|