patchrelay 0.56.1 → 0.58.0
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/agent-session-plan.js +45 -9
- package/dist/agent-session-presentation.js +98 -11
- package/dist/build-info.json +3 -3
- package/dist/github-linear-session-sync.js +7 -0
- package/dist/http.js +2 -2
- package/dist/linear-agent-activity-recovery.js +83 -0
- package/dist/linear-agent-session-client.js +7 -0
- package/dist/linear-client.js +68 -0
- package/dist/prompting/patchrelay.js +6 -0
- package/dist/run-orchestrator.js +14 -2
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ function implementationPlan() {
|
|
|
15
15
|
return [
|
|
16
16
|
{ content: "Prepare workspace", status: "pending" },
|
|
17
17
|
{ content: "Implementing", status: "pending" },
|
|
18
|
-
{ content: "
|
|
18
|
+
{ content: "Fresh head pushed", status: "pending" },
|
|
19
19
|
{ content: "Merge", status: "pending" },
|
|
20
20
|
];
|
|
21
21
|
}
|
|
@@ -30,8 +30,8 @@ function orchestrationPlan() {
|
|
|
30
30
|
function reviewFixPlan() {
|
|
31
31
|
return [
|
|
32
32
|
{ content: "Prepare workspace", status: "completed" },
|
|
33
|
-
{ content: "Addressing
|
|
34
|
-
{ content: "
|
|
33
|
+
{ content: "Addressing requested changes", status: "pending" },
|
|
34
|
+
{ content: "Fresh head pushed", status: "pending" },
|
|
35
35
|
{ content: "Merge", status: "pending" },
|
|
36
36
|
];
|
|
37
37
|
}
|
|
@@ -39,7 +39,7 @@ function branchUpkeepPlan() {
|
|
|
39
39
|
return [
|
|
40
40
|
{ content: "Prepare workspace", status: "completed" },
|
|
41
41
|
{ content: "Repairing branch upkeep", status: "pending" },
|
|
42
|
-
{ content: "
|
|
42
|
+
{ content: "Fresh head pushed", status: "pending" },
|
|
43
43
|
{ content: "Merge", status: "pending" },
|
|
44
44
|
];
|
|
45
45
|
}
|
|
@@ -55,7 +55,7 @@ function mainRepairPlan(attempt) {
|
|
|
55
55
|
return [
|
|
56
56
|
{ content: "Inspect main failure", status: "pending" },
|
|
57
57
|
{ content: `Repairing main (${attemptLabel(attempt)})`, status: "pending" },
|
|
58
|
-
{ content: "
|
|
58
|
+
{ content: "Fresh head pushed", status: "pending" },
|
|
59
59
|
{ content: "Priority merge", status: "pending" },
|
|
60
60
|
];
|
|
61
61
|
}
|
|
@@ -152,7 +152,12 @@ export function buildAgentSessionPlan(params) {
|
|
|
152
152
|
? mainRepairPlan(params.ciRepairAttempts ?? 1)
|
|
153
153
|
: planForRunType("implementation", params), ["completed", "inProgress", "pending", "pending"]);
|
|
154
154
|
case "pr_open":
|
|
155
|
-
return setStatuses(
|
|
155
|
+
return setStatuses([
|
|
156
|
+
{ content: "Prepare workspace", status: "completed" },
|
|
157
|
+
{ content: "Implementing", status: "completed" },
|
|
158
|
+
{ content: prOpenGateLabel(params), status: "inProgress" },
|
|
159
|
+
{ content: "Merge", status: "pending" },
|
|
160
|
+
], ["completed", "completed", "inProgress", "pending"]);
|
|
156
161
|
case "changes_requested":
|
|
157
162
|
return setStatuses(reviewFixPlan(), ["completed", "inProgress", "pending", "pending"]);
|
|
158
163
|
case "repairing_ci":
|
|
@@ -160,9 +165,9 @@ export function buildAgentSessionPlan(params) {
|
|
|
160
165
|
case "awaiting_queue":
|
|
161
166
|
return setStatuses([
|
|
162
167
|
{ content: "Prepare workspace", status: "completed" },
|
|
163
|
-
{ content: "
|
|
168
|
+
{ content: "Fresh head pushed", status: "completed" },
|
|
164
169
|
{ content: "Verification passed", status: "completed" },
|
|
165
|
-
{ content: "Awaiting
|
|
170
|
+
{ content: "Awaiting queue", status: "inProgress" },
|
|
166
171
|
], ["completed", "completed", "completed", "inProgress"]);
|
|
167
172
|
case "repairing_queue":
|
|
168
173
|
return setStatuses(queueRepairPlan(params.queueRepairAttempts ?? 1), ["completed", "completed", "completed", "inProgress"]);
|
|
@@ -175,12 +180,41 @@ export function buildAgentSessionPlan(params) {
|
|
|
175
180
|
case "done":
|
|
176
181
|
return setStatuses([
|
|
177
182
|
{ content: "Prepare workspace", status: "completed" },
|
|
178
|
-
{ content: "
|
|
183
|
+
{ content: "Fresh head pushed", status: "completed" },
|
|
179
184
|
{ content: "Verification passed", status: "completed" },
|
|
180
185
|
{ content: "Merged", status: "completed" },
|
|
181
186
|
], ["completed", "completed", "completed", "completed"]);
|
|
182
187
|
}
|
|
183
188
|
}
|
|
189
|
+
function prOpenGateLabel(params) {
|
|
190
|
+
const reviewState = normalizeState(params.prReviewState);
|
|
191
|
+
const checkStatus = normalizeState(params.prCheckStatus);
|
|
192
|
+
if (isPendingCheckStatus(checkStatus)) {
|
|
193
|
+
return "Awaiting checks";
|
|
194
|
+
}
|
|
195
|
+
if (isAwaitingReviewState(reviewState)) {
|
|
196
|
+
return "Awaiting review";
|
|
197
|
+
}
|
|
198
|
+
if (isApprovedReviewState(reviewState) && isPassedCheckStatus(checkStatus)) {
|
|
199
|
+
return "Awaiting queue";
|
|
200
|
+
}
|
|
201
|
+
return "Fresh head pushed";
|
|
202
|
+
}
|
|
203
|
+
function isAwaitingReviewState(value) {
|
|
204
|
+
return value === "review_required" || value === "commented" || value === "changes_requested";
|
|
205
|
+
}
|
|
206
|
+
function isApprovedReviewState(value) {
|
|
207
|
+
return value === "approved";
|
|
208
|
+
}
|
|
209
|
+
function isPendingCheckStatus(value) {
|
|
210
|
+
return value === "pending" || value === "queued" || value === "in_progress" || value === "waiting";
|
|
211
|
+
}
|
|
212
|
+
function isPassedCheckStatus(value) {
|
|
213
|
+
return value === "success" || value === "passed";
|
|
214
|
+
}
|
|
215
|
+
function normalizeState(value) {
|
|
216
|
+
return (value ?? "").trim().toLowerCase().replace(/[-/\s]+/g, "_");
|
|
217
|
+
}
|
|
184
218
|
function planForRunType(runType, params) {
|
|
185
219
|
switch (runType) {
|
|
186
220
|
case "main_repair":
|
|
@@ -206,6 +240,8 @@ export function buildAgentSessionPlanForIssue(issue, options) {
|
|
|
206
240
|
...(issue.issueClass ? { issueClass: issue.issueClass } : {}),
|
|
207
241
|
...(issue.orchestrationSettleUntil ? { orchestrationSettleUntil: issue.orchestrationSettleUntil } : {}),
|
|
208
242
|
...(issue.pendingRunType ? { pendingRunType: issue.pendingRunType } : {}),
|
|
243
|
+
...(issue.prReviewState ? { prReviewState: issue.prReviewState } : {}),
|
|
244
|
+
...(issue.prCheckStatus ? { prCheckStatus: issue.prCheckStatus } : {}),
|
|
209
245
|
...(options?.activeRunType ? { activeRunType: options.activeRunType } : {}),
|
|
210
246
|
});
|
|
211
247
|
}
|
|
@@ -2,19 +2,13 @@ import { buildSessionStatusUrl, createSessionStatusToken, deriveSessionStatusSig
|
|
|
2
2
|
const SESSION_STATUS_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
3
3
|
export function buildAgentSessionExternalUrls(config, params) {
|
|
4
4
|
const urls = [];
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
ttlSeconds: SESSION_STATUS_TTL_SECONDS,
|
|
10
|
-
});
|
|
5
|
+
const statusUrl = params.issueKey && config.server.publicBaseUrl
|
|
6
|
+
? buildPublicStatusUrl(config, params.issueKey)
|
|
7
|
+
: undefined;
|
|
8
|
+
if (statusUrl) {
|
|
11
9
|
urls.push({
|
|
12
10
|
label: "PatchRelay status",
|
|
13
|
-
url:
|
|
14
|
-
publicBaseUrl: config.server.publicBaseUrl,
|
|
15
|
-
issueKey: params.issueKey,
|
|
16
|
-
token: token.token,
|
|
17
|
-
}),
|
|
11
|
+
url: statusUrl,
|
|
18
12
|
});
|
|
19
13
|
}
|
|
20
14
|
if (params.prUrl) {
|
|
@@ -23,5 +17,98 @@ export function buildAgentSessionExternalUrls(config, params) {
|
|
|
23
17
|
url: params.prUrl,
|
|
24
18
|
});
|
|
25
19
|
}
|
|
20
|
+
const reviewQuillUrl = buildReviewQuillUrl(params);
|
|
21
|
+
if (reviewQuillUrl) {
|
|
22
|
+
urls.push({
|
|
23
|
+
label: "Review-quill status",
|
|
24
|
+
url: reviewQuillUrl,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const mergeStewardUrl = buildMergeStewardUrl(params);
|
|
28
|
+
if (mergeStewardUrl) {
|
|
29
|
+
urls.push({
|
|
30
|
+
label: "Merge-steward queue",
|
|
31
|
+
url: mergeStewardUrl,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (statusUrl && params.activeRunId !== undefined) {
|
|
35
|
+
urls.push({
|
|
36
|
+
label: "Active run",
|
|
37
|
+
url: withFragment(statusUrl, "current-view"),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
26
40
|
return urls.length > 0 ? urls : undefined;
|
|
27
41
|
}
|
|
42
|
+
function buildPublicStatusUrl(config, issueKey) {
|
|
43
|
+
if (!config.server.publicBaseUrl)
|
|
44
|
+
return undefined;
|
|
45
|
+
const token = createSessionStatusToken({
|
|
46
|
+
issueKey,
|
|
47
|
+
secret: deriveSessionStatusSigningSecret(config.linear.tokenEncryptionKey),
|
|
48
|
+
ttlSeconds: SESSION_STATUS_TTL_SECONDS,
|
|
49
|
+
});
|
|
50
|
+
return buildSessionStatusUrl({
|
|
51
|
+
publicBaseUrl: config.server.publicBaseUrl,
|
|
52
|
+
issueKey,
|
|
53
|
+
token: token.token,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function buildReviewQuillUrl(params) {
|
|
57
|
+
if (isReviewQuillCheck(params.lastGitHubFailureCheckName) && params.lastGitHubFailureCheckUrl) {
|
|
58
|
+
return params.lastGitHubFailureCheckUrl;
|
|
59
|
+
}
|
|
60
|
+
if (!params.prUrl || !hasReviewQuillContext(params)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return `${trimTrailingSlash(params.prUrl)}/checks`;
|
|
64
|
+
}
|
|
65
|
+
function buildMergeStewardUrl(params) {
|
|
66
|
+
const incidentUrl = parseQueueIncidentUrl(params.lastQueueIncidentJson);
|
|
67
|
+
if (incidentUrl)
|
|
68
|
+
return incidentUrl;
|
|
69
|
+
if (params.lastGitHubFailureSource === "queue_eviction"
|
|
70
|
+
|| isMergeStewardCheck(params.lastGitHubFailureCheckName)) {
|
|
71
|
+
return params.lastGitHubFailureCheckUrl;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
function hasReviewQuillContext(params) {
|
|
76
|
+
if (isReviewQuillCheck(params.lastGitHubFailureCheckName))
|
|
77
|
+
return true;
|
|
78
|
+
const reviewState = normalizeState(params.prReviewState);
|
|
79
|
+
if (reviewState === "review_required" || reviewState === "changes_requested" || reviewState === "commented") {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return normalizeState(params.prCheckStatus).includes("review");
|
|
83
|
+
}
|
|
84
|
+
function isReviewQuillCheck(checkName) {
|
|
85
|
+
return normalizeState(checkName).includes("review_quill");
|
|
86
|
+
}
|
|
87
|
+
function isMergeStewardCheck(checkName) {
|
|
88
|
+
return normalizeState(checkName).includes("merge_steward");
|
|
89
|
+
}
|
|
90
|
+
function parseQueueIncidentUrl(value) {
|
|
91
|
+
if (!value)
|
|
92
|
+
return undefined;
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(value);
|
|
95
|
+
if (!parsed || typeof parsed !== "object")
|
|
96
|
+
return undefined;
|
|
97
|
+
const incidentUrl = parsed.incidentUrl;
|
|
98
|
+
return typeof incidentUrl === "string" && incidentUrl.trim() ? incidentUrl.trim() : undefined;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function withFragment(url, fragment) {
|
|
105
|
+
const parsed = new URL(url);
|
|
106
|
+
parsed.hash = fragment;
|
|
107
|
+
return parsed.toString();
|
|
108
|
+
}
|
|
109
|
+
function trimTrailingSlash(value) {
|
|
110
|
+
return value.replace(/\/+$/, "");
|
|
111
|
+
}
|
|
112
|
+
function normalizeState(value) {
|
|
113
|
+
return (value ?? "").trim().toLowerCase().replace(/[-/\s]+/g, "_");
|
|
114
|
+
}
|
package/dist/build-info.json
CHANGED
|
@@ -43,6 +43,13 @@ export async function syncGitHubLinearSession(params) {
|
|
|
43
43
|
const externalUrls = buildAgentSessionExternalUrls(config, {
|
|
44
44
|
...(issue.issueKey ? { issueKey: issue.issueKey } : {}),
|
|
45
45
|
...(issue.prUrl ? { prUrl: issue.prUrl } : {}),
|
|
46
|
+
...(issue.activeRunId !== undefined ? { activeRunId: issue.activeRunId } : {}),
|
|
47
|
+
...(issue.prReviewState ? { prReviewState: issue.prReviewState } : {}),
|
|
48
|
+
...(issue.prCheckStatus ? { prCheckStatus: issue.prCheckStatus } : {}),
|
|
49
|
+
...(issue.lastGitHubFailureSource ? { lastGitHubFailureSource: issue.lastGitHubFailureSource } : {}),
|
|
50
|
+
...(issue.lastGitHubFailureCheckName ? { lastGitHubFailureCheckName: issue.lastGitHubFailureCheckName } : {}),
|
|
51
|
+
...(issue.lastGitHubFailureCheckUrl ? { lastGitHubFailureCheckUrl: issue.lastGitHubFailureCheckUrl } : {}),
|
|
52
|
+
...(issue.lastQueueIncidentJson ? { lastQueueIncidentJson: issue.lastQueueIncidentJson } : {}),
|
|
46
53
|
});
|
|
47
54
|
await linear.updateAgentSession({
|
|
48
55
|
agentSessionId: issue.agentSessionId,
|
package/dist/http.js
CHANGED
|
@@ -578,7 +578,7 @@ function renderAgentSessionStatusPage(params) {
|
|
|
578
578
|
<span class="chip"><strong>Latest:</strong> ${latestStage}</span>
|
|
579
579
|
<span class="chip"><strong>Thread:</strong> ${threadInfo}</span>
|
|
580
580
|
</div>
|
|
581
|
-
<div class="section">
|
|
581
|
+
<div id="current-view" class="section">
|
|
582
582
|
<h2>Current View</h2>
|
|
583
583
|
<table>
|
|
584
584
|
<tbody>
|
|
@@ -600,7 +600,7 @@ function renderAgentSessionStatusPage(params) {
|
|
|
600
600
|
<span class="chip"><strong>CI repairs:</strong> ${escapeHtml(String(ciAttempts))}</span>
|
|
601
601
|
<span class="chip"><strong>Steward repairs:</strong> ${escapeHtml(String(queueAttempts))}</span>
|
|
602
602
|
</div>
|
|
603
|
-
<div class="section">
|
|
603
|
+
<div id="recent-stages" class="section">
|
|
604
604
|
<h2>Recent Stages</h2>
|
|
605
605
|
<table>
|
|
606
606
|
<thead>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const ACTIVITY_RECOVERY_LIMIT = 20;
|
|
2
|
+
const MAX_CONTEXT_ACTIVITIES = 8;
|
|
3
|
+
const MAX_ACTIVITY_TEXT_LENGTH = 500;
|
|
4
|
+
function trimBounded(value, maxLength = MAX_ACTIVITY_TEXT_LENGTH) {
|
|
5
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
6
|
+
if (normalized.length <= maxLength)
|
|
7
|
+
return normalized;
|
|
8
|
+
return `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
|
|
9
|
+
}
|
|
10
|
+
function hasRecoveredContext(context) {
|
|
11
|
+
return typeof context?.linearAgentActivityContext === "string" && context.linearAgentActivityContext.trim().length > 0;
|
|
12
|
+
}
|
|
13
|
+
function hasLocalHumanContext(context) {
|
|
14
|
+
if (hasRecoveredContext(context))
|
|
15
|
+
return true;
|
|
16
|
+
for (const key of ["promptContext", "promptBody", "operatorPrompt", "userComment"]) {
|
|
17
|
+
const value = context?.[key];
|
|
18
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(context?.followUps))
|
|
22
|
+
return false;
|
|
23
|
+
return context.followUps.some((entry) => {
|
|
24
|
+
if (!entry || typeof entry !== "object")
|
|
25
|
+
return false;
|
|
26
|
+
const text = entry.text;
|
|
27
|
+
return typeof text === "string" && text.trim().length > 0;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function activitySortKey(activity) {
|
|
31
|
+
const parsed = activity.updatedAt ? Date.parse(activity.updatedAt) : NaN;
|
|
32
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
33
|
+
}
|
|
34
|
+
function describeActivity(activity) {
|
|
35
|
+
const type = activity.type?.trim() || "activity";
|
|
36
|
+
const body = typeof activity.body === "string" ? trimBounded(activity.body) : "";
|
|
37
|
+
if (body) {
|
|
38
|
+
return `${type}: ${body}`;
|
|
39
|
+
}
|
|
40
|
+
if (activity.action || activity.parameter || activity.result) {
|
|
41
|
+
const action = activity.action ? trimBounded(activity.action, 120) : "action";
|
|
42
|
+
const parameter = activity.parameter ? ` ${trimBounded(activity.parameter, 180)}` : "";
|
|
43
|
+
const result = activity.result ? ` -> ${trimBounded(activity.result, 180)}` : "";
|
|
44
|
+
return `${type}: ${action}${parameter}${result}`;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
export function summarizeLinearAgentActivities(activities) {
|
|
49
|
+
const lines = [...activities]
|
|
50
|
+
.sort((left, right) => activitySortKey(left) - activitySortKey(right))
|
|
51
|
+
.map(describeActivity)
|
|
52
|
+
.filter((line) => Boolean(line))
|
|
53
|
+
.slice(-MAX_CONTEXT_ACTIVITIES);
|
|
54
|
+
if (lines.length === 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
return {
|
|
57
|
+
linearAgentActivityContext: lines.map((line) => `- ${line}`).join("\n"),
|
|
58
|
+
linearAgentActivityCount: lines.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function recoverLinearAgentActivityContext(params) {
|
|
62
|
+
if (!params.agentSessionId || hasLocalHumanContext(params.context)) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const linear = await params.linearProvider.forProject(params.projectId);
|
|
67
|
+
if (!linear?.listAgentSessionActivities) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const activities = await linear.listAgentSessionActivities(params.agentSessionId, {
|
|
71
|
+
first: ACTIVITY_RECOVERY_LIMIT,
|
|
72
|
+
});
|
|
73
|
+
return summarizeLinearAgentActivities(activities);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
params.logger.warn({
|
|
77
|
+
issueKey: params.issueKey,
|
|
78
|
+
agentSessionId: params.agentSessionId,
|
|
79
|
+
error: error instanceof Error ? error.message : String(error),
|
|
80
|
+
}, "Failed to recover Linear agent activity context");
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -75,6 +75,13 @@ export class LinearAgentSessionClient {
|
|
|
75
75
|
const externalUrls = buildAgentSessionExternalUrls(this.config, {
|
|
76
76
|
...(issue.issueKey ? { issueKey: issue.issueKey } : {}),
|
|
77
77
|
...(issue.prUrl ? { prUrl: issue.prUrl } : {}),
|
|
78
|
+
...(issue.activeRunId !== undefined ? { activeRunId: issue.activeRunId } : {}),
|
|
79
|
+
...(issue.prReviewState ? { prReviewState: issue.prReviewState } : {}),
|
|
80
|
+
...(issue.prCheckStatus ? { prCheckStatus: issue.prCheckStatus } : {}),
|
|
81
|
+
...(issue.lastGitHubFailureSource ? { lastGitHubFailureSource: issue.lastGitHubFailureSource } : {}),
|
|
82
|
+
...(issue.lastGitHubFailureCheckName ? { lastGitHubFailureCheckName: issue.lastGitHubFailureCheckName } : {}),
|
|
83
|
+
...(issue.lastGitHubFailureCheckUrl ? { lastGitHubFailureCheckUrl: issue.lastGitHubFailureCheckUrl } : {}),
|
|
84
|
+
...(issue.lastQueueIncidentJson ? { lastQueueIncidentJson: issue.lastQueueIncidentJson } : {}),
|
|
78
85
|
});
|
|
79
86
|
await linear.updateAgentSession({
|
|
80
87
|
agentSessionId: issue.agentSessionId,
|
package/dist/linear-client.js
CHANGED
|
@@ -241,6 +241,55 @@ export class LinearGraphqlClient {
|
|
|
241
241
|
}
|
|
242
242
|
return response.agentSessionUpdate.agentSession;
|
|
243
243
|
}
|
|
244
|
+
async listAgentSessionActivities(agentSessionId, options) {
|
|
245
|
+
const response = await this.request(`
|
|
246
|
+
query PatchRelayAgentSessionActivities($id: String!, $first: Int!) {
|
|
247
|
+
agentSession(id: $id) {
|
|
248
|
+
activities(first: $first) {
|
|
249
|
+
edges {
|
|
250
|
+
node {
|
|
251
|
+
id
|
|
252
|
+
updatedAt
|
|
253
|
+
content {
|
|
254
|
+
__typename
|
|
255
|
+
... on AgentActivityThoughtContent {
|
|
256
|
+
body
|
|
257
|
+
}
|
|
258
|
+
... on AgentActivityActionContent {
|
|
259
|
+
action
|
|
260
|
+
parameter
|
|
261
|
+
result
|
|
262
|
+
}
|
|
263
|
+
... on AgentActivityElicitationContent {
|
|
264
|
+
body
|
|
265
|
+
}
|
|
266
|
+
... on AgentActivityResponseContent {
|
|
267
|
+
body
|
|
268
|
+
}
|
|
269
|
+
... on AgentActivityErrorContent {
|
|
270
|
+
body
|
|
271
|
+
}
|
|
272
|
+
... on AgentActivityPromptContent {
|
|
273
|
+
body
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
`, {
|
|
282
|
+
id: agentSessionId,
|
|
283
|
+
first: Math.max(1, Math.min(options?.first ?? 20, 50)),
|
|
284
|
+
});
|
|
285
|
+
const activityEdges = response.agentSession?.activities?.edges;
|
|
286
|
+
const rawActivities = activityEdges
|
|
287
|
+
? activityEdges.map((edge) => edge?.node)
|
|
288
|
+
: response.agentSession?.activities?.nodes ?? [];
|
|
289
|
+
return rawActivities
|
|
290
|
+
.filter((activity) => Boolean(activity))
|
|
291
|
+
.map(mapAgentActivity);
|
|
292
|
+
}
|
|
244
293
|
async updateIssueLabels(params) {
|
|
245
294
|
const issue = await this.getIssue(params.issueId);
|
|
246
295
|
const addIds = this.resolveLabelIds(issue, params.addNames ?? []);
|
|
@@ -439,6 +488,25 @@ function mapIssueRelation(raw) {
|
|
|
439
488
|
...(raw.state?.type ? { stateType: raw.state.type } : {}),
|
|
440
489
|
};
|
|
441
490
|
}
|
|
491
|
+
function mapAgentActivity(raw) {
|
|
492
|
+
const content = raw.content ?? {};
|
|
493
|
+
return {
|
|
494
|
+
id: raw.id,
|
|
495
|
+
...(content.__typename ? { type: normalizeAgentActivityType(content.__typename) } : {}),
|
|
496
|
+
...(content.body ? { body: content.body } : {}),
|
|
497
|
+
...(content.action ? { action: content.action } : {}),
|
|
498
|
+
...(content.parameter ? { parameter: content.parameter } : {}),
|
|
499
|
+
...(content.result ? { result: content.result } : {}),
|
|
500
|
+
...(raw.updatedAt ? { updatedAt: raw.updatedAt } : {}),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function normalizeAgentActivityType(typename) {
|
|
504
|
+
return typename
|
|
505
|
+
.replace(/^AgentActivity/, "")
|
|
506
|
+
.replace(/Content$/, "")
|
|
507
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
508
|
+
.toLowerCase();
|
|
509
|
+
}
|
|
442
510
|
export class DatabaseBackedLinearClientProvider {
|
|
443
511
|
config;
|
|
444
512
|
db;
|
|
@@ -197,6 +197,9 @@ function buildHumanContextLines(context) {
|
|
|
197
197
|
const latestPrompt = typeof context?.promptBody === "string" ? context.promptBody.trim() : "";
|
|
198
198
|
const operatorPrompt = typeof context?.operatorPrompt === "string" ? context.operatorPrompt.trim() : "";
|
|
199
199
|
const userComment = typeof context?.userComment === "string" ? context.userComment.trim() : "";
|
|
200
|
+
const linearAgentActivityContext = typeof context?.linearAgentActivityContext === "string"
|
|
201
|
+
? context.linearAgentActivityContext.trim()
|
|
202
|
+
: "";
|
|
200
203
|
const lines = [];
|
|
201
204
|
if (promptContext) {
|
|
202
205
|
lines.push("Linear session context:", promptContext, "");
|
|
@@ -210,6 +213,9 @@ function buildHumanContextLines(context) {
|
|
|
210
213
|
if (userComment) {
|
|
211
214
|
lines.push("Human follow-up comment:", userComment, "");
|
|
212
215
|
}
|
|
216
|
+
if (linearAgentActivityContext) {
|
|
217
|
+
lines.push("Recovered Linear agent activity context:", linearAgentActivityContext, "");
|
|
218
|
+
}
|
|
213
219
|
return lines;
|
|
214
220
|
}
|
|
215
221
|
function resolveRequestedChangesMode(runType, context) {
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -8,6 +8,7 @@ import { MainBranchHealthMonitor } from "./main-branch-health-monitor.js";
|
|
|
8
8
|
import { QueueHealthMonitor } from "./queue-health-monitor.js";
|
|
9
9
|
import { IdleIssueReconciler } from "./idle-reconciliation.js";
|
|
10
10
|
import { LinearSessionSync } from "./linear-session-sync.js";
|
|
11
|
+
import { recoverLinearAgentActivityContext } from "./linear-agent-activity-recovery.js";
|
|
11
12
|
import { IssueSessionLeaseService } from "./issue-session-lease-service.js";
|
|
12
13
|
import { InterruptedRunRecovery } from "./interrupted-run-recovery.js";
|
|
13
14
|
import { RunCompletionPolicy } from "./run-completion-policy.js";
|
|
@@ -234,12 +235,23 @@ export class RunOrchestrator {
|
|
|
234
235
|
const baseContext = isRequestedChangesRunType(runType)
|
|
235
236
|
? await this.runCompletionPolicy.resolveRequestedChangesWakeContext(issue, runType, context)
|
|
236
237
|
: context;
|
|
238
|
+
const recoveredLinearActivityContext = await recoverLinearAgentActivityContext({
|
|
239
|
+
linearProvider: this.linearProvider,
|
|
240
|
+
projectId: issue.projectId,
|
|
241
|
+
agentSessionId: issue.agentSessionId,
|
|
242
|
+
context: baseContext,
|
|
243
|
+
issueKey: issue.issueKey,
|
|
244
|
+
logger: this.logger,
|
|
245
|
+
});
|
|
246
|
+
const baseContextWithRecoveredActivity = recoveredLinearActivityContext
|
|
247
|
+
? { ...baseContext, ...recoveredLinearActivityContext }
|
|
248
|
+
: baseContext;
|
|
237
249
|
const coordinationContext = runType === "implementation"
|
|
238
250
|
? this.buildRelatedIssueContext(issue)
|
|
239
251
|
: undefined;
|
|
240
252
|
const effectiveContext = coordinationContext
|
|
241
|
-
? { ...coordinationContext, ...
|
|
242
|
-
:
|
|
253
|
+
? { ...coordinationContext, ...baseContextWithRecoveredActivity }
|
|
254
|
+
: baseContextWithRecoveredActivity;
|
|
243
255
|
const sourceHeadSha = typeof effectiveContext?.failureHeadSha === "string"
|
|
244
256
|
? effectiveContext.failureHeadSha
|
|
245
257
|
: typeof effectiveContext?.headSha === "string"
|