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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
- "version": "0.10.11",
3
+ "version": "0.10.13",
4
4
  "description": "Unified provider lifecycle runner for Agent Relay",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
3
  "description": "Thin Agent Relay runner bridge for Claude Code",
4
- "version": "0.10.11"
4
+ "version": "0.10.13"
5
5
  }
@@ -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 proc of processes) proc.kill(opts.graceful ? "SIGTERM" : "SIGKILL");
169
+ for (const pid of pids) killPid(pid, signal);
167
170
  } catch {
168
171
  return;
169
172
  }
170
- const timeout = new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), opts.timeoutMs));
171
- const result = await Promise.race([Promise.all(processes.map((proc) => proc.exited)), timeout]);
172
- if (result === "timeout") {
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 proc of processes) proc.kill("SIGKILL");
177
+ for (const pid of alive) killPid(pid, "SIGKILL");
175
178
  } catch {}
176
- await Promise.all(processes.map((proc) => proc.exited.catch(() => {})));
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
+ }