patchrelay 0.35.5 → 0.35.7
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/run-orchestrator.js +9 -2
- package/dist/webhook-handler.js +92 -57
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/run-orchestrator.js
CHANGED
|
@@ -461,6 +461,7 @@ export class RunOrchestrator {
|
|
|
461
461
|
projectId: run.projectId,
|
|
462
462
|
linearIssueId: run.linearIssueId,
|
|
463
463
|
activeRunId: null,
|
|
464
|
+
...(postRunState === "awaiting_queue" ? { queueLabelApplied: false } : {}),
|
|
464
465
|
...(postRunState ? { factoryState: postRunState } : {}),
|
|
465
466
|
...(postRunState === "awaiting_queue" || postRunState === "done"
|
|
466
467
|
? {
|
|
@@ -742,11 +743,12 @@ export class RunOrchestrator {
|
|
|
742
743
|
const inferProject = this.config.projects.find((p) => p.id === issue.projectId);
|
|
743
744
|
const inferProtocol = resolveMergeQueueProtocol(inferProject);
|
|
744
745
|
let inferred = "branch_ci";
|
|
745
|
-
|
|
746
|
+
const probeSha = issue.lastGitHubFailureHeadSha ?? issue.lastGitHubCiSnapshotHeadSha;
|
|
747
|
+
if (inferProject?.github?.repoFullName && issue.prNumber && probeSha) {
|
|
746
748
|
try {
|
|
747
749
|
const { stdout } = await execCommand("gh", [
|
|
748
750
|
"api",
|
|
749
|
-
`repos/${inferProject.github.repoFullName}/commits/${
|
|
751
|
+
`repos/${inferProject.github.repoFullName}/commits/${probeSha}/check-runs`,
|
|
750
752
|
"--jq", `.check_runs[] | select(.name == "${inferProtocol.evictionCheckName}" and .conclusion == "failure") | .name`,
|
|
751
753
|
], { timeoutMs: 10_000 });
|
|
752
754
|
if (stdout.trim().length > 0)
|
|
@@ -823,6 +825,9 @@ export class RunOrchestrator {
|
|
|
823
825
|
return;
|
|
824
826
|
}
|
|
825
827
|
this.logger.info({ issueKey: issue.issueKey, from: issue.factoryState, to: newState, pendingRunType: options?.pendingRunType }, "Reconciliation: advancing idle issue");
|
|
828
|
+
// Reset queueLabelApplied when entering or leaving awaiting_queue so
|
|
829
|
+
// the retry loop re-applies the label on each queue cycle.
|
|
830
|
+
const resetQueueLabel = newState === "awaiting_queue" || issue.factoryState === "awaiting_queue";
|
|
826
831
|
this.db.upsertIssue({
|
|
827
832
|
projectId: issue.projectId,
|
|
828
833
|
linearIssueId: issue.linearIssueId,
|
|
@@ -833,6 +838,7 @@ export class RunOrchestrator {
|
|
|
833
838
|
pendingRunContextJson: options.pendingRunContext ? JSON.stringify(options.pendingRunContext) : null,
|
|
834
839
|
}
|
|
835
840
|
: {}),
|
|
841
|
+
...(resetQueueLabel ? { queueLabelApplied: false } : {}),
|
|
836
842
|
...(options?.clearFailureProvenance
|
|
837
843
|
? {
|
|
838
844
|
lastGitHubFailureSource: null,
|
|
@@ -1068,6 +1074,7 @@ export class RunOrchestrator {
|
|
|
1068
1074
|
projectId: run.projectId,
|
|
1069
1075
|
linearIssueId: run.linearIssueId,
|
|
1070
1076
|
activeRunId: null,
|
|
1077
|
+
...(postRunState === "awaiting_queue" ? { queueLabelApplied: false } : {}),
|
|
1071
1078
|
...(postRunState ? { factoryState: postRunState } : {}),
|
|
1072
1079
|
...(postRunState === "awaiting_queue" || postRunState === "done"
|
|
1073
1080
|
? {
|
package/dist/webhook-handler.js
CHANGED
|
@@ -185,74 +185,74 @@ export class WebhookHandler {
|
|
|
185
185
|
if (!normalizedIssue) {
|
|
186
186
|
return { issue: undefined, desiredStage: undefined, delegated: false };
|
|
187
187
|
}
|
|
188
|
+
// ── 1. Fetch data ────────────────────────────────────────────
|
|
188
189
|
const existingIssue = this.db.getIssue(project.id, normalizedIssue.id);
|
|
189
190
|
const activeRun = existingIssue?.activeRunId ? this.db.getRun(existingIssue.activeRunId) : undefined;
|
|
190
191
|
const delegated = this.isDelegatedToPatchRelay(project, normalized);
|
|
191
192
|
const triggerAllowed = triggerEventAllowed(project, normalized.triggerEvent);
|
|
192
|
-
|
|
193
|
-
if (!shouldTrack) {
|
|
193
|
+
if (!existingIssue && !delegated) {
|
|
194
194
|
return { issue: undefined, desiredStage: undefined, delegated };
|
|
195
195
|
}
|
|
196
196
|
const hydratedIssue = await this.syncIssueDependencies(project.id, normalizedIssue);
|
|
197
197
|
const unresolvedBlockers = this.db.countUnresolvedBlockers(project.id, normalizedIssue.id);
|
|
198
198
|
const pendingRunContextJson = mergePendingImplementationContext(existingIssue?.pendingRunContextJson, normalized);
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
199
|
+
const terminal = isTerminalDelegationState(existingIssue, hydratedIssue);
|
|
200
|
+
// ── 2. Pure decisions ────────────────────────────────────────
|
|
201
|
+
const pendingRunType = decideRunIntent({
|
|
202
|
+
delegated, triggerAllowed, unresolvedBlockers,
|
|
203
|
+
hasActiveRun: Boolean(activeRun),
|
|
204
|
+
hasPendingRun: Boolean(existingIssue?.pendingRunType),
|
|
205
|
+
terminal,
|
|
206
|
+
});
|
|
207
|
+
const runRelease = decideActiveRunRelease({
|
|
208
|
+
hasActiveRun: Boolean(activeRun),
|
|
209
|
+
terminal,
|
|
210
|
+
triggerEvent: normalized.triggerEvent,
|
|
211
|
+
delegated,
|
|
212
|
+
});
|
|
213
|
+
const undelegation = decideUnDelegation({
|
|
214
|
+
triggerEvent: normalized.triggerEvent,
|
|
215
|
+
delegated,
|
|
216
|
+
currentState: existingIssue?.factoryState,
|
|
217
|
+
});
|
|
218
|
+
const clearPending = (unresolvedBlockers > 0 && existingIssue?.pendingRunType === "implementation" && !activeRun)
|
|
219
|
+
|| undelegation.clearPending;
|
|
220
|
+
const agentSessionId = decideAgentSession({
|
|
221
|
+
sessionId: normalized.agentSession?.id,
|
|
222
|
+
hasActiveRun: Boolean(activeRun),
|
|
223
|
+
hasPendingRun: Boolean(pendingRunType),
|
|
224
|
+
triggerEvent: normalized.triggerEvent,
|
|
225
|
+
delegated,
|
|
226
|
+
});
|
|
227
|
+
// ── 3. Transactional commit ──────────────────────────────────
|
|
228
|
+
const issue = this.db.transaction(() => {
|
|
229
|
+
const record = this.db.upsertIssue({
|
|
230
|
+
projectId: project.id,
|
|
231
|
+
linearIssueId: normalizedIssue.id,
|
|
232
|
+
...(hydratedIssue.identifier ? { issueKey: hydratedIssue.identifier } : {}),
|
|
233
|
+
...(hydratedIssue.title ? { title: hydratedIssue.title } : {}),
|
|
234
|
+
...(hydratedIssue.description ? { description: hydratedIssue.description } : {}),
|
|
235
|
+
...(hydratedIssue.url ? { url: hydratedIssue.url } : {}),
|
|
236
|
+
...(hydratedIssue.priority != null ? { priority: hydratedIssue.priority } : {}),
|
|
237
|
+
...(hydratedIssue.estimate != null ? { estimate: hydratedIssue.estimate } : {}),
|
|
238
|
+
...(hydratedIssue.stateName ? { currentLinearState: hydratedIssue.stateName } : {}),
|
|
239
|
+
...(hydratedIssue.stateType ? { currentLinearStateType: hydratedIssue.stateType } : {}),
|
|
240
|
+
...(pendingRunType ? { pendingRunType, factoryState: "delegated" } : {}),
|
|
241
|
+
...(clearPending ? { pendingRunType: null } : {}),
|
|
242
|
+
...((pendingRunType || existingIssue?.pendingRunType === "implementation") && pendingRunContextJson
|
|
243
|
+
? { pendingRunContextJson }
|
|
244
|
+
: {}),
|
|
245
|
+
...(agentSessionId !== undefined ? { agentSessionId } : {}),
|
|
246
|
+
...(runRelease.release ? { activeRunId: null } : {}),
|
|
247
|
+
...(undelegation.factoryState ? { factoryState: undelegation.factoryState } : {}),
|
|
248
|
+
});
|
|
249
|
+
if (runRelease.release && activeRun && runRelease.reason) {
|
|
250
|
+
this.db.finishRun(activeRun.id, { status: "released", failureReason: runRelease.reason });
|
|
225
251
|
}
|
|
226
|
-
|
|
227
|
-
// Resolve agent session
|
|
228
|
-
const agentSessionId = normalized.agentSession?.id ??
|
|
229
|
-
(!activeRun && (pendingRunType || (normalized.triggerEvent === "delegateChanged" && !delegated)) ? null : undefined);
|
|
230
|
-
// Upsert the issue
|
|
231
|
-
const issue = this.db.upsertIssue({
|
|
232
|
-
projectId: project.id,
|
|
233
|
-
linearIssueId: normalizedIssue.id,
|
|
234
|
-
...(hydratedIssue.identifier ? { issueKey: hydratedIssue.identifier } : {}),
|
|
235
|
-
...(hydratedIssue.title ? { title: hydratedIssue.title } : {}),
|
|
236
|
-
...(hydratedIssue.description ? { description: hydratedIssue.description } : {}),
|
|
237
|
-
...(hydratedIssue.url ? { url: hydratedIssue.url } : {}),
|
|
238
|
-
...(hydratedIssue.priority != null ? { priority: hydratedIssue.priority } : {}),
|
|
239
|
-
...(hydratedIssue.estimate != null ? { estimate: hydratedIssue.estimate } : {}),
|
|
240
|
-
...(hydratedIssue.stateName ? { currentLinearState: hydratedIssue.stateName } : {}),
|
|
241
|
-
...(hydratedIssue.stateType ? { currentLinearStateType: hydratedIssue.stateType } : {}),
|
|
242
|
-
...(pendingRunType ? { pendingRunType, factoryState: "delegated" } : {}),
|
|
243
|
-
...(clearPendingImplementation ? { pendingRunType: null } : {}),
|
|
244
|
-
...((pendingRunType || existingIssue?.pendingRunType === "implementation") && pendingRunContextJson
|
|
245
|
-
? { pendingRunContextJson }
|
|
246
|
-
: {}),
|
|
247
|
-
...(agentSessionId !== undefined ? { agentSessionId } : {}),
|
|
248
|
-
...(clearActiveRun ? { activeRunId: null } : {}),
|
|
249
|
-
...(undelegatedFactoryState ? { factoryState: undelegatedFactoryState } : {}),
|
|
252
|
+
return record;
|
|
250
253
|
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.db.finishRun(activeRun.id, { status: "released", failureReason: reason });
|
|
254
|
-
}
|
|
255
|
-
if (undelegatedFactoryState) {
|
|
254
|
+
// ── 4. Side effects (after transaction) ──────────────────────
|
|
255
|
+
if (undelegation.factoryState) {
|
|
256
256
|
this.feed?.publish({
|
|
257
257
|
level: "warn",
|
|
258
258
|
kind: "stage",
|
|
@@ -612,6 +612,41 @@ export class WebhookHandler {
|
|
|
612
612
|
return undefined;
|
|
613
613
|
}
|
|
614
614
|
}
|
|
615
|
+
// ─── Pure decision functions for recordDesiredStage ──────────────
|
|
616
|
+
function decideRunIntent(p) {
|
|
617
|
+
if (p.delegated && p.triggerAllowed && p.unresolvedBlockers === 0
|
|
618
|
+
&& !p.hasActiveRun && !p.hasPendingRun && !p.terminal) {
|
|
619
|
+
return "implementation";
|
|
620
|
+
}
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
623
|
+
function decideActiveRunRelease(p) {
|
|
624
|
+
if (!p.hasActiveRun)
|
|
625
|
+
return { release: false };
|
|
626
|
+
if (p.terminal)
|
|
627
|
+
return { release: true, reason: "Issue reached terminal state during active run" };
|
|
628
|
+
if (p.triggerEvent === "delegateChanged" && !p.delegated)
|
|
629
|
+
return { release: true, reason: "Un-delegated from PatchRelay" };
|
|
630
|
+
return { release: false };
|
|
631
|
+
}
|
|
632
|
+
function decideUnDelegation(p) {
|
|
633
|
+
if (p.triggerEvent !== "delegateChanged" || p.delegated)
|
|
634
|
+
return { clearPending: false };
|
|
635
|
+
if (!p.currentState)
|
|
636
|
+
return { clearPending: false };
|
|
637
|
+
const pastNoReturn = p.currentState === "awaiting_queue" || TERMINAL_STATES.has(p.currentState);
|
|
638
|
+
if (pastNoReturn)
|
|
639
|
+
return { clearPending: false };
|
|
640
|
+
return { factoryState: "awaiting_input", clearPending: true };
|
|
641
|
+
}
|
|
642
|
+
function decideAgentSession(p) {
|
|
643
|
+
if (p.sessionId)
|
|
644
|
+
return p.sessionId;
|
|
645
|
+
if (!p.hasActiveRun && (p.hasPendingRun || (p.triggerEvent === "delegateChanged" && !p.delegated)))
|
|
646
|
+
return null;
|
|
647
|
+
return undefined;
|
|
648
|
+
}
|
|
649
|
+
// ─── Helper predicates ──────────────────────────────────────────
|
|
615
650
|
function isResolvedLinearState(stateType, stateName) {
|
|
616
651
|
return stateType === "completed" || stateName?.trim().toLowerCase() === "done";
|
|
617
652
|
}
|