patchrelay 0.67.2 → 0.68.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 +26 -0
- package/dist/run-launcher.js +48 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/codex-app-server.js
CHANGED
|
@@ -206,6 +206,20 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
206
206
|
status: String(response.turn.status),
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
|
+
async setThreadGoal(options) {
|
|
210
|
+
const params = {
|
|
211
|
+
threadId: options.threadId,
|
|
212
|
+
objective: options.objective,
|
|
213
|
+
};
|
|
214
|
+
if (options.status !== undefined) {
|
|
215
|
+
params.status = options.status;
|
|
216
|
+
}
|
|
217
|
+
if (options.tokenBudget !== undefined) {
|
|
218
|
+
params.tokenBudget = options.tokenBudget;
|
|
219
|
+
}
|
|
220
|
+
const response = (await this.sendRequest("thread/goal/set", params));
|
|
221
|
+
return this.mapThreadGoal(response.goal);
|
|
222
|
+
}
|
|
209
223
|
async readThread(threadId, includeTurns = true) {
|
|
210
224
|
const response = (await this.sendRequest("thread/read", {
|
|
211
225
|
threadId,
|
|
@@ -230,6 +244,18 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
230
244
|
],
|
|
231
245
|
});
|
|
232
246
|
}
|
|
247
|
+
mapThreadGoal(goal) {
|
|
248
|
+
return {
|
|
249
|
+
threadId: String(goal.threadId),
|
|
250
|
+
objective: String(goal.objective ?? ""),
|
|
251
|
+
status: String(goal.status ?? "active"),
|
|
252
|
+
...(goal.tokenBudget === null || goal.tokenBudget === undefined ? { tokenBudget: null } : { tokenBudget: Number(goal.tokenBudget) }),
|
|
253
|
+
tokensUsed: Number(goal.tokensUsed ?? 0),
|
|
254
|
+
timeUsedSeconds: Number(goal.timeUsedSeconds ?? 0),
|
|
255
|
+
createdAt: Number(goal.createdAt ?? 0),
|
|
256
|
+
updatedAt: Number(goal.updatedAt ?? 0),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
233
259
|
sendNotification(method, params) {
|
|
234
260
|
this.writeMessage({
|
|
235
261
|
jsonrpc: "2.0",
|
package/dist/run-launcher.js
CHANGED
|
@@ -3,6 +3,7 @@ import { buildRunFailureActivity } from "./linear-session-reporting.js";
|
|
|
3
3
|
import { loadPatchRelayRepoPrompting } from "./patchrelay-customization.js";
|
|
4
4
|
import { buildRunPrompt as buildPatchRelayRunPrompt, findDisallowedPatchRelayPromptSectionIds, findUnknownPatchRelayPromptSectionIds, mergePromptCustomizationLayers, resolvePromptLayers, } from "./prompting/patchrelay.js";
|
|
5
5
|
import { configureGitHubBotAuthForWorktree } from "./github-worktree-auth.js";
|
|
6
|
+
import { sanitizeDiagnosticText } from "./utils.js";
|
|
6
7
|
function slugify(value) {
|
|
7
8
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
8
9
|
}
|
|
@@ -15,6 +16,28 @@ function shouldCompactThread(issue, threadGeneration, context) {
|
|
|
15
16
|
&& (threadGeneration ?? 0) >= 4
|
|
16
17
|
&& followUpCount >= 4;
|
|
17
18
|
}
|
|
19
|
+
function compactGoalText(value, maxLength = 600) {
|
|
20
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
21
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
|
|
22
|
+
}
|
|
23
|
+
function extractIssueSection(description, heading) {
|
|
24
|
+
if (!description)
|
|
25
|
+
return undefined;
|
|
26
|
+
const headingLine = `## ${heading}`.toLowerCase();
|
|
27
|
+
const lines = description.split(/\r?\n/);
|
|
28
|
+
const start = lines.findIndex((line) => line.trim().toLowerCase() === headingLine);
|
|
29
|
+
if (start === -1)
|
|
30
|
+
return undefined;
|
|
31
|
+
const end = lines.findIndex((line, index) => index > start && /^##\s+/.test(line));
|
|
32
|
+
const body = lines.slice(start + 1, end === -1 ? undefined : end).join("\n").trim();
|
|
33
|
+
return body && body.length > 0 ? body : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function buildInitialImplementationGoal(issue) {
|
|
36
|
+
const title = issue.title?.trim() || `Complete ${issue.issueKey ?? issue.linearIssueId}`;
|
|
37
|
+
const description = issue.description?.trim();
|
|
38
|
+
const goal = extractIssueSection(description, "Goal");
|
|
39
|
+
return compactGoalText(goal ? `${title}. ${goal}` : title);
|
|
40
|
+
}
|
|
18
41
|
export function shouldReuseIssueThread(params) {
|
|
19
42
|
return Boolean(params.existingThreadId) && !params.compactThread && params.resumeThread;
|
|
20
43
|
}
|
|
@@ -125,6 +148,8 @@ export class RunLauncher {
|
|
|
125
148
|
let threadId;
|
|
126
149
|
let turnId;
|
|
127
150
|
let parentThreadId;
|
|
151
|
+
let createdThreadForRun = false;
|
|
152
|
+
const firstThreadForIssue = !params.issue.threadId;
|
|
128
153
|
try {
|
|
129
154
|
await this.worktreeManager.ensureIssueWorktree(params.project.repoPath, params.project.worktreeRoot, params.worktreePath, params.branchName, { allowExistingOutsideRoot: params.issue.branchName !== undefined });
|
|
130
155
|
if (params.botIdentity) {
|
|
@@ -157,6 +182,7 @@ export class RunLauncher {
|
|
|
157
182
|
else {
|
|
158
183
|
const thread = await this.codex.startThread({ cwd: params.worktreePath });
|
|
159
184
|
threadId = thread.id;
|
|
185
|
+
createdThreadForRun = true;
|
|
160
186
|
this.db.issueSessions.upsertIssueWithLease({ projectId: params.project.id, linearIssueId: params.issue.linearIssueId, leaseId: params.leaseId }, { projectId: params.project.id, linearIssueId: params.issue.linearIssueId, threadId });
|
|
161
187
|
}
|
|
162
188
|
try {
|
|
@@ -169,6 +195,7 @@ export class RunLauncher {
|
|
|
169
195
|
this.logger.info({ issueKey: params.issue.issueKey, staleThreadId: threadId }, "Thread is stale, retrying with fresh thread");
|
|
170
196
|
const thread = await this.codex.startThread({ cwd: params.worktreePath });
|
|
171
197
|
threadId = thread.id;
|
|
198
|
+
createdThreadForRun = true;
|
|
172
199
|
this.db.issueSessions.upsertIssueWithLease({ projectId: params.project.id, linearIssueId: params.issue.linearIssueId, leaseId: params.leaseId }, { projectId: params.project.id, linearIssueId: params.issue.linearIssueId, threadId });
|
|
173
200
|
const turn = await this.codex.startTurn({ threadId, cwd: params.worktreePath, input: params.prompt });
|
|
174
201
|
turnId = turn.turnId;
|
|
@@ -177,6 +204,9 @@ export class RunLauncher {
|
|
|
177
204
|
throw turnError;
|
|
178
205
|
}
|
|
179
206
|
}
|
|
207
|
+
if (createdThreadForRun && firstThreadForIssue && params.runType === "implementation") {
|
|
208
|
+
await this.setInitialImplementationGoal(threadId, params.issue);
|
|
209
|
+
}
|
|
180
210
|
params.assertLaunchLease(params.run, "after starting the Codex turn");
|
|
181
211
|
return { threadId, turnId, ...(parentThreadId ? { parentThreadId } : {}) };
|
|
182
212
|
}
|
|
@@ -204,4 +234,22 @@ export class RunLauncher {
|
|
|
204
234
|
throw error;
|
|
205
235
|
}
|
|
206
236
|
}
|
|
237
|
+
async setInitialImplementationGoal(threadId, issue) {
|
|
238
|
+
const goalSetter = this.codex.setThreadGoal;
|
|
239
|
+
if (typeof goalSetter !== "function") {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const objective = buildInitialImplementationGoal(issue);
|
|
243
|
+
try {
|
|
244
|
+
await goalSetter.call(this.codex, { threadId, objective, status: "active" });
|
|
245
|
+
this.logger.info({ issueKey: issue.issueKey, threadId }, "Set Codex thread goal for implementation run");
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
this.logger.warn({
|
|
249
|
+
issueKey: issue.issueKey,
|
|
250
|
+
threadId,
|
|
251
|
+
error: sanitizeDiagnosticText(error instanceof Error ? error.message : String(error)),
|
|
252
|
+
}, "Failed to set Codex thread goal for implementation run");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
207
255
|
}
|