patchrelay 0.7.8 → 0.7.10
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-client.js +10 -0
- package/dist/service-stage-runner.js +41 -13
- package/dist/service-webhook-processor.js +132 -23
- package/dist/stage-failure.js +1 -1
- package/dist/stage-lifecycle-publisher.js +2 -2
- package/dist/webhook-agent-session-handler.js +2 -2
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/linear-client.js
CHANGED
|
@@ -15,6 +15,10 @@ export class LinearGraphqlClient {
|
|
|
15
15
|
identifier
|
|
16
16
|
title
|
|
17
17
|
url
|
|
18
|
+
delegate {
|
|
19
|
+
id
|
|
20
|
+
name
|
|
21
|
+
}
|
|
18
22
|
state {
|
|
19
23
|
id
|
|
20
24
|
name
|
|
@@ -65,6 +69,10 @@ export class LinearGraphqlClient {
|
|
|
65
69
|
identifier
|
|
66
70
|
title
|
|
67
71
|
url
|
|
72
|
+
delegate {
|
|
73
|
+
id
|
|
74
|
+
name
|
|
75
|
+
}
|
|
68
76
|
state {
|
|
69
77
|
id
|
|
70
78
|
name
|
|
@@ -290,6 +298,8 @@ export class LinearGraphqlClient {
|
|
|
290
298
|
...(issue.state?.name ? { stateName: issue.state.name } : {}),
|
|
291
299
|
...(issue.team?.id ? { teamId: issue.team.id } : {}),
|
|
292
300
|
...(issue.team?.key ? { teamKey: issue.team.key } : {}),
|
|
301
|
+
...(issue.delegate?.id ? { delegateId: issue.delegate.id } : {}),
|
|
302
|
+
...(issue.delegate?.name ? { delegateName: issue.delegate.name } : {}),
|
|
293
303
|
workflowStates: (issue.team?.states?.nodes ?? []).map((state) => ({
|
|
294
304
|
id: state.id,
|
|
295
305
|
name: state.name,
|
|
@@ -3,6 +3,7 @@ import { syncFailedStageToLinear } from "./stage-failure.js";
|
|
|
3
3
|
import { buildFailedStageReport } from "./stage-reporting.js";
|
|
4
4
|
import { StageLifecyclePublisher } from "./stage-lifecycle-publisher.js";
|
|
5
5
|
import { StageTurnInputDispatcher } from "./stage-turn-input-dispatcher.js";
|
|
6
|
+
import { safeJsonParse } from "./utils.js";
|
|
6
7
|
import { WorktreeManager } from "./worktree-manager.js";
|
|
7
8
|
export class ServiceStageRunner {
|
|
8
9
|
config;
|
|
@@ -86,11 +87,16 @@ export class ServiceStageRunner {
|
|
|
86
87
|
});
|
|
87
88
|
await this.lifecyclePublisher.markStageActive(project, claim.issue, claim.stageRun);
|
|
88
89
|
threadLaunch = await this.launchStageThread(item.projectId, item.issueId, claim.stageRun.id, plan.worktreePath, issue.issueKey);
|
|
90
|
+
const pendingLaunchInput = this.collectPendingLaunchInput(item.projectId, item.issueId);
|
|
91
|
+
const initialTurnInput = pendingLaunchInput.combinedInput
|
|
92
|
+
? [plan.prompt, "", pendingLaunchInput.combinedInput].join("\n")
|
|
93
|
+
: plan.prompt;
|
|
89
94
|
turn = await this.codex.startTurn({
|
|
90
95
|
threadId: threadLaunch.threadId,
|
|
91
96
|
cwd: plan.worktreePath,
|
|
92
|
-
input:
|
|
97
|
+
input: initialTurnInput,
|
|
93
98
|
});
|
|
99
|
+
this.completeDeliveredLaunchInput(pendingLaunchInput.obligationIds, claim.stageRun.id, threadLaunch.threadId, turn.turnId);
|
|
94
100
|
}
|
|
95
101
|
catch (error) {
|
|
96
102
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -122,19 +128,8 @@ export class ServiceStageRunner {
|
|
|
122
128
|
turnId: turn.turnId,
|
|
123
129
|
});
|
|
124
130
|
this.inputDispatcher.routePendingInputs(claim.stageRun, threadLaunch.threadId, turn.turnId);
|
|
125
|
-
await this.inputDispatcher.flush({
|
|
126
|
-
id: claim.stageRun.id,
|
|
127
|
-
projectId: claim.stageRun.projectId,
|
|
128
|
-
linearIssueId: claim.stageRun.linearIssueId,
|
|
129
|
-
threadId: threadLaunch.threadId,
|
|
130
|
-
turnId: turn.turnId,
|
|
131
|
-
}, {
|
|
132
|
-
logFailures: true,
|
|
133
|
-
failureMessage: "Failed to deliver queued Linear comment during stage startup",
|
|
134
|
-
...(claim.issue.issueKey ? { issueKey: claim.issue.issueKey } : {}),
|
|
135
|
-
});
|
|
136
131
|
const deliveredToSession = await this.lifecyclePublisher.publishStageStarted(claim.issue, claim.stageRun.stage);
|
|
137
|
-
if (!deliveredToSession) {
|
|
132
|
+
if (!deliveredToSession && !claim.issue.activeAgentSessionId) {
|
|
138
133
|
await this.lifecyclePublisher.refreshRunningStatusComment(item.projectId, item.issueId, claim.stageRun.id, issue.issueKey);
|
|
139
134
|
}
|
|
140
135
|
this.logger.info({
|
|
@@ -156,6 +151,39 @@ export class ServiceStageRunner {
|
|
|
156
151
|
detail: `Turn ${turn.turnId} is running in ${plan.branchName}.`,
|
|
157
152
|
});
|
|
158
153
|
}
|
|
154
|
+
collectPendingLaunchInput(projectId, issueId) {
|
|
155
|
+
const obligationIds = [];
|
|
156
|
+
const bodies = [];
|
|
157
|
+
for (const obligation of this.stores.obligations.listPendingObligations({ kind: "deliver_turn_input" })) {
|
|
158
|
+
if (obligation.projectId !== projectId ||
|
|
159
|
+
obligation.linearIssueId !== issueId ||
|
|
160
|
+
!obligation.source.startsWith("linear-agent-launch:")) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const payload = safeJsonParse(obligation.payloadJson);
|
|
164
|
+
const body = payload?.body?.trim();
|
|
165
|
+
if (!body) {
|
|
166
|
+
this.stores.obligations.markObligationStatus(obligation.id, "failed", "obligation payload had no deliverable body");
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
obligationIds.push(obligation.id);
|
|
170
|
+
bodies.push(body);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
...(bodies.length > 0 ? { combinedInput: bodies.join("\n\n") } : {}),
|
|
174
|
+
obligationIds,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
completeDeliveredLaunchInput(obligationIds, runLeaseId, threadId, turnId) {
|
|
178
|
+
for (const obligationId of obligationIds) {
|
|
179
|
+
this.stores.obligations.updateObligationRouting(obligationId, {
|
|
180
|
+
runLeaseId,
|
|
181
|
+
threadId,
|
|
182
|
+
turnId,
|
|
183
|
+
});
|
|
184
|
+
this.stores.obligations.markObligationStatus(obligationId, "completed");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
159
187
|
async ensureLaunchIssueMirror(project, linearIssueId, _desiredStage, _desiredWebhookId) {
|
|
160
188
|
const existing = this.stores.issueWorkflows.getTrackedIssue(project.id, linearIssueId);
|
|
161
189
|
if (existing?.issueKey && existing.title && existing.issueUrl && existing.currentLinearState) {
|
|
@@ -10,6 +10,7 @@ import { normalizeWebhook } from "./webhooks.js";
|
|
|
10
10
|
export class ServiceWebhookProcessor {
|
|
11
11
|
config;
|
|
12
12
|
stores;
|
|
13
|
+
linearProvider;
|
|
13
14
|
enqueueIssue;
|
|
14
15
|
logger;
|
|
15
16
|
feed;
|
|
@@ -20,6 +21,7 @@ export class ServiceWebhookProcessor {
|
|
|
20
21
|
constructor(config, stores, linearProvider, codex, enqueueIssue, logger, feed) {
|
|
21
22
|
this.config = config;
|
|
22
23
|
this.stores = stores;
|
|
24
|
+
this.linearProvider = linearProvider;
|
|
23
25
|
this.enqueueIssue = enqueueIssue;
|
|
24
26
|
this.logger = logger;
|
|
25
27
|
this.feed = feed;
|
|
@@ -43,7 +45,7 @@ export class ServiceWebhookProcessor {
|
|
|
43
45
|
this.markEventReceiptProcessed(event.webhookId, "failed");
|
|
44
46
|
throw new Error(`Stored webhook payload is invalid JSON: event ${webhookEventId}`);
|
|
45
47
|
}
|
|
46
|
-
|
|
48
|
+
let normalized = normalizeWebhook({
|
|
47
49
|
webhookId: event.webhookId,
|
|
48
50
|
payload,
|
|
49
51
|
});
|
|
@@ -67,12 +69,25 @@ export class ServiceWebhookProcessor {
|
|
|
67
69
|
this.markEventReceiptProcessed(event.webhookId, "processed");
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
let project = resolveProject(this.config, normalized.issue);
|
|
71
73
|
if (!project) {
|
|
74
|
+
const routed = await this.tryHydrateProjectRoute(normalized);
|
|
75
|
+
if (routed) {
|
|
76
|
+
normalized = routed.normalized;
|
|
77
|
+
project = routed.project;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!project) {
|
|
81
|
+
const unresolvedIssue = normalized.issue;
|
|
82
|
+
if (!unresolvedIssue) {
|
|
83
|
+
this.stores.webhookEvents.markWebhookProcessed(webhookEventId, "failed");
|
|
84
|
+
this.markEventReceiptProcessed(event.webhookId, "failed");
|
|
85
|
+
throw new Error(`Normalized issue context disappeared before routing webhook ${event.webhookId}`);
|
|
86
|
+
}
|
|
72
87
|
this.feed?.publish({
|
|
73
88
|
level: "warn",
|
|
74
89
|
kind: "webhook",
|
|
75
|
-
issueKey:
|
|
90
|
+
issueKey: unresolvedIssue.identifier,
|
|
76
91
|
status: "ignored",
|
|
77
92
|
summary: "Ignored webhook with no matching project route",
|
|
78
93
|
detail: normalized.triggerEvent,
|
|
@@ -80,21 +95,27 @@ export class ServiceWebhookProcessor {
|
|
|
80
95
|
this.logger.info({
|
|
81
96
|
webhookEventId,
|
|
82
97
|
webhookId: event.webhookId,
|
|
83
|
-
issueKey:
|
|
84
|
-
issueId:
|
|
85
|
-
teamId:
|
|
86
|
-
teamKey:
|
|
98
|
+
issueKey: unresolvedIssue.identifier,
|
|
99
|
+
issueId: unresolvedIssue.id,
|
|
100
|
+
teamId: unresolvedIssue.teamId,
|
|
101
|
+
teamKey: unresolvedIssue.teamKey,
|
|
87
102
|
triggerEvent: normalized.triggerEvent,
|
|
88
103
|
}, "Ignoring webhook because no project route matched the Linear issue");
|
|
89
104
|
this.stores.webhookEvents.markWebhookProcessed(webhookEventId, "processed");
|
|
90
105
|
this.markEventReceiptProcessed(event.webhookId, "processed");
|
|
91
106
|
return;
|
|
92
107
|
}
|
|
108
|
+
const routedIssue = normalized.issue;
|
|
109
|
+
if (!routedIssue) {
|
|
110
|
+
this.stores.webhookEvents.markWebhookProcessed(webhookEventId, "failed");
|
|
111
|
+
this.markEventReceiptProcessed(event.webhookId, "failed");
|
|
112
|
+
throw new Error(`Normalized issue context disappeared while routing webhook ${event.webhookId}`);
|
|
113
|
+
}
|
|
93
114
|
if (!trustedActorAllowed(project, normalized.actor)) {
|
|
94
115
|
this.feed?.publish({
|
|
95
116
|
level: "warn",
|
|
96
117
|
kind: "webhook",
|
|
97
|
-
issueKey:
|
|
118
|
+
issueKey: routedIssue.identifier,
|
|
98
119
|
projectId: project.id,
|
|
99
120
|
status: "ignored",
|
|
100
121
|
summary: "Ignored webhook from an untrusted actor",
|
|
@@ -109,19 +130,21 @@ export class ServiceWebhookProcessor {
|
|
|
109
130
|
actorEmail: normalized.actor?.email,
|
|
110
131
|
}, "Ignoring webhook from untrusted Linear actor");
|
|
111
132
|
this.stores.webhookEvents.markWebhookProcessed(webhookEventId, "processed");
|
|
112
|
-
this.assignEventReceiptContext(event.webhookId, project.id,
|
|
133
|
+
this.assignEventReceiptContext(event.webhookId, project.id, routedIssue.id);
|
|
113
134
|
this.markEventReceiptProcessed(event.webhookId, "processed");
|
|
114
135
|
return;
|
|
115
136
|
}
|
|
116
137
|
this.stores.webhookEvents.assignWebhookProject(webhookEventId, project.id);
|
|
117
|
-
const receipt = this.ensureEventReceipt(event, project.id,
|
|
118
|
-
const
|
|
119
|
-
const
|
|
138
|
+
const receipt = this.ensureEventReceipt(event, project.id, routedIssue.id);
|
|
139
|
+
const hydrated = await this.hydrateIssueContext(project.id, normalized);
|
|
140
|
+
const hydratedIssue = hydrated.issue ?? routedIssue;
|
|
141
|
+
const issueState = this.desiredStageRecorder.record(project, hydrated, receipt ? { eventReceiptId: receipt.id } : undefined);
|
|
142
|
+
const observation = describeWebhookObservation(hydrated, issueState.delegatedToPatchRelay);
|
|
120
143
|
if (observation) {
|
|
121
144
|
this.feed?.publish({
|
|
122
145
|
level: "info",
|
|
123
146
|
kind: observation.kind,
|
|
124
|
-
issueKey:
|
|
147
|
+
issueKey: hydratedIssue.identifier,
|
|
125
148
|
projectId: project.id,
|
|
126
149
|
...(observation.status ? { status: observation.status } : {}),
|
|
127
150
|
summary: observation.summary,
|
|
@@ -129,45 +152,45 @@ export class ServiceWebhookProcessor {
|
|
|
129
152
|
});
|
|
130
153
|
}
|
|
131
154
|
await this.agentSessionHandler.handle({
|
|
132
|
-
normalized,
|
|
155
|
+
normalized: hydrated,
|
|
133
156
|
project,
|
|
134
157
|
issue: issueState.issue,
|
|
135
158
|
desiredStage: issueState.desiredStage,
|
|
136
159
|
delegatedToPatchRelay: issueState.delegatedToPatchRelay,
|
|
137
160
|
});
|
|
138
|
-
await this.commentHandler.handle(
|
|
161
|
+
await this.commentHandler.handle(hydrated, project);
|
|
139
162
|
this.stores.webhookEvents.markWebhookProcessed(webhookEventId, "processed");
|
|
140
163
|
this.markEventReceiptProcessed(event.webhookId, "processed");
|
|
141
164
|
if (issueState.desiredStage) {
|
|
142
165
|
this.feed?.publish({
|
|
143
166
|
level: "info",
|
|
144
167
|
kind: "stage",
|
|
145
|
-
issueKey:
|
|
168
|
+
issueKey: hydratedIssue.identifier,
|
|
146
169
|
projectId: project.id,
|
|
147
170
|
stage: issueState.desiredStage,
|
|
148
171
|
status: "queued",
|
|
149
172
|
summary: `Queued ${issueState.desiredStage} workflow`,
|
|
150
|
-
detail: `Triggered by ${
|
|
173
|
+
detail: `Triggered by ${hydrated.triggerEvent}${hydratedIssue.stateName ? ` from ${hydratedIssue.stateName}` : ""}.`,
|
|
151
174
|
});
|
|
152
175
|
this.logger.info({
|
|
153
176
|
webhookEventId,
|
|
154
177
|
webhookId: event.webhookId,
|
|
155
178
|
projectId: project.id,
|
|
156
|
-
issueKey:
|
|
157
|
-
issueId:
|
|
179
|
+
issueKey: hydratedIssue.identifier,
|
|
180
|
+
issueId: hydratedIssue.id,
|
|
158
181
|
desiredStage: issueState.desiredStage,
|
|
159
182
|
delegatedToPatchRelay: issueState.delegatedToPatchRelay,
|
|
160
183
|
}, "Recorded desired stage from webhook and enqueued issue execution");
|
|
161
|
-
this.enqueueIssue(project.id,
|
|
184
|
+
this.enqueueIssue(project.id, hydratedIssue.id);
|
|
162
185
|
return;
|
|
163
186
|
}
|
|
164
187
|
this.logger.info({
|
|
165
188
|
webhookEventId,
|
|
166
189
|
webhookId: event.webhookId,
|
|
167
190
|
projectId: project.id,
|
|
168
|
-
issueKey:
|
|
169
|
-
issueId:
|
|
170
|
-
triggerEvent:
|
|
191
|
+
issueKey: hydratedIssue.identifier,
|
|
192
|
+
issueId: hydratedIssue.id,
|
|
193
|
+
triggerEvent: hydrated.triggerEvent,
|
|
171
194
|
delegatedToPatchRelay: issueState.delegatedToPatchRelay,
|
|
172
195
|
}, "Processed webhook without enqueuing a new stage run");
|
|
173
196
|
}
|
|
@@ -194,6 +217,74 @@ export class ServiceWebhookProcessor {
|
|
|
194
217
|
throw err;
|
|
195
218
|
}
|
|
196
219
|
}
|
|
220
|
+
async hydrateIssueContext(projectId, normalized) {
|
|
221
|
+
if (!normalized.issue) {
|
|
222
|
+
return normalized;
|
|
223
|
+
}
|
|
224
|
+
if (normalized.triggerEvent !== "agentSessionCreated" && normalized.triggerEvent !== "agentPrompted") {
|
|
225
|
+
return normalized;
|
|
226
|
+
}
|
|
227
|
+
if (hasCompleteIssueContext(normalized.issue)) {
|
|
228
|
+
return normalized;
|
|
229
|
+
}
|
|
230
|
+
const linear = await this.linearProvider.forProject(projectId);
|
|
231
|
+
if (!linear) {
|
|
232
|
+
return normalized;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const liveIssue = await linear.getIssue(normalized.issue.id);
|
|
236
|
+
return {
|
|
237
|
+
...normalized,
|
|
238
|
+
issue: mergeIssueMetadata(normalized.issue, liveIssue),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
this.logger.warn({
|
|
243
|
+
projectId,
|
|
244
|
+
issueId: normalized.issue.id,
|
|
245
|
+
triggerEvent: normalized.triggerEvent,
|
|
246
|
+
error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
|
|
247
|
+
}, "Failed to hydrate sparse Linear issue context for agent session webhook");
|
|
248
|
+
return normalized;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async tryHydrateProjectRoute(normalized) {
|
|
252
|
+
if (!normalized.issue) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
if (normalized.triggerEvent !== "agentSessionCreated" && normalized.triggerEvent !== "agentPrompted") {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
for (const candidate of this.config.projects) {
|
|
259
|
+
const linear = await this.linearProvider.forProject(candidate.id);
|
|
260
|
+
if (!linear) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const liveIssue = await linear.getIssue(normalized.issue.id);
|
|
265
|
+
const hydrated = {
|
|
266
|
+
...normalized,
|
|
267
|
+
issue: mergeIssueMetadata(normalized.issue, liveIssue),
|
|
268
|
+
};
|
|
269
|
+
const resolved = resolveProject(this.config, hydrated.issue);
|
|
270
|
+
if (resolved) {
|
|
271
|
+
return {
|
|
272
|
+
project: resolved,
|
|
273
|
+
normalized: hydrated,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
this.logger.debug({
|
|
279
|
+
candidateProjectId: candidate.id,
|
|
280
|
+
issueId: normalized.issue.id,
|
|
281
|
+
triggerEvent: normalized.triggerEvent,
|
|
282
|
+
error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
|
|
283
|
+
}, "Failed to hydrate Linear issue context while resolving project route");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
197
288
|
assignEventReceiptContext(webhookId, projectId, linearIssueId) {
|
|
198
289
|
const receipt = this.lookupEventReceipt(webhookId);
|
|
199
290
|
if (!receipt) {
|
|
@@ -234,6 +325,24 @@ export class ServiceWebhookProcessor {
|
|
|
234
325
|
return this.stores.eventReceipts.getEventReceipt(inserted.id);
|
|
235
326
|
}
|
|
236
327
|
}
|
|
328
|
+
function hasCompleteIssueContext(issue) {
|
|
329
|
+
return Boolean(issue.stateName && issue.delegateId && issue.teamId && issue.teamKey);
|
|
330
|
+
}
|
|
331
|
+
function mergeIssueMetadata(issue, liveIssue) {
|
|
332
|
+
return {
|
|
333
|
+
...issue,
|
|
334
|
+
...(issue.identifier ? {} : liveIssue.identifier ? { identifier: liveIssue.identifier } : {}),
|
|
335
|
+
...(issue.title ? {} : liveIssue.title ? { title: liveIssue.title } : {}),
|
|
336
|
+
...(issue.url ? {} : liveIssue.url ? { url: liveIssue.url } : {}),
|
|
337
|
+
...(issue.teamId ? {} : liveIssue.teamId ? { teamId: liveIssue.teamId } : {}),
|
|
338
|
+
...(issue.teamKey ? {} : liveIssue.teamKey ? { teamKey: liveIssue.teamKey } : {}),
|
|
339
|
+
...(issue.stateId ? {} : liveIssue.stateId ? { stateId: liveIssue.stateId } : {}),
|
|
340
|
+
...(issue.stateName ? {} : liveIssue.stateName ? { stateName: liveIssue.stateName } : {}),
|
|
341
|
+
...(issue.delegateId ? {} : liveIssue.delegateId ? { delegateId: liveIssue.delegateId } : {}),
|
|
342
|
+
...(issue.delegateName ? {} : liveIssue.delegateName ? { delegateName: liveIssue.delegateName } : {}),
|
|
343
|
+
labelNames: issue.labelNames.length > 0 ? issue.labelNames : (liveIssue.labels ?? []).map((label) => label.name),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
237
346
|
function describeWebhookObservation(normalized, delegatedToPatchRelay) {
|
|
238
347
|
switch (normalized.triggerEvent) {
|
|
239
348
|
case "delegateChanged":
|
package/dist/stage-failure.js
CHANGED
|
@@ -71,7 +71,7 @@ export async function syncFailedStageToLinear(params) {
|
|
|
71
71
|
.then(() => true)
|
|
72
72
|
.catch(() => false)) || deliveredToSession;
|
|
73
73
|
}
|
|
74
|
-
if (!deliveredToSession) {
|
|
74
|
+
if (!deliveredToSession && !params.issue.activeAgentSessionId) {
|
|
75
75
|
const result = await linear
|
|
76
76
|
.upsertIssueComment({
|
|
77
77
|
issueId: params.stageRun.linearIssueId,
|
|
@@ -84,7 +84,7 @@ export class StageLifecyclePublisher {
|
|
|
84
84
|
await linear.createAgentActivity({
|
|
85
85
|
agentSessionId: issue.activeAgentSessionId,
|
|
86
86
|
content: {
|
|
87
|
-
type: "
|
|
87
|
+
type: "response",
|
|
88
88
|
body: `PatchRelay started the ${stage} workflow and is working in the background.`,
|
|
89
89
|
},
|
|
90
90
|
});
|
|
@@ -155,7 +155,7 @@ export class StageLifecyclePublisher {
|
|
|
155
155
|
type: "elicitation",
|
|
156
156
|
body: `PatchRelay finished the ${stageRun.stage} workflow. Move the issue to its next workflow state or leave a follow-up prompt to continue.`,
|
|
157
157
|
})) || deliveredToSession;
|
|
158
|
-
if (!deliveredToSession) {
|
|
158
|
+
if (!deliveredToSession && !refreshedIssue.activeAgentSessionId) {
|
|
159
159
|
const result = await linear.upsertIssueComment({
|
|
160
160
|
issueId: stageRun.linearIssueId,
|
|
161
161
|
...(refreshedIssue.statusCommentId ? { commentId: refreshedIssue.statusCommentId } : {}),
|
|
@@ -66,7 +66,7 @@ export class AgentSessionWebhookHandler {
|
|
|
66
66
|
if (desiredStage) {
|
|
67
67
|
await this.agentActivity.updateSession(buildSessionUpdateParams(project.id, normalized.agentSession.id, issue?.issueKey ?? normalized.issue?.identifier, buildPreparingSessionPlan(desiredStage)));
|
|
68
68
|
await this.agentActivity.publishForSession(project.id, normalized.agentSession.id, {
|
|
69
|
-
type: "
|
|
69
|
+
type: "response",
|
|
70
70
|
body: `PatchRelay started working on the ${desiredStage} workflow and is preparing the workspace.`,
|
|
71
71
|
}, { ephemeral: false });
|
|
72
72
|
return;
|
|
@@ -137,7 +137,7 @@ export class AgentSessionWebhookHandler {
|
|
|
137
137
|
if (!activeRunLease && desiredStage) {
|
|
138
138
|
await this.agentActivity.updateSession(buildSessionUpdateParams(project.id, normalized.agentSession.id, issue?.issueKey ?? normalized.issue?.identifier, buildPreparingSessionPlan(desiredStage)));
|
|
139
139
|
await this.agentActivity.publishForSession(project.id, normalized.agentSession.id, {
|
|
140
|
-
type: "
|
|
140
|
+
type: "response",
|
|
141
141
|
body: `PatchRelay is preparing the ${desiredStage} workflow from your latest prompt.`,
|
|
142
142
|
}, { ephemeral: false });
|
|
143
143
|
return;
|