patchrelay 0.69.4 → 0.69.5

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.69.4",
4
- "commit": "56b3535ee459",
5
- "builtAt": "2026-05-22T22:23:51.490Z"
3
+ "version": "0.69.5",
4
+ "commit": "ab14b628b055",
5
+ "builtAt": "2026-05-22T23:00:59.300Z"
6
6
  }
@@ -1,13 +1,11 @@
1
- import { classifyIssue } from "../issue-class.js";
2
- import { computeOrchestrationSettleUntil, wakeOrchestrationParentsForChildEvent, } from "../orchestration-parent-wake.js";
1
+ import { wakeOrchestrationParentsForChildEvent } from "../orchestration-parent-wake.js";
3
2
  import { triggerEventAllowed } from "../project-resolution.js";
4
- import { resolveAwaitingInputReason } from "../awaiting-input-reason.js";
5
- import { decideActiveRunRelease, decideAgentSession, decideRunIntent, decideUnDelegation, isResolvedLinearState, isTerminalDelegationState, resolveReDelegationResume, } from "./decision-helpers.js";
3
+ import { isResolvedLinearState } from "./decision-helpers.js";
6
4
  import { isDelegatedToPatchRelay, resolveDelegationTruth } from "./delegation-truth.js";
7
5
  import { syncIssueDependencies } from "./issue-dependency-sync.js";
8
6
  import { resolveLinkedPrAdoption } from "./linked-pr-adoption.js";
9
7
  import { buildOperatorRetryEvent } from "../operator-retry-event.js";
10
- import { resolveIssueUpdatePlan } from "./issue-update-plan.js";
8
+ import { planIssueWebhookWorkflow } from "./issue-webhook-workflow-planner.js";
11
9
  export class DesiredStageRecorder {
12
10
  db;
13
11
  linearProvider;
@@ -56,108 +54,25 @@ export class DesiredStageRecorder {
56
54
  triggerEvent: params.normalized.triggerEvent,
57
55
  });
58
56
  const unresolvedBlockers = this.db.issues.countUnresolvedBlockers(params.project.id, normalizedIssue.id);
59
- const terminal = isTerminalDelegationState(existingIssue, hydratedIssue);
60
- const openPrExists = existingIssue?.prNumber !== undefined
61
- && existingIssue.prState !== "closed"
62
- && existingIssue.prState !== "merged";
63
- const blockerPausedImplementation = unresolvedBlockers > 0
64
- && activeRun?.runType === "implementation"
65
- && !openPrExists;
66
- const desiredStage = linkedPrAdoption
67
- ? undefined
68
- : decideRunIntent({
69
- delegated,
70
- triggerAllowed,
71
- triggerEvent: params.normalized.triggerEvent,
72
- unresolvedBlockers,
73
- hasActiveRun: Boolean(activeRun),
74
- hasPendingWake,
75
- terminal,
76
- currentState: existingIssue?.factoryState,
77
- });
78
57
  const childIssueCount = this.db.issues.listCanonicalChildIssues(params.project.id, normalizedIssue.id).length;
79
- const classification = classifyIssue({
80
- issue: {
81
- issueClass: existingIssue?.issueClass,
82
- issueClassSource: existingIssue?.issueClassSource,
83
- title: hydratedIssue.title ?? existingIssue?.title,
84
- description: hydratedIssue.description ?? existingIssue?.description,
85
- parentLinearIssueId: hydratedIssue.parentId ?? existingIssue?.parentLinearIssueId,
86
- },
87
- childIssueCount,
88
- });
89
- const shouldEnterOrchestrationSettle = Boolean(delegated
90
- && desiredStage === "implementation"
91
- && classification.issueClass === "orchestration"
92
- && childIssueCount === 0
93
- && !existingIssue?.threadId
94
- && !activeRun
95
- && !terminal);
96
- const runRelease = decideActiveRunRelease({
97
- hasActiveRun: Boolean(activeRun),
98
- terminal,
99
- triggerEvent: params.normalized.triggerEvent,
100
- delegated,
101
- });
102
- const effectiveRunRelease = blockerPausedImplementation
103
- ? { release: true, reason: "Issue became blocked during implementation" }
104
- : runRelease;
105
- const undelegation = decideUnDelegation({
106
- triggerEvent: params.normalized.triggerEvent,
107
- delegated,
108
- currentState: existingIssue?.factoryState,
109
- hasPr: existingIssue?.prNumber !== undefined && existingIssue?.prState !== "merged",
110
- });
111
- const startupResume = linkedPrAdoption
112
- ? {
113
- factoryState: linkedPrAdoption.factoryState,
114
- pendingRunType: linkedPrAdoption.pendingRunType,
115
- pendingRunContext: linkedPrAdoption.pendingRunContext,
116
- source: "linked_pr_adoption",
117
- }
118
- : {
119
- ...resolveReDelegationResume({
120
- delegated,
121
- previouslyDelegated: existingIssue?.delegatedToPatchRelay,
122
- currentState: existingIssue?.factoryState,
123
- awaitingInputReason: existingIssue
124
- ? resolveAwaitingInputReason({ issue: existingIssue, latestRun })
125
- : undefined,
126
- unresolvedBlockers,
127
- prNumber: existingIssue?.prNumber,
128
- prState: existingIssue?.prState,
129
- prIsDraft: existingIssue?.prIsDraft,
130
- prReviewState: existingIssue?.prReviewState,
131
- prCheckStatus: existingIssue?.prCheckStatus,
132
- latestFailureSource: existingIssue?.lastGitHubFailureSource,
133
- }),
134
- source: "re_delegated",
135
- };
136
58
  const existingWakeRunType = existingIssue
137
59
  ? params.peekPendingSessionWakeRunType(params.project.id, normalizedIssue.id)
138
60
  : undefined;
139
- const clearPending = (unresolvedBlockers > 0 && existingWakeRunType === "implementation" && !activeRun)
140
- || undelegation.clearPending;
141
- const agentSessionId = decideAgentSession({
142
- sessionId: params.normalized.agentSession?.id,
143
- triggerEvent: params.normalized.triggerEvent,
144
- delegated,
145
- });
146
- const terminalRunRelease = effectiveRunRelease.release && terminal;
147
- const resolvedPlan = resolveIssueUpdatePlan({
148
- existingIssue: Boolean(existingIssue),
61
+ const workflowPlan = planIssueWebhookWorkflow({
62
+ existingIssue,
63
+ hydratedIssue,
64
+ latestRun,
149
65
  delegated,
66
+ linkedPrAdoption,
67
+ triggerAllowed,
68
+ triggerEvent: params.normalized.triggerEvent,
69
+ unresolvedBlockers,
70
+ hasActiveRun: Boolean(activeRun),
71
+ activeRunType: activeRun?.runType,
72
+ hasPendingWake,
73
+ existingWakeRunType,
150
74
  incomingAgentSessionId,
151
- startupResume,
152
- desiredStage,
153
- terminalRunRelease,
154
- blockerPausedImplementation,
155
- undelegation,
156
- clearPending,
157
- effectiveRunRelease,
158
- shouldEnterOrchestrationSettle,
159
- agentSessionId,
160
- computeOrchestrationSettleUntil,
75
+ childIssueCount,
161
76
  });
162
77
  const commitIssueUpdate = () => {
163
78
  const record = this.db.issues.upsertIssue({
@@ -166,8 +81,8 @@ export class DesiredStageRecorder {
166
81
  ...(hydratedIssue.identifier ? { issueKey: hydratedIssue.identifier } : {}),
167
82
  ...(hydratedIssue.parentId !== undefined ? { parentLinearIssueId: hydratedIssue.parentId ?? null } : {}),
168
83
  ...(hydratedIssue.parentIdentifier !== undefined ? { parentIssueKey: hydratedIssue.parentIdentifier ?? null } : {}),
169
- issueClass: classification.issueClass,
170
- issueClassSource: classification.issueClassSource,
84
+ issueClass: workflowPlan.classification.issueClass,
85
+ issueClassSource: workflowPlan.classification.issueClassSource,
171
86
  ...(hydratedIssue.title ? { title: hydratedIssue.title } : {}),
172
87
  ...(hydratedIssue.description ? { description: hydratedIssue.description } : {}),
173
88
  ...(hydratedIssue.url ? { url: hydratedIssue.url } : {}),
@@ -177,10 +92,10 @@ export class DesiredStageRecorder {
177
92
  ...(hydratedIssue.stateType ? { currentLinearStateType: hydratedIssue.stateType } : {}),
178
93
  ...linkedPrAdoption?.issueUpdates,
179
94
  delegatedToPatchRelay: delegated,
180
- ...resolvedPlan,
95
+ ...workflowPlan.resolvedIssueUpdate,
181
96
  });
182
- if (effectiveRunRelease.release && activeRun && effectiveRunRelease.reason) {
183
- this.db.runs.finishRun(activeRun.id, { status: "released", failureReason: effectiveRunRelease.reason });
97
+ if (workflowPlan.effectiveRunRelease.release && activeRun && workflowPlan.effectiveRunRelease.reason) {
98
+ this.db.runs.finishRun(activeRun.id, { status: "released", failureReason: workflowPlan.effectiveRunRelease.reason });
184
99
  }
185
100
  return record;
186
101
  };
@@ -196,7 +111,7 @@ export class DesiredStageRecorder {
196
111
  const currentParentIssueId = issue.parentLinearIssueId;
197
112
  const wasResolved = isResolvedLinearState(existingIssue?.currentLinearStateType, existingIssue?.currentLinearState);
198
113
  const isResolved = isResolvedLinearState(issue.currentLinearStateType, issue.currentLinearState);
199
- if (undelegation.factoryState) {
114
+ if (workflowPlan.undelegation.factoryState) {
200
115
  if (activeRun?.threadId && activeRun.turnId) {
201
116
  await params.stopActiveRun(activeRun, "STOP: The issue was un-delegated from PatchRelay. Stop working immediately and exit.");
202
117
  }
@@ -220,7 +135,7 @@ export class DesiredStageRecorder {
220
135
  : `Issue un-delegated from PatchRelay; ${issue.factoryState} is now paused`,
221
136
  });
222
137
  }
223
- else if (blockerPausedImplementation) {
138
+ else if (workflowPlan.blockerPausedImplementation) {
224
139
  if (activeRun?.threadId && activeRun.turnId) {
225
140
  await params.stopActiveRun(activeRun, "STOP: The issue is now blocked by another task. Stop working immediately and exit without publishing.");
226
141
  }
@@ -236,14 +151,14 @@ export class DesiredStageRecorder {
236
151
  summary: `Implementation paused because ${issue.issueKey ?? normalizedIssue.id} is now blocked`,
237
152
  });
238
153
  }
239
- else if (startupResume.pendingRunType) {
154
+ else if (workflowPlan.startupResume.pendingRunType) {
240
155
  this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(params.project.id, normalizedIssue.id, {
241
156
  projectId: params.project.id,
242
157
  linearIssueId: normalizedIssue.id,
243
- ...buildOperatorRetryEvent(issue, startupResume.pendingRunType, startupResume.source),
158
+ ...buildOperatorRetryEvent(issue, workflowPlan.startupResume.pendingRunType, workflowPlan.startupResume.source),
244
159
  });
245
160
  }
246
- else if (shouldEnterOrchestrationSettle) {
161
+ else if (workflowPlan.shouldEnterOrchestrationSettle) {
247
162
  this.feed?.publish({
248
163
  level: "info",
249
164
  kind: "stage",
@@ -254,10 +169,9 @@ export class DesiredStageRecorder {
254
169
  summary: "Waiting briefly for child issues to settle before orchestration starts",
255
170
  });
256
171
  }
257
- else if (!startupResume.factoryState
258
- && !startupResume.pendingRunType
259
- &&
260
- desiredStage === "implementation"
172
+ else if (!workflowPlan.startupResume.factoryState
173
+ && !workflowPlan.startupResume.pendingRunType
174
+ && workflowPlan.desiredStage === "implementation"
261
175
  && params.normalized.triggerEvent !== "commentCreated"
262
176
  && params.normalized.triggerEvent !== "commentUpdated"
263
177
  && params.normalized.triggerEvent !== "agentPrompted") {
@@ -0,0 +1,124 @@
1
+ import { resolveAwaitingInputReason } from "../awaiting-input-reason.js";
2
+ import { computeOrchestrationSettleUntil as computeDefaultOrchestrationSettleUntil } from "../orchestration-parent-wake.js";
3
+ import { classifyIssue } from "../issue-class.js";
4
+ import { decideActiveRunRelease, decideAgentSession, decideRunIntent, decideUnDelegation, isTerminalDelegationState, resolveReDelegationResume, } from "./decision-helpers.js";
5
+ import { resolveIssueUpdatePlan } from "./issue-update-plan.js";
6
+ function resolveStartupResume(input) {
7
+ if (input.linkedPrAdoption) {
8
+ return {
9
+ factoryState: input.linkedPrAdoption.factoryState,
10
+ pendingRunType: input.linkedPrAdoption.pendingRunType,
11
+ pendingRunContext: input.linkedPrAdoption.pendingRunContext,
12
+ source: "linked_pr_adoption",
13
+ };
14
+ }
15
+ const awaitingInputReason = input.existingIssue
16
+ ? resolveAwaitingInputReason({ issue: input.existingIssue, latestRun: input.latestRun })
17
+ : undefined;
18
+ return {
19
+ ...resolveReDelegationResume({
20
+ delegated: input.delegated,
21
+ previouslyDelegated: input.existingIssue?.delegatedToPatchRelay,
22
+ currentState: input.existingIssue?.factoryState,
23
+ awaitingInputReason,
24
+ unresolvedBlockers: input.unresolvedBlockers,
25
+ prNumber: input.existingIssue?.prNumber,
26
+ prState: input.existingIssue?.prState,
27
+ prIsDraft: input.existingIssue?.prIsDraft,
28
+ prReviewState: input.existingIssue?.prReviewState,
29
+ prCheckStatus: input.existingIssue?.prCheckStatus,
30
+ latestFailureSource: input.existingIssue?.lastGitHubFailureSource,
31
+ }),
32
+ source: "re_delegated",
33
+ };
34
+ }
35
+ export function planIssueWebhookWorkflow(input) {
36
+ const terminal = isTerminalDelegationState(input.existingIssue, input.hydratedIssue);
37
+ const openPrExists = input.existingIssue?.prNumber !== undefined
38
+ && input.existingIssue.prState !== "closed"
39
+ && input.existingIssue.prState !== "merged";
40
+ const blockerPausedImplementation = input.unresolvedBlockers > 0
41
+ && input.activeRunType === "implementation"
42
+ && !openPrExists;
43
+ const desiredStage = input.linkedPrAdoption
44
+ ? undefined
45
+ : decideRunIntent({
46
+ delegated: input.delegated,
47
+ triggerAllowed: input.triggerAllowed,
48
+ triggerEvent: input.triggerEvent,
49
+ unresolvedBlockers: input.unresolvedBlockers,
50
+ hasActiveRun: input.hasActiveRun,
51
+ hasPendingWake: input.hasPendingWake,
52
+ terminal,
53
+ currentState: input.existingIssue?.factoryState,
54
+ });
55
+ const classification = classifyIssue({
56
+ issue: {
57
+ issueClass: input.existingIssue?.issueClass,
58
+ issueClassSource: input.existingIssue?.issueClassSource,
59
+ title: input.hydratedIssue.title ?? input.existingIssue?.title,
60
+ description: input.hydratedIssue.description ?? input.existingIssue?.description,
61
+ parentLinearIssueId: input.hydratedIssue.parentId ?? input.existingIssue?.parentLinearIssueId,
62
+ },
63
+ childIssueCount: input.childIssueCount,
64
+ });
65
+ const shouldEnterOrchestrationSettle = Boolean(input.delegated
66
+ && desiredStage === "implementation"
67
+ && classification.issueClass === "orchestration"
68
+ && input.childIssueCount === 0
69
+ && !input.existingIssue?.threadId
70
+ && !input.hasActiveRun
71
+ && !terminal);
72
+ const runRelease = decideActiveRunRelease({
73
+ hasActiveRun: input.hasActiveRun,
74
+ terminal,
75
+ triggerEvent: input.triggerEvent,
76
+ delegated: input.delegated,
77
+ });
78
+ const effectiveRunRelease = blockerPausedImplementation
79
+ ? { release: true, reason: "Issue became blocked during implementation" }
80
+ : runRelease;
81
+ const undelegation = decideUnDelegation({
82
+ triggerEvent: input.triggerEvent,
83
+ delegated: input.delegated,
84
+ currentState: input.existingIssue?.factoryState,
85
+ hasPr: input.existingIssue?.prNumber !== undefined && input.existingIssue?.prState !== "merged",
86
+ });
87
+ const startupResume = resolveStartupResume(input);
88
+ const clearPending = (input.unresolvedBlockers > 0 && input.existingWakeRunType === "implementation" && !input.hasActiveRun)
89
+ || undelegation.clearPending;
90
+ const agentSessionId = decideAgentSession({
91
+ sessionId: input.incomingAgentSessionId,
92
+ triggerEvent: input.triggerEvent,
93
+ delegated: input.delegated,
94
+ });
95
+ const terminalRunRelease = effectiveRunRelease.release && terminal;
96
+ const resolvedIssueUpdate = resolveIssueUpdatePlan({
97
+ existingIssue: Boolean(input.existingIssue),
98
+ delegated: input.delegated,
99
+ incomingAgentSessionId: input.incomingAgentSessionId,
100
+ startupResume,
101
+ desiredStage,
102
+ terminalRunRelease,
103
+ blockerPausedImplementation,
104
+ undelegation,
105
+ clearPending,
106
+ effectiveRunRelease,
107
+ shouldEnterOrchestrationSettle,
108
+ agentSessionId,
109
+ computeOrchestrationSettleUntil: input.computeOrchestrationSettleUntil ?? computeDefaultOrchestrationSettleUntil,
110
+ });
111
+ return {
112
+ classification,
113
+ terminal,
114
+ desiredStage,
115
+ blockerPausedImplementation,
116
+ undelegation,
117
+ startupResume,
118
+ effectiveRunRelease,
119
+ clearPending,
120
+ agentSessionId,
121
+ shouldEnterOrchestrationSettle,
122
+ resolvedIssueUpdate,
123
+ };
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.69.4",
3
+ "version": "0.69.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {