agent-relay-runner 0.10.11 → 0.10.13
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/package.json
CHANGED
package/src/adapters/codex.ts
CHANGED
|
@@ -162,17 +162,79 @@ async function terminateProcess(process: ManagedProcess, opts: { graceful: boole
|
|
|
162
162
|
process.process,
|
|
163
163
|
].filter(Boolean) as Bun.Subprocess[];
|
|
164
164
|
if (processes.length === 0) return;
|
|
165
|
+
const rootPids = processes.map((proc) => proc.pid).filter((pid): pid is number => typeof pid === "number" && pid > 0);
|
|
166
|
+
const pids = await processTreePids(rootPids).catch(() => rootPids);
|
|
167
|
+
const signal = opts.graceful ? "SIGTERM" : "SIGKILL";
|
|
165
168
|
try {
|
|
166
|
-
for (const
|
|
169
|
+
for (const pid of pids) killPid(pid, signal);
|
|
167
170
|
} catch {
|
|
168
171
|
return;
|
|
169
172
|
}
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
+
const exited = await waitForPidsExit(pids, opts.timeoutMs);
|
|
174
|
+
const alive = pids.filter(isPidAlive);
|
|
175
|
+
if (!exited || alive.length > 0) {
|
|
173
176
|
try {
|
|
174
|
-
for (const
|
|
177
|
+
for (const pid of alive) killPid(pid, "SIGKILL");
|
|
175
178
|
} catch {}
|
|
176
|
-
await
|
|
179
|
+
await waitForPidsExit(alive, 1_000);
|
|
180
|
+
await Promise.race([
|
|
181
|
+
Promise.all(processes.map((proc) => proc.exited.catch(() => {}))),
|
|
182
|
+
Bun.sleep(1_000),
|
|
183
|
+
]);
|
|
177
184
|
}
|
|
178
185
|
}
|
|
186
|
+
|
|
187
|
+
export function processTreePidsFromTable(table: string, rootPids: number[]): number[] {
|
|
188
|
+
const childrenByParent = new Map<number, number[]>();
|
|
189
|
+
for (const line of table.split("\n")) {
|
|
190
|
+
const match = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
191
|
+
if (!match) continue;
|
|
192
|
+
const pid = Number(match[1]);
|
|
193
|
+
const ppid = Number(match[2]);
|
|
194
|
+
if (!Number.isFinite(pid) || !Number.isFinite(ppid)) continue;
|
|
195
|
+
const children = childrenByParent.get(ppid) ?? [];
|
|
196
|
+
children.push(pid);
|
|
197
|
+
childrenByParent.set(ppid, children);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const seen = new Set<number>();
|
|
201
|
+
const visit = (pid: number) => {
|
|
202
|
+
if (seen.has(pid)) return;
|
|
203
|
+
seen.add(pid);
|
|
204
|
+
for (const child of childrenByParent.get(pid) ?? []) visit(child);
|
|
205
|
+
};
|
|
206
|
+
for (const pid of rootPids) visit(pid);
|
|
207
|
+
return [...seen].sort((a, b) => b - a);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function processTreePids(rootPids: number[]): Promise<number[]> {
|
|
211
|
+
if (rootPids.length === 0) return [];
|
|
212
|
+
const proc = Bun.spawn(["ps", "-e", "-o", "pid=,ppid="], { stdout: "pipe", stderr: "ignore" });
|
|
213
|
+
const table = await new Response(proc.stdout).text();
|
|
214
|
+
await proc.exited;
|
|
215
|
+
return processTreePidsFromTable(table, rootPids);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function killPid(pid: number, signal: "SIGTERM" | "SIGKILL"): void {
|
|
219
|
+
try {
|
|
220
|
+
process.kill(pid, signal);
|
|
221
|
+
} catch {}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function isPidAlive(pid: number): boolean {
|
|
225
|
+
try {
|
|
226
|
+
process.kill(pid, 0);
|
|
227
|
+
return true;
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function waitForPidsExit(pids: number[], timeoutMs: number): Promise<boolean> {
|
|
234
|
+
const deadline = Date.now() + timeoutMs;
|
|
235
|
+
while (Date.now() < deadline) {
|
|
236
|
+
if (!pids.some(isPidAlive)) return true;
|
|
237
|
+
await Bun.sleep(100);
|
|
238
|
+
}
|
|
239
|
+
return !pids.some(isPidAlive);
|
|
240
|
+
}
|