patchrelay 0.35.2 → 0.35.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/db.js +14 -0
- package/dist/run-orchestrator.js +12 -0
- package/dist/webhook-handler.js +74 -1
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/db.js
CHANGED
|
@@ -484,6 +484,20 @@ export class PatchRelayDatabase {
|
|
|
484
484
|
.all();
|
|
485
485
|
return rows.map(mapIssueRow);
|
|
486
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Issues in delegated state with dependencies but no pending/active run.
|
|
489
|
+
* Candidates for unblocking when their blockers complete.
|
|
490
|
+
*/
|
|
491
|
+
listBlockedDelegatedIssues() {
|
|
492
|
+
const rows = this.connection
|
|
493
|
+
.prepare(`SELECT DISTINCT i.* FROM issues i
|
|
494
|
+
JOIN issue_dependencies d ON d.project_id = i.project_id AND d.linear_issue_id = i.linear_issue_id
|
|
495
|
+
WHERE i.factory_state = 'delegated'
|
|
496
|
+
AND i.active_run_id IS NULL
|
|
497
|
+
AND i.pending_run_type IS NULL`)
|
|
498
|
+
.all();
|
|
499
|
+
return rows.map(mapIssueRow);
|
|
500
|
+
}
|
|
487
501
|
/**
|
|
488
502
|
* Issues waiting in the merge queue with no active or pending run.
|
|
489
503
|
* Used by the queue health monitor to probe GitHub for stuck PRs.
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -763,6 +763,18 @@ export class RunOrchestrator {
|
|
|
763
763
|
await this.reconcileFromGitHub(issue);
|
|
764
764
|
}
|
|
765
765
|
}
|
|
766
|
+
// Unblock delegated issues whose blockers have been resolved.
|
|
767
|
+
for (const issue of this.db.listBlockedDelegatedIssues()) {
|
|
768
|
+
const unresolved = this.db.countUnresolvedBlockers(issue.projectId, issue.linearIssueId);
|
|
769
|
+
if (unresolved === 0) {
|
|
770
|
+
this.db.upsertIssue({
|
|
771
|
+
projectId: issue.projectId,
|
|
772
|
+
linearIssueId: issue.linearIssueId,
|
|
773
|
+
pendingRunType: "implementation",
|
|
774
|
+
});
|
|
775
|
+
this.enqueueIssue(issue.projectId, issue.linearIssueId);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
766
778
|
}
|
|
767
779
|
async reconcileFromGitHub(issue) {
|
|
768
780
|
const project = this.config.projects.find((p) => p.id === issue.projectId);
|
package/dist/webhook-handler.js
CHANGED
|
@@ -98,6 +98,40 @@ export class WebhookHandler {
|
|
|
98
98
|
const result = await this.recordDesiredStage(project, hydrated);
|
|
99
99
|
const trackedIssue = result.issue;
|
|
100
100
|
const newlyReadyDependents = this.reconcileDependentReadiness(project.id, issue.id);
|
|
101
|
+
// Handle issue removal: release active runs, mark as failed.
|
|
102
|
+
if (hydrated.triggerEvent === "issueRemoved" && trackedIssue) {
|
|
103
|
+
const removedIssue = this.db.getIssue(project.id, issue.id);
|
|
104
|
+
if (removedIssue?.activeRunId) {
|
|
105
|
+
const run = this.db.getRun(removedIssue.activeRunId);
|
|
106
|
+
if (run) {
|
|
107
|
+
this.db.finishRun(run.id, { status: "released", failureReason: "Issue removed from Linear" });
|
|
108
|
+
}
|
|
109
|
+
this.db.upsertIssue({
|
|
110
|
+
projectId: project.id,
|
|
111
|
+
linearIssueId: issue.id,
|
|
112
|
+
activeRunId: null,
|
|
113
|
+
pendingRunType: null,
|
|
114
|
+
factoryState: "failed",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else if (removedIssue && !TERMINAL_STATES.has(removedIssue.factoryState)) {
|
|
118
|
+
this.db.upsertIssue({
|
|
119
|
+
projectId: project.id,
|
|
120
|
+
linearIssueId: issue.id,
|
|
121
|
+
pendingRunType: null,
|
|
122
|
+
factoryState: "failed",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
this.feed?.publish({
|
|
126
|
+
level: "warn",
|
|
127
|
+
kind: "stage",
|
|
128
|
+
issueKey: issue.identifier,
|
|
129
|
+
projectId: project.id,
|
|
130
|
+
stage: "failed",
|
|
131
|
+
status: "issue_removed",
|
|
132
|
+
summary: "Issue removed from Linear",
|
|
133
|
+
});
|
|
134
|
+
}
|
|
101
135
|
// Handle agent session events
|
|
102
136
|
await this.handleAgentSession(hydrated, project, trackedIssue, result.desiredStage, result.delegated);
|
|
103
137
|
// Handle comments during active run
|
|
@@ -167,7 +201,29 @@ export class WebhookHandler {
|
|
|
167
201
|
if (delegated && triggerAllowed && unresolvedBlockers === 0 && !activeRun && !existingIssue?.pendingRunType && !terminalForAutomation) {
|
|
168
202
|
pendingRunType = "implementation";
|
|
169
203
|
}
|
|
170
|
-
|
|
204
|
+
let clearPendingImplementation = unresolvedBlockers > 0 && existingIssue?.pendingRunType === "implementation" && !activeRun;
|
|
205
|
+
// Release active run when issue reaches a terminal state or is un-delegated.
|
|
206
|
+
let clearActiveRun = false;
|
|
207
|
+
if (activeRun && existingIssue) {
|
|
208
|
+
if (terminalForAutomation) {
|
|
209
|
+
clearActiveRun = true;
|
|
210
|
+
}
|
|
211
|
+
if (normalized.triggerEvent === "delegateChanged" && !delegated) {
|
|
212
|
+
clearActiveRun = true;
|
|
213
|
+
clearPendingImplementation = Boolean(existingIssue.pendingRunType);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Un-delegation: transition to awaiting_input unless past point of no return.
|
|
217
|
+
// awaiting_queue means the PR is approved and in the merge queue — let it merge.
|
|
218
|
+
let undelegatedFactoryState;
|
|
219
|
+
if (normalized.triggerEvent === "delegateChanged" && !delegated && existingIssue) {
|
|
220
|
+
const pastNoReturn = existingIssue.factoryState === "awaiting_queue"
|
|
221
|
+
|| TERMINAL_STATES.has(existingIssue.factoryState);
|
|
222
|
+
if (!pastNoReturn) {
|
|
223
|
+
undelegatedFactoryState = "awaiting_input";
|
|
224
|
+
clearPendingImplementation = Boolean(existingIssue.pendingRunType);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
171
227
|
// Resolve agent session
|
|
172
228
|
const agentSessionId = normalized.agentSession?.id ??
|
|
173
229
|
(!activeRun && (pendingRunType || (normalized.triggerEvent === "delegateChanged" && !delegated)) ? null : undefined);
|
|
@@ -189,7 +245,24 @@ export class WebhookHandler {
|
|
|
189
245
|
? { pendingRunContextJson }
|
|
190
246
|
: {}),
|
|
191
247
|
...(agentSessionId !== undefined ? { agentSessionId } : {}),
|
|
248
|
+
...(clearActiveRun ? { activeRunId: null } : {}),
|
|
249
|
+
...(undelegatedFactoryState ? { factoryState: undelegatedFactoryState } : {}),
|
|
192
250
|
});
|
|
251
|
+
if (clearActiveRun && activeRun) {
|
|
252
|
+
const reason = terminalForAutomation ? "Issue reached terminal state during active run" : "Un-delegated from PatchRelay";
|
|
253
|
+
this.db.finishRun(activeRun.id, { status: "released", failureReason: reason });
|
|
254
|
+
}
|
|
255
|
+
if (undelegatedFactoryState) {
|
|
256
|
+
this.feed?.publish({
|
|
257
|
+
level: "warn",
|
|
258
|
+
kind: "stage",
|
|
259
|
+
issueKey: issue.issueKey,
|
|
260
|
+
projectId: project.id,
|
|
261
|
+
stage: "awaiting_input",
|
|
262
|
+
status: "un_delegated",
|
|
263
|
+
summary: "Issue un-delegated from PatchRelay",
|
|
264
|
+
});
|
|
265
|
+
}
|
|
193
266
|
return {
|
|
194
267
|
issue: this.db.issueToTrackedIssue(issue),
|
|
195
268
|
desiredStage: pendingRunType,
|