patchrelay 0.12.5 → 0.12.6
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/github-webhook-handler.js +19 -19
- package/dist/run-orchestrator.js +23 -0
- package/dist/service-runtime.js +6 -1
- package/dist/service.js +23 -6
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -226,32 +226,32 @@ export class GitHubWebhookHandler {
|
|
|
226
226
|
async emitLinearActivity(issue, newState, event) {
|
|
227
227
|
if (!issue.agentSessionId)
|
|
228
228
|
return;
|
|
229
|
-
const linear = await this.linearProvider.forProject(issue.projectId);
|
|
230
|
-
if (!linear?.createAgentActivity)
|
|
231
|
-
return;
|
|
232
|
-
const messages = {
|
|
233
|
-
pr_open: `PR #${event.prNumber ?? ""} opened.${event.prUrl ? ` ${event.prUrl}` : ""}`,
|
|
234
|
-
awaiting_queue: "PR approved. Preparing merge.",
|
|
235
|
-
changes_requested: `Review requested changes.${event.reviewerName ? ` Reviewer: ${event.reviewerName}` : ""}`,
|
|
236
|
-
repairing_ci: `CI check failed${event.checkName ? `: ${event.checkName}` : ""}. Starting repair.`,
|
|
237
|
-
repairing_queue: "Merge conflict with base branch. Starting repair.",
|
|
238
|
-
done: `PR merged and deployed.${event.prNumber ? ` PR #${event.prNumber}` : ""}`,
|
|
239
|
-
failed: "PR was closed without merging.",
|
|
240
|
-
};
|
|
241
|
-
const body = messages[newState];
|
|
242
|
-
if (!body)
|
|
243
|
-
return;
|
|
244
|
-
const type = newState === "failed" || newState === "repairing_ci" || newState === "repairing_queue"
|
|
245
|
-
? "error"
|
|
246
|
-
: "response";
|
|
247
229
|
try {
|
|
230
|
+
const linear = await this.linearProvider.forProject(issue.projectId);
|
|
231
|
+
if (!linear?.createAgentActivity)
|
|
232
|
+
return;
|
|
233
|
+
const messages = {
|
|
234
|
+
pr_open: `PR #${event.prNumber ?? ""} opened.${event.prUrl ? ` ${event.prUrl}` : ""}`,
|
|
235
|
+
awaiting_queue: "PR approved. Preparing merge.",
|
|
236
|
+
changes_requested: `Review requested changes.${event.reviewerName ? ` Reviewer: ${event.reviewerName}` : ""}`,
|
|
237
|
+
repairing_ci: `CI check failed${event.checkName ? `: ${event.checkName}` : ""}. Starting repair.`,
|
|
238
|
+
repairing_queue: "Merge conflict with base branch. Starting repair.",
|
|
239
|
+
done: `PR merged and deployed.${event.prNumber ? ` PR #${event.prNumber}` : ""}`,
|
|
240
|
+
failed: "PR was closed without merging.",
|
|
241
|
+
};
|
|
242
|
+
const body = messages[newState];
|
|
243
|
+
if (!body)
|
|
244
|
+
return;
|
|
245
|
+
const type = newState === "failed" || newState === "repairing_ci" || newState === "repairing_queue"
|
|
246
|
+
? "error"
|
|
247
|
+
: "response";
|
|
248
248
|
await linear.createAgentActivity({
|
|
249
249
|
agentSessionId: issue.agentSessionId,
|
|
250
250
|
content: { type, body },
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
253
|
catch {
|
|
254
|
-
// Non-blocking — don't
|
|
254
|
+
// Non-blocking — don't crash the webhook handler for a Linear activity error
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
}
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { ACTIVE_RUN_STATES } from "./factory-state.js";
|
|
3
4
|
import { buildHookEnv, runProjectHook } from "./hook-runner.js";
|
|
4
5
|
import { buildRunningSessionPlan, buildCompletedSessionPlan, buildFailedSessionPlan, } from "./agent-session-plan.js";
|
|
5
6
|
import { buildStageReport, countEventMethods, extractTurnId, resolveRunCompletionStatus, summarizeCurrentThread, } from "./run-reporting.js";
|
|
@@ -275,6 +276,22 @@ export class RunOrchestrator {
|
|
|
275
276
|
// Complete the run
|
|
276
277
|
const trackedIssue = this.db.issueToTrackedIssue(issue);
|
|
277
278
|
const report = buildStageReport(run, trackedIssue, thread, countEventMethods(this.db.listThreadEvents(run.id)));
|
|
279
|
+
// Determine post-run state. When a re-run finds the PR already exists
|
|
280
|
+
// and makes no changes, no pr_opened webhook arrives — the state would
|
|
281
|
+
// stay in the active-run state forever. Advance based on PR metadata.
|
|
282
|
+
const freshIssue = this.db.getIssue(run.projectId, run.linearIssueId) ?? issue;
|
|
283
|
+
let postRunState;
|
|
284
|
+
if (ACTIVE_RUN_STATES.has(freshIssue.factoryState) && freshIssue.prNumber) {
|
|
285
|
+
if (freshIssue.prReviewState === "approved") {
|
|
286
|
+
postRunState = "awaiting_queue";
|
|
287
|
+
}
|
|
288
|
+
else if (freshIssue.prState === "merged") {
|
|
289
|
+
postRunState = "done";
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
postRunState = "pr_open";
|
|
293
|
+
}
|
|
294
|
+
}
|
|
278
295
|
this.db.transaction(() => {
|
|
279
296
|
this.db.finishRun(run.id, {
|
|
280
297
|
status: "completed",
|
|
@@ -287,8 +304,14 @@ export class RunOrchestrator {
|
|
|
287
304
|
projectId: run.projectId,
|
|
288
305
|
linearIssueId: run.linearIssueId,
|
|
289
306
|
activeRunId: null,
|
|
307
|
+
...(postRunState ? { factoryState: postRunState } : {}),
|
|
308
|
+
...(postRunState === "awaiting_queue" ? { pendingMergePrep: true } : {}),
|
|
290
309
|
});
|
|
291
310
|
});
|
|
311
|
+
// If we advanced to awaiting_queue, enqueue for merge prep
|
|
312
|
+
if (postRunState === "awaiting_queue") {
|
|
313
|
+
this.enqueueIssue(run.projectId, run.linearIssueId);
|
|
314
|
+
}
|
|
292
315
|
this.feed?.publish({
|
|
293
316
|
level: "info",
|
|
294
317
|
kind: "turn",
|
package/dist/service-runtime.js
CHANGED
|
@@ -14,6 +14,7 @@ export class ServiceRuntime {
|
|
|
14
14
|
webhookQueue;
|
|
15
15
|
issueQueue;
|
|
16
16
|
ready = false;
|
|
17
|
+
linearConnected = false;
|
|
17
18
|
startupError;
|
|
18
19
|
reconcileTimer;
|
|
19
20
|
reconcileInProgress = false;
|
|
@@ -54,10 +55,14 @@ export class ServiceRuntime {
|
|
|
54
55
|
enqueueIssue(projectId, issueId) {
|
|
55
56
|
this.issueQueue.enqueue({ projectId, issueId });
|
|
56
57
|
}
|
|
58
|
+
setLinearConnected(connected) {
|
|
59
|
+
this.linearConnected = connected;
|
|
60
|
+
}
|
|
57
61
|
getReadiness() {
|
|
58
62
|
return {
|
|
59
|
-
ready: this.ready && this.codex.isStarted(),
|
|
63
|
+
ready: this.ready && this.codex.isStarted() && this.linearConnected,
|
|
60
64
|
codexStarted: this.codex.isStarted(),
|
|
65
|
+
linearConnected: this.linearConnected,
|
|
61
66
|
...(this.startupError ? { startupError: this.startupError } : {}),
|
|
62
67
|
};
|
|
63
68
|
}
|
package/dist/service.js
CHANGED
|
@@ -73,24 +73,37 @@ export class PatchRelayService {
|
|
|
73
73
|
this.githubAppTokenManager = createGitHubAppTokenManager(ghAppCredentials, logger);
|
|
74
74
|
}
|
|
75
75
|
this.codex.on("notification", (notification) => {
|
|
76
|
-
|
|
76
|
+
this.orchestrator.handleCodexNotification(notification).catch((error) => {
|
|
77
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
78
|
+
logger.error({ method: notification.method, error: msg }, "Unhandled error in Codex notification handler");
|
|
79
|
+
});
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
async start() {
|
|
80
83
|
// Verify Linear connectivity for all configured projects before starting.
|
|
81
|
-
//
|
|
84
|
+
// Auth errors do not prevent startup (the OAuth callback must be reachable
|
|
85
|
+
// for `patchrelay connect`), but the service reports NOT READY until at
|
|
86
|
+
// least one project has a working Linear token.
|
|
87
|
+
let anyLinearConnected = false;
|
|
82
88
|
for (const project of this.config.projects) {
|
|
83
89
|
try {
|
|
84
90
|
const client = await this.linearProvider.forProject(project.id);
|
|
85
|
-
if (
|
|
86
|
-
|
|
91
|
+
if (client) {
|
|
92
|
+
anyLinearConnected = true;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.logger.warn({ projectId: project.id }, "No Linear installation linked — run 'patchrelay connect' to authorize");
|
|
87
96
|
}
|
|
88
97
|
}
|
|
89
98
|
catch (error) {
|
|
90
99
|
const msg = error instanceof Error ? error.message : String(error);
|
|
91
|
-
|
|
100
|
+
this.logger.error({ projectId: project.id, error: msg }, "Linear auth failed — run 'patchrelay connect' to refresh the token. Runs for this project will fail until re-authorized.");
|
|
92
101
|
}
|
|
93
102
|
}
|
|
103
|
+
this.runtime.setLinearConnected(anyLinearConnected);
|
|
104
|
+
if (!anyLinearConnected && this.config.projects.length > 0) {
|
|
105
|
+
this.logger.error("No projects have working Linear auth — service is NOT READY. Run 'patchrelay connect' to authorize.");
|
|
106
|
+
}
|
|
94
107
|
if (this.githubAppTokenManager) {
|
|
95
108
|
await ensureGhWrapper(this.logger);
|
|
96
109
|
await this.githubAppTokenManager.start();
|
|
@@ -110,7 +123,11 @@ export class PatchRelayService {
|
|
|
110
123
|
return await this.oauthService.createStart(params);
|
|
111
124
|
}
|
|
112
125
|
async completeLinearOAuth(params) {
|
|
113
|
-
|
|
126
|
+
const result = await this.oauthService.complete(params);
|
|
127
|
+
// A successful OAuth completion means at least one project now has
|
|
128
|
+
// working Linear auth — update readiness.
|
|
129
|
+
this.runtime.setLinearConnected(true);
|
|
130
|
+
return result;
|
|
114
131
|
}
|
|
115
132
|
getLinearOAuthStateStatus(state) {
|
|
116
133
|
return this.oauthService.getStateStatus(state);
|