patchrelay 0.36.11 → 0.36.12
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/db.js +10 -45
- package/dist/implementation-outcome-policy.js +127 -0
- package/dist/issue-overview-query.js +232 -0
- package/dist/issue-query-service.js +6 -218
- package/dist/reactive-run-policy.js +288 -0
- package/dist/run-completion-policy.js +12 -378
- package/dist/run-notification-handler.js +123 -0
- package/dist/run-orchestrator.js +12 -210
- package/dist/run-reconciler.js +132 -0
- package/dist/tracked-issue-query.js +67 -0
- package/dist/webhook-handler.js +12 -93
- package/dist/webhooks/context-loader.js +70 -0
- package/dist/webhooks/dependency-readiness-handler.js +52 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/db.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deriveIssueSessionReactiveIntent, } from "./issue-session.js";
|
|
2
2
|
import {} from "./issue-session-events.js";
|
|
3
3
|
import { IssueStore } from "./db/issue-store.js";
|
|
4
4
|
import { IssueSessionStore } from "./db/issue-session-store.js";
|
|
@@ -10,7 +10,7 @@ import { WebhookEventStore } from "./db/webhook-event-store.js";
|
|
|
10
10
|
import { runPatchRelayMigrations } from "./db/migrations.js";
|
|
11
11
|
import { SqliteConnection } from "./db/shared.js";
|
|
12
12
|
import { syncIssueSessionFromIssue } from "./issue-session-projector.js";
|
|
13
|
-
import {
|
|
13
|
+
import { TrackedIssueQuery } from "./tracked-issue-query.js";
|
|
14
14
|
function parseObjectJson(raw) {
|
|
15
15
|
if (!raw)
|
|
16
16
|
return undefined;
|
|
@@ -97,6 +97,7 @@ export class PatchRelayDatabase {
|
|
|
97
97
|
issues;
|
|
98
98
|
issueSessions;
|
|
99
99
|
runs;
|
|
100
|
+
trackedIssues;
|
|
100
101
|
constructor(databasePath, wal) {
|
|
101
102
|
this.connection = new SqliteConnection(databasePath);
|
|
102
103
|
this.connection.pragma("foreign_keys = ON");
|
|
@@ -117,6 +118,7 @@ export class PatchRelayDatabase {
|
|
|
117
118
|
...(options ? { options } : {}),
|
|
118
119
|
}));
|
|
119
120
|
this.issueSessions = new IssueSessionStore(this.connection, mapIssueSessionRow, mapIssueSessionEventRow, this.issues, this.runs, deriveImplicitReactiveWake);
|
|
121
|
+
this.trackedIssues = new TrackedIssueQuery(this.issues, this.issueSessions, this.runs);
|
|
120
122
|
}
|
|
121
123
|
runMigrations() {
|
|
122
124
|
runPatchRelayMigrations(this.connection);
|
|
@@ -161,27 +163,7 @@ export class PatchRelayDatabase {
|
|
|
161
163
|
return this.issues.countUnresolvedBlockers(projectId, linearIssueId);
|
|
162
164
|
}
|
|
163
165
|
listIssuesReadyForExecution() {
|
|
164
|
-
return this.
|
|
165
|
-
.filter((issue) => isIssueSessionReadyForExecution({
|
|
166
|
-
factoryState: issue.factoryState,
|
|
167
|
-
sessionState: deriveIssueSessionState({
|
|
168
|
-
activeRunId: issue.activeRunId,
|
|
169
|
-
factoryState: issue.factoryState,
|
|
170
|
-
}),
|
|
171
|
-
activeRunId: issue.activeRunId,
|
|
172
|
-
blockedByCount: this.issues.countUnresolvedBlockers(issue.projectId, issue.linearIssueId),
|
|
173
|
-
hasPendingWake: this.issueSessions.peekIssueSessionWake(issue.projectId, issue.linearIssueId) !== undefined,
|
|
174
|
-
hasLegacyPendingRun: issue.pendingRunType !== undefined,
|
|
175
|
-
prNumber: issue.prNumber,
|
|
176
|
-
prState: issue.prState,
|
|
177
|
-
prReviewState: issue.prReviewState,
|
|
178
|
-
prCheckStatus: issue.prCheckStatus,
|
|
179
|
-
latestFailureSource: issue.lastGitHubFailureSource,
|
|
180
|
-
}))
|
|
181
|
-
.map((issue) => ({
|
|
182
|
-
projectId: issue.projectId,
|
|
183
|
-
linearIssueId: issue.linearIssueId,
|
|
184
|
-
}));
|
|
166
|
+
return this.trackedIssues.listIssuesReadyForExecution();
|
|
185
167
|
}
|
|
186
168
|
/**
|
|
187
169
|
* Issues idle in pr_open with no active run — candidates for state
|
|
@@ -205,26 +187,17 @@ export class PatchRelayDatabase {
|
|
|
205
187
|
return this.issues.listAwaitingQueueIssues();
|
|
206
188
|
}
|
|
207
189
|
listIssuesByState(projectId, state) {
|
|
208
|
-
return this.
|
|
190
|
+
return this.trackedIssues.listIssuesByState(projectId, state);
|
|
209
191
|
}
|
|
210
192
|
// ─── View builders ──────────────────────────────────────────────
|
|
211
193
|
issueToTrackedIssue(issue) {
|
|
212
|
-
return
|
|
213
|
-
issue,
|
|
214
|
-
session: this.issueSessions.getIssueSession(issue.projectId, issue.linearIssueId),
|
|
215
|
-
blockedBy: this.issues.listIssueDependencies(issue.projectId, issue.linearIssueId),
|
|
216
|
-
hasPendingWake: this.issueSessions.peekIssueSessionWake(issue.projectId, issue.linearIssueId) !== undefined,
|
|
217
|
-
latestRun: this.runs.getLatestRunForIssue(issue.projectId, issue.linearIssueId),
|
|
218
|
-
latestEvent: this.issueSessions.listIssueSessionEvents(issue.projectId, issue.linearIssueId, { limit: 1 }).at(-1),
|
|
219
|
-
});
|
|
194
|
+
return this.trackedIssues.issueToTrackedIssue(issue);
|
|
220
195
|
}
|
|
221
196
|
getTrackedIssue(projectId, linearIssueId) {
|
|
222
|
-
|
|
223
|
-
return issue ? this.issueToTrackedIssue(issue) : undefined;
|
|
197
|
+
return this.trackedIssues.getTrackedIssue(projectId, linearIssueId);
|
|
224
198
|
}
|
|
225
199
|
getTrackedIssueByKey(issueKey) {
|
|
226
|
-
|
|
227
|
-
return issue ? this.issueToTrackedIssue(issue) : undefined;
|
|
200
|
+
return this.trackedIssues.getTrackedIssueByKey(issueKey);
|
|
228
201
|
}
|
|
229
202
|
listIssues() {
|
|
230
203
|
return this.issues.listIssues();
|
|
@@ -234,15 +207,7 @@ export class PatchRelayDatabase {
|
|
|
234
207
|
}
|
|
235
208
|
// ─── Issue overview for query service ─────────────────────────────
|
|
236
209
|
getIssueOverview(issueKey) {
|
|
237
|
-
|
|
238
|
-
if (!issue)
|
|
239
|
-
return undefined;
|
|
240
|
-
const tracked = this.issueToTrackedIssue(issue);
|
|
241
|
-
const activeRun = issue.activeRunId ? this.runs.getRunById(issue.activeRunId) : undefined;
|
|
242
|
-
return {
|
|
243
|
-
issue: tracked,
|
|
244
|
-
...(activeRun ? { activeRun } : {}),
|
|
245
|
-
};
|
|
210
|
+
return this.trackedIssues.getIssueOverview(issueKey);
|
|
246
211
|
}
|
|
247
212
|
}
|
|
248
213
|
// ─── Row mappers ──────────────────────────────────────────────────
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { execCommand } from "./utils.js";
|
|
2
|
+
import { resolveImplementationDeliveryMode, } from "./prompting/patchrelay.js";
|
|
3
|
+
export class ImplementationOutcomePolicy {
|
|
4
|
+
config;
|
|
5
|
+
db;
|
|
6
|
+
logger;
|
|
7
|
+
withHeldLease;
|
|
8
|
+
constructor(config, db, logger, withHeldLease) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this.withHeldLease = withHeldLease;
|
|
13
|
+
}
|
|
14
|
+
async verifyPublishedRunOutcome(run, issue) {
|
|
15
|
+
if (run.runType !== "implementation") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const project = this.config.projects.find((entry) => entry.id === run.projectId);
|
|
19
|
+
const baseBranch = project?.github?.baseBranch ?? "main";
|
|
20
|
+
const deliveryMode = resolveImplementationDeliveryMode(issue, undefined, run.promptText);
|
|
21
|
+
if (deliveryMode === "linear_only") {
|
|
22
|
+
if (issue.prNumber !== undefined) {
|
|
23
|
+
return `Planning-only implementation should not open a PR, but PR #${issue.prNumber} was observed`;
|
|
24
|
+
}
|
|
25
|
+
return this.describeLocalImplementationOutcome(issue, baseBranch, deliveryMode);
|
|
26
|
+
}
|
|
27
|
+
if (issue.prNumber && issue.prState && issue.prState !== "closed") {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (project?.github?.repoFullName && issue.branchName) {
|
|
31
|
+
try {
|
|
32
|
+
const { stdout, exitCode } = await execCommand("gh", [
|
|
33
|
+
"pr",
|
|
34
|
+
"list",
|
|
35
|
+
"--repo",
|
|
36
|
+
project.github.repoFullName,
|
|
37
|
+
"--head",
|
|
38
|
+
issue.branchName,
|
|
39
|
+
"--state",
|
|
40
|
+
"all",
|
|
41
|
+
"--json",
|
|
42
|
+
"number,url,state,author,headRefOid",
|
|
43
|
+
], { timeoutMs: 10_000 });
|
|
44
|
+
if (exitCode === 0) {
|
|
45
|
+
const matches = JSON.parse(stdout);
|
|
46
|
+
const pr = matches[0];
|
|
47
|
+
if (pr?.number) {
|
|
48
|
+
this.upsertIssueIfLeaseHeld(issue.projectId, issue.linearIssueId, {
|
|
49
|
+
projectId: issue.projectId,
|
|
50
|
+
linearIssueId: issue.linearIssueId,
|
|
51
|
+
prNumber: pr.number,
|
|
52
|
+
...(pr.url ? { prUrl: pr.url } : {}),
|
|
53
|
+
...(pr.state ? { prState: pr.state.toLowerCase() } : {}),
|
|
54
|
+
...(pr.headRefOid ? { prHeadSha: pr.headRefOid } : {}),
|
|
55
|
+
...(pr.author?.login ? { prAuthorLogin: pr.author.login } : {}),
|
|
56
|
+
}, "published PR verification refresh");
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
this.logger.debug({
|
|
63
|
+
issueKey: issue.issueKey,
|
|
64
|
+
branchName: issue.branchName,
|
|
65
|
+
repoFullName: project.github.repoFullName,
|
|
66
|
+
error: error instanceof Error ? error.message : String(error),
|
|
67
|
+
}, "Failed to verify published PR state after implementation");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const details = await this.describeLocalImplementationOutcome(issue, baseBranch, deliveryMode);
|
|
71
|
+
return details ?? `Implementation completed without opening a PR for branch ${issue.branchName ?? issue.linearIssueId}`;
|
|
72
|
+
}
|
|
73
|
+
upsertIssueIfLeaseHeld(projectId, linearIssueId, params, context) {
|
|
74
|
+
const updated = this.withHeldLease(projectId, linearIssueId, (lease) => this.db.issueSessions.upsertIssueWithLease(lease, params));
|
|
75
|
+
if (updated === undefined) {
|
|
76
|
+
this.logger.warn({ projectId, linearIssueId, context }, "Skipping issue write after losing issue-session lease");
|
|
77
|
+
}
|
|
78
|
+
return updated;
|
|
79
|
+
}
|
|
80
|
+
async describeLocalImplementationOutcome(issue, baseBranch, deliveryMode = "publish_pr") {
|
|
81
|
+
if (!issue.worktreePath) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const status = await execCommand(this.config.runner.gitBin, [
|
|
86
|
+
"-C",
|
|
87
|
+
issue.worktreePath,
|
|
88
|
+
"status",
|
|
89
|
+
"--short",
|
|
90
|
+
], { timeoutMs: 10_000 });
|
|
91
|
+
const dirtyEntries = status.exitCode === 0
|
|
92
|
+
? status.stdout.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
93
|
+
: [];
|
|
94
|
+
if (dirtyEntries.length > 0) {
|
|
95
|
+
if (deliveryMode === "linear_only") {
|
|
96
|
+
return `Planning-only implementation should not modify the repo; worktree still has ${dirtyEntries.length} uncommitted change(s)`;
|
|
97
|
+
}
|
|
98
|
+
return `Implementation completed without opening a PR; worktree still has ${dirtyEntries.length} uncommitted change(s)`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Best effort only.
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const ahead = await execCommand(this.config.runner.gitBin, [
|
|
106
|
+
"-C",
|
|
107
|
+
issue.worktreePath,
|
|
108
|
+
"rev-list",
|
|
109
|
+
"--count",
|
|
110
|
+
`origin/${baseBranch}..HEAD`,
|
|
111
|
+
], { timeoutMs: 10_000 });
|
|
112
|
+
if (ahead.exitCode === 0) {
|
|
113
|
+
const count = Number(ahead.stdout.trim());
|
|
114
|
+
if (Number.isFinite(count) && count > 0) {
|
|
115
|
+
if (deliveryMode === "linear_only") {
|
|
116
|
+
return `Planning-only implementation should not create repo commits; worktree is ${count} local commit(s) ahead of origin/${baseBranch}`;
|
|
117
|
+
}
|
|
118
|
+
return `Implementation completed with ${count} local commit(s) ahead of origin/${baseBranch} but no PR was observed`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Best effort only.
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { parseGitHubFailureContext } from "./github-failure-context.js";
|
|
2
|
+
import { isIssueSessionReadyForExecution } from "./issue-session.js";
|
|
3
|
+
import { deriveIssueStatusNote } from "./status-note.js";
|
|
4
|
+
import { derivePatchRelayWaitingReason } from "./waiting-reason.js";
|
|
5
|
+
export function parseStageReport(reportJson, runStatus) {
|
|
6
|
+
if (!reportJson)
|
|
7
|
+
return undefined;
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(reportJson);
|
|
10
|
+
return { ...parsed, status: runStatus };
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class IssueOverviewQuery {
|
|
17
|
+
db;
|
|
18
|
+
codex;
|
|
19
|
+
runStatusProvider;
|
|
20
|
+
constructor(db, codex, runStatusProvider) {
|
|
21
|
+
this.db = db;
|
|
22
|
+
this.codex = codex;
|
|
23
|
+
this.runStatusProvider = runStatusProvider;
|
|
24
|
+
}
|
|
25
|
+
async getIssueOverview(issueKey) {
|
|
26
|
+
const session = this.db.issueSessions.getIssueSessionByKey(issueKey);
|
|
27
|
+
if (!session) {
|
|
28
|
+
return await this.getLegacyIssueOverview(issueKey);
|
|
29
|
+
}
|
|
30
|
+
return await this.getSessionIssueOverview(issueKey, session);
|
|
31
|
+
}
|
|
32
|
+
buildRuns(projectId, linearIssueId) {
|
|
33
|
+
return this.db.runs.listRunsForIssue(projectId, linearIssueId).map((run) => ({
|
|
34
|
+
id: run.id,
|
|
35
|
+
runType: run.runType,
|
|
36
|
+
status: run.status,
|
|
37
|
+
startedAt: run.startedAt,
|
|
38
|
+
...(run.endedAt ? { endedAt: run.endedAt } : {}),
|
|
39
|
+
...(run.threadId ? { threadId: run.threadId } : {}),
|
|
40
|
+
...(() => {
|
|
41
|
+
const report = parseStageReport(run.reportJson, run.status);
|
|
42
|
+
return report ? { report } : {};
|
|
43
|
+
})(),
|
|
44
|
+
...(() => {
|
|
45
|
+
const events = this.db.runs.listThreadEvents(run.id).flatMap((event) => {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(event.eventJson);
|
|
48
|
+
return [{
|
|
49
|
+
id: event.id,
|
|
50
|
+
method: event.method,
|
|
51
|
+
createdAt: event.createdAt,
|
|
52
|
+
...(parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
53
|
+
? { parsedEvent: parsed }
|
|
54
|
+
: {}),
|
|
55
|
+
}];
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return [{
|
|
59
|
+
id: event.id,
|
|
60
|
+
method: event.method,
|
|
61
|
+
createdAt: event.createdAt,
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return events.length > 0 ? { events } : {};
|
|
66
|
+
})(),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
async getLegacyIssueOverview(issueKey) {
|
|
70
|
+
const legacy = this.db.getIssueOverview(issueKey);
|
|
71
|
+
if (!legacy)
|
|
72
|
+
return undefined;
|
|
73
|
+
const issueRecord = this.db.issues.getIssueByKey(issueKey);
|
|
74
|
+
const activeStatus = await this.runStatusProvider.getActiveRunStatus(issueKey);
|
|
75
|
+
const activeRun = activeStatus?.run ?? legacy.activeRun;
|
|
76
|
+
const latestRun = this.db.runs.getLatestRunForIssue(legacy.issue.projectId, legacy.issue.linearIssueId);
|
|
77
|
+
const latestEvent = this.db.issueSessions.listIssueSessionEvents(legacy.issue.projectId, legacy.issue.linearIssueId, { limit: 1 }).at(-1);
|
|
78
|
+
const runs = this.buildRuns(legacy.issue.projectId, legacy.issue.linearIssueId);
|
|
79
|
+
const runCount = runs.length;
|
|
80
|
+
const liveThread = await this.readLiveThread(activeRun);
|
|
81
|
+
const statusNote = issueRecord
|
|
82
|
+
? deriveIssueStatusNote({
|
|
83
|
+
issue: issueRecord,
|
|
84
|
+
latestRun,
|
|
85
|
+
latestEvent,
|
|
86
|
+
failureSummary: legacy.issue.latestFailureSummary,
|
|
87
|
+
blockedByKeys: legacy.issue.blockedByKeys,
|
|
88
|
+
waitingReason: legacy.issue.waitingReason,
|
|
89
|
+
})
|
|
90
|
+
: legacy.issue.statusNote;
|
|
91
|
+
return {
|
|
92
|
+
issue: {
|
|
93
|
+
...legacy.issue,
|
|
94
|
+
...(statusNote ? { statusNote } : {}),
|
|
95
|
+
},
|
|
96
|
+
...(activeRun ? { activeRun } : {}),
|
|
97
|
+
...(latestRun ? { latestRun } : {}),
|
|
98
|
+
...(liveThread ? { liveThread } : {}),
|
|
99
|
+
...(runs.length > 0 ? { runs } : {}),
|
|
100
|
+
...(issueRecord
|
|
101
|
+
? {
|
|
102
|
+
issueContext: {
|
|
103
|
+
...(issueRecord.description ? { description: issueRecord.description } : {}),
|
|
104
|
+
...(issueRecord.currentLinearState ? { currentLinearState: issueRecord.currentLinearState } : {}),
|
|
105
|
+
...(issueRecord.url ? { issueUrl: issueRecord.url } : {}),
|
|
106
|
+
...(issueRecord.worktreePath ? { worktreePath: issueRecord.worktreePath } : {}),
|
|
107
|
+
...(issueRecord.branchName ? { branchName: issueRecord.branchName } : {}),
|
|
108
|
+
...(issueRecord.prUrl ? { prUrl: issueRecord.prUrl } : {}),
|
|
109
|
+
...(issueRecord.priority != null ? { priority: issueRecord.priority } : {}),
|
|
110
|
+
...(issueRecord.estimate != null ? { estimate: issueRecord.estimate } : {}),
|
|
111
|
+
ciRepairAttempts: issueRecord.ciRepairAttempts,
|
|
112
|
+
queueRepairAttempts: issueRecord.queueRepairAttempts,
|
|
113
|
+
reviewFixAttempts: issueRecord.reviewFixAttempts,
|
|
114
|
+
...(legacy.issue.latestFailureSource ? { latestFailureSource: legacy.issue.latestFailureSource } : {}),
|
|
115
|
+
...(legacy.issue.latestFailureHeadSha ? { latestFailureHeadSha: legacy.issue.latestFailureHeadSha } : {}),
|
|
116
|
+
...(legacy.issue.latestFailureCheckName ? { latestFailureCheckName: legacy.issue.latestFailureCheckName } : {}),
|
|
117
|
+
...(legacy.issue.latestFailureStepName ? { latestFailureStepName: legacy.issue.latestFailureStepName } : {}),
|
|
118
|
+
...(legacy.issue.latestFailureSummary ? { latestFailureSummary: legacy.issue.latestFailureSummary } : {}),
|
|
119
|
+
runCount,
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
: {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async getSessionIssueOverview(issueKey, session) {
|
|
126
|
+
const issueRecord = this.db.issues.getIssueByKey(issueKey);
|
|
127
|
+
const blockedBy = this.db.issues.listIssueDependencies(session.projectId, session.linearIssueId);
|
|
128
|
+
const unresolvedBlockedBy = blockedBy.filter((entry) => (entry.blockerCurrentLinearStateType !== "completed"
|
|
129
|
+
&& entry.blockerCurrentLinearState?.trim().toLowerCase() !== "done"));
|
|
130
|
+
const blockedByKeys = unresolvedBlockedBy.map((entry) => entry.blockerIssueKey ?? entry.blockerLinearIssueId);
|
|
131
|
+
const activeStatus = await this.runStatusProvider.getActiveRunStatus(issueKey);
|
|
132
|
+
const activeRun = activeStatus?.run
|
|
133
|
+
?? (session.activeRunId !== undefined ? this.db.runs.getRunById(session.activeRunId) : undefined);
|
|
134
|
+
const latestRun = this.db.runs.getLatestRunForIssue(session.projectId, session.linearIssueId);
|
|
135
|
+
const latestEvent = this.db.issueSessions.listIssueSessionEvents(session.projectId, session.linearIssueId, { limit: 1 }).at(-1);
|
|
136
|
+
const runs = this.buildRuns(session.projectId, session.linearIssueId);
|
|
137
|
+
const runCount = runs.length;
|
|
138
|
+
const liveThread = await this.readLiveThread(activeRun);
|
|
139
|
+
const failureContext = parseGitHubFailureContext(issueRecord?.lastGitHubFailureContextJson);
|
|
140
|
+
const waitingReason = session.waitingReason ?? derivePatchRelayWaitingReason({
|
|
141
|
+
...(activeRun ? { activeRunType: activeRun.runType } : {}),
|
|
142
|
+
blockedByKeys,
|
|
143
|
+
factoryState: issueRecord?.factoryState ?? "delegated",
|
|
144
|
+
pendingRunType: issueRecord?.pendingRunType,
|
|
145
|
+
prNumber: session.prNumber,
|
|
146
|
+
prHeadSha: issueRecord?.prHeadSha ?? session.prHeadSha,
|
|
147
|
+
prReviewState: issueRecord?.prReviewState,
|
|
148
|
+
prCheckStatus: issueRecord?.prCheckStatus,
|
|
149
|
+
lastBlockingReviewHeadSha: issueRecord?.lastBlockingReviewHeadSha,
|
|
150
|
+
latestFailureCheckName: issueRecord?.lastGitHubFailureCheckName,
|
|
151
|
+
});
|
|
152
|
+
const issue = {
|
|
153
|
+
id: issueRecord?.id ?? session.id,
|
|
154
|
+
projectId: session.projectId,
|
|
155
|
+
linearIssueId: session.linearIssueId,
|
|
156
|
+
...(session.issueKey ? { issueKey: session.issueKey } : {}),
|
|
157
|
+
...(issueRecord?.title ? { title: issueRecord.title } : {}),
|
|
158
|
+
...(issueRecord?.url ? { issueUrl: issueRecord.url } : {}),
|
|
159
|
+
...(issueRecord?.currentLinearState ? { currentLinearState: issueRecord.currentLinearState } : {}),
|
|
160
|
+
sessionState: session.sessionState,
|
|
161
|
+
factoryState: issueRecord?.factoryState ?? "delegated",
|
|
162
|
+
blockedByCount: unresolvedBlockedBy.length,
|
|
163
|
+
blockedByKeys,
|
|
164
|
+
readyForExecution: isIssueSessionReadyForExecution({
|
|
165
|
+
sessionState: session.sessionState,
|
|
166
|
+
factoryState: issueRecord?.factoryState ?? "delegated",
|
|
167
|
+
...(activeRun ? { activeRunId: activeRun.id } : {}),
|
|
168
|
+
blockedByCount: unresolvedBlockedBy.length,
|
|
169
|
+
hasPendingWake: this.db.issueSessions.peekIssueSessionWake(session.projectId, session.linearIssueId) !== undefined,
|
|
170
|
+
hasLegacyPendingRun: issueRecord?.pendingRunType !== undefined,
|
|
171
|
+
...(session.prNumber !== undefined ? { prNumber: session.prNumber } : {}),
|
|
172
|
+
...(issueRecord?.prState ? { prState: issueRecord.prState } : {}),
|
|
173
|
+
...(issueRecord?.prReviewState ? { prReviewState: issueRecord.prReviewState } : {}),
|
|
174
|
+
...(issueRecord?.prCheckStatus ? { prCheckStatus: issueRecord.prCheckStatus } : {}),
|
|
175
|
+
...(issueRecord?.lastGitHubFailureSource ? { latestFailureSource: issueRecord.lastGitHubFailureSource } : {}),
|
|
176
|
+
}),
|
|
177
|
+
...(issueRecord?.lastGitHubFailureSource ? { latestFailureSource: issueRecord.lastGitHubFailureSource } : {}),
|
|
178
|
+
...(issueRecord?.lastGitHubFailureHeadSha ? { latestFailureHeadSha: issueRecord.lastGitHubFailureHeadSha } : {}),
|
|
179
|
+
...(issueRecord?.lastGitHubFailureCheckName ? { latestFailureCheckName: issueRecord.lastGitHubFailureCheckName } : {}),
|
|
180
|
+
...(() => {
|
|
181
|
+
const statusNote = issueRecord
|
|
182
|
+
? deriveIssueStatusNote({
|
|
183
|
+
issue: issueRecord,
|
|
184
|
+
sessionSummary: session.summaryText,
|
|
185
|
+
latestRun,
|
|
186
|
+
latestEvent,
|
|
187
|
+
failureSummary: failureContext?.summary,
|
|
188
|
+
blockedByKeys,
|
|
189
|
+
waitingReason,
|
|
190
|
+
})
|
|
191
|
+
: undefined;
|
|
192
|
+
return statusNote ? { statusNote } : {};
|
|
193
|
+
})(),
|
|
194
|
+
...(waitingReason ? { waitingReason } : {}),
|
|
195
|
+
...(activeRun ? { activeRunId: activeRun.id } : {}),
|
|
196
|
+
...(issueRecord?.agentSessionId ? { activeAgentSessionId: issueRecord.agentSessionId } : {}),
|
|
197
|
+
updatedAt: session.updatedAt,
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
issue,
|
|
201
|
+
session,
|
|
202
|
+
...(activeRun ? { activeRun } : {}),
|
|
203
|
+
...(latestRun ? { latestRun } : {}),
|
|
204
|
+
...(liveThread ? { liveThread } : {}),
|
|
205
|
+
...(runs.length > 0 ? { runs } : {}),
|
|
206
|
+
issueContext: {
|
|
207
|
+
...(issueRecord?.description ? { description: issueRecord.description } : {}),
|
|
208
|
+
...(issueRecord?.currentLinearState ? { currentLinearState: issueRecord.currentLinearState } : {}),
|
|
209
|
+
...(issueRecord?.url ? { issueUrl: issueRecord.url } : {}),
|
|
210
|
+
...(session.worktreePath ? { worktreePath: session.worktreePath } : {}),
|
|
211
|
+
...(session.branchName ? { branchName: session.branchName } : {}),
|
|
212
|
+
...(issueRecord?.prUrl ? { prUrl: issueRecord.prUrl } : {}),
|
|
213
|
+
...(issueRecord?.priority != null ? { priority: issueRecord.priority } : {}),
|
|
214
|
+
...(issueRecord?.estimate != null ? { estimate: issueRecord.estimate } : {}),
|
|
215
|
+
ciRepairAttempts: issueRecord?.ciRepairAttempts ?? session.ciRepairAttempts,
|
|
216
|
+
queueRepairAttempts: issueRecord?.queueRepairAttempts ?? session.queueRepairAttempts,
|
|
217
|
+
reviewFixAttempts: issueRecord?.reviewFixAttempts ?? session.reviewFixAttempts,
|
|
218
|
+
...(issue.latestFailureSource ? { latestFailureSource: issue.latestFailureSource } : {}),
|
|
219
|
+
...(issue.latestFailureHeadSha ? { latestFailureHeadSha: issue.latestFailureHeadSha } : {}),
|
|
220
|
+
...(issue.latestFailureCheckName ? { latestFailureCheckName: issue.latestFailureCheckName } : {}),
|
|
221
|
+
...(issue.latestFailureStepName ? { latestFailureStepName: issue.latestFailureStepName } : {}),
|
|
222
|
+
...(issue.latestFailureSummary ? { latestFailureSummary: issue.latestFailureSummary } : {}),
|
|
223
|
+
runCount,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async readLiveThread(run) {
|
|
228
|
+
if (!run?.threadId)
|
|
229
|
+
return undefined;
|
|
230
|
+
return await this.codex.readThread(run.threadId, true).catch(() => undefined);
|
|
231
|
+
}
|
|
232
|
+
}
|