patchrelay 0.36.17 → 0.36.18

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.36.17",
4
- "commit": "a9609f2ce42d",
5
- "builtAt": "2026-04-10T12:32:40.306Z"
3
+ "version": "0.36.18",
4
+ "commit": "2d0aa4ca856e",
5
+ "builtAt": "2026-04-10T12:35:59.590Z"
6
6
  }
@@ -1,13 +1,13 @@
1
- import { extractStageSummary, summarizeCurrentThread } from "./run-reporting.js";
2
- import { IssueOverviewQuery, parseStageReport, } from "./issue-overview-query.js";
1
+ import { IssueOverviewQuery, } from "./issue-overview-query.js";
2
+ import { PublicAgentSessionStatusQuery } from "./public-agent-session-status-query.js";
3
3
  export class IssueQueryService {
4
- db;
5
4
  runStatusProvider;
6
5
  overviewQuery;
6
+ publicStatusQuery;
7
7
  constructor(db, codex, runStatusProvider) {
8
- this.db = db;
9
8
  this.runStatusProvider = runStatusProvider;
10
9
  this.overviewQuery = new IssueOverviewQuery(db, codex, runStatusProvider);
10
+ this.publicStatusQuery = new PublicAgentSessionStatusQuery(db, this.overviewQuery);
11
11
  }
12
12
  async getIssueOverview(issueKey) {
13
13
  return await this.overviewQuery.getIssueOverview(issueKey);
@@ -16,45 +16,6 @@ export class IssueQueryService {
16
16
  return await this.runStatusProvider.getActiveRunStatus(issueKey);
17
17
  }
18
18
  async getPublicAgentSessionStatus(issueKey) {
19
- const overview = await this.overviewQuery.getIssueOverview(issueKey);
20
- if (!overview)
21
- return undefined;
22
- const issueRecord = this.db.issues.getIssueByKey(issueKey);
23
- const latestRunReport = parseStageReport(overview.latestRun?.reportJson, overview.latestRun?.status ?? "unknown");
24
- const runs = (overview.runs ?? this.overviewQuery.buildRuns(overview.issue.projectId, overview.issue.linearIssueId)).map((run) => ({
25
- run: {
26
- id: run.id,
27
- runType: run.runType,
28
- status: run.status,
29
- startedAt: run.startedAt,
30
- ...(run.endedAt ? { endedAt: run.endedAt } : {}),
31
- },
32
- ...(run.report ? { report: run.report } : {}),
33
- }));
34
- return {
35
- issue: {
36
- issueKey: overview.issue.issueKey,
37
- title: overview.issue.title,
38
- issueUrl: overview.issue.issueUrl,
39
- currentLinearState: overview.issue.currentLinearState,
40
- ...(overview.session?.sessionState ? { sessionState: overview.session.sessionState } : {}),
41
- factoryState: overview.issue.factoryState,
42
- ...(overview.session?.prNumber !== undefined ? { prNumber: overview.session.prNumber } : {}),
43
- ...(issueRecord?.prUrl ? { prUrl: issueRecord.prUrl } : {}),
44
- ...(issueRecord?.prState ? { prState: issueRecord.prState } : {}),
45
- ...(issueRecord?.prReviewState ? { prReviewState: issueRecord.prReviewState } : {}),
46
- ...(issueRecord?.prCheckStatus ? { prCheckStatus: issueRecord.prCheckStatus } : {}),
47
- ...(issueRecord ? { ciRepairAttempts: issueRecord.ciRepairAttempts, queueRepairAttempts: issueRecord.queueRepairAttempts } : {}),
48
- ...(overview.issue.waitingReason ? { waitingReason: overview.issue.waitingReason } : {}),
49
- ...(overview.issue.statusNote ? { statusNote: overview.issue.statusNote } : {}),
50
- ...(overview.session?.lastWakeReason ? { lastWakeReason: overview.session.lastWakeReason } : {}),
51
- },
52
- ...(overview.activeRun ? { activeRun: overview.activeRun } : {}),
53
- ...(overview.latestRun ? { latestRun: overview.latestRun } : {}),
54
- ...(overview.liveThread ? { liveThread: summarizeCurrentThread(overview.liveThread) } : {}),
55
- ...(latestRunReport ? { latestReportSummary: extractStageSummary(latestRunReport) } : {}),
56
- runs,
57
- generatedAt: new Date().toISOString(),
58
- };
19
+ return await this.publicStatusQuery.getStatus(issueKey);
59
20
  }
60
21
  }
@@ -0,0 +1,48 @@
1
+ import { resolvePreferredCompletedLinearState } from "./linear-workflow.js";
2
+ export class MergedLinearCompletionReconciler {
3
+ db;
4
+ linearProvider;
5
+ logger;
6
+ constructor(db, linearProvider, logger) {
7
+ this.db = db;
8
+ this.linearProvider = linearProvider;
9
+ this.logger = logger;
10
+ }
11
+ async reconcile() {
12
+ for (const issue of this.db.issues.listIssues()) {
13
+ if (issue.prState !== "merged")
14
+ continue;
15
+ if (issue.currentLinearStateType?.trim().toLowerCase() === "completed")
16
+ continue;
17
+ const linear = await this.linearProvider.forProject(issue.projectId).catch(() => undefined);
18
+ if (!linear)
19
+ continue;
20
+ try {
21
+ const liveIssue = await linear.getIssue(issue.linearIssueId);
22
+ const targetState = resolvePreferredCompletedLinearState(liveIssue);
23
+ if (!targetState)
24
+ continue;
25
+ const normalizedCurrent = liveIssue.stateName?.trim().toLowerCase();
26
+ if (normalizedCurrent === targetState.trim().toLowerCase()) {
27
+ this.db.issues.upsertIssue({
28
+ projectId: issue.projectId,
29
+ linearIssueId: issue.linearIssueId,
30
+ ...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
31
+ ...(liveIssue.stateType ? { currentLinearStateType: liveIssue.stateType } : {}),
32
+ });
33
+ continue;
34
+ }
35
+ const updated = await linear.setIssueState(issue.linearIssueId, targetState);
36
+ this.db.issues.upsertIssue({
37
+ projectId: issue.projectId,
38
+ linearIssueId: issue.linearIssueId,
39
+ ...(updated.stateName ? { currentLinearState: updated.stateName } : {}),
40
+ ...(updated.stateType ? { currentLinearStateType: updated.stateType } : {}),
41
+ });
42
+ }
43
+ catch (error) {
44
+ this.logger.warn({ issueKey: issue.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to reconcile merged issue to a completed Linear state");
45
+ }
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,52 @@
1
+ import { extractStageSummary, summarizeCurrentThread } from "./run-reporting.js";
2
+ import { parseStageReport } from "./issue-overview-query.js";
3
+ export class PublicAgentSessionStatusQuery {
4
+ db;
5
+ overviewQuery;
6
+ constructor(db, overviewQuery) {
7
+ this.db = db;
8
+ this.overviewQuery = overviewQuery;
9
+ }
10
+ async getStatus(issueKey) {
11
+ const overview = await this.overviewQuery.getIssueOverview(issueKey);
12
+ if (!overview)
13
+ return undefined;
14
+ const issueRecord = this.db.issues.getIssueByKey(issueKey);
15
+ const latestRunReport = parseStageReport(overview.latestRun?.reportJson, overview.latestRun?.status ?? "unknown");
16
+ const runs = (overview.runs ?? this.overviewQuery.buildRuns(overview.issue.projectId, overview.issue.linearIssueId)).map((run) => ({
17
+ run: {
18
+ id: run.id,
19
+ runType: run.runType,
20
+ status: run.status,
21
+ startedAt: run.startedAt,
22
+ ...(run.endedAt ? { endedAt: run.endedAt } : {}),
23
+ },
24
+ ...(run.report ? { report: run.report } : {}),
25
+ }));
26
+ return {
27
+ issue: {
28
+ issueKey: overview.issue.issueKey,
29
+ title: overview.issue.title,
30
+ issueUrl: overview.issue.issueUrl,
31
+ currentLinearState: overview.issue.currentLinearState,
32
+ ...(overview.session?.sessionState ? { sessionState: overview.session.sessionState } : {}),
33
+ factoryState: overview.issue.factoryState,
34
+ ...(overview.session?.prNumber !== undefined ? { prNumber: overview.session.prNumber } : {}),
35
+ ...(issueRecord?.prUrl ? { prUrl: issueRecord.prUrl } : {}),
36
+ ...(issueRecord?.prState ? { prState: issueRecord.prState } : {}),
37
+ ...(issueRecord?.prReviewState ? { prReviewState: issueRecord.prReviewState } : {}),
38
+ ...(issueRecord?.prCheckStatus ? { prCheckStatus: issueRecord.prCheckStatus } : {}),
39
+ ...(issueRecord ? { ciRepairAttempts: issueRecord.ciRepairAttempts, queueRepairAttempts: issueRecord.queueRepairAttempts } : {}),
40
+ ...(overview.issue.waitingReason ? { waitingReason: overview.issue.waitingReason } : {}),
41
+ ...(overview.issue.statusNote ? { statusNote: overview.issue.statusNote } : {}),
42
+ ...(overview.session?.lastWakeReason ? { lastWakeReason: overview.session.lastWakeReason } : {}),
43
+ },
44
+ ...(overview.activeRun ? { activeRun: overview.activeRun } : {}),
45
+ ...(overview.latestRun ? { latestRun: overview.latestRun } : {}),
46
+ ...(overview.liveThread ? { liveThread: summarizeCurrentThread(overview.liveThread) } : {}),
47
+ ...(latestRunReport ? { latestReportSummary: extractStageSummary(latestRunReport) } : {}),
48
+ runs,
49
+ generatedAt: new Date().toISOString(),
50
+ };
51
+ }
52
+ }
@@ -2,7 +2,7 @@ import { summarizeCurrentThread } from "./run-reporting.js";
2
2
  import { buildRunStartedActivity, } from "./linear-session-reporting.js";
3
3
  import { CompletionCheckService } from "./completion-check.js";
4
4
  import { WorktreeManager } from "./worktree-manager.js";
5
- import { resolvePreferredCompletedLinearState } from "./linear-workflow.js";
5
+ import { MergedLinearCompletionReconciler } from "./merged-linear-completion-reconciler.js";
6
6
  import { QueueHealthMonitor } from "./queue-health-monitor.js";
7
7
  import { IdleIssueReconciler, resolveBranchOwnerForStateTransition } from "./idle-reconciliation.js";
8
8
  import { LinearSessionSync } from "./linear-session-sync.js";
@@ -53,6 +53,7 @@ export class RunOrchestrator {
53
53
  completionCheck;
54
54
  runNotificationHandler;
55
55
  runReconciler;
56
+ mergedLinearCompletionReconciler;
56
57
  activeSessionLeases;
57
58
  botIdentity;
58
59
  constructor(config, db, codex, linearProvider, enqueueIssue, logger, feed) {
@@ -79,6 +80,7 @@ export class RunOrchestrator {
79
80
  this.idleReconciler = new IdleIssueReconciler(db, config, {
80
81
  enqueueIssue: (projectId, issueId) => this.enqueueIssue(projectId, issueId),
81
82
  }, logger, feed);
83
+ this.mergedLinearCompletionReconciler = new MergedLinearCompletionReconciler(db, linearProvider, logger);
82
84
  this.queueHealthMonitor = new QueueHealthMonitor(db, config, {
83
85
  advanceIdleIssue: (issue, newState, options) => this.idleReconciler.advanceIdleIssue(issue, newState, options),
84
86
  enqueueIssue: (projectId, issueId) => this.enqueueIssue(projectId, issueId),
@@ -254,44 +256,7 @@ export class RunOrchestrator {
254
256
  // Advance issues stuck in pr_open whose stored PR metadata already
255
257
  // shows they should transition (e.g. approved PR, missed webhook).
256
258
  await this.idleReconciler.reconcile();
257
- await this.reconcileMergedLinearCompletion();
258
- }
259
- async reconcileMergedLinearCompletion() {
260
- for (const issue of this.db.issues.listIssues()) {
261
- if (issue.prState !== "merged")
262
- continue;
263
- if (issue.currentLinearStateType?.trim().toLowerCase() === "completed")
264
- continue;
265
- const linear = await this.linearProvider.forProject(issue.projectId).catch(() => undefined);
266
- if (!linear)
267
- continue;
268
- try {
269
- const liveIssue = await linear.getIssue(issue.linearIssueId);
270
- const targetState = resolvePreferredCompletedLinearState(liveIssue);
271
- if (!targetState)
272
- continue;
273
- const normalizedCurrent = liveIssue.stateName?.trim().toLowerCase();
274
- if (normalizedCurrent === targetState.trim().toLowerCase()) {
275
- this.db.issues.upsertIssue({
276
- projectId: issue.projectId,
277
- linearIssueId: issue.linearIssueId,
278
- ...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
279
- ...(liveIssue.stateType ? { currentLinearStateType: liveIssue.stateType } : {}),
280
- });
281
- continue;
282
- }
283
- const updated = await linear.setIssueState(issue.linearIssueId, targetState);
284
- this.db.issues.upsertIssue({
285
- projectId: issue.projectId,
286
- linearIssueId: issue.linearIssueId,
287
- ...(updated.stateName ? { currentLinearState: updated.stateName } : {}),
288
- ...(updated.stateType ? { currentLinearStateType: updated.stateType } : {}),
289
- });
290
- }
291
- catch (error) {
292
- this.logger.warn({ issueKey: issue.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to reconcile merged issue to a completed Linear state");
293
- }
294
- }
259
+ await this.mergedLinearCompletionReconciler.reconcile();
295
260
  }
296
261
  // advanceIdleIssue is now on IdleIssueReconciler — delegate for internal callers
297
262
  advanceIdleIssue(issue, newState, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.36.17",
3
+ "version": "0.36.18",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {