patchrelay 0.68.7 → 0.69.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/build-info.json +3 -3
- package/dist/codex-app-server.js +18 -0
- package/dist/codex-conversation-adapter.js +270 -0
- package/dist/followup-intent.js +167 -54
- package/dist/issue-session-events.js +12 -0
- package/dist/linear-progress-reporter.js +81 -2
- package/dist/linear-session-reporting.js +74 -14
- package/dist/prompting/patchrelay.js +26 -3
- package/dist/reactive-run-policy.js +12 -25
- package/dist/run-finalizer.js +23 -0
- package/dist/run-orchestrator.js +11 -2
- package/dist/webhook-handler.js +8 -4
- package/dist/webhooks/agent-session-handler.js +18 -93
- package/dist/webhooks/comment-policy.js +19 -1
- package/dist/webhooks/comment-wake-handler.js +25 -161
- package/package.json +1 -1
|
@@ -31,6 +31,7 @@ export function isPatchRelayGeneratedActivityComment(body) {
|
|
|
31
31
|
|| body.startsWith("PatchRelay is already working on ")
|
|
32
32
|
|| body.startsWith("PatchRelay received the ")
|
|
33
33
|
|| body.startsWith("PatchRelay routed your latest instructions into ")
|
|
34
|
+
|| body.startsWith("PatchRelay could not route your latest instructions into ")
|
|
34
35
|
|| body.startsWith("PatchRelay status:")
|
|
35
36
|
|| body.startsWith("PatchRelay did not start implementation ")
|
|
36
37
|
|| body.startsWith("PatchRelay has stopped work as requested.")
|
|
@@ -38,5 +39,22 @@ export function isPatchRelayGeneratedActivityComment(body) {
|
|
|
38
39
|
|| body === "This thread is for an agent session with patchrelay.";
|
|
39
40
|
}
|
|
40
41
|
export function hasExplicitPatchRelayWakeIntent(body) {
|
|
41
|
-
return
|
|
42
|
+
return extractPatchRelayAddressedText(body) !== undefined;
|
|
43
|
+
}
|
|
44
|
+
export function extractPatchRelayAddressedText(body) {
|
|
45
|
+
const trimmed = body.trim();
|
|
46
|
+
if (!trimmed)
|
|
47
|
+
return undefined;
|
|
48
|
+
const patterns = [
|
|
49
|
+
/^@?patchrelay\b[\s,:;-]*/i,
|
|
50
|
+
/^\bhey\s+@?patchrelay\b[\s,:;-]*/i,
|
|
51
|
+
];
|
|
52
|
+
for (const pattern of patterns) {
|
|
53
|
+
const match = trimmed.match(pattern);
|
|
54
|
+
if (!match)
|
|
55
|
+
continue;
|
|
56
|
+
const rest = trimmed.slice(match[0].length).trim();
|
|
57
|
+
return rest || trimmed;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
42
60
|
}
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { classifyFollowupIntent, followupIntentIsNonActionable, followupIntentQueuesWork } from "../followup-intent.js";
|
|
2
1
|
import { triggerEventAllowed } from "../project-resolution.js";
|
|
3
|
-
import {
|
|
4
|
-
import { classifyIssue } from "../issue-class.js";
|
|
5
|
-
const ENQUEUEABLE_STATES = new Set(["pr_open", "changes_requested", "implementing", "delegated", "awaiting_input"]);
|
|
2
|
+
import { extractPatchRelayAddressedText, isInertPatchRelayComment, isPatchRelayManagedCommentAuthor, } from "./comment-policy.js";
|
|
6
3
|
export class CommentWakeHandler {
|
|
7
4
|
db;
|
|
8
|
-
codex;
|
|
9
5
|
wakeDispatcher;
|
|
10
|
-
logger;
|
|
11
6
|
feed;
|
|
12
|
-
|
|
7
|
+
conversationAdapter;
|
|
8
|
+
emitLinearActivity;
|
|
9
|
+
constructor(db, wakeDispatcher, feed, conversationAdapter, emitLinearActivity) {
|
|
13
10
|
this.db = db;
|
|
14
|
-
this.codex = codex;
|
|
15
11
|
this.wakeDispatcher = wakeDispatcher;
|
|
16
|
-
this.logger = logger;
|
|
17
12
|
this.feed = feed;
|
|
13
|
+
this.conversationAdapter = conversationAdapter;
|
|
14
|
+
this.emitLinearActivity = emitLinearActivity;
|
|
18
15
|
}
|
|
19
16
|
async handle(params) {
|
|
20
17
|
const { normalized, project, trackedIssue } = params;
|
|
@@ -28,10 +25,6 @@ export class CommentWakeHandler {
|
|
|
28
25
|
const issue = this.db.issues.getIssue(project.id, normalized.issue.id);
|
|
29
26
|
if (!issue)
|
|
30
27
|
return;
|
|
31
|
-
const issueClass = classifyIssue({
|
|
32
|
-
issue,
|
|
33
|
-
childIssueCount: this.db.issues.listCanonicalChildIssues(project.id, normalized.issue.id).length,
|
|
34
|
-
}).issueClass;
|
|
35
28
|
const trimmedBody = normalized.comment.body.trim();
|
|
36
29
|
const installation = this.db.linearInstallations.getLinearInstallationForProject(project.id);
|
|
37
30
|
const selfAuthored = isPatchRelayManagedCommentAuthor(installation, normalized.actor, normalized.comment.userName);
|
|
@@ -58,168 +51,39 @@ export class CommentWakeHandler {
|
|
|
58
51
|
});
|
|
59
52
|
return;
|
|
60
53
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
if (!issue.activeRunId) {
|
|
64
|
-
if (ENQUEUEABLE_STATES.has(issue.factoryState)) {
|
|
65
|
-
const wakeIntent = issueClass === "orchestration" || directReply || hasExplicitPatchRelayWakeIntent(trimmedBody);
|
|
66
|
-
if (!wakeIntent) {
|
|
67
|
-
this.feed?.publish({
|
|
68
|
-
level: "info",
|
|
69
|
-
kind: "comment",
|
|
70
|
-
projectId: project.id,
|
|
71
|
-
issueKey: trackedIssue?.issueKey,
|
|
72
|
-
status: "ignored",
|
|
73
|
-
summary: "Ignored comment with no explicit PatchRelay wake intent",
|
|
74
|
-
detail: trimmedBody.slice(0, 200),
|
|
75
|
-
});
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (intent === "stop") {
|
|
79
|
-
this.wakeDispatcher.recordEventAndDispatch(project.id, normalized.issue.id, {
|
|
80
|
-
eventType: "stop_requested",
|
|
81
|
-
eventJson: JSON.stringify({
|
|
82
|
-
body: trimmedBody,
|
|
83
|
-
author: normalized.comment.userName,
|
|
84
|
-
}),
|
|
85
|
-
});
|
|
86
|
-
this.db.issueSessions.clearPendingIssueSessionEventsRespectingActiveLease(project.id, normalized.issue.id);
|
|
87
|
-
this.feed?.publish({
|
|
88
|
-
level: "info",
|
|
89
|
-
kind: "comment",
|
|
90
|
-
projectId: project.id,
|
|
91
|
-
issueKey: trackedIssue?.issueKey,
|
|
92
|
-
status: "stopped",
|
|
93
|
-
summary: "Stop request recorded from Linear comment",
|
|
94
|
-
detail: trimmedBody.slice(0, 200),
|
|
95
|
-
});
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (!directReply && !followupIntentQueuesWork(intent)) {
|
|
99
|
-
this.feed?.publish({
|
|
100
|
-
level: "info",
|
|
101
|
-
kind: "comment",
|
|
102
|
-
projectId: project.id,
|
|
103
|
-
issueKey: trackedIssue?.issueKey,
|
|
104
|
-
status: intent === "status" ? "status_requested" : "ignored",
|
|
105
|
-
summary: intent === "status"
|
|
106
|
-
? "Ignored status comment without queueing work"
|
|
107
|
-
: "Ignored non-actionable follow-up comment",
|
|
108
|
-
detail: trimmedBody.slice(0, 200),
|
|
109
|
-
});
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const runType = issue.prReviewState === "changes_requested" ? "review_fix" : "implementation";
|
|
113
|
-
const queuedRunType = this.wakeDispatcher.recordEventAndDispatch(project.id, normalized.issue.id, {
|
|
114
|
-
eventType: directReply ? "direct_reply" : "followup_comment",
|
|
115
|
-
eventJson: JSON.stringify({
|
|
116
|
-
body: trimmedBody,
|
|
117
|
-
author: normalized.comment.userName,
|
|
118
|
-
}),
|
|
119
|
-
});
|
|
120
|
-
this.feed?.publish({
|
|
121
|
-
level: "info",
|
|
122
|
-
kind: "comment",
|
|
123
|
-
projectId: project.id,
|
|
124
|
-
issueKey: trackedIssue?.issueKey,
|
|
125
|
-
status: "enqueued",
|
|
126
|
-
summary: `Comment enqueued ${(queuedRunType ?? runType)} run`,
|
|
127
|
-
detail: trimmedBody.slice(0, 200),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
const run = this.db.runs.getRunById(issue.activeRunId);
|
|
133
|
-
if (!run?.threadId || !run.turnId)
|
|
134
|
-
return;
|
|
135
|
-
if (intent === "stop") {
|
|
136
|
-
try {
|
|
137
|
-
await this.codex.steerTurn({
|
|
138
|
-
threadId: run.threadId,
|
|
139
|
-
turnId: run.turnId,
|
|
140
|
-
input: "STOP: The user has requested you stop working immediately. Do not make further changes. Wrap up and exit.",
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
this.logger.warn({ issueKey: trackedIssue?.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to steer Codex turn for comment stop request");
|
|
145
|
-
}
|
|
146
|
-
this.db.runs.finishRun(run.id, { status: "released", threadId: run.threadId, turnId: run.turnId });
|
|
147
|
-
this.db.issueSessions.upsertIssueRespectingActiveLease(project.id, normalized.issue.id, {
|
|
148
|
-
projectId: project.id,
|
|
149
|
-
linearIssueId: normalized.issue.id,
|
|
150
|
-
activeRunId: null,
|
|
151
|
-
factoryState: "awaiting_input",
|
|
152
|
-
});
|
|
153
|
-
this.wakeDispatcher.recordEventAndDispatch(project.id, normalized.issue.id, {
|
|
154
|
-
eventType: "stop_requested",
|
|
155
|
-
eventJson: JSON.stringify({
|
|
156
|
-
body: trimmedBody,
|
|
157
|
-
author: normalized.comment.userName,
|
|
158
|
-
}),
|
|
159
|
-
});
|
|
160
|
-
this.db.issueSessions.clearPendingIssueSessionEventsRespectingActiveLease(project.id, normalized.issue.id);
|
|
54
|
+
const addressedText = extractPatchRelayAddressedText(trimmedBody);
|
|
55
|
+
if (!addressedText) {
|
|
161
56
|
this.feed?.publish({
|
|
162
57
|
level: "info",
|
|
163
58
|
kind: "comment",
|
|
164
59
|
projectId: project.id,
|
|
165
60
|
issueKey: trackedIssue?.issueKey,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
summary: "Stop request delivered to active workflow",
|
|
169
|
-
});
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (!directReply && followupIntentIsNonActionable(intent)) {
|
|
173
|
-
this.feed?.publish({
|
|
174
|
-
level: "info",
|
|
175
|
-
kind: "comment",
|
|
176
|
-
projectId: project.id,
|
|
177
|
-
issueKey: trackedIssue?.issueKey,
|
|
178
|
-
stage: run.runType,
|
|
179
|
-
status: intent === "status" ? "status_requested" : "ignored",
|
|
180
|
-
summary: intent === "status"
|
|
181
|
-
? "Ignored status comment without steering active workflow"
|
|
182
|
-
: "Ignored non-actionable follow-up comment",
|
|
61
|
+
status: "ignored",
|
|
62
|
+
summary: "Ignored issue comment because it did not address PatchRelay",
|
|
183
63
|
detail: trimmedBody.slice(0, 200),
|
|
184
64
|
});
|
|
185
65
|
return;
|
|
186
66
|
}
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
"",
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
67
|
+
const result = await this.conversationAdapter?.deliverAgentInput({
|
|
68
|
+
project,
|
|
69
|
+
issue,
|
|
70
|
+
source: "addressed_issue_comment",
|
|
71
|
+
body: addressedText,
|
|
72
|
+
author: normalized.comment.userName,
|
|
73
|
+
directReply: params.isDirectReplyToOutstandingQuestion(issue),
|
|
74
|
+
emitActivity: this.emitLinearActivity
|
|
75
|
+
? (content, options) => this.emitLinearActivity(issue, content, options)
|
|
76
|
+
: undefined,
|
|
77
|
+
});
|
|
78
|
+
if (result?.queuedRunType) {
|
|
195
79
|
this.feed?.publish({
|
|
196
80
|
level: "info",
|
|
197
81
|
kind: "comment",
|
|
198
82
|
projectId: project.id,
|
|
199
83
|
issueKey: trackedIssue?.issueKey,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
this.logger.warn({ issueKey: trackedIssue?.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to deliver follow-up comment");
|
|
207
|
-
const directReply = params.isDirectReplyToOutstandingQuestion(issue);
|
|
208
|
-
this.wakeDispatcher.recordEventAndDispatch(project.id, normalized.issue.id, {
|
|
209
|
-
eventType: directReply ? "direct_reply" : "followup_comment",
|
|
210
|
-
eventJson: JSON.stringify({
|
|
211
|
-
body: trimmedBody,
|
|
212
|
-
author: normalized.comment.userName,
|
|
213
|
-
}),
|
|
214
|
-
});
|
|
215
|
-
this.feed?.publish({
|
|
216
|
-
level: "warn",
|
|
217
|
-
kind: "comment",
|
|
218
|
-
projectId: project.id,
|
|
219
|
-
issueKey: trackedIssue?.issueKey,
|
|
220
|
-
stage: run.runType,
|
|
221
|
-
status: "delivery_failed",
|
|
222
|
-
summary: `Could not deliver follow-up comment to active ${run.runType} workflow`,
|
|
84
|
+
status: "enqueued",
|
|
85
|
+
summary: `Comment enqueued ${result.queuedRunType} run`,
|
|
86
|
+
detail: addressedText.slice(0, 200),
|
|
223
87
|
});
|
|
224
88
|
}
|
|
225
89
|
}
|