patchrelay 0.53.0 → 0.53.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
CHANGED
package/dist/codex-app-server.js
CHANGED
|
@@ -45,6 +45,7 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
45
45
|
stdoutBuffer = "";
|
|
46
46
|
started = false;
|
|
47
47
|
stopping = false;
|
|
48
|
+
startPromise;
|
|
48
49
|
constructor(config, logger, spawnProcess = spawn) {
|
|
49
50
|
super();
|
|
50
51
|
this.config = config;
|
|
@@ -68,76 +69,16 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
68
69
|
if (this.started) {
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stdout error");
|
|
82
|
-
});
|
|
83
|
-
this.child.stderr.on("error", (error) => {
|
|
84
|
-
this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stderr error");
|
|
85
|
-
});
|
|
86
|
-
this.child.stderr.on("data", (chunk) => {
|
|
87
|
-
const line = chunk.toString().trim();
|
|
88
|
-
if (line) {
|
|
89
|
-
this.logger.warn({ output: sanitizeDiagnosticText(line) }, "Codex app-server stderr");
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
this.child.on("error", (error) => {
|
|
93
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
94
|
-
this.logger.error({
|
|
95
|
-
error: sanitizeDiagnosticText(err.message),
|
|
96
|
-
pendingRequestCount: this.pending.size,
|
|
97
|
-
}, "Codex app-server process errored");
|
|
98
|
-
this.rejectAllPending(err);
|
|
99
|
-
});
|
|
100
|
-
this.child.on("close", (code, signal) => {
|
|
101
|
-
this.started = false;
|
|
102
|
-
const log = this.stopping ? this.logger.info.bind(this.logger) : this.logger.warn.bind(this.logger);
|
|
103
|
-
log({
|
|
104
|
-
code: code ?? 1,
|
|
105
|
-
signal: signal ?? null,
|
|
106
|
-
pendingRequestCount: this.pending.size,
|
|
107
|
-
}, this.stopping ? "Codex app-server stopped" : "Codex app-server exited");
|
|
108
|
-
this.stopping = false;
|
|
109
|
-
this.rejectAllPending(new Error(`Codex app-server exited with code ${code ?? 1}`));
|
|
110
|
-
});
|
|
111
|
-
this.child.stdout.on("data", (chunk) => {
|
|
112
|
-
this.stdoutBuffer += chunk.toString("utf8");
|
|
113
|
-
if (this.stdoutBuffer.length > 50 * 1024 * 1024) {
|
|
114
|
-
this.logger.error({ bufferSize: this.stdoutBuffer.length }, "Codex app-server stdout buffer exceeded 50 MB — killing process");
|
|
115
|
-
this.stdoutBuffer = "";
|
|
116
|
-
this.rejectAllPending(new Error("Codex app-server stdout buffer overflow"));
|
|
117
|
-
this.child?.kill("SIGTERM");
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
this.drainMessages();
|
|
121
|
-
});
|
|
122
|
-
const initializeResponse = await this.sendRequest("initialize", {
|
|
123
|
-
clientInfo: {
|
|
124
|
-
name: "patchrelay",
|
|
125
|
-
title: "PatchRelay",
|
|
126
|
-
version: "0.1.0",
|
|
127
|
-
},
|
|
128
|
-
capabilities: {
|
|
129
|
-
experimentalApi: true,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
const serverInfo = initializeResponse && typeof initializeResponse === "object" && "serverInfo" in initializeResponse
|
|
133
|
-
? initializeResponse.serverInfo
|
|
134
|
-
: undefined;
|
|
135
|
-
this.logger.info({
|
|
136
|
-
serverName: typeof serverInfo?.name === "string" ? serverInfo.name : undefined,
|
|
137
|
-
serverVersion: typeof serverInfo?.version === "string" ? serverInfo.version : undefined,
|
|
138
|
-
}, "Connected to Codex app-server");
|
|
139
|
-
this.sendNotification("initialized");
|
|
140
|
-
this.started = true;
|
|
72
|
+
if (this.startPromise) {
|
|
73
|
+
return await this.startPromise;
|
|
74
|
+
}
|
|
75
|
+
this.startPromise = this.startInternal();
|
|
76
|
+
try {
|
|
77
|
+
await this.startPromise;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
this.startPromise = undefined;
|
|
81
|
+
}
|
|
141
82
|
}
|
|
142
83
|
async stop() {
|
|
143
84
|
const child = this.child;
|
|
@@ -276,9 +217,7 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
276
217
|
});
|
|
277
218
|
}
|
|
278
219
|
async sendRequest(method, params) {
|
|
279
|
-
|
|
280
|
-
throw new Error("Codex app-server is not running");
|
|
281
|
-
}
|
|
220
|
+
await this.ensureRunningForRequest(method);
|
|
282
221
|
const id = this.nextRequestId++;
|
|
283
222
|
const requestTimeoutMs = this.config.requestTimeoutMs ?? CodexAppServerClient.DEFAULT_REQUEST_TIMEOUT_MS;
|
|
284
223
|
const promise = new Promise((resolve, reject) => {
|
|
@@ -316,6 +255,93 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
316
255
|
throw err;
|
|
317
256
|
});
|
|
318
257
|
}
|
|
258
|
+
async startInternal() {
|
|
259
|
+
this.stopping = false;
|
|
260
|
+
this.stdoutBuffer = "";
|
|
261
|
+
const launch = resolveCodexAppServerLaunch(this.config);
|
|
262
|
+
this.logger.info({ command: launch.command, args: launch.args }, "Starting Codex app-server");
|
|
263
|
+
this.child = this.spawnProcess(launch.command, launch.args, {
|
|
264
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
265
|
+
});
|
|
266
|
+
this.child.stdin.on("error", (error) => {
|
|
267
|
+
this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stdin error");
|
|
268
|
+
});
|
|
269
|
+
this.child.stdout.on("error", (error) => {
|
|
270
|
+
this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stdout error");
|
|
271
|
+
});
|
|
272
|
+
this.child.stderr.on("error", (error) => {
|
|
273
|
+
this.logger.error({ error: sanitizeDiagnosticText(error.message) }, "Codex app-server stderr error");
|
|
274
|
+
});
|
|
275
|
+
this.child.stderr.on("data", (chunk) => {
|
|
276
|
+
const line = chunk.toString().trim();
|
|
277
|
+
if (line) {
|
|
278
|
+
this.logger.warn({ output: sanitizeDiagnosticText(line) }, "Codex app-server stderr");
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
this.child.on("error", (error) => {
|
|
282
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
283
|
+
this.logger.error({
|
|
284
|
+
error: sanitizeDiagnosticText(err.message),
|
|
285
|
+
pendingRequestCount: this.pending.size,
|
|
286
|
+
}, "Codex app-server process errored");
|
|
287
|
+
this.rejectAllPending(err);
|
|
288
|
+
});
|
|
289
|
+
this.child.on("close", (code, signal) => {
|
|
290
|
+
this.started = false;
|
|
291
|
+
this.child = undefined;
|
|
292
|
+
this.stdoutBuffer = "";
|
|
293
|
+
const log = this.stopping ? this.logger.info.bind(this.logger) : this.logger.warn.bind(this.logger);
|
|
294
|
+
log({
|
|
295
|
+
code: code ?? 1,
|
|
296
|
+
signal: signal ?? null,
|
|
297
|
+
pendingRequestCount: this.pending.size,
|
|
298
|
+
}, this.stopping ? "Codex app-server stopped" : "Codex app-server exited");
|
|
299
|
+
this.stopping = false;
|
|
300
|
+
this.rejectAllPending(new Error(`Codex app-server exited with code ${code ?? 1}`));
|
|
301
|
+
});
|
|
302
|
+
this.child.stdout.on("data", (chunk) => {
|
|
303
|
+
this.stdoutBuffer += chunk.toString("utf8");
|
|
304
|
+
if (this.stdoutBuffer.length > 50 * 1024 * 1024) {
|
|
305
|
+
this.logger.error({ bufferSize: this.stdoutBuffer.length }, "Codex app-server stdout buffer exceeded 50 MB — killing process");
|
|
306
|
+
this.stdoutBuffer = "";
|
|
307
|
+
this.rejectAllPending(new Error("Codex app-server stdout buffer overflow"));
|
|
308
|
+
this.child?.kill("SIGTERM");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
this.drainMessages();
|
|
312
|
+
});
|
|
313
|
+
const initializeResponse = await this.sendRequest("initialize", {
|
|
314
|
+
clientInfo: {
|
|
315
|
+
name: "patchrelay",
|
|
316
|
+
title: "PatchRelay",
|
|
317
|
+
version: "0.1.0",
|
|
318
|
+
},
|
|
319
|
+
capabilities: {
|
|
320
|
+
experimentalApi: true,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const serverInfo = initializeResponse && typeof initializeResponse === "object" && "serverInfo" in initializeResponse
|
|
324
|
+
? initializeResponse.serverInfo
|
|
325
|
+
: undefined;
|
|
326
|
+
this.logger.info({
|
|
327
|
+
serverName: typeof serverInfo?.name === "string" ? serverInfo.name : undefined,
|
|
328
|
+
serverVersion: typeof serverInfo?.version === "string" ? serverInfo.version : undefined,
|
|
329
|
+
}, "Connected to Codex app-server");
|
|
330
|
+
this.sendNotification("initialized");
|
|
331
|
+
this.started = true;
|
|
332
|
+
}
|
|
333
|
+
async ensureRunningForRequest(method) {
|
|
334
|
+
if (this.child?.stdin) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (method !== "initialize") {
|
|
338
|
+
this.logger.warn({ method }, "Codex app-server is unavailable before request; restarting");
|
|
339
|
+
}
|
|
340
|
+
await this.start();
|
|
341
|
+
if (!this.child?.stdin) {
|
|
342
|
+
throw new Error("Codex app-server is not running");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
319
345
|
writeMessage(message) {
|
|
320
346
|
if (!this.child?.stdin) {
|
|
321
347
|
throw new Error("Codex app-server stdin is unavailable");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveMergeQueueProtocol } from "./merge-queue-protocol.js";
|
|
2
|
+
import { resolvePreferredCompletedLinearState } from "./linear-workflow.js";
|
|
2
3
|
import { buildMainRepairBranchName, buildMainRepairDescription, buildMainRepairPromptContext, buildMainRepairTitle, isMainRepairIssue, } from "./main-repair.js";
|
|
3
4
|
import { execCommand } from "./utils.js";
|
|
4
5
|
const MAIN_BRANCH_HEALTH_GRACE_MS = 120_000;
|
|
@@ -41,7 +42,7 @@ export class MainBranchHealthMonitor {
|
|
|
41
42
|
const summary = await this.readMainBranchFailure(project.github.repoFullName, baseBranch);
|
|
42
43
|
if (!summary) {
|
|
43
44
|
if (existing) {
|
|
44
|
-
this.resolveRecoveredMainRepair(existing);
|
|
45
|
+
await this.resolveRecoveredMainRepair(existing);
|
|
45
46
|
}
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
@@ -170,12 +171,39 @@ export class MainBranchHealthMonitor {
|
|
|
170
171
|
this.enqueueIssue(issue.projectId, issue.linearIssueId);
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
|
-
resolveRecoveredMainRepair(issue) {
|
|
174
|
+
async resolveRecoveredMainRepair(issue) {
|
|
174
175
|
if (issue.activeRunId !== undefined)
|
|
175
176
|
return;
|
|
176
177
|
if (issue.prState === "open" || issue.factoryState === "awaiting_queue" || issue.factoryState === "pr_open") {
|
|
177
178
|
return;
|
|
178
179
|
}
|
|
180
|
+
const linear = await this.linearProvider.forProject(issue.projectId).catch(() => undefined);
|
|
181
|
+
if (linear) {
|
|
182
|
+
const liveIssue = await linear.getIssue(issue.linearIssueId).catch(() => undefined);
|
|
183
|
+
if (liveIssue) {
|
|
184
|
+
const targetState = resolvePreferredCompletedLinearState(liveIssue);
|
|
185
|
+
const normalizedCurrent = liveIssue.stateName?.trim().toLowerCase();
|
|
186
|
+
if (targetState && normalizedCurrent !== targetState.trim().toLowerCase()) {
|
|
187
|
+
const updated = await linear.setIssueState(issue.linearIssueId, targetState).catch(() => undefined);
|
|
188
|
+
if (updated) {
|
|
189
|
+
this.db.upsertIssue({
|
|
190
|
+
projectId: issue.projectId,
|
|
191
|
+
linearIssueId: issue.linearIssueId,
|
|
192
|
+
...(updated.stateName ? { currentLinearState: updated.stateName } : {}),
|
|
193
|
+
...(updated.stateType ? { currentLinearStateType: updated.stateType } : {}),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
this.db.upsertIssue({
|
|
199
|
+
projectId: issue.projectId,
|
|
200
|
+
linearIssueId: issue.linearIssueId,
|
|
201
|
+
...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
|
|
202
|
+
...(liveIssue.stateType ? { currentLinearStateType: liveIssue.stateType } : {}),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
179
207
|
this.db.issueSessions.clearPendingIssueSessionEventsRespectingActiveLease(issue.projectId, issue.linearIssueId);
|
|
180
208
|
this.db.upsertIssue({
|
|
181
209
|
projectId: issue.projectId,
|