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,84 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { buildCarryForwardPrompt } from "./stage-handoff.js";
4
- import { listWorkflowStageIds, resolveWorkflowStageConfig } from "./workflow-policy.js";
5
- function slugify(value) {
6
- return value
7
- .toLowerCase()
8
- .replace(/[^a-z0-9]+/g, "-")
9
- .replace(/^-+|-+$/g, "")
10
- .slice(0, 60);
11
- }
12
- function sanitizePathSegment(value) {
13
- return value.replace(/[^a-zA-Z0-9._-]+/g, "-");
14
- }
15
- export function isCodexThreadId(value) {
16
- if (!value) {
17
- return false;
18
- }
19
- return !value.startsWith("missing-thread-") && !value.startsWith("launch-failed-");
20
- }
21
- export function buildStageLaunchPlan(project, issue, stage, options) {
22
- const workflow = resolveWorkflowStageConfig(project, stage, issue.selectedWorkflowId);
23
- if (!workflow) {
24
- throw new Error(`Workflow "${stage}" is not configured for project ${project.id}`);
25
- }
26
- const issueRef = sanitizePathSegment(issue.issueKey ?? issue.linearIssueId);
27
- const slug = issue.title ? slugify(issue.title) : "";
28
- const branchSuffix = slug ? `${issueRef}-${slug}` : issueRef;
29
- return {
30
- branchName: options?.branchName ?? `${project.branchPrefix}/${branchSuffix}`,
31
- worktreePath: options?.worktreePath ?? path.join(project.worktreeRoot, issueRef),
32
- workflowFile: workflow.workflowFile,
33
- stage,
34
- prompt: buildStagePrompt(project, issue, workflow.id, workflow.whenState, workflow.workflowFile, {
35
- branchName: options?.branchName ?? `${project.branchPrefix}/${branchSuffix}`,
36
- worktreePath: options?.worktreePath ?? path.join(project.worktreeRoot, issueRef),
37
- ...(issue.selectedWorkflowId ? { workflowDefinitionId: issue.selectedWorkflowId } : {}),
38
- ...(options?.previousStageRun ? { previousStageRun: options.previousStageRun } : {}),
39
- ...(options?.workspace ? { workspace: options.workspace } : {}),
40
- stageHistory: options?.stageHistory ?? [],
41
- }),
42
- };
43
- }
44
- export function buildStagePrompt(project, issue, stage, triggerState, workflowFile, options) {
45
- const workflowBody = existsSync(workflowFile) ? readFileSync(workflowFile, "utf8").trim() : "";
46
- const carryForward = buildCarryForwardPrompt({
47
- project,
48
- currentStage: stage,
49
- ...(options?.workflowDefinitionId ? { workflowDefinitionId: options.workflowDefinitionId } : {}),
50
- ...(options?.previousStageRun ? { previousStageRun: options.previousStageRun } : {}),
51
- ...(options?.workspace ? { workspace: options.workspace } : {}),
52
- stageHistory: options?.stageHistory ?? [],
53
- });
54
- const availableStages = listWorkflowStageIds(project, options?.workflowDefinitionId).join(", ");
55
- return [
56
- `Issue: ${issue.issueKey ?? issue.linearIssueId}`,
57
- issue.title ? `Title: ${issue.title}` : undefined,
58
- issue.issueUrl ? `Linear URL: ${issue.issueUrl}` : undefined,
59
- issue.currentLinearState ? `Current Linear State: ${issue.currentLinearState}` : undefined,
60
- `Workflow: ${stage}`,
61
- `Triggered By State: ${triggerState}`,
62
- options?.branchName ? `Branch: ${options.branchName}` : undefined,
63
- options?.worktreePath ? `Worktree: ${options.worktreePath}` : undefined,
64
- "",
65
- "Complete only the current workflow stage. Do not invent a new workflow or skip directly to another stage.",
66
- "If the correct next step is unclear, say so plainly and use `human_needed` as the next likely stage.",
67
- "",
68
- carryForward ? "Carry-forward Context:" : undefined,
69
- carryForward,
70
- "",
71
- "Operate only inside the prepared worktree for this issue. Continue the issue lifecycle in this workspace.",
72
- "Use the repo workflow instructions below for this stage.",
73
- "End with a short `Stage result:` section in plain text with exactly four bullets:",
74
- "- what happened",
75
- "- key facts or artifacts",
76
- `- Next likely stage: one of ${availableStages}, done, or human_needed`,
77
- "- what the next stage or human should pay attention to",
78
- "",
79
- `Workflow File: ${path.basename(workflowFile)}`,
80
- workflowBody,
81
- ]
82
- .filter(Boolean)
83
- .join("\n");
84
- }
@@ -1,284 +0,0 @@
1
- import { buildAwaitingHandoffSessionPlan, buildCompletedSessionPlan, buildPreparingSessionPlan, buildRunningSessionPlan, } from "./agent-session-plan.js";
2
- import { buildAgentSessionExternalUrls } from "./agent-session-presentation.js";
3
- import { buildAwaitingHandoffComment, buildHumanNeededComment, buildRunningStatusComment, resolveActiveLinearState, resolveWorkflowLabelCleanup, resolveWorkflowLabelNames, } from "./linear-workflow.js";
4
- import { sanitizeDiagnosticText } from "./utils.js";
5
- export class StageLifecyclePublisher {
6
- config;
7
- stores;
8
- linearProvider;
9
- logger;
10
- feed;
11
- constructor(config, stores, linearProvider, logger, feed) {
12
- this.config = config;
13
- this.stores = stores;
14
- this.linearProvider = linearProvider;
15
- this.logger = logger;
16
- this.feed = feed;
17
- }
18
- async markStageActive(project, issue, stageRun) {
19
- const activeState = resolveActiveLinearState(project, stageRun.stage, issue.selectedWorkflowId);
20
- const linear = await this.linearProvider.forProject(stageRun.projectId);
21
- if (!activeState || !linear) {
22
- return;
23
- }
24
- await linear.setIssueState(stageRun.linearIssueId, activeState);
25
- const labels = resolveWorkflowLabelNames(project, "working");
26
- if (labels.add.length > 0 || labels.remove.length > 0) {
27
- await linear.updateIssueLabels({
28
- issueId: stageRun.linearIssueId,
29
- ...(labels.add.length > 0 ? { addNames: labels.add } : {}),
30
- ...(labels.remove.length > 0 ? { removeNames: labels.remove } : {}),
31
- });
32
- }
33
- this.stores.workflowCoordinator.upsertTrackedIssue({
34
- projectId: stageRun.projectId,
35
- linearIssueId: stageRun.linearIssueId,
36
- currentLinearState: activeState,
37
- statusCommentId: issue.statusCommentId ?? null,
38
- activeAgentSessionId: issue.activeAgentSessionId ?? null,
39
- lifecycleStatus: "running",
40
- });
41
- }
42
- async refreshRunningStatusComment(projectId, issueId, stageRunId, issueKey) {
43
- const linear = await this.linearProvider.forProject(projectId);
44
- if (!linear) {
45
- return;
46
- }
47
- const issue = this.stores.issueWorkflows.getTrackedIssue(projectId, issueId);
48
- const stageRun = this.stores.issueWorkflows.getStageRun(stageRunId);
49
- const workspace = stageRun ? this.stores.issueWorkflows.getWorkspace(stageRun.workspaceId) : undefined;
50
- if (!issue || !stageRun || !workspace) {
51
- return;
52
- }
53
- try {
54
- const result = await linear.upsertIssueComment({
55
- issueId,
56
- ...(issue.statusCommentId ? { commentId: issue.statusCommentId } : {}),
57
- body: buildRunningStatusComment({
58
- issue,
59
- stageRun,
60
- branchName: workspace.branchName,
61
- }),
62
- });
63
- this.stores.workflowCoordinator.setIssueStatusComment(projectId, issueId, result.id);
64
- }
65
- catch (error) {
66
- this.logger.warn({
67
- issueKey,
68
- stageRunId,
69
- issueId,
70
- error: error instanceof Error ? error.message : String(error),
71
- }, "Failed to refresh running status comment after stage startup");
72
- }
73
- }
74
- async publishStageStarted(issue, stage) {
75
- if (!issue.activeAgentSessionId) {
76
- return false;
77
- }
78
- const linear = await this.linearProvider.forProject(issue.projectId);
79
- if (!linear) {
80
- return false;
81
- }
82
- const sessionUpdated = await this.updateAgentSession(linear, issue, buildRunningSessionPlan(stage), stage);
83
- try {
84
- await linear.createAgentActivity({
85
- agentSessionId: issue.activeAgentSessionId,
86
- content: {
87
- type: "response",
88
- body: `PatchRelay started the ${stage} workflow and is working in the background.`,
89
- },
90
- });
91
- return true;
92
- }
93
- catch (error) {
94
- this.logger.warn({
95
- issueKey: issue.issueKey,
96
- stage,
97
- agentSessionId: issue.activeAgentSessionId,
98
- error: error instanceof Error ? error.message : String(error),
99
- }, "Failed to publish Linear agent activity after stage startup");
100
- return sessionUpdated;
101
- }
102
- }
103
- async publishStageCompletion(stageRun, enqueueIssue) {
104
- const refreshedIssue = this.stores.issueWorkflows.getTrackedIssue(stageRun.projectId, stageRun.linearIssueId);
105
- if (refreshedIssue?.desiredStage) {
106
- const linear = await this.linearProvider.forProject(stageRun.projectId);
107
- if (refreshedIssue.activeAgentSessionId && linear) {
108
- await this.updateAgentSession(linear, refreshedIssue, buildPreparingSessionPlan(refreshedIssue.desiredStage), refreshedIssue.desiredStage);
109
- }
110
- this.feed?.publish({
111
- level: "info",
112
- kind: "stage",
113
- issueKey: refreshedIssue.issueKey,
114
- projectId: refreshedIssue.projectId,
115
- stage: stageRun.stage,
116
- ...(refreshedIssue.selectedWorkflowId ? { workflowId: refreshedIssue.selectedWorkflowId } : {}),
117
- nextStage: refreshedIssue.desiredStage,
118
- status: "queued",
119
- summary: `Completed ${stageRun.stage} workflow and queued ${refreshedIssue.desiredStage}`,
120
- });
121
- await this.publishAgentCompletion(refreshedIssue, {
122
- type: "thought",
123
- body: `The ${stageRun.stage} workflow finished. PatchRelay is preparing the ${refreshedIssue.desiredStage} workflow next.`,
124
- });
125
- enqueueIssue(stageRun.projectId, stageRun.linearIssueId);
126
- return;
127
- }
128
- const project = this.config.projects.find((candidate) => candidate.id === stageRun.projectId);
129
- const activeState = project ? resolveActiveLinearState(project, stageRun.stage, refreshedIssue?.selectedWorkflowId) : undefined;
130
- const linear = project ? await this.linearProvider.forProject(stageRun.projectId) : undefined;
131
- if (refreshedIssue && linear && project && activeState) {
132
- try {
133
- const linearIssue = await linear.getIssue(stageRun.linearIssueId);
134
- if (linearIssue.stateName?.trim().toLowerCase() === activeState.trim().toLowerCase()) {
135
- const labels = resolveWorkflowLabelNames(project, "awaitingHandoff");
136
- if (labels.add.length > 0 || labels.remove.length > 0) {
137
- await linear.updateIssueLabels({
138
- issueId: stageRun.linearIssueId,
139
- ...(labels.add.length > 0 ? { addNames: labels.add } : {}),
140
- ...(labels.remove.length > 0 ? { removeNames: labels.remove } : {}),
141
- });
142
- }
143
- this.stores.workflowCoordinator.setIssueLifecycleStatus(stageRun.projectId, stageRun.linearIssueId, "paused");
144
- const finalStageRun = this.stores.issueWorkflows.getStageRun(stageRun.id) ?? stageRun;
145
- let deliveredToSession = false;
146
- if (refreshedIssue.activeAgentSessionId) {
147
- deliveredToSession = await this.updateAgentSession(linear, refreshedIssue, buildAwaitingHandoffSessionPlan(stageRun.stage));
148
- }
149
- this.feed?.publish({
150
- level: "info",
151
- kind: "stage",
152
- issueKey: refreshedIssue.issueKey,
153
- projectId: refreshedIssue.projectId,
154
- stage: stageRun.stage,
155
- ...(refreshedIssue.selectedWorkflowId ? { workflowId: refreshedIssue.selectedWorkflowId } : {}),
156
- status: "handoff",
157
- summary: `Completed ${stageRun.stage} workflow`,
158
- detail: `Waiting for a Linear state change or follow-up input while the issue remains in ${activeState}.`,
159
- });
160
- deliveredToSession =
161
- (await this.publishAgentCompletion(refreshedIssue, {
162
- type: "elicitation",
163
- body: `PatchRelay finished the ${stageRun.stage} workflow. Move the issue to its next workflow state or leave a follow-up prompt to continue.`,
164
- })) || deliveredToSession;
165
- if (!deliveredToSession && !refreshedIssue.activeAgentSessionId) {
166
- const result = await linear.upsertIssueComment({
167
- issueId: stageRun.linearIssueId,
168
- ...(refreshedIssue.statusCommentId ? { commentId: refreshedIssue.statusCommentId } : {}),
169
- body: buildAwaitingHandoffComment({
170
- issue: refreshedIssue,
171
- stageRun: finalStageRun,
172
- activeState,
173
- }),
174
- });
175
- this.stores.workflowCoordinator.setIssueStatusComment(stageRun.projectId, stageRun.linearIssueId, result.id);
176
- }
177
- return;
178
- }
179
- const cleanup = resolveWorkflowLabelCleanup(project);
180
- if (cleanup.remove.length > 0) {
181
- await linear.updateIssueLabels({
182
- issueId: stageRun.linearIssueId,
183
- removeNames: cleanup.remove,
184
- });
185
- }
186
- }
187
- catch (error) {
188
- this.logger.warn({
189
- issueKey: refreshedIssue.issueKey,
190
- issueId: stageRun.linearIssueId,
191
- stageRunId: stageRun.id,
192
- stage: stageRun.stage,
193
- error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
194
- }, "Stage completed locally but PatchRelay could not finish the final Linear sync");
195
- }
196
- }
197
- if (refreshedIssue) {
198
- let deliveredToSession = false;
199
- if (refreshedIssue.activeAgentSessionId && linear) {
200
- deliveredToSession = await this.updateAgentSession(linear, refreshedIssue, refreshedIssue.lifecycleStatus === "paused"
201
- ? buildAwaitingHandoffSessionPlan(stageRun.stage)
202
- : buildCompletedSessionPlan(stageRun.stage));
203
- }
204
- this.feed?.publish({
205
- level: "info",
206
- kind: "stage",
207
- issueKey: refreshedIssue.issueKey,
208
- projectId: refreshedIssue.projectId,
209
- stage: stageRun.stage,
210
- ...(refreshedIssue.selectedWorkflowId ? { workflowId: refreshedIssue.selectedWorkflowId } : {}),
211
- status: "completed",
212
- summary: `Completed ${stageRun.stage} workflow`,
213
- });
214
- deliveredToSession =
215
- (await this.publishAgentCompletion(refreshedIssue, {
216
- type: refreshedIssue.lifecycleStatus === "paused" ? "elicitation" : "response",
217
- body: refreshedIssue.lifecycleStatus === "paused"
218
- ? `PatchRelay finished the ${stageRun.stage} workflow and now needs human input before it can continue.`
219
- : `PatchRelay finished the ${stageRun.stage} workflow.`,
220
- })) || deliveredToSession;
221
- if (refreshedIssue.lifecycleStatus === "paused" && linear && !deliveredToSession) {
222
- const result = await linear.upsertIssueComment({
223
- issueId: stageRun.linearIssueId,
224
- ...(refreshedIssue.statusCommentId ? { commentId: refreshedIssue.statusCommentId } : {}),
225
- body: buildHumanNeededComment({
226
- issue: refreshedIssue,
227
- stageRun: this.stores.issueWorkflows.getStageRun(stageRun.id) ?? stageRun,
228
- }),
229
- });
230
- this.stores.workflowCoordinator.setIssueStatusComment(stageRun.projectId, stageRun.linearIssueId, result.id);
231
- }
232
- }
233
- }
234
- async updateAgentSession(linear, issue, plan, stage) {
235
- if (!issue.activeAgentSessionId) {
236
- return false;
237
- }
238
- try {
239
- const externalUrls = buildAgentSessionExternalUrls(this.config, issue.issueKey);
240
- await linear.updateAgentSession?.({
241
- agentSessionId: issue.activeAgentSessionId,
242
- ...(externalUrls ? { externalUrls } : {}),
243
- plan,
244
- });
245
- return true;
246
- }
247
- catch (error) {
248
- this.logger.warn({
249
- issueKey: issue.issueKey,
250
- issueId: issue.linearIssueId,
251
- ...(stage ? { stage } : {}),
252
- agentSessionId: issue.activeAgentSessionId,
253
- error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
254
- }, "Failed to update Linear agent session");
255
- return false;
256
- }
257
- }
258
- async publishAgentCompletion(issue, content) {
259
- if (!issue.activeAgentSessionId) {
260
- return false;
261
- }
262
- const linear = await this.linearProvider.forProject(issue.projectId);
263
- if (!linear) {
264
- return false;
265
- }
266
- try {
267
- await linear.createAgentActivity({
268
- agentSessionId: issue.activeAgentSessionId,
269
- content,
270
- });
271
- return true;
272
- }
273
- catch (error) {
274
- this.logger.warn({
275
- issueKey: issue.issueKey,
276
- issueId: issue.linearIssueId,
277
- agentSessionId: issue.activeAgentSessionId,
278
- activityType: content.type,
279
- error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
280
- }, "Failed to publish Linear agent activity");
281
- return false;
282
- }
283
- }
284
- }
@@ -1,104 +0,0 @@
1
- import { sanitizeDiagnosticText } from "./utils.js";
2
- import { safeJsonParse } from "./utils.js";
3
- export class StageTurnInputDispatcher {
4
- inputs;
5
- codex;
6
- logger;
7
- constructor(inputs, codex, logger) {
8
- this.inputs = inputs;
9
- this.codex = codex;
10
- this.logger = logger;
11
- }
12
- routePendingInputs(stageRun, threadId, turnId) {
13
- const issueControl = this.inputs.issueControl.getIssueControl(stageRun.projectId, stageRun.linearIssueId);
14
- if (!issueControl?.activeRunLeaseId) {
15
- return;
16
- }
17
- for (const obligation of this.listPendingInputObligations(stageRun.projectId, stageRun.linearIssueId, issueControl.activeRunLeaseId)) {
18
- this.inputs.obligations.updateObligationRouting(obligation.id, {
19
- runLeaseId: issueControl.activeRunLeaseId,
20
- threadId,
21
- turnId,
22
- });
23
- }
24
- }
25
- async flush(stageRun, options) {
26
- if (!stageRun.threadId || !stageRun.turnId) {
27
- return { deliveredInputIds: [], deliveredObligationIds: [], deliveredCount: 0, failedObligationIds: [] };
28
- }
29
- const issueControl = this.inputs.issueControl.getIssueControl(stageRun.projectId, stageRun.linearIssueId);
30
- if (!issueControl?.activeRunLeaseId) {
31
- return { deliveredInputIds: [], deliveredObligationIds: [], deliveredCount: 0, failedObligationIds: [] };
32
- }
33
- const deliveredInputIds = [];
34
- const deliveredObligationIds = [];
35
- const failedObligationIds = [];
36
- let deliveredCount = 0;
37
- const obligationQuery = options?.retryInProgress ? { includeInProgress: true } : undefined;
38
- for (const obligation of this.listPendingInputObligations(stageRun.projectId, stageRun.linearIssueId, issueControl.activeRunLeaseId, obligationQuery)) {
39
- const payload = safeJsonParse(obligation.payloadJson);
40
- const body = payload?.body?.trim();
41
- if (!body) {
42
- this.inputs.obligations.markObligationStatus(obligation.id, "failed", "obligation payload had no deliverable body");
43
- continue;
44
- }
45
- const claimed = obligation.status === "in_progress" && options?.retryInProgress
46
- ? true
47
- : this.inputs.obligations.claimPendingObligation(obligation.id, {
48
- runLeaseId: issueControl.activeRunLeaseId,
49
- threadId: stageRun.threadId,
50
- turnId: stageRun.turnId,
51
- });
52
- if (!claimed) {
53
- continue;
54
- }
55
- try {
56
- if (obligation.status === "in_progress") {
57
- this.inputs.obligations.updateObligationRouting(obligation.id, {
58
- runLeaseId: issueControl.activeRunLeaseId,
59
- threadId: stageRun.threadId,
60
- turnId: stageRun.turnId,
61
- });
62
- }
63
- await this.codex.steerTurn({
64
- threadId: stageRun.threadId,
65
- turnId: stageRun.turnId,
66
- input: body,
67
- });
68
- deliveredObligationIds.push(obligation.id);
69
- this.inputs.obligations.markObligationStatus(obligation.id, "completed");
70
- deliveredCount += 1;
71
- this.logger.debug({
72
- threadId: stageRun.threadId,
73
- turnId: stageRun.turnId,
74
- obligationId: obligation.id,
75
- source: obligation.source,
76
- }, "Delivered queued turn input to Codex");
77
- }
78
- catch (error) {
79
- this.inputs.obligations.markObligationStatus(obligation.id, "pending", error instanceof Error ? error.message : String(error));
80
- failedObligationIds.push(obligation.id);
81
- this.logger.warn({
82
- issueKey: options?.issueKey,
83
- threadId: stageRun.threadId,
84
- turnId: stageRun.turnId,
85
- obligationId: obligation.id,
86
- source: obligation.source,
87
- error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
88
- }, options?.failureMessage ?? "Failed to deliver queued turn input");
89
- break;
90
- }
91
- }
92
- return { deliveredInputIds, deliveredObligationIds, deliveredCount, failedObligationIds };
93
- }
94
- listPendingInputObligations(projectId, linearIssueId, activeRunLeaseId, options) {
95
- const query = options?.includeInProgress
96
- ? { kind: "deliver_turn_input", includeInProgress: true }
97
- : { kind: "deliver_turn_input" };
98
- return this.inputs.obligations
99
- .listPendingObligations(query)
100
- .filter((obligation) => obligation.projectId === projectId &&
101
- obligation.linearIssueId === linearIssueId &&
102
- (obligation.runLeaseId === undefined || obligation.runLeaseId === activeRunLeaseId));
103
- }
104
- }