patchrelay 0.36.18 → 0.36.19
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/linear-agent-session-client.js +109 -0
- package/dist/linear-progress-reporter.js +185 -0
- package/dist/linear-session-sync.js +23 -519
- package/dist/linear-status-comment-sync.js +152 -0
- package/dist/linear-workflow-state-sync.js +103 -0
- package/dist/no-pr-completion-check.js +199 -0
- package/dist/operator-retry-event.js +58 -0
- package/dist/run-finalizer.js +72 -237
- package/dist/service-issue-actions.js +164 -0
- package/dist/service-startup-recovery.js +104 -0
- package/dist/service.js +15 -556
- package/dist/tracked-issue-list-query.js +259 -0
- package/package.json +1 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { parseGitHubFailureContext, summarizeGitHubFailureContext } from "./github-failure-context.js";
|
|
2
|
+
import { derivePatchRelayWaitingReason } from "./waiting-reason.js";
|
|
3
|
+
import { deriveIssueStatusNote } from "./status-note.js";
|
|
4
|
+
import { isIssueSessionReadyForExecution } from "./issue-session.js";
|
|
5
|
+
function shouldSuppressStatusNote(params) {
|
|
6
|
+
if (!params.activeRunType && params.sessionState !== "running")
|
|
7
|
+
return false;
|
|
8
|
+
const note = params.statusNote?.trim().toLowerCase();
|
|
9
|
+
if (!note)
|
|
10
|
+
return true;
|
|
11
|
+
return note === "codex turn was interrupted"
|
|
12
|
+
|| note.startsWith("zombie: never started")
|
|
13
|
+
|| note === "stale thread after restart"
|
|
14
|
+
|| note === "patchrelay received your mention. delegate the issue to patchrelay to start work.";
|
|
15
|
+
}
|
|
16
|
+
export function parseCiSnapshotSummary(snapshotJson) {
|
|
17
|
+
if (!snapshotJson)
|
|
18
|
+
return undefined;
|
|
19
|
+
try {
|
|
20
|
+
const snapshot = JSON.parse(snapshotJson);
|
|
21
|
+
const rawChecks = Array.isArray(snapshot.checks) ? snapshot.checks : [];
|
|
22
|
+
const checks = collapseEffectiveChecks(rawChecks);
|
|
23
|
+
if (checks.length === 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
let pending = 0;
|
|
28
|
+
const failedNames = [];
|
|
29
|
+
for (const check of checks) {
|
|
30
|
+
if (check.status === "success")
|
|
31
|
+
passed++;
|
|
32
|
+
else if (check.status === "failure") {
|
|
33
|
+
failed++;
|
|
34
|
+
failedNames.push(check.name);
|
|
35
|
+
}
|
|
36
|
+
else
|
|
37
|
+
pending++;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
total: checks.length,
|
|
41
|
+
completed: passed + failed,
|
|
42
|
+
passed,
|
|
43
|
+
failed,
|
|
44
|
+
pending,
|
|
45
|
+
overall: snapshot.gateCheckStatus,
|
|
46
|
+
...(failedNames.length > 0 ? { failedNames } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function collapseEffectiveChecks(checks) {
|
|
54
|
+
const effective = new Map();
|
|
55
|
+
for (const check of checks) {
|
|
56
|
+
const name = typeof check?.name === "string" ? check.name.trim() : "";
|
|
57
|
+
if (!name || effective.has(name))
|
|
58
|
+
continue;
|
|
59
|
+
effective.set(name, check);
|
|
60
|
+
}
|
|
61
|
+
return [...effective.values()];
|
|
62
|
+
}
|
|
63
|
+
export function parseStringArray(value) {
|
|
64
|
+
if (!value)
|
|
65
|
+
return [];
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(value);
|
|
68
|
+
return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export class TrackedIssueListQuery {
|
|
75
|
+
db;
|
|
76
|
+
constructor(db) {
|
|
77
|
+
this.db = db;
|
|
78
|
+
}
|
|
79
|
+
listTrackedIssues() {
|
|
80
|
+
const rows = this.db.connection
|
|
81
|
+
.prepare(`SELECT
|
|
82
|
+
s.project_id, s.linear_issue_id, s.issue_key, i.title,
|
|
83
|
+
i.current_linear_state, i.factory_state, s.session_state, s.waiting_reason, s.summary_text, s.updated_at,
|
|
84
|
+
i.pending_run_type,
|
|
85
|
+
i.pr_number, i.pr_head_sha, i.pr_review_state, i.pr_check_status, i.last_blocking_review_head_sha,
|
|
86
|
+
i.last_github_ci_snapshot_json,
|
|
87
|
+
i.last_github_failure_source,
|
|
88
|
+
i.last_github_failure_head_sha,
|
|
89
|
+
i.last_github_failure_check_name,
|
|
90
|
+
i.last_github_failure_context_json,
|
|
91
|
+
active_run.run_type AS active_run_type,
|
|
92
|
+
active_run.completion_check_thread_id AS active_completion_check_thread_id,
|
|
93
|
+
active_run.completion_check_outcome AS active_completion_check_outcome,
|
|
94
|
+
latest_run.run_type AS latest_run_type,
|
|
95
|
+
latest_run.status AS latest_run_status,
|
|
96
|
+
latest_run.summary_json AS latest_run_summary_json,
|
|
97
|
+
latest_run.report_json AS latest_run_report_json,
|
|
98
|
+
latest_run.completion_check_thread_id AS latest_run_completion_check_thread_id,
|
|
99
|
+
latest_run.completion_check_outcome AS latest_run_completion_check_outcome,
|
|
100
|
+
latest_run.completion_check_summary AS latest_run_completion_check_summary,
|
|
101
|
+
latest_run.completion_check_question AS latest_run_completion_check_question,
|
|
102
|
+
latest_run.completion_check_why AS latest_run_completion_check_why,
|
|
103
|
+
latest_run.completion_check_recommended_reply AS latest_run_completion_check_recommended_reply,
|
|
104
|
+
(
|
|
105
|
+
SELECT COUNT(*)
|
|
106
|
+
FROM issue_session_events e
|
|
107
|
+
WHERE e.project_id = s.project_id
|
|
108
|
+
AND e.linear_issue_id = s.linear_issue_id
|
|
109
|
+
AND e.processed_at IS NULL
|
|
110
|
+
) AS pending_session_event_count,
|
|
111
|
+
(
|
|
112
|
+
SELECT COUNT(*)
|
|
113
|
+
FROM issue_dependencies d
|
|
114
|
+
LEFT JOIN issues blockers
|
|
115
|
+
ON blockers.project_id = d.project_id
|
|
116
|
+
AND blockers.linear_issue_id = d.blocker_linear_issue_id
|
|
117
|
+
WHERE d.project_id = s.project_id
|
|
118
|
+
AND d.linear_issue_id = s.linear_issue_id
|
|
119
|
+
AND (
|
|
120
|
+
COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
|
|
121
|
+
AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
|
|
122
|
+
)
|
|
123
|
+
) AS blocked_by_count,
|
|
124
|
+
(
|
|
125
|
+
SELECT json_group_array(COALESCE(blockers.issue_key, d.blocker_issue_key, d.blocker_linear_issue_id))
|
|
126
|
+
FROM issue_dependencies d
|
|
127
|
+
LEFT JOIN issues blockers
|
|
128
|
+
ON blockers.project_id = d.project_id
|
|
129
|
+
AND blockers.linear_issue_id = d.blocker_linear_issue_id
|
|
130
|
+
WHERE d.project_id = s.project_id
|
|
131
|
+
AND d.linear_issue_id = s.linear_issue_id
|
|
132
|
+
AND (
|
|
133
|
+
COALESCE(blockers.current_linear_state_type, d.blocker_current_linear_state_type, '') != 'completed'
|
|
134
|
+
AND LOWER(TRIM(COALESCE(blockers.current_linear_state, d.blocker_current_linear_state, ''))) != 'done'
|
|
135
|
+
)
|
|
136
|
+
) AS blocked_by_keys_json
|
|
137
|
+
FROM issue_sessions s
|
|
138
|
+
LEFT JOIN issues i
|
|
139
|
+
ON i.project_id = s.project_id
|
|
140
|
+
AND i.linear_issue_id = s.linear_issue_id
|
|
141
|
+
LEFT JOIN runs active_run ON active_run.id = COALESCE(s.active_run_id, i.active_run_id)
|
|
142
|
+
LEFT JOIN runs latest_run ON latest_run.id = (
|
|
143
|
+
SELECT r.id FROM runs r
|
|
144
|
+
WHERE r.project_id = s.project_id AND r.linear_issue_id = s.linear_issue_id
|
|
145
|
+
ORDER BY r.id DESC LIMIT 1
|
|
146
|
+
)
|
|
147
|
+
ORDER BY s.updated_at DESC, s.issue_key ASC`)
|
|
148
|
+
.all();
|
|
149
|
+
return rows.map((row) => {
|
|
150
|
+
const failureContext = parseGitHubFailureContext(typeof row.last_github_failure_context_json === "string" ? row.last_github_failure_context_json : undefined);
|
|
151
|
+
const prChecksSummary = parseCiSnapshotSummary(typeof row.last_github_ci_snapshot_json === "string" ? row.last_github_ci_snapshot_json : undefined);
|
|
152
|
+
const blockedByKeys = parseStringArray(typeof row.blocked_by_keys_json === "string" ? row.blocked_by_keys_json : undefined);
|
|
153
|
+
const blockedByCount = Number(row.blocked_by_count ?? 0);
|
|
154
|
+
const hasPendingSessionEvents = Number(row.pending_session_event_count ?? 0) > 0;
|
|
155
|
+
const hasPendingWake = hasPendingSessionEvents
|
|
156
|
+
|| this.db.issueSessions.peekIssueSessionWake(String(row.project_id), String(row.linear_issue_id)) !== undefined;
|
|
157
|
+
const readyForExecution = isIssueSessionReadyForExecution({
|
|
158
|
+
...(typeof row.session_state === "string" ? { sessionState: String(row.session_state) } : {}),
|
|
159
|
+
factoryState: String(row.factory_state ?? "delegated"),
|
|
160
|
+
...(row.active_run_type !== null ? { activeRunId: 1 } : {}),
|
|
161
|
+
blockedByCount,
|
|
162
|
+
hasPendingWake,
|
|
163
|
+
hasLegacyPendingRun: row.pending_run_type !== null && row.pending_run_type !== undefined,
|
|
164
|
+
...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
|
|
165
|
+
...(row.pr_state !== null ? { prState: String(row.pr_state) } : {}),
|
|
166
|
+
...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
|
|
167
|
+
...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
|
|
168
|
+
...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
|
|
169
|
+
});
|
|
170
|
+
const failureSummary = summarizeGitHubFailureContext(failureContext);
|
|
171
|
+
const sessionWaitingReason = typeof row.waiting_reason === "string" && row.waiting_reason.trim().length > 0
|
|
172
|
+
? row.waiting_reason
|
|
173
|
+
: undefined;
|
|
174
|
+
const sessionSummary = typeof row.summary_text === "string" && row.summary_text.trim().length > 0
|
|
175
|
+
? row.summary_text
|
|
176
|
+
: undefined;
|
|
177
|
+
const waitingReason = sessionWaitingReason ?? derivePatchRelayWaitingReason({
|
|
178
|
+
...(row.active_run_type !== null ? { activeRunType: String(row.active_run_type) } : {}),
|
|
179
|
+
blockedByKeys,
|
|
180
|
+
factoryState: String(row.factory_state ?? "delegated"),
|
|
181
|
+
...(row.pending_run_type !== null ? { pendingRunType: String(row.pending_run_type) } : {}),
|
|
182
|
+
...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
|
|
183
|
+
...(row.pr_head_sha !== null ? { prHeadSha: String(row.pr_head_sha) } : {}),
|
|
184
|
+
...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
|
|
185
|
+
...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
|
|
186
|
+
...(row.last_blocking_review_head_sha !== null ? { lastBlockingReviewHeadSha: String(row.last_blocking_review_head_sha) } : {}),
|
|
187
|
+
...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
|
|
188
|
+
});
|
|
189
|
+
const latestRun = row.latest_run_type !== null && row.latest_run_status !== null
|
|
190
|
+
? {
|
|
191
|
+
id: 0,
|
|
192
|
+
issueId: 0,
|
|
193
|
+
projectId: String(row.project_id),
|
|
194
|
+
linearIssueId: String(row.linear_issue_id),
|
|
195
|
+
runType: String(row.latest_run_type),
|
|
196
|
+
status: String(row.latest_run_status),
|
|
197
|
+
...(typeof row.latest_run_summary_json === "string" ? { summaryJson: row.latest_run_summary_json } : {}),
|
|
198
|
+
...(typeof row.latest_run_report_json === "string" ? { reportJson: row.latest_run_report_json } : {}),
|
|
199
|
+
...(typeof row.latest_run_completion_check_thread_id === "string" ? { completionCheckThreadId: row.latest_run_completion_check_thread_id } : {}),
|
|
200
|
+
...(typeof row.latest_run_completion_check_outcome === "string" ? { completionCheckOutcome: row.latest_run_completion_check_outcome } : {}),
|
|
201
|
+
...(typeof row.latest_run_completion_check_summary === "string" ? { completionCheckSummary: row.latest_run_completion_check_summary } : {}),
|
|
202
|
+
...(typeof row.latest_run_completion_check_question === "string" ? { completionCheckQuestion: row.latest_run_completion_check_question } : {}),
|
|
203
|
+
...(typeof row.latest_run_completion_check_why === "string" ? { completionCheckWhy: row.latest_run_completion_check_why } : {}),
|
|
204
|
+
...(typeof row.latest_run_completion_check_recommended_reply === "string" ? { completionCheckRecommendedReply: row.latest_run_completion_check_recommended_reply } : {}),
|
|
205
|
+
startedAt: String(row.updated_at),
|
|
206
|
+
}
|
|
207
|
+
: undefined;
|
|
208
|
+
const latestEvent = this.db.issueSessions.listIssueSessionEvents(String(row.project_id), String(row.linear_issue_id), { limit: 1 }).at(-1);
|
|
209
|
+
const statusNoteCandidate = deriveIssueStatusNote({
|
|
210
|
+
issue: { factoryState: String(row.factory_state ?? "delegated") },
|
|
211
|
+
sessionSummary,
|
|
212
|
+
latestRun: latestRun,
|
|
213
|
+
latestEvent,
|
|
214
|
+
failureSummary,
|
|
215
|
+
blockedByKeys,
|
|
216
|
+
waitingReason,
|
|
217
|
+
}) ?? waitingReason;
|
|
218
|
+
const statusNoteForReturn = shouldSuppressStatusNote({
|
|
219
|
+
activeRunType: row.active_run_type,
|
|
220
|
+
sessionState: row.session_state,
|
|
221
|
+
statusNote: statusNoteCandidate,
|
|
222
|
+
})
|
|
223
|
+
? undefined
|
|
224
|
+
: statusNoteCandidate;
|
|
225
|
+
const completionCheckActive = typeof row.active_completion_check_thread_id === "string"
|
|
226
|
+
&& row.active_completion_check_thread_id.length > 0
|
|
227
|
+
&& row.active_completion_check_outcome === null
|
|
228
|
+
&& row.active_run_type !== null;
|
|
229
|
+
return {
|
|
230
|
+
...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
|
|
231
|
+
...(row.title !== null ? { title: String(row.title) } : {}),
|
|
232
|
+
...(statusNoteForReturn ? { statusNote: statusNoteForReturn } : {}),
|
|
233
|
+
projectId: String(row.project_id),
|
|
234
|
+
...(row.session_state !== null ? { sessionState: String(row.session_state) } : {}),
|
|
235
|
+
factoryState: String(row.factory_state ?? "delegated"),
|
|
236
|
+
blockedByCount,
|
|
237
|
+
blockedByKeys,
|
|
238
|
+
readyForExecution,
|
|
239
|
+
...(row.current_linear_state !== null ? { currentLinearState: String(row.current_linear_state) } : {}),
|
|
240
|
+
...(row.active_run_type !== null ? { activeRunType: String(row.active_run_type) } : {}),
|
|
241
|
+
...(row.pending_run_type !== null ? { pendingRunType: String(row.pending_run_type) } : {}),
|
|
242
|
+
...(row.latest_run_type !== null ? { latestRunType: String(row.latest_run_type) } : {}),
|
|
243
|
+
...(row.latest_run_status !== null ? { latestRunStatus: String(row.latest_run_status) } : {}),
|
|
244
|
+
...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
|
|
245
|
+
...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
|
|
246
|
+
...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
|
|
247
|
+
...(prChecksSummary ? { prChecksSummary } : {}),
|
|
248
|
+
...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
|
|
249
|
+
...(row.last_github_failure_head_sha !== null ? { latestFailureHeadSha: String(row.last_github_failure_head_sha) } : {}),
|
|
250
|
+
...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
|
|
251
|
+
...(failureContext?.stepName ? { latestFailureStepName: failureContext.stepName } : {}),
|
|
252
|
+
...(failureContext?.summary ? { latestFailureSummary: failureContext.summary } : {}),
|
|
253
|
+
...(waitingReason ? { waitingReason } : {}),
|
|
254
|
+
...(completionCheckActive ? { completionCheckActive } : {}),
|
|
255
|
+
updatedAt: String(row.updated_at),
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|