patchrelay 0.73.2 → 0.73.3

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.73.2",
4
- "commit": "af2df414ed0e",
5
- "builtAt": "2026-05-26T00:15:44.683Z"
3
+ "version": "0.73.3",
4
+ "commit": "3342a5c6bbc1",
5
+ "builtAt": "2026-05-26T13:58:45.864Z"
6
6
  }
@@ -17,15 +17,17 @@ export class IdleIssueReconciler {
17
17
  logger;
18
18
  feed;
19
19
  deployEvaluator;
20
+ syncIssue;
20
21
  constructor(db, config, wakeDispatcher, logger, feed,
21
22
  // Injectable for tests; production uses the real `gh`-backed watcher.
22
- deployEvaluator = evaluateDeploy) {
23
+ deployEvaluator = evaluateDeploy, syncIssue) {
23
24
  this.db = db;
24
25
  this.config = config;
25
26
  this.wakeDispatcher = wakeDispatcher;
26
27
  this.logger = logger;
27
28
  this.feed = feed;
28
29
  this.deployEvaluator = deployEvaluator;
30
+ this.syncIssue = syncIssue;
29
31
  }
30
32
  async reconcile() {
31
33
  // Wrap the entire reconcile pass in a dispatcher tick. Every
@@ -251,6 +253,12 @@ export class IdleIssueReconciler {
251
253
  }
252
254
  : {}),
253
255
  });
256
+ const updatedIssue = this.db.issues.getIssue(issue.projectId, issue.linearIssueId) ?? issue;
257
+ if (this.syncIssue) {
258
+ void Promise.resolve(this.syncIssue(updatedIssue)).catch((error) => {
259
+ this.logger.warn({ issueKey: issue.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to sync Linear workflow state after idle reconciliation");
260
+ });
261
+ }
254
262
  if (options?.pendingRunType) {
255
263
  this.recordWakeEvent(issue, options.pendingRunType, options.pendingRunContext, "idle_reconciliation");
256
264
  }
@@ -134,7 +134,7 @@ export class RunOrchestrator {
134
134
  this.interruptedRunRecovery = new InterruptedRunRecovery(db, logger, this.linearSync, this.leasePorts.withHeldLease, this.leasePorts.releaseLease, this.recoveryPorts.failRunAndClear, this.recoveryPorts.restoreIdleWorktree, this.runCompletionPolicy, (projectId, issueId) => this.enqueueIssue(projectId, issueId), feed);
135
135
  this.runReconciler = new RunReconciler(db, logger, linearProvider, this.linearSync, this.interruptedRunRecovery, this.runFinalizer, this.leasePorts.withHeldLease, this.leasePorts.releaseLease, this.threadPorts.readThreadWithRetry, this.recoveryPorts.recoverOrEscalate, (projectId) => this.config.projects.find((project) => project.id === projectId)?.github?.repoFullName, feed);
136
136
  this.runWakePlanner = new RunWakePlanner(db);
137
- this.idleReconciler = new IdleIssueReconciler(db, config, this.wakeDispatcher, logger, feed);
137
+ this.idleReconciler = new IdleIssueReconciler(db, config, this.wakeDispatcher, logger, feed, undefined, (issue) => this.linearSync.syncSession(issue));
138
138
  this.mergedLinearCompletionReconciler = new MergedLinearCompletionReconciler(db, linearProvider, logger);
139
139
  this.queueHealthMonitor = new QueueHealthMonitor(db, config, {
140
140
  advanceIdleIssue: (issue, newState, options) => this.idleReconciler.advanceIdleIssue(issue, newState, options),
@@ -18,12 +18,12 @@ export function decideActiveRunRelease(p) {
18
18
  return { release: false };
19
19
  if (p.terminal)
20
20
  return { release: true, reason: "Issue reached terminal state during active run" };
21
- if (p.triggerEvent === "delegateChanged" && !p.delegated)
21
+ if (!p.delegated)
22
22
  return { release: true, reason: "Un-delegated from PatchRelay" };
23
23
  return { release: false };
24
24
  }
25
25
  export function decideUnDelegation(p) {
26
- if (p.triggerEvent !== "delegateChanged" || p.delegated)
26
+ if (p.delegated)
27
27
  return { clearPending: false };
28
28
  if (!p.currentState)
29
29
  return { clearPending: false };
@@ -18,11 +18,14 @@ export function resolveDelegationTruth(input) {
18
18
  const observedDelegated = isDelegatedToPatchRelay(input.db, input.project, input.hydratedIssue);
19
19
  const explicitDelegateSignal = input.triggerEvent === "delegateChanged";
20
20
  const hasObservedDelegate = input.hydratedIssue.delegateId !== undefined;
21
+ const authoritativeDelegateObservation = hasObservedDelegate || explicitDelegateSignal || input.hydration === "live_linear";
21
22
  let delegated = observedDelegated;
22
23
  let reason = hasObservedDelegate
23
24
  ? "delegate_id_present"
24
- : `missing_delegate_identity_after_${input.hydration}`;
25
- if (!hasObservedDelegate && !explicitDelegateSignal && previousDelegated !== undefined) {
25
+ : input.hydration === "live_linear"
26
+ ? "live_linear_delegate_absent"
27
+ : `missing_delegate_identity_after_${input.hydration}`;
28
+ if (!authoritativeDelegateObservation && previousDelegated !== undefined) {
26
29
  delegated = previousDelegated;
27
30
  reason = `preserved_previous_delegation_after_${input.hydration}`;
28
31
  }
package/dist/webhooks.js CHANGED
@@ -75,15 +75,15 @@ function deriveTriggerEvent(payload) {
75
75
  return "issueRemoved";
76
76
  }
77
77
  const updatedFields = new Set(Object.keys(payload.updatedFrom ?? {}));
78
+ if (updatedFields.has("delegateId") || updatedFields.has("delegate")) {
79
+ return "delegateChanged";
80
+ }
78
81
  if (updatedFields.has("labels")) {
79
82
  return "labelChanged";
80
83
  }
81
84
  if (updatedFields.has("stateId") || updatedFields.has("state")) {
82
85
  return "statusChanged";
83
86
  }
84
- if (updatedFields.has("delegateId") || updatedFields.has("delegate")) {
85
- return "delegateChanged";
86
- }
87
87
  if (updatedFields.has("assigneeId") || updatedFields.has("assignee")) {
88
88
  return "assignmentChanged";
89
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.73.2",
3
+ "version": "0.73.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {