patchrelay 0.51.0 → 0.51.2

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.51.0",
4
- "commit": "c8ad40f06a1c",
5
- "builtAt": "2026-04-21T21:25:15.871Z"
3
+ "version": "0.51.2",
4
+ "commit": "525db768a1e0",
5
+ "builtAt": "2026-04-22T03:51:24.647Z"
6
6
  }
package/dist/cli/data.js CHANGED
@@ -70,6 +70,9 @@ function summarizeRun(run) {
70
70
  : completionCheck.summary;
71
71
  }
72
72
  const summary = parseObjectJson(run.summaryJson);
73
+ if (typeof summary?.publicationRecapSummary === "string" && summary.publicationRecapSummary.trim()) {
74
+ return summary.publicationRecapSummary.trim();
75
+ }
73
76
  if (typeof summary?.latestAssistantMessage === "string" && summary.latestAssistantMessage.trim()) {
74
77
  return summary.latestAssistantMessage.trim();
75
78
  }
@@ -9,6 +9,14 @@ const COMPLETION_CHECK_DEVELOPER_INSTRUCTIONS = [
9
9
  "Use only the prior thread context and the facts in the current prompt.",
10
10
  "Return only the requested JSON object.",
11
11
  ].join("\n");
12
+ const PUBLICATION_RECAP_DEVELOPER_INSTRUCTIONS = [
13
+ "You are PatchRelay's publication recap helper.",
14
+ "This is a read-only follow-up used only to produce one concise Linear-visible summary for a successful run.",
15
+ "Keep reasoning light and concise.",
16
+ "Do not run commands, do not call tools, do not edit files, and do not inspect or modify the repository.",
17
+ "Use only the prior thread context and the facts in the current prompt.",
18
+ "Return only the requested JSON object.",
19
+ ].join("\n");
12
20
  export function resolveCodexAppServerLaunch(config) {
13
21
  if (!config.sourceBashrc) {
14
22
  return {
@@ -210,6 +218,14 @@ export class CodexAppServerClient extends EventEmitter {
210
218
  developerInstructions: COMPLETION_CHECK_DEVELOPER_INSTRUCTIONS,
211
219
  });
212
220
  }
221
+ async forkThreadForPublicationRecap(threadId) {
222
+ return await this.forkThread(threadId, tmpdir(), {
223
+ approvalPolicy: "never",
224
+ sandboxMode: "read-only",
225
+ reasoningEffort: "low",
226
+ developerInstructions: PUBLICATION_RECAP_DEVELOPER_INSTRUCTIONS,
227
+ });
228
+ }
213
229
  async startTurn(options) {
214
230
  const response = (await this.sendRequest("turn/start", {
215
231
  threadId: options.threadId,
@@ -149,6 +149,9 @@ export function extractLatestAssistantSummary(run) {
149
149
  if (run.summaryJson) {
150
150
  try {
151
151
  const parsed = JSON.parse(run.summaryJson);
152
+ if (typeof parsed.publicationRecapSummary === "string" && parsed.publicationRecapSummary.trim()) {
153
+ return sanitizeOperatorFacingText(parsed.publicationRecapSummary);
154
+ }
152
155
  if (typeof parsed.latestAssistantMessage === "string" && parsed.latestAssistantMessage.trim()) {
153
156
  return sanitizeOperatorFacingText(parsed.latestAssistantMessage);
154
157
  }
@@ -2,6 +2,13 @@ import { resolveMergeQueueProtocol } from "./merge-queue-protocol.js";
2
2
  import { buildMainRepairBranchName, buildMainRepairDescription, buildMainRepairPromptContext, buildMainRepairTitle, isMainRepairIssue, } from "./main-repair.js";
3
3
  import { execCommand } from "./utils.js";
4
4
  const MAIN_BRANCH_HEALTH_GRACE_MS = 120_000;
5
+ function isUnhealthyMainConclusion(conclusion) {
6
+ return conclusion === "failure"
7
+ || conclusion === "timed_out"
8
+ || conclusion === "cancelled"
9
+ || conclusion === "action_required"
10
+ || conclusion === "stale";
11
+ }
5
12
  export class MainBranchHealthMonitor {
6
13
  db;
7
14
  config;
@@ -36,17 +43,20 @@ export class MainBranchHealthMonitor {
36
43
  && issue.factoryState !== "done"
37
44
  && issue.factoryState !== "failed"
38
45
  && issue.factoryState !== "escalated"));
46
+ const summary = await this.readMainBranchFailure(project.github.repoFullName, baseBranch);
47
+ if (!summary) {
48
+ if (existing) {
49
+ this.resolveRecoveredMainRepair(existing);
50
+ }
51
+ return;
52
+ }
53
+ const protocol = resolveMergeQueueProtocol(project);
39
54
  if (existing) {
40
55
  const age = Date.now() - Date.parse(existing.updatedAt);
41
56
  if (age < MAIN_BRANCH_HEALTH_GRACE_MS) {
42
57
  return;
43
58
  }
44
59
  }
45
- const summary = await this.readMainBranchFailure(project.github.repoFullName, baseBranch);
46
- if (!summary) {
47
- return;
48
- }
49
- const protocol = resolveMergeQueueProtocol(project);
50
60
  if (existing) {
51
61
  this.queueExistingMainRepair(existing, summary, protocol.priorityLabel);
52
62
  return;
@@ -128,6 +138,29 @@ export class MainBranchHealthMonitor {
128
138
  this.enqueueIssue(issue.projectId, issue.linearIssueId);
129
139
  }
130
140
  }
141
+ resolveRecoveredMainRepair(issue) {
142
+ if (issue.activeRunId !== undefined)
143
+ return;
144
+ if (issue.prState === "open" || issue.factoryState === "awaiting_queue" || issue.factoryState === "pr_open") {
145
+ return;
146
+ }
147
+ this.db.issueSessions.clearPendingIssueSessionEventsRespectingActiveLease(issue.projectId, issue.linearIssueId);
148
+ this.db.upsertIssue({
149
+ projectId: issue.projectId,
150
+ linearIssueId: issue.linearIssueId,
151
+ factoryState: "done",
152
+ pendingRunType: null,
153
+ });
154
+ this.feed?.publish({
155
+ level: "info",
156
+ kind: "github",
157
+ issueKey: issue.issueKey,
158
+ projectId: issue.projectId,
159
+ stage: "done",
160
+ status: "main_repair_resolved",
161
+ summary: "Closed stale main_repair after main recovered externally",
162
+ });
163
+ }
131
164
  async readMainBranchFailure(repoFullName, baseBranch) {
132
165
  const { stdout: shaOut } = await execCommand("gh", [
133
166
  "api",
@@ -146,7 +179,7 @@ export class MainBranchHealthMonitor {
146
179
  ], { timeoutMs: 10_000 });
147
180
  const runs = JSON.parse(checksOut || "[]");
148
181
  const failingChecks = runs
149
- .filter((run) => run.status === "completed" && run.conclusion === "failure" && typeof run.name === "string" && run.name.trim())
182
+ .filter((run) => run.status === "completed" && isUnhealthyMainConclusion(run.conclusion) && typeof run.name === "string" && run.name.trim())
150
183
  .map((run) => ({ name: run.name.trim(), ...(run.details_url ? { url: run.details_url } : {}) }));
151
184
  if (failingChecks.length === 0) {
152
185
  return undefined;
@@ -0,0 +1,113 @@
1
+ import { getThreadTurns } from "./codex-thread-utils.js";
2
+ import { extractLatestAssistantSummary } from "./issue-session-events.js";
3
+ import { sanitizeOperatorFacingText } from "./presentation-text.js";
4
+ import { extractFirstJsonObject, safeJsonParse } from "./utils.js";
5
+ const PUBLICATION_RECAP_TIMEOUT_MS = 45_000;
6
+ const PUBLICATION_RECAP_POLL_MS = 1_000;
7
+ export class PublicationRecapService {
8
+ codex;
9
+ logger;
10
+ constructor(codex, logger) {
11
+ this.codex = codex;
12
+ this.logger = logger;
13
+ }
14
+ async run(params) {
15
+ const threadId = params.run.threadId;
16
+ if (!threadId) {
17
+ throw new Error("Publication recap could not run because the main thread is missing.");
18
+ }
19
+ const fork = await this.codex.forkThreadForPublicationRecap(threadId);
20
+ const turn = await this.codex.startTurn({
21
+ threadId: fork.id,
22
+ ...(fork.cwd ? { cwd: fork.cwd } : {}),
23
+ input: buildPublicationRecapPrompt(params),
24
+ });
25
+ const completedThread = await this.waitForTurn(fork.id, turn.turnId);
26
+ const completedTurn = getThreadTurns(completedThread).find((entry) => entry.id === turn.turnId);
27
+ const latestMessage = completedTurn?.items
28
+ .filter((item) => item.type === "agentMessage")
29
+ .at(-1)?.text;
30
+ const parsed = parsePublicationRecapResult(latestMessage);
31
+ if (!parsed) {
32
+ this.logger.warn({ runId: params.run.id, issueKey: params.issue.issueKey, threadId: fork.id, turnId: turn.turnId }, "Publication recap returned invalid JSON");
33
+ throw new Error("Publication recap returned an invalid result.");
34
+ }
35
+ return {
36
+ ...parsed,
37
+ threadId: fork.id,
38
+ turnId: turn.turnId,
39
+ };
40
+ }
41
+ async waitForTurn(threadId, turnId) {
42
+ const deadline = Date.now() + PUBLICATION_RECAP_TIMEOUT_MS;
43
+ while (Date.now() < deadline) {
44
+ const thread = await this.codex.readThread(threadId, true);
45
+ const turn = getThreadTurns(thread).find((entry) => entry.id === turnId);
46
+ if (turn?.status === "completed") {
47
+ return thread;
48
+ }
49
+ if (turn?.status === "failed" || turn?.status === "interrupted") {
50
+ throw new Error(`Publication recap turn ${turnId} ended with status ${turn.status}`);
51
+ }
52
+ await new Promise((resolve) => setTimeout(resolve, PUBLICATION_RECAP_POLL_MS));
53
+ }
54
+ throw new Error(`Publication recap timed out after ${PUBLICATION_RECAP_TIMEOUT_MS}ms`);
55
+ }
56
+ }
57
+ function buildPublicationRecapPrompt(params) {
58
+ const latestSummary = params.facts?.latestAssistantSummary
59
+ ? sanitizeOperatorFacingText(params.facts.latestAssistantSummary)
60
+ : extractLatestAssistantSummary(params.run);
61
+ return [
62
+ "PatchRelay publication recap",
63
+ "",
64
+ "The main task run succeeded.",
65
+ "This is a read-only follow-up used only to produce one concise Linear-visible recap for that successful run.",
66
+ "Do not run commands, call tools, edit files, or inspect the repository.",
67
+ "Use only the prior thread context and the facts in this prompt.",
68
+ "Return exactly one JSON object and no extra prose.",
69
+ "",
70
+ "Schema:",
71
+ '{',
72
+ ' "summary": "one short sentence, max 30 words"',
73
+ '}',
74
+ "",
75
+ "Writing rules:",
76
+ "- Focus on what this session chunk achieved.",
77
+ "- Mention the wake reason only when it makes the change clearer, for example requested changes, a failing CI check, or a merge queue incident.",
78
+ "- Do not list touched files, test commands, branch names, commit SHAs, or internal process details.",
79
+ "- Do not say that you reviewed files or ran checks unless that is the only meaningful achievement.",
80
+ "- For implementation runs, summarize the delivered user-facing or system-facing change.",
81
+ "- For review-fix runs, summarize the concern that was addressed and imply that a newer head was published.",
82
+ "- For CI repair runs, summarize the failure that was fixed if known.",
83
+ "- For queue repair runs, summarize the queue or merge issue that was resolved if known.",
84
+ "",
85
+ "Facts:",
86
+ `- Issue: ${params.issue.issueKey ?? params.issue.linearIssueId}`,
87
+ ...(params.issue.title ? [`- Title: ${params.issue.title}`] : []),
88
+ `- Run type: ${params.run.runType}`,
89
+ ...(params.facts?.postRunState ? [`- Post-run state: ${params.facts.postRunState}`] : []),
90
+ ...(params.facts?.wakeReason ? [`- Wake reason: ${params.facts.wakeReason}`] : []),
91
+ ...(params.facts?.prNumber !== undefined ? [`- PR number: ${params.facts.prNumber}`] : []),
92
+ ...(params.facts?.reviewerName ? [`- Reviewer: ${params.facts.reviewerName}`] : []),
93
+ ...(params.facts?.reviewSummary ? [`- Review summary: ${sanitizeOperatorFacingText(params.facts.reviewSummary)}`] : []),
94
+ ...(params.facts?.failingCheckName ? [`- Failing check: ${params.facts.failingCheckName}`] : []),
95
+ ...(params.facts?.failureSummary ? [`- Failure summary: ${sanitizeOperatorFacingText(params.facts.failureSummary)}`] : []),
96
+ ...(params.facts?.queueIncidentSummary ? [`- Queue incident: ${sanitizeOperatorFacingText(params.facts.queueIncidentSummary)}`] : []),
97
+ ...(latestSummary ? [`- Latest assistant summary: ${latestSummary}`] : []),
98
+ ...(params.run.failureReason ? [`- Failure reason: ${sanitizeOperatorFacingText(params.run.failureReason)}`] : []),
99
+ ...(params.issue.description ? ["", "Issue description:", params.issue.description] : []),
100
+ ].join("\n");
101
+ }
102
+ function parsePublicationRecapResult(text) {
103
+ const raw = sanitizeOperatorFacingText(text);
104
+ if (!raw)
105
+ return undefined;
106
+ const candidate = safeJsonParse(raw) ?? safeJsonParse(extractFirstJsonObject(raw) ?? "");
107
+ if (!candidate)
108
+ return undefined;
109
+ const summary = typeof candidate.summary === "string" ? sanitizeOperatorFacingText(candidate.summary) : undefined;
110
+ if (!summary)
111
+ return undefined;
112
+ return { summary };
113
+ }
@@ -2,6 +2,30 @@ import { buildStageReport, countEventMethods } from "./run-reporting.js";
2
2
  import { buildRunCompletedActivity, buildRunFailureActivity } from "./linear-session-reporting.js";
3
3
  import { handleNoPrCompletionCheck } from "./no-pr-completion-check.js";
4
4
  import { resolveCompletedRunState } from "./run-completion-policy.js";
5
+ function parseEventJson(eventJson) {
6
+ if (!eventJson)
7
+ return undefined;
8
+ try {
9
+ const parsed = JSON.parse(eventJson);
10
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ function buildRunSummaryJson(report, publicationRecapSummary) {
17
+ return JSON.stringify({
18
+ latestAssistantMessage: report.assistantMessages.at(-1) ?? null,
19
+ publicationRecapSummary: publicationRecapSummary ?? null,
20
+ });
21
+ }
22
+ function shouldGeneratePublicationRecap(runType) {
23
+ return runType === "implementation"
24
+ || runType === "review_fix"
25
+ || runType === "branch_upkeep"
26
+ || runType === "ci_repair"
27
+ || runType === "queue_repair";
28
+ }
5
29
  export class RunFinalizer {
6
30
  db;
7
31
  logger;
@@ -13,8 +37,9 @@ export class RunFinalizer {
13
37
  failRunAndClear;
14
38
  completionPolicy;
15
39
  completionCheck;
40
+ publicationRecap;
16
41
  feed;
17
- constructor(db, logger, linearSync, enqueueIssue, withHeldLease, releaseLease, appendWakeEventWithLease, failRunAndClear, completionPolicy, completionCheck, feed) {
42
+ constructor(db, logger, linearSync, enqueueIssue, withHeldLease, releaseLease, appendWakeEventWithLease, failRunAndClear, completionPolicy, completionCheck, publicationRecap, feed) {
18
43
  this.db = db;
19
44
  this.logger = logger;
20
45
  this.linearSync = linearSync;
@@ -25,6 +50,7 @@ export class RunFinalizer {
25
50
  this.failRunAndClear = failRunAndClear;
26
51
  this.completionPolicy = completionPolicy;
27
52
  this.completionCheck = completionCheck;
53
+ this.publicationRecap = publicationRecap;
28
54
  this.feed = feed;
29
55
  }
30
56
  buildCompletedRunUpdate(params) {
@@ -32,10 +58,79 @@ export class RunFinalizer {
32
58
  status: "completed",
33
59
  threadId: params.threadId,
34
60
  ...(params.completedTurnId ? { turnId: params.completedTurnId } : {}),
35
- summaryJson: JSON.stringify({ latestAssistantMessage: params.report.assistantMessages.at(-1) ?? null }),
61
+ summaryJson: buildRunSummaryJson(params.report, params.publicationRecapSummary),
36
62
  reportJson: JSON.stringify(params.report),
37
63
  };
38
64
  }
65
+ resolveConsumedWakeEvent(run) {
66
+ return this.db.issueSessions
67
+ .listIssueSessionEvents(run.projectId, run.linearIssueId)
68
+ .filter((event) => event.consumedByRunId === run.id)
69
+ .at(-1);
70
+ }
71
+ resolvePublicationRecapFacts(params) {
72
+ const session = this.db.issueSessions.getIssueSession(params.run.projectId, params.run.linearIssueId);
73
+ const facts = {
74
+ ...(session?.lastWakeReason ? { wakeReason: session.lastWakeReason } : {}),
75
+ ...(params.postRunState ? { postRunState: params.postRunState } : {}),
76
+ ...(params.issue.prNumber !== undefined ? { prNumber: params.issue.prNumber } : {}),
77
+ ...(params.latestAssistantSummary ? { latestAssistantSummary: params.latestAssistantSummary } : {}),
78
+ };
79
+ const wakeEvent = this.resolveConsumedWakeEvent(params.run);
80
+ const payload = parseEventJson(wakeEvent?.eventJson);
81
+ if (!wakeEvent || !payload) {
82
+ return facts;
83
+ }
84
+ switch (wakeEvent.eventType) {
85
+ case "review_changes_requested":
86
+ return {
87
+ ...facts,
88
+ ...(typeof payload.reviewerName === "string" ? { reviewerName: payload.reviewerName } : {}),
89
+ ...(typeof payload.reviewBody === "string" ? { reviewSummary: payload.reviewBody } : {}),
90
+ };
91
+ case "settled_red_ci":
92
+ return {
93
+ ...facts,
94
+ ...(typeof payload.jobName === "string"
95
+ ? { failingCheckName: payload.jobName }
96
+ : typeof payload.checkName === "string" ? { failingCheckName: payload.checkName } : {}),
97
+ ...(typeof payload.summary === "string" ? { failureSummary: payload.summary } : {}),
98
+ };
99
+ case "merge_steward_incident":
100
+ return {
101
+ ...facts,
102
+ ...(typeof payload.incidentSummary === "string" ? { queueIncidentSummary: payload.incidentSummary } : {}),
103
+ };
104
+ default:
105
+ return facts;
106
+ }
107
+ }
108
+ async generatePublicationRecap(params) {
109
+ if (!this.publicationRecap || !shouldGeneratePublicationRecap(params.run.runType)) {
110
+ return undefined;
111
+ }
112
+ try {
113
+ const result = await this.publicationRecap.run({
114
+ issue: params.issue,
115
+ run: params.run,
116
+ facts: this.resolvePublicationRecapFacts({
117
+ run: params.run,
118
+ issue: params.issue,
119
+ postRunState: params.postRunState,
120
+ latestAssistantSummary: params.latestAssistantSummary,
121
+ }),
122
+ });
123
+ return result.summary;
124
+ }
125
+ catch (error) {
126
+ this.logger.warn({
127
+ runId: params.run.id,
128
+ issueKey: params.issue.issueKey,
129
+ error: error instanceof Error ? error.message : String(error),
130
+ }, "Publication recap failed; falling back to the main run summary");
131
+ return undefined;
132
+ }
133
+ }
39
134
  clearProgressAndRelease(run) {
40
135
  this.linearSync.clearProgress(run.id);
41
136
  this.releaseLease(run.projectId, run.linearIssueId);
@@ -142,14 +237,19 @@ export class RunFinalizer {
142
237
  const refreshedIssue = await this.completionPolicy.refreshIssueAfterReactivePublish(run, freshIssue);
143
238
  const postRunFollowUp = await this.completionPolicy.resolvePostRunFollowUp(run, refreshedIssue);
144
239
  const postRunState = postRunFollowUp?.factoryState ?? resolveCompletedRunState(refreshedIssue, run);
240
+ const publicationRecapSummary = await this.generatePublicationRecap({
241
+ run,
242
+ issue: refreshedIssue,
243
+ postRunState,
244
+ latestAssistantSummary: report.assistantMessages.at(-1),
245
+ });
145
246
  const completed = this.withHeldLease(run.projectId, run.linearIssueId, (lease) => {
146
- this.db.runs.finishRun(run.id, {
147
- status: "completed",
247
+ this.db.runs.finishRun(run.id, this.buildCompletedRunUpdate({
148
248
  threadId,
149
- ...(params.completedTurnId ? { turnId: params.completedTurnId } : {}),
150
- summaryJson: JSON.stringify({ latestAssistantMessage: report.assistantMessages.at(-1) ?? null }),
151
- reportJson: JSON.stringify(report),
152
- });
249
+ ...(params.completedTurnId ? { completedTurnId: params.completedTurnId } : {}),
250
+ report,
251
+ publicationRecapSummary,
252
+ }));
153
253
  this.db.issues.upsertIssue({
154
254
  projectId: run.projectId,
155
255
  linearIssueId: run.linearIssueId,
@@ -204,10 +304,12 @@ export class RunFinalizer {
204
304
  summary: params.source === "notification"
205
305
  ? `Turn completed for ${run.runType}`
206
306
  : `Reconciliation: ${run.runType} completed${postRunState ? ` -> ${postRunState}` : ""}`,
207
- detail: report.assistantMessages.at(-1),
307
+ detail: publicationRecapSummary ?? report.assistantMessages.at(-1),
208
308
  });
209
309
  const updatedIssue = this.db.issues.getIssue(run.projectId, run.linearIssueId) ?? refreshedIssue;
210
- const completionSummary = report.assistantMessages.at(-1)?.slice(0, 300) ?? `${run.runType} completed.`;
310
+ const completionSummary = publicationRecapSummary
311
+ ?? report.assistantMessages.at(-1)?.slice(0, 300)
312
+ ?? `${run.runType} completed.`;
211
313
  const linearActivity = buildRunCompletedActivity({
212
314
  runType: run.runType,
213
315
  completionSummary,
@@ -1,6 +1,7 @@
1
1
  import { summarizeCurrentThread } from "./run-reporting.js";
2
2
  import { buildRunStartedActivity, } from "./linear-session-reporting.js";
3
3
  import { CompletionCheckService } from "./completion-check.js";
4
+ import { PublicationRecapService } from "./publication-recap.js";
4
5
  import { WorktreeManager } from "./worktree-manager.js";
5
6
  import { MergedLinearCompletionReconciler } from "./merged-linear-completion-reconciler.js";
6
7
  import { MainBranchHealthMonitor } from "./main-branch-health-monitor.js";
@@ -59,6 +60,7 @@ export class RunOrchestrator {
59
60
  interruptedRunRecovery;
60
61
  runCompletionPolicy;
61
62
  completionCheck;
63
+ publicationRecap;
62
64
  runNotificationHandler;
63
65
  runReconciler;
64
66
  mergedLinearCompletionReconciler;
@@ -95,7 +97,8 @@ export class RunOrchestrator {
95
97
  this.activeSessionLeases = this.leaseService.activeSessionLeases;
96
98
  this.runCompletionPolicy = new RunCompletionPolicy(config, db, logger, this.leasePorts.withHeldLease);
97
99
  this.completionCheck = new CompletionCheckService(codex, logger);
98
- this.runFinalizer = new RunFinalizer(db, logger, this.linearSync, this.enqueueIssue, this.leasePorts.withHeldLease, this.leasePorts.releaseLease, (lease, issue, runType, context, dedupeScope) => this.appendWakeEventWithLease(lease, issue, runType, context, dedupeScope), this.recoveryPorts.failRunAndClear, this.runCompletionPolicy, this.completionCheck, feed);
100
+ this.publicationRecap = new PublicationRecapService(codex, logger);
101
+ this.runFinalizer = new RunFinalizer(db, logger, this.linearSync, this.enqueueIssue, this.leasePorts.withHeldLease, this.leasePorts.releaseLease, (lease, issue, runType, context, dedupeScope) => this.appendWakeEventWithLease(lease, issue, runType, context, dedupeScope), this.recoveryPorts.failRunAndClear, this.runCompletionPolicy, this.completionCheck, this.publicationRecap, feed);
99
102
  this.runLauncher = new RunLauncher(config, db, codex, logger, this.worktreeManager);
100
103
  this.runNotificationHandler = new RunNotificationHandler(config, db, logger, this.linearSync, this.runFinalizer, this.threadPorts.readThreadWithRetry, this.leasePorts.withHeldLease, this.leasePorts.heartbeatLease, this.leasePorts.releaseLease, feed);
101
104
  this.runRecovery = new RunRecoveryService(db, logger, this.linearSync, this.leasePorts.withHeldLease, this.leasePorts.getHeldLease, (lease, issue, runType, context, dedupeScope) => this.appendWakeEventWithLease(lease, issue, runType, context, dedupeScope), this.leasePorts.releaseLease, (projectId, issueId) => this.enqueueIssue(projectId, issueId), feed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.51.0",
3
+ "version": "0.51.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {