@byok-sdk/client 0.4.0 → 0.4.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/README.md CHANGED
@@ -70,4 +70,4 @@ OS-backed refresh-token custody, a PKCE desktop Google OAuth flow, exact domain
70
70
  policy, a real read-only Gmail metadata adapter, and a closed metadata-only MCP
71
71
  result.
72
72
 
73
- MIT licensed. Node.js 22.19.0 or newer.
73
+ MIT licensed. Node.js 22.22.0 or newer.
@@ -97,7 +97,13 @@ export declare class ClaudeProcessClient {
97
97
  * post-mortem without separate log scraping.
98
98
  */
99
99
  recordUnmappedFrame(label: string): void;
100
- /** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
100
+ /**
101
+ * Immediate process-tree termination request. `dispose()` is the settlement
102
+ * receipt, so this stays fire-and-forget: an interrupt must not block on a
103
+ * terminator. A request that could not be spawned is left unrecorded, so
104
+ * `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
105
+ * swallowing it here loses nothing.
106
+ */
101
107
  kill(): void;
102
108
  waitClosed(): Promise<void>;
103
109
  dispose(): Promise<void>;
@@ -77,6 +77,11 @@ export declare class CodexProcessRunner {
77
77
  * cleanly resumable afterward via `codex exec resume` (no corruption from
78
78
  * killing mid-turn). `taskkill /T /F` on Windows, mirroring
79
79
  * `../pi/rpc-client.ts`'s own cross-platform convention.
80
+ *
81
+ * Fire-and-forget by design: an interrupt must not block on a terminator,
82
+ * and `dispose()` is the settlement receipt. A request that could not be
83
+ * spawned is left unrecorded, so `dispose()` re-issues it and raises the
84
+ * typed `stage:'signal'` failure — swallowing it here loses nothing.
80
85
  */
81
86
  kill(): void;
82
87
  dispose(): Promise<void>;
@@ -1,4 +1,4 @@
1
- import { execFile, spawn, spawnSync } from 'child_process';
1
+ import { execFile, spawn } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import { promises, existsSync, readFileSync, realpathSync } from 'fs';
4
4
  import path3 from 'path';
@@ -128,12 +128,12 @@ function resolvePiBin() {
128
128
  }
129
129
  } catch (cause) {
130
130
  throw new Error(
131
- `Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.19+ pi sidecar`,
131
+ `Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.22+ pi sidecar`,
132
132
  { cause }
133
133
  );
134
134
  }
135
135
  throw new Error(
136
- `Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.19+ pi sidecar`
136
+ `Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.22+ pi sidecar`
137
137
  );
138
138
  }
139
139
 
@@ -172,6 +172,24 @@ function mapPermissionPolicyToPiArgs(policy) {
172
172
  }
173
173
 
174
174
  // src/adapters/pi/events.ts
175
+ function requireToolCallId(msg) {
176
+ if (typeof msg.toolCallId === "string" && msg.toolCallId.trim().length > 0) return msg.toolCallId;
177
+ throw new RuntimeExecutionFailure({
178
+ phase: "run",
179
+ category: "authority",
180
+ retry: "non-retryable",
181
+ reason: `pi ${msg.type} frame had no authoritative tool call id`
182
+ });
183
+ }
184
+ function requireToolResultOutcome(msg) {
185
+ if (typeof msg.isError === "boolean") return msg.isError;
186
+ throw new RuntimeExecutionFailure({
187
+ phase: "run",
188
+ category: "authority",
189
+ retry: "non-retryable",
190
+ reason: "pi tool_execution_end frame had no authoritative isError outcome"
191
+ });
192
+ }
175
193
  function mapPiMessageToAgentEvent(msg) {
176
194
  switch (msg.type) {
177
195
  case "message_update": {
@@ -182,16 +200,22 @@ function mapPiMessageToAgentEvent(msg) {
182
200
  return void 0;
183
201
  }
184
202
  case "tool_execution_start": {
203
+ const toolCallId = requireToolCallId(msg);
185
204
  if (typeof msg.toolName !== "string") return void 0;
186
- return { type: "tool_use", tool: msg.toolName, input: msg.args };
205
+ return { type: "tool_use", tool: msg.toolName, input: msg.args, toolCallId };
187
206
  }
188
207
  case "tool_execution_end": {
208
+ const toolCallId = requireToolCallId(msg);
209
+ const isError = requireToolResultOutcome(msg);
189
210
  if (typeof msg.toolName !== "string") return void 0;
190
- return {
211
+ const event = {
191
212
  type: "tool_result",
192
213
  tool: msg.toolName,
193
- output: { result: msg.result, isError: msg.isError === true }
214
+ output: { result: msg.result },
215
+ toolCallId,
216
+ isError
194
217
  };
218
+ return event;
195
219
  }
196
220
  case "agent_settled":
197
221
  return { type: "turn_end" };
@@ -338,11 +362,54 @@ var AsyncQueue = class {
338
362
  };
339
363
  }
340
364
  };
365
+
366
+ // src/adapters/taskkill-pid-set.ts
367
+ var INTEGER_PATTERN = /\d+/g;
368
+ function isCandidatePid(value) {
369
+ return Number.isSafeInteger(value) && value > 0;
370
+ }
371
+ function walkTaskkillPidSet(text, rootPid, excludedPids = []) {
372
+ const excluded = new Set(excludedPids);
373
+ const accepted = /* @__PURE__ */ new Set();
374
+ if (isCandidatePid(rootPid)) accepted.add(rootPid);
375
+ const lines = text.split(/\r?\n/).map((line) => {
376
+ const pids = [];
377
+ for (const match of line.matchAll(INTEGER_PATTERN)) {
378
+ const pid = Number(match[0]);
379
+ if (isCandidatePid(pid) && !excluded.has(pid)) pids.push(pid);
380
+ }
381
+ return pids;
382
+ });
383
+ let changed = true;
384
+ while (changed) {
385
+ changed = false;
386
+ for (const pids of lines) {
387
+ if (!pids.some((pid) => accepted.has(pid))) continue;
388
+ for (const pid of pids) {
389
+ if (accepted.has(pid)) continue;
390
+ accepted.add(pid);
391
+ changed = true;
392
+ }
393
+ }
394
+ }
395
+ return accepted;
396
+ }
397
+
398
+ // src/adapters/process-tree.ts
341
399
  var DEFAULT_TERM_GRACE_MS = 750;
342
400
  var DEFAULT_KILL_GRACE_MS = 2e3;
343
401
  var POLL_MS = 20;
344
- var terminationRequested = /* @__PURE__ */ new WeakSet();
345
- var terminationRequestFailed = /* @__PURE__ */ new WeakSet();
402
+ var terminationState = /* @__PURE__ */ new WeakMap();
403
+ function stateFor(child) {
404
+ const existing = terminationState.get(child);
405
+ if (existing) return existing;
406
+ const created = { requested: false, acceptedPids: /* @__PURE__ */ new Set() };
407
+ terminationState.set(child, created);
408
+ return created;
409
+ }
410
+ function defaultKill(pid, signal) {
411
+ process.kill(pid, signal);
412
+ }
346
413
  function withOwnedProcessTree(options) {
347
414
  return {
348
415
  ...options,
@@ -360,9 +427,9 @@ function positivePid(child, label) {
360
427
  }
361
428
  return pid;
362
429
  }
363
- function groupExists(pid, label) {
430
+ function groupExists(pid, label, kill) {
364
431
  try {
365
- process.kill(-pid, 0);
432
+ kill(-pid, 0);
366
433
  return true;
367
434
  } catch (cause) {
368
435
  const code = cause.code;
@@ -374,9 +441,23 @@ function groupExists(pid, label) {
374
441
  }, { cause });
375
442
  }
376
443
  }
377
- function signalGroup(pid, signal, label) {
444
+ function processExists(pid, label, kill) {
378
445
  try {
379
- process.kill(-pid, signal);
446
+ kill(pid, 0);
447
+ return true;
448
+ } catch (cause) {
449
+ const code = cause.code;
450
+ if (code === "ESRCH") return false;
451
+ if (code === "EPERM") return true;
452
+ throw new RuntimeDisposalFailure({
453
+ stage: "quiescence",
454
+ reason: `${label} runtime process ${pid} state could not be verified`
455
+ }, { cause });
456
+ }
457
+ }
458
+ function signalGroup(pid, signal, label, kill) {
459
+ try {
460
+ kill(-pid, signal);
380
461
  } catch (cause) {
381
462
  const code = cause.code;
382
463
  if (code === "ESRCH" || code === "EPERM") return;
@@ -386,6 +467,39 @@ function signalGroup(pid, signal, label) {
386
467
  }, { cause });
387
468
  }
388
469
  }
470
+ async function runTaskkill(pid, options) {
471
+ const spawnFn = options.spawnFn ?? spawn;
472
+ return new Promise((resolve, reject) => {
473
+ const signalFailure = (cause) => {
474
+ reject(new RuntimeDisposalFailure({
475
+ stage: "signal",
476
+ reason: `${options.label} runtime process tree termination could not be requested`
477
+ }, { cause }));
478
+ };
479
+ let taskkill;
480
+ try {
481
+ taskkill = spawnFn("taskkill", ["/PID", String(pid), "/T", "/F"], {
482
+ windowsHide: true,
483
+ stdio: ["ignore", "pipe", "pipe"]
484
+ });
485
+ } catch (cause) {
486
+ signalFailure(cause);
487
+ return;
488
+ }
489
+ const chunks = [];
490
+ taskkill.stdout?.on("data", (chunk) => chunks.push(chunk));
491
+ taskkill.stderr?.on("data", (chunk) => chunks.push(chunk));
492
+ taskkill.once("error", signalFailure);
493
+ taskkill.once("close", () => resolve(Buffer.concat(chunks).toString("latin1")));
494
+ });
495
+ }
496
+ function liveAcceptedPids(accepted, label, kill) {
497
+ const live = [];
498
+ for (const pid of accepted) {
499
+ if (processExists(pid, label, kill)) live.push(pid);
500
+ }
501
+ return live;
502
+ }
389
503
  async function waitUntil(predicate, timeoutMs) {
390
504
  const deadline = Date.now() + timeoutMs;
391
505
  while (predicate()) {
@@ -423,29 +537,27 @@ async function waitWithDeadline(promise, timeoutMs) {
423
537
  );
424
538
  });
425
539
  }
426
- function requestOwnedProcessTreeTermination(options) {
427
- if (options.isClosed()) return;
540
+ async function requestOwnedProcessTreeTermination(options) {
541
+ const platform = options.platform ?? process.platform;
542
+ if (platform !== "win32" && options.isClosed()) return;
428
543
  const pid = positivePid(options.child, options.label);
429
544
  if (pid === void 0) return;
430
- if (process.platform === "win32") {
431
- const result = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true });
432
- if (result.error) {
433
- throw new RuntimeDisposalFailure({
434
- stage: "signal",
435
- reason: `${options.label} runtime process tree could not be terminated`
436
- }, { cause: result.error });
437
- }
438
- terminationRequested.add(options.child);
439
- if (result.status !== 0) terminationRequestFailed.add(options.child);
545
+ if (platform === "win32") {
546
+ const output = await runTaskkill(pid, options);
547
+ const state = stateFor(options.child);
548
+ for (const walked of walkTaskkillPidSet(output, pid, [process.pid])) state.acceptedPids.add(walked);
549
+ state.requested = true;
440
550
  return;
441
551
  }
442
- signalGroup(pid, "SIGTERM", options.label);
443
- terminationRequested.add(options.child);
552
+ signalGroup(pid, "SIGTERM", options.label, options.killFn ?? defaultKill);
553
+ stateFor(options.child).requested = true;
444
554
  }
445
555
  async function disposeOwnedProcessTree(options) {
446
556
  const pid = positivePid(options.child, options.label);
447
557
  const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
448
558
  const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
559
+ const platform = options.platform ?? process.platform;
560
+ const kill = options.killFn ?? defaultKill;
449
561
  if (pid === void 0) {
450
562
  if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
451
563
  throw new RuntimeDisposalFailure({
@@ -453,27 +565,50 @@ async function disposeOwnedProcessTree(options) {
453
565
  reason: `${options.label} runtime process did not settle after spawn failure`
454
566
  });
455
567
  }
456
- if (process.platform === "win32") {
457
- if (!options.isClosed() && !terminationRequested.has(options.child)) requestOwnedProcessTreeTermination(options);
458
- if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
459
- if (terminationRequestFailed.has(options.child)) {
568
+ if (platform === "win32") {
569
+ if (!terminationState.get(options.child)?.requested) {
570
+ await requestOwnedProcessTreeTermination(options);
571
+ }
572
+ const accepted = terminationState.get(options.child)?.acceptedPids ?? /* @__PURE__ */ new Set();
573
+ const deadline = Date.now() + killGraceMs;
574
+ const resweepAt = Date.now() + Math.floor(killGraceMs / 2);
575
+ let reswept = false;
576
+ let live = liveAcceptedPids(accepted, options.label, kill);
577
+ while (live.length > 0) {
578
+ if (Date.now() >= deadline) {
579
+ throw new RuntimeDisposalFailure({
580
+ stage: "quiescence",
581
+ reason: `${options.label} runtime process tree did not quiesce: ${live.length} of ${accepted.size} walked process ids were still alive at the disposal deadline`
582
+ });
583
+ }
584
+ if (!reswept && Date.now() >= resweepAt) {
585
+ reswept = true;
586
+ for (const livePid of live) {
587
+ for (const walked of walkTaskkillPidSet(await runTaskkill(livePid, options), livePid, [process.pid])) {
588
+ accepted.add(walked);
589
+ }
590
+ }
591
+ }
592
+ await new Promise((resolve) => {
593
+ setTimeout(resolve, POLL_MS);
594
+ });
595
+ live = liveAcceptedPids(accepted, options.label, kill);
596
+ }
597
+ if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
460
598
  throw new RuntimeDisposalFailure({
461
- stage: "signal",
462
- reason: `${options.label} runtime process tree could not be terminated`
599
+ stage: "quiescence",
600
+ reason: `${options.label} runtime root did not emit close after its process tree quiesced`
463
601
  });
464
602
  }
465
- throw new RuntimeDisposalFailure({
466
- stage: "quiescence",
467
- reason: `${options.label} runtime process tree did not close before the disposal deadline`
468
- });
603
+ return;
469
604
  }
470
- if (groupExists(pid, options.label) && !terminationRequested.has(options.child)) {
471
- signalGroup(pid, "SIGTERM", options.label);
472
- terminationRequested.add(options.child);
605
+ if (groupExists(pid, options.label, kill) && !terminationState.get(options.child)?.requested) {
606
+ signalGroup(pid, "SIGTERM", options.label, kill);
607
+ stateFor(options.child).requested = true;
473
608
  }
474
- if (!await waitUntil(() => groupExists(pid, options.label), termGraceMs)) {
475
- signalGroup(pid, "SIGKILL", options.label);
476
- if (!await waitUntil(() => groupExists(pid, options.label), killGraceMs)) {
609
+ if (!await waitUntil(() => groupExists(pid, options.label, kill), termGraceMs)) {
610
+ signalGroup(pid, "SIGKILL", options.label, kill);
611
+ if (!await waitUntil(() => groupExists(pid, options.label, kill), killGraceMs)) {
477
612
  throw new RuntimeDisposalFailure({
478
613
  stage: "quiescence",
479
614
  reason: `${options.label} runtime process group remained live after SIGKILL`
@@ -571,9 +706,16 @@ var PiRpcClient = class {
571
706
  );
572
707
  }
573
708
  }
574
- /** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
709
+ /**
710
+ * Immediate process-tree termination request. `dispose()` is the settlement
711
+ * receipt, so this stays fire-and-forget: an interrupt must not block on a
712
+ * terminator. A request that could not be spawned is left unrecorded, so
713
+ * `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
714
+ * swallowing it here loses nothing.
715
+ */
575
716
  kill() {
576
- requestOwnedProcessTreeTermination(this.processTreeOptions());
717
+ void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
718
+ });
577
719
  }
578
720
  waitClosed() {
579
721
  return this.closedPromise;
@@ -1085,6 +1227,17 @@ function subtractDenied(tools, denyTools) {
1085
1227
  function createToolUseCorrelation() {
1086
1228
  return { toolNameByUseId: /* @__PURE__ */ new Map() };
1087
1229
  }
1230
+ function missingToolCallIdFailure(frame) {
1231
+ return new RuntimeExecutionFailure({
1232
+ phase: "run",
1233
+ category: "authority",
1234
+ retry: "non-retryable",
1235
+ reason: `claude ${frame} frame had no authoritative tool call id`
1236
+ });
1237
+ }
1238
+ function isAuthoritativeToolCallId(value) {
1239
+ return typeof value === "string" && value.trim().length > 0;
1240
+ }
1088
1241
  var ROUTINE_CLAUDE_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set([
1089
1242
  "init",
1090
1243
  "hook_started",
@@ -1128,9 +1281,12 @@ function mapAssistant(msg, correlation) {
1128
1281
  }
1129
1282
  break;
1130
1283
  case "tool_use":
1131
- if (typeof block.id === "string" && typeof block.name === "string") {
1284
+ if (!isAuthoritativeToolCallId(block.id)) {
1285
+ return { events: [], terminalFailure: missingToolCallIdFailure("tool_use") };
1286
+ }
1287
+ if (typeof block.name === "string") {
1132
1288
  correlation.toolNameByUseId.set(block.id, block.name);
1133
- events.push({ type: "tool_use", tool: block.name, input: block.input });
1289
+ events.push({ type: "tool_use", tool: block.name, input: block.input, toolCallId: block.id });
1134
1290
  }
1135
1291
  break;
1136
1292
  // Deliberately NOT mapped to `progress` — mirrors pi's own choice to
@@ -1166,11 +1322,20 @@ function mapUser(msg, correlation, options) {
1166
1322
  unmappedLabel = unmappedLabel ?? `user-block:${String(block.type)}`;
1167
1323
  continue;
1168
1324
  }
1169
- const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : void 0;
1170
- const tool = toolUseId && correlation.toolNameByUseId.get(toolUseId) || "unknown";
1171
- const isError = block.is_error === true;
1172
- events.push({ type: "tool_result", tool, output: { content: block.content, isError } });
1173
- if (!isError && FILE_WRITING_TOOLS.has(tool)) {
1325
+ if (!isAuthoritativeToolCallId(block.tool_use_id)) {
1326
+ return { events: [], terminalFailure: missingToolCallIdFailure("tool_result") };
1327
+ }
1328
+ const tool = correlation.toolNameByUseId.get(block.tool_use_id) ?? "unknown";
1329
+ const isError = typeof block.is_error === "boolean" ? block.is_error : void 0;
1330
+ const event = {
1331
+ type: "tool_result",
1332
+ tool,
1333
+ output: { content: block.content },
1334
+ toolCallId: block.tool_use_id
1335
+ };
1336
+ if (isError !== void 0) event.isError = isError;
1337
+ events.push(event);
1338
+ if (isError === false && FILE_WRITING_TOOLS.has(tool)) {
1174
1339
  const artifact = tryBuildArtifactEvent(msg, options.workspaceDir);
1175
1340
  if (artifact) events.push(artifact);
1176
1341
  }
@@ -1376,9 +1541,16 @@ var ClaudeProcessClient = class {
1376
1541
  );
1377
1542
  }
1378
1543
  }
1379
- /** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
1544
+ /**
1545
+ * Immediate process-tree termination request. `dispose()` is the settlement
1546
+ * receipt, so this stays fire-and-forget: an interrupt must not block on a
1547
+ * terminator. A request that could not be spawned is left unrecorded, so
1548
+ * `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
1549
+ * swallowing it here loses nothing.
1550
+ */
1380
1551
  kill() {
1381
- requestOwnedProcessTreeTermination(this.processTreeOptions());
1552
+ void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
1553
+ });
1382
1554
  }
1383
1555
  waitClosed() {
1384
1556
  return this.closedPromise;
@@ -1786,8 +1958,8 @@ var ClaudeSession = class {
1786
1958
  for (; ; ) {
1787
1959
  const buffered = pending.shift();
1788
1960
  if (buffered) return { value: buffered, done: false };
1961
+ if (terminalFailure) throw terminalFailure;
1789
1962
  if (turnSettled) {
1790
- if (terminalFailure) throw terminalFailure;
1791
1963
  return { value: void 0, done: true };
1792
1964
  }
1793
1965
  let raw;
@@ -1989,6 +2161,15 @@ function extractCodexUsageEvent(rawUsage) {
1989
2161
  function toNonNegativeInt2(value) {
1990
2162
  return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
1991
2163
  }
2164
+ function requireToolCallId2(item) {
2165
+ if (typeof item.id === "string" && item.id.trim().length > 0) return item.id;
2166
+ throw new RuntimeExecutionFailure({
2167
+ phase: "run",
2168
+ category: "authority",
2169
+ retry: "non-retryable",
2170
+ reason: "codex tool item had no authoritative tool call id"
2171
+ });
2172
+ }
1992
2173
  function mapItem(rawItem, phase, workspaceDir) {
1993
2174
  if (!rawItem || typeof rawItem !== "object") return [];
1994
2175
  const item = rawItem;
@@ -1999,9 +2180,10 @@ function mapItem(rawItem, phase, workspaceDir) {
1999
2180
  return typeof item.text === "string" ? [{ type: "progress", text: item.text }] : [];
2000
2181
  }
2001
2182
  case "command_execution": {
2183
+ const toolCallId = requireToolCallId2(item);
2002
2184
  const command = typeof item.command === "string" ? item.command : void 0;
2003
2185
  if (phase === "started") {
2004
- return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command } }] : [];
2186
+ return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command }, toolCallId }] : [];
2005
2187
  }
2006
2188
  return [
2007
2189
  {
@@ -2012,17 +2194,19 @@ function mapItem(rawItem, phase, workspaceDir) {
2012
2194
  aggregatedOutput: item.aggregated_output,
2013
2195
  exitCode: item.exit_code,
2014
2196
  status: item.status
2015
- }
2197
+ },
2198
+ toolCallId
2016
2199
  }
2017
2200
  ];
2018
2201
  }
2019
2202
  case "file_change": {
2203
+ const toolCallId = requireToolCallId2(item);
2020
2204
  const changes = Array.isArray(item.changes) ? item.changes : [];
2021
2205
  if (phase === "started") {
2022
- return [{ type: "tool_use", tool: "file_change", input: { changes } }];
2206
+ return [{ type: "tool_use", tool: "file_change", input: { changes }, toolCallId }];
2023
2207
  }
2024
2208
  return [
2025
- { type: "tool_result", tool: "file_change", output: { changes, status: item.status } },
2209
+ { type: "tool_result", tool: "file_change", output: { changes, status: item.status }, toolCallId },
2026
2210
  ...extractArtifactEvents(changes, workspaceDir)
2027
2211
  ];
2028
2212
  }
@@ -2143,9 +2327,15 @@ var CodexProcessRunner = class {
2143
2327
  * cleanly resumable afterward via `codex exec resume` (no corruption from
2144
2328
  * killing mid-turn). `taskkill /T /F` on Windows, mirroring
2145
2329
  * `../pi/rpc-client.ts`'s own cross-platform convention.
2330
+ *
2331
+ * Fire-and-forget by design: an interrupt must not block on a terminator,
2332
+ * and `dispose()` is the settlement receipt. A request that could not be
2333
+ * spawned is left unrecorded, so `dispose()` re-issues it and raises the
2334
+ * typed `stage:'signal'` failure — swallowing it here loses nothing.
2146
2335
  */
2147
2336
  kill() {
2148
- requestOwnedProcessTreeTermination(this.processTreeOptions());
2337
+ void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
2338
+ });
2149
2339
  }
2150
2340
  dispose() {
2151
2341
  if (!this.disposalAttempt) {
@@ -2439,7 +2629,15 @@ async function runCodexTurn(params) {
2439
2629
  }
2440
2630
  return;
2441
2631
  }
2442
- const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
2632
+ let mapped;
2633
+ try {
2634
+ mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
2635
+ } catch (cause) {
2636
+ if (!isRuntimeExecutionFailure(cause)) throw cause;
2637
+ params.terminal.failure = cause;
2638
+ params.queue.end();
2639
+ return;
2640
+ }
2443
2641
  for (const agentEvent of mapped) {
2444
2642
  if (agentEvent.type === "turn_end") turnEnded = true;
2445
2643
  params.queue.push(agentEvent);