patchrelay 0.52.0 → 0.52.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.
- package/dist/build-info.json +3 -3
- package/dist/cli/data.js +2 -52
- package/dist/linear-progress-facts.js +15 -14
- package/dist/linear-session-reporting.js +27 -12
- package/dist/operator-retry-event.js +3 -3
- package/dist/reactive-run-policy.js +46 -4
- package/dist/remote-pr-review.js +60 -0
- package/dist/run-launcher.js +14 -1
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/cli/data.js
CHANGED
|
@@ -6,6 +6,7 @@ import { extractCompletionCheck } from "../completion-check.js";
|
|
|
6
6
|
import { getThreadTurns } from "../codex-thread-utils.js";
|
|
7
7
|
import { PatchRelayDatabase } from "../db.js";
|
|
8
8
|
import { buildManualRetryAttemptReset, resolveRetryTarget } from "../manual-issue-actions.js";
|
|
9
|
+
import { buildOperatorRetryEvent } from "../operator-retry-event.js";
|
|
9
10
|
import { WorktreeManager } from "../worktree-manager.js";
|
|
10
11
|
import { parseDelegationObservedPayload, parseRunReleasedAuthorityPayload } from "../delegation-audit.js";
|
|
11
12
|
import { CliOperatorApiClient } from "./operator-client.js";
|
|
@@ -386,61 +387,10 @@ export class CliDataAccess extends CliOperatorApiClient {
|
|
|
386
387
|
};
|
|
387
388
|
}
|
|
388
389
|
appendRetryWake(issue, runType) {
|
|
389
|
-
if (runType === "queue_repair") {
|
|
390
|
-
const queueIncident = parseObjectJson(issue.lastQueueIncidentJson);
|
|
391
|
-
const failureContext = parseObjectJson(issue.lastGitHubFailureContextJson);
|
|
392
|
-
this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
|
|
393
|
-
projectId: issue.projectId,
|
|
394
|
-
linearIssueId: issue.linearIssueId,
|
|
395
|
-
eventType: "merge_steward_incident",
|
|
396
|
-
eventJson: JSON.stringify({
|
|
397
|
-
...(queueIncident ?? {}),
|
|
398
|
-
...(failureContext ?? {}),
|
|
399
|
-
source: "operator_retry",
|
|
400
|
-
}),
|
|
401
|
-
dedupeKey: `operator_retry:queue_repair:${issue.linearIssueId}:${issue.prHeadSha ?? issue.lastGitHubFailureHeadSha ?? "unknown-sha"}`,
|
|
402
|
-
});
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
if (runType === "ci_repair") {
|
|
406
|
-
const failureContext = parseObjectJson(issue.lastGitHubFailureContextJson);
|
|
407
|
-
this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
|
|
408
|
-
projectId: issue.projectId,
|
|
409
|
-
linearIssueId: issue.linearIssueId,
|
|
410
|
-
eventType: "settled_red_ci",
|
|
411
|
-
eventJson: JSON.stringify({
|
|
412
|
-
...(failureContext ?? {}),
|
|
413
|
-
source: "operator_retry",
|
|
414
|
-
}),
|
|
415
|
-
dedupeKey: `operator_retry:ci_repair:${issue.linearIssueId}:${issue.lastGitHubFailureSignature ?? issue.prHeadSha ?? "unknown-sha"}`,
|
|
416
|
-
});
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (runType === "review_fix" || runType === "branch_upkeep") {
|
|
420
|
-
this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
|
|
421
|
-
projectId: issue.projectId,
|
|
422
|
-
linearIssueId: issue.linearIssueId,
|
|
423
|
-
eventType: "review_changes_requested",
|
|
424
|
-
eventJson: JSON.stringify({
|
|
425
|
-
reviewBody: runType === "branch_upkeep"
|
|
426
|
-
? "Operator requested retry of branch upkeep after requested changes."
|
|
427
|
-
: "Operator requested retry of review-fix work.",
|
|
428
|
-
...(runType === "branch_upkeep" ? { branchUpkeepRequired: true, wakeReason: "branch_upkeep" } : {}),
|
|
429
|
-
source: "operator_retry",
|
|
430
|
-
}),
|
|
431
|
-
dedupeKey: `operator_retry:${runType}:${issue.linearIssueId}:${issue.prHeadSha ?? "unknown-sha"}`,
|
|
432
|
-
});
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
390
|
this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
|
|
436
391
|
projectId: issue.projectId,
|
|
437
392
|
linearIssueId: issue.linearIssueId,
|
|
438
|
-
|
|
439
|
-
eventJson: JSON.stringify({
|
|
440
|
-
promptContext: "Operator requested retry of PatchRelay work.",
|
|
441
|
-
source: "operator_retry",
|
|
442
|
-
}),
|
|
443
|
-
dedupeKey: `operator_retry:implementation:${issue.linearIssueId}`,
|
|
393
|
+
...buildOperatorRetryEvent(issue, runType),
|
|
444
394
|
});
|
|
445
395
|
}
|
|
446
396
|
list(options) {
|
|
@@ -18,32 +18,33 @@ function deriveProgressFactFromCompletedItem(rawItem, issue) {
|
|
|
18
18
|
if (item.type !== "agentMessage" || typeof item.text !== "string") {
|
|
19
19
|
return undefined;
|
|
20
20
|
}
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
21
|
+
const fullBody = sanitizeOperatorFacingText(item.text)?.replace(/\s+/g, " ").trim();
|
|
22
|
+
if (!fullBody) {
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const ephemeralBody = compactOperatorSentence(fullBody) ?? fullBody;
|
|
26
|
+
if (looksLikeVerification(fullBody)) {
|
|
26
27
|
return {
|
|
27
28
|
kind: "verification_started",
|
|
28
|
-
meaningKey: `verification:${normalizeMeaningKey(
|
|
29
|
-
ephemeralContent: { type: "thought", body },
|
|
30
|
-
historyContent: { type: "thought", body },
|
|
29
|
+
meaningKey: `verification:${normalizeMeaningKey(fullBody)}`,
|
|
30
|
+
ephemeralContent: { type: "thought", body: ephemeralBody },
|
|
31
|
+
historyContent: { type: "thought", body: fullBody },
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
|
-
if (looksLikePublishing(
|
|
34
|
+
if (looksLikePublishing(fullBody)) {
|
|
34
35
|
return {
|
|
35
36
|
kind: "publishing_started",
|
|
36
|
-
meaningKey: `publishing:${normalizeMeaningKey(
|
|
37
|
-
ephemeralContent: { type: "thought", body },
|
|
38
|
-
historyContent: { type: "thought", body },
|
|
37
|
+
meaningKey: `publishing:${normalizeMeaningKey(fullBody)}`,
|
|
38
|
+
ephemeralContent: { type: "thought", body: ephemeralBody },
|
|
39
|
+
historyContent: { type: "thought", body: fullBody },
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
if (looksLikeRootCause(
|
|
42
|
+
if (looksLikeRootCause(fullBody)) {
|
|
42
43
|
return {
|
|
43
44
|
kind: "root_cause_found",
|
|
44
|
-
meaningKey: `finding:${normalizeMeaningKey(
|
|
45
|
-
ephemeralContent: { type: "thought", body },
|
|
46
|
-
historyContent: { type: "thought", body },
|
|
45
|
+
meaningKey: `finding:${normalizeMeaningKey(fullBody)}`,
|
|
46
|
+
ephemeralContent: { type: "thought", body: ephemeralBody },
|
|
47
|
+
historyContent: { type: "thought", body: fullBody },
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
return undefined;
|
|
@@ -77,20 +77,35 @@ export function buildRunCompletedActivity(params) {
|
|
|
77
77
|
}
|
|
78
78
|
return undefined;
|
|
79
79
|
case "review_fix":
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
return summary
|
|
81
|
+
? {
|
|
82
|
+
type: "response",
|
|
83
|
+
body: summary,
|
|
84
|
+
}
|
|
85
|
+
: {
|
|
86
|
+
type: "response",
|
|
87
|
+
body: `Updated ${prLabel} to address review feedback.`,
|
|
88
|
+
};
|
|
84
89
|
case "ci_repair":
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
return summary
|
|
91
|
+
? {
|
|
92
|
+
type: "response",
|
|
93
|
+
body: summary,
|
|
94
|
+
}
|
|
95
|
+
: {
|
|
96
|
+
type: "response",
|
|
97
|
+
body: `Updated ${prLabel} after CI repair.`,
|
|
98
|
+
};
|
|
89
99
|
case "queue_repair":
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
return summary
|
|
101
|
+
? {
|
|
102
|
+
type: "response",
|
|
103
|
+
body: summary,
|
|
104
|
+
}
|
|
105
|
+
: {
|
|
106
|
+
type: "response",
|
|
107
|
+
body: `Updated ${prLabel} after merge-queue repair.`,
|
|
108
|
+
};
|
|
94
109
|
case "branch_upkeep":
|
|
95
110
|
return undefined;
|
|
96
111
|
default: {
|
|
@@ -38,9 +38,9 @@ export function buildOperatorRetryEvent(issue, runType, source = "operator_retry
|
|
|
38
38
|
return {
|
|
39
39
|
eventType: "review_changes_requested",
|
|
40
40
|
eventJson: JSON.stringify({
|
|
41
|
-
|
|
42
|
-
? `${humanizeSource(source)} requested retry of branch upkeep after requested changes.`
|
|
43
|
-
: `${humanizeSource(source)} requested retry of review-fix work
|
|
41
|
+
...(runType === "branch_upkeep"
|
|
42
|
+
? { reviewBody: `${humanizeSource(source)} requested retry of branch upkeep after requested changes.` }
|
|
43
|
+
: { promptContext: `${humanizeSource(source)} requested retry of review-fix work.` }),
|
|
44
44
|
...(runType === "branch_upkeep" ? { branchUpkeepRequired: true, wakeReason: "branch_upkeep" } : {}),
|
|
45
45
|
source,
|
|
46
46
|
}),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildReviewFixBranchUpkeepContext, isDirtyMergeStateStatus, isRequestedChangesRunType, readReactivePrSnapshot, } from "./reactive-pr-state.js";
|
|
2
|
+
import { readLatestRequestedChangesReviewContext } from "./remote-pr-review.js";
|
|
2
3
|
export class ReactiveRunPolicy {
|
|
3
4
|
config;
|
|
4
5
|
db;
|
|
@@ -136,6 +137,7 @@ export class ReactiveRunPolicy {
|
|
|
136
137
|
const snapshot = await readReactivePrSnapshot(this.config, issue.projectId, issue.prNumber);
|
|
137
138
|
if (!snapshot)
|
|
138
139
|
return context;
|
|
140
|
+
const refreshedContext = await this.hydrateRequestedChangesContext(issue.projectId, issue.prNumber, snapshot.repoFullName, snapshot.headSha, context);
|
|
139
141
|
this.upsertIssueIfLeaseHeld(issue.projectId, issue.linearIssueId, {
|
|
140
142
|
projectId: issue.projectId,
|
|
141
143
|
linearIssueId: issue.linearIssueId,
|
|
@@ -144,12 +146,12 @@ export class ReactiveRunPolicy {
|
|
|
144
146
|
...(snapshot.reviewState ? { prReviewState: snapshot.reviewState } : {}),
|
|
145
147
|
}, "review-fix wake refresh");
|
|
146
148
|
if (snapshot.prState !== "open")
|
|
147
|
-
return
|
|
149
|
+
return refreshedContext;
|
|
148
150
|
if (snapshot.reviewState && snapshot.reviewState !== "changes_requested")
|
|
149
|
-
return
|
|
151
|
+
return refreshedContext;
|
|
150
152
|
if (!isDirtyMergeStateStatus(snapshot.pr.mergeStateStatus))
|
|
151
|
-
return
|
|
152
|
-
return buildReviewFixBranchUpkeepContext(issue.prNumber, snapshot.baseBranch, snapshot.pr,
|
|
153
|
+
return refreshedContext;
|
|
154
|
+
return buildReviewFixBranchUpkeepContext(issue.prNumber, snapshot.baseBranch, snapshot.pr, refreshedContext);
|
|
153
155
|
}
|
|
154
156
|
catch (error) {
|
|
155
157
|
this.logger.debug({
|
|
@@ -210,4 +212,44 @@ export class ReactiveRunPolicy {
|
|
|
210
212
|
}
|
|
211
213
|
return updated;
|
|
212
214
|
}
|
|
215
|
+
async hydrateRequestedChangesContext(projectId, prNumber, repoFullName, headSha, context) {
|
|
216
|
+
const merged = {
|
|
217
|
+
...(context ?? {}),
|
|
218
|
+
...(headSha ? { headSha } : {}),
|
|
219
|
+
};
|
|
220
|
+
if (hasStructuredReviewContext(merged)) {
|
|
221
|
+
return merged;
|
|
222
|
+
}
|
|
223
|
+
const liveReview = await readLatestRequestedChangesReviewContext(repoFullName, prNumber);
|
|
224
|
+
if (!liveReview) {
|
|
225
|
+
return Object.keys(merged).length > 0 ? merged : context;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
...merged,
|
|
229
|
+
...(liveReview.reviewId !== undefined ? { reviewId: liveReview.reviewId } : {}),
|
|
230
|
+
...(liveReview.reviewCommitId ? { reviewCommitId: liveReview.reviewCommitId } : {}),
|
|
231
|
+
...(liveReview.reviewUrl ? { reviewUrl: liveReview.reviewUrl } : {}),
|
|
232
|
+
...(liveReview.reviewerName ? { reviewerName: liveReview.reviewerName } : {}),
|
|
233
|
+
...(liveReview.reviewBody ? { reviewBody: liveReview.reviewBody } : {}),
|
|
234
|
+
...(liveReview.reviewComments ? { reviewComments: liveReview.reviewComments } : {}),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function hasStructuredReviewContext(context) {
|
|
239
|
+
if (!context)
|
|
240
|
+
return false;
|
|
241
|
+
const reviewBody = typeof context.reviewBody === "string" ? context.reviewBody.trim() : "";
|
|
242
|
+
const isOperatorRetryPlaceholder = typeof context.source === "string"
|
|
243
|
+
&& context.source === "operator_retry"
|
|
244
|
+
&& /^operator requested retry of review-fix work\.?$/i.test(reviewBody);
|
|
245
|
+
if (isOperatorRetryPlaceholder) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (reviewBody)
|
|
249
|
+
return true;
|
|
250
|
+
if (typeof context.reviewUrl === "string" && context.reviewUrl.trim())
|
|
251
|
+
return true;
|
|
252
|
+
if (typeof context.reviewerName === "string" && context.reviewerName.trim())
|
|
253
|
+
return true;
|
|
254
|
+
return Array.isArray(context.reviewComments) && context.reviewComments.length > 0;
|
|
213
255
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { execCommand, safeJsonParse } from "./utils.js";
|
|
2
|
+
export async function readLatestRequestedChangesReviewContext(repoFullName, prNumber) {
|
|
3
|
+
const [owner, repo] = repoFullName.split("/", 2);
|
|
4
|
+
if (!owner || !repo) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
const reviewsResult = await execCommand("gh", [
|
|
8
|
+
"api",
|
|
9
|
+
`repos/${owner}/${repo}/pulls/${prNumber}/reviews?per_page=100`,
|
|
10
|
+
], { timeoutMs: 10_000 });
|
|
11
|
+
if (reviewsResult.exitCode !== 0) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const reviews = safeJsonParse(reviewsResult.stdout);
|
|
15
|
+
if (!Array.isArray(reviews)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const review = [...reviews].reverse().find((entry) => entry?.state?.trim().toUpperCase() === "CHANGES_REQUESTED");
|
|
19
|
+
if (!review?.id) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const comments = await readReviewComments(owner, repo, prNumber, review.id);
|
|
23
|
+
return {
|
|
24
|
+
reviewId: review.id,
|
|
25
|
+
...(typeof review.commit_id === "string" && review.commit_id.trim() ? { reviewCommitId: review.commit_id.trim() } : {}),
|
|
26
|
+
...(typeof review.html_url === "string" && review.html_url.trim() ? { reviewUrl: review.html_url.trim() } : {}),
|
|
27
|
+
...(typeof review.body === "string" && review.body.trim() ? { reviewBody: review.body.trim() } : {}),
|
|
28
|
+
...(typeof review.user?.login === "string" && review.user.login.trim() ? { reviewerName: review.user.login.trim() } : {}),
|
|
29
|
+
...(comments.length > 0 ? { reviewComments: comments } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function readReviewComments(owner, repo, prNumber, reviewId) {
|
|
33
|
+
const commentsResult = await execCommand("gh", [
|
|
34
|
+
"api",
|
|
35
|
+
`repos/${owner}/${repo}/pulls/${prNumber}/reviews/${reviewId}/comments?per_page=100`,
|
|
36
|
+
], { timeoutMs: 10_000 });
|
|
37
|
+
if (commentsResult.exitCode !== 0) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const comments = safeJsonParse(commentsResult.stdout);
|
|
41
|
+
if (!Array.isArray(comments)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return comments.flatMap((entry) => {
|
|
45
|
+
const body = typeof entry.body === "string" ? entry.body.trim() : "";
|
|
46
|
+
if (!body) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return [{
|
|
50
|
+
body,
|
|
51
|
+
...(typeof entry.path === "string" ? { path: entry.path } : {}),
|
|
52
|
+
...(typeof entry.line === "number" ? { line: entry.line } : {}),
|
|
53
|
+
...(typeof entry.side === "string" ? { side: entry.side } : {}),
|
|
54
|
+
...(typeof entry.start_line === "number" ? { startLine: entry.start_line } : {}),
|
|
55
|
+
...(typeof entry.start_side === "string" ? { startSide: entry.start_side } : {}),
|
|
56
|
+
...(typeof entry.html_url === "string" ? { url: entry.html_url } : {}),
|
|
57
|
+
...(typeof entry.user?.login === "string" ? { authorLogin: entry.user.login } : {}),
|
|
58
|
+
}];
|
|
59
|
+
});
|
|
60
|
+
}
|
package/dist/run-launcher.js
CHANGED
|
@@ -18,6 +18,16 @@ function shouldCompactThread(issue, threadGeneration, context) {
|
|
|
18
18
|
export function shouldReuseIssueThread(params) {
|
|
19
19
|
return Boolean(params.existingThreadId) && !params.compactThread && params.resumeThread;
|
|
20
20
|
}
|
|
21
|
+
export function shouldFreshenWorktreeBeforeLaunch(params) {
|
|
22
|
+
if (params.runType === "queue_repair") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (params.runType === "review_fix") {
|
|
26
|
+
return params.effectiveContext?.branchUpkeepRequired === true
|
|
27
|
+
|| params.effectiveContext?.reviewFixMode === "branch_upkeep";
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
21
31
|
export class RunLauncher {
|
|
22
32
|
config;
|
|
23
33
|
db;
|
|
@@ -125,7 +135,10 @@ export class RunLauncher {
|
|
|
125
135
|
});
|
|
126
136
|
}
|
|
127
137
|
await this.worktreeManager.resetWorktreeToTrackedBranch(params.worktreePath, params.branchName, params.issue, this.logger);
|
|
128
|
-
if (
|
|
138
|
+
if (shouldFreshenWorktreeBeforeLaunch({
|
|
139
|
+
runType: params.runType,
|
|
140
|
+
...(params.effectiveContext ? { effectiveContext: params.effectiveContext } : {}),
|
|
141
|
+
})) {
|
|
129
142
|
await this.worktreeManager.freshenWorktree(params.worktreePath, params.project, params.issue, this.logger);
|
|
130
143
|
}
|
|
131
144
|
const hookEnv = buildHookEnv(params.issue.issueKey ?? params.issue.linearIssueId, params.branchName, params.runType, params.worktreePath);
|