acpx 0.13.2 → 0.15.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.
Files changed (34) hide show
  1. package/README.md +4 -3
  2. package/dist/{cli-DJT3MRRI.js → cli-Cen2Rb6S.js} +4 -4
  3. package/dist/{cli-DJT3MRRI.js.map → cli-Cen2Rb6S.js.map} +1 -1
  4. package/dist/cli.d.ts +5 -6
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +11 -8
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{client-CxNllqui.d.ts → client-Cvz6msGc.d.ts} +8 -3
  9. package/dist/client-Cvz6msGc.d.ts.map +1 -0
  10. package/dist/{flags-CM2rAdBo.js → flags-CRh8BJre.js} +21 -4
  11. package/dist/flags-CRh8BJre.js.map +1 -0
  12. package/dist/{flows-BiRKgCnW.js → flows-D-F3Y2o9.js} +436 -63
  13. package/dist/flows-D-F3Y2o9.js.map +1 -0
  14. package/dist/flows.d.ts +27 -17
  15. package/dist/flows.d.ts.map +1 -1
  16. package/dist/flows.js +1 -1
  17. package/dist/{live-checkpoint-Gw2oGjhe.js → live-checkpoint-CYRs-D9t.js} +312 -124
  18. package/dist/live-checkpoint-CYRs-D9t.js.map +1 -0
  19. package/dist/{output-DiPPprGk.js → output-CIVecnHU.js} +42 -9
  20. package/dist/output-CIVecnHU.js.map +1 -0
  21. package/dist/runtime.d.ts +22 -15
  22. package/dist/runtime.d.ts.map +1 -1
  23. package/dist/runtime.js +46 -8
  24. package/dist/runtime.js.map +1 -1
  25. package/dist/{session-options-DwRDODlr.d.ts → session-options-DyIGRNfu.d.ts} +48 -2
  26. package/dist/session-options-DyIGRNfu.d.ts.map +1 -0
  27. package/package.json +10 -10
  28. package/skills/acpx/SKILL.md +5 -0
  29. package/dist/client-CxNllqui.d.ts.map +0 -1
  30. package/dist/flags-CM2rAdBo.js.map +0 -1
  31. package/dist/flows-BiRKgCnW.js.map +0 -1
  32. package/dist/live-checkpoint-Gw2oGjhe.js.map +0 -1
  33. package/dist/output-DiPPprGk.js.map +0 -1
  34. package/dist/session-options-DwRDODlr.d.ts.map +0 -1
@@ -38,7 +38,12 @@ var SessionResolutionError = class extends AcpxOperationalError {};
38
38
  var AgentSpawnError = class extends AcpxOperationalError {
39
39
  agentCommand;
40
40
  constructor(agentCommand, cause) {
41
- super(`Failed to spawn agent command: ${agentCommand}`, { cause: cause instanceof Error ? cause : void 0 });
41
+ const spawnEnoent = (cause instanceof Error ? cause.code : void 0) === "ENOENT";
42
+ const message = spawnEnoent ? `Failed to spawn agent command: ${agentCommand}. The agent process could not start because a required executable, interpreter, working directory, or other launch path was not found. Check the command, effective PATH, and working directory, or verify the custom agent's configured argv.` : `Failed to spawn agent command: ${agentCommand}`;
43
+ super(message, {
44
+ cause: cause instanceof Error ? cause : void 0,
45
+ ...spawnEnoent ? { detailCode: "AGENT_SPAWN_ENOENT" } : {}
46
+ });
42
47
  this.agentCommand = agentCommand;
43
48
  }
44
49
  };
@@ -449,6 +454,7 @@ const AGENT_REGISTRY = {
449
454
  kilocode: "npx -y @kilocode/cli acp",
450
455
  kimi: "kimi acp",
451
456
  kiro: "kiro-cli-chat acp",
457
+ mcode: "mcode acp",
452
458
  mux: `npx -y mux@${ACP_ADAPTER_PACKAGE_RANGES.mux} acp`,
453
459
  opencode: "npx -y opencode-ai acp",
454
460
  pool: "pool acp",
@@ -502,6 +508,7 @@ const AGENT_ARGV_REGISTRY = {
502
508
  ],
503
509
  kimi: ["kimi", "acp"],
504
510
  kiro: ["kiro-cli-chat", "acp"],
511
+ mcode: ["mcode", "acp"],
505
512
  mux: [
506
513
  "npx",
507
514
  "-y",
@@ -711,19 +718,17 @@ async function withInterrupt(run, onInterrupt) {
711
718
  process.off("SIGHUP", onSighup);
712
719
  cb();
713
720
  };
714
- const rejectInterrupted = () => {
715
- onInterrupt().finally(() => {
716
- finish(() => reject(new InterruptedError()));
717
- });
721
+ const rejectInterrupted = (signal) => {
722
+ onInterrupt(signal).then(() => finish(() => reject(new InterruptedError())), (error) => finish(() => reject(error)));
718
723
  };
719
724
  const onSigint = () => {
720
- rejectInterrupted();
725
+ rejectInterrupted("SIGINT");
721
726
  };
722
727
  const onSigterm = () => {
723
- rejectInterrupted();
728
+ rejectInterrupted("SIGTERM");
724
729
  };
725
730
  const onSighup = () => {
726
- rejectInterrupted();
731
+ rejectInterrupted("SIGHUP");
727
732
  };
728
733
  process.once("SIGINT", onSigint);
729
734
  process.once("SIGTERM", onSigterm);
@@ -1661,7 +1666,11 @@ const ZED_TAG_KEYS = /* @__PURE__ */ new Set([
1661
1666
  "RedactedThinking",
1662
1667
  "ToolUse"
1663
1668
  ]);
1664
- const MAP_OBJECT_PATHS = /* @__PURE__ */ new Set(["request_token_usage", "messages.Agent.tool_results"]);
1669
+ const MAP_OBJECT_PATHS = /* @__PURE__ */ new Set([
1670
+ "request_token_usage",
1671
+ "messages.Agent.tool_results",
1672
+ "acpx.session_options.env"
1673
+ ]);
1665
1674
  const OPAQUE_VALUE_PATHS = /* @__PURE__ */ new Set([
1666
1675
  "agent_capabilities",
1667
1676
  "messages.Agent.content.ToolUse.input",
@@ -2633,6 +2642,9 @@ function getAcpxVersion() {
2633
2642
  cachedVersion = resolveAcpxVersion();
2634
2643
  return cachedVersion;
2635
2644
  }
2645
+ //#endregion
2646
+ //#region src/acp/client-process.ts
2647
+ const PROCESS_HELPER_TIMEOUT_MS = 8e3;
2636
2648
  async function runTimedExecFile(command, args, options = {}) {
2637
2649
  const timeoutMs = Math.max(1, Math.round(options.timeoutMs ?? 8e3));
2638
2650
  return await new Promise((resolve, reject) => {
@@ -3135,14 +3147,18 @@ function promotePrefixedAuthEnvironment(env) {
3135
3147
  }
3136
3148
  return protectedKeys;
3137
3149
  }
3138
- function buildAgentEnvironment(authCredentials, sessionEnv) {
3150
+ function validateAgentProcessEnv(agentProcessEnv) {
3151
+ for (const [key, value] of Object.entries(agentProcessEnv ?? {})) if (typeof value !== "string" || key.includes("=") || key.includes("\0") || value.includes("\0")) throw new Error("Invalid agentProcessEnv: environment entries cannot contain NUL or names with '='");
3152
+ }
3153
+ function buildAgentEnvironment(authCredentials, sessionEnv, agentProcessEnv) {
3154
+ validateAgentProcessEnv(agentProcessEnv);
3139
3155
  const env = { ...process.env };
3140
3156
  const protectedAuthEnvKeys = promotePrefixedAuthEnvironment(env);
3141
3157
  if (authCredentials) for (const [methodId, credential] of Object.entries(authCredentials)) {
3142
3158
  addAuthCredentialEnvKeys(protectedAuthEnvKeys, methodId, credential);
3143
3159
  assignAuthCredentialEnv(env, methodId, credential);
3144
3160
  }
3145
- if (sessionEnv) for (const [key, value] of Object.entries(sessionEnv)) {
3161
+ for (const overlay of [sessionEnv, agentProcessEnv]) for (const [key, value] of Object.entries(overlay ?? {})) {
3146
3162
  if (typeof value !== "string" || protectedAuthEnvKeys.has(protectedEnvKey(key))) continue;
3147
3163
  assignSessionEnv(env, key, value);
3148
3164
  }
@@ -3178,10 +3194,10 @@ function resolveConfiguredAuthCredential(methodId, authCredentials) {
3178
3194
  const configCredentials = authCredentials ?? {};
3179
3195
  return configCredentials[methodId] ?? configCredentials[toEnvToken(methodId)];
3180
3196
  }
3181
- function buildAgentSpawnOptions(cwd, authCredentials, sessionEnv) {
3197
+ function buildAgentSpawnOptions(cwd, authCredentials, sessionEnv, agentProcessEnv) {
3182
3198
  return {
3183
3199
  cwd,
3184
- env: buildAgentEnvironment(authCredentials, sessionEnv),
3200
+ env: buildAgentEnvironment(authCredentials, sessionEnv, agentProcessEnv),
3185
3201
  stdio: [
3186
3202
  "pipe",
3187
3203
  "pipe",
@@ -3310,6 +3326,93 @@ function assertRequestedModelSupported(params) {
3310
3326
  }
3311
3327
  }
3312
3328
  //#endregion
3329
+ //#region src/acp/ndjson-stream.ts
3330
+ var AcpMessageLimitError = class extends AcpxOperationalError {
3331
+ constructor(limit) {
3332
+ super(`ACP message exceeded ACPX_MAX_ACP_MESSAGE_BYTES (${limit} bytes)`, {
3333
+ outputCode: "RUNTIME",
3334
+ detailCode: "ACP_MESSAGE_TOO_LARGE",
3335
+ origin: "acp",
3336
+ retryable: false
3337
+ });
3338
+ }
3339
+ };
3340
+ function readMaxAcpMessageBytes(raw = process.env.ACPX_MAX_ACP_MESSAGE_BYTES) {
3341
+ const value = raw?.trim();
3342
+ if (!value) return;
3343
+ const bytes = Number(value);
3344
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(bytes)) throw new Error("ACPX_MAX_ACP_MESSAGE_BYTES must be a non-negative safe integer; zero is unlimited");
3345
+ return bytes === 0 ? void 0 : bytes;
3346
+ }
3347
+ function countLineBytes(chunk, retained, limit) {
3348
+ let start = 0;
3349
+ while (start < chunk.length) {
3350
+ const newline = chunk.indexOf(10, start);
3351
+ const end = newline < 0 ? chunk.length : newline;
3352
+ retained += end - start;
3353
+ if (retained > limit) throw new AcpMessageLimitError(limit);
3354
+ if (newline < 0) return retained;
3355
+ retained = 0;
3356
+ start = end + 1;
3357
+ }
3358
+ return retained;
3359
+ }
3360
+ function parseAcpJsonMessageLine(line) {
3361
+ const message = JSON.parse(line);
3362
+ return isAcpMessageObject(message) ? message : void 0;
3363
+ }
3364
+ function enqueueNdJsonLine(agentCommand, line, controller) {
3365
+ const trimmedLine = line.trim();
3366
+ if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
3367
+ try {
3368
+ const message = parseAcpJsonMessageLine(trimmedLine);
3369
+ if (message) controller.enqueue(message);
3370
+ } catch (err) {
3371
+ console.error("Failed to parse JSON message:", trimmedLine, err);
3372
+ }
3373
+ }
3374
+ function enqueueNdJsonLines(agentCommand, lines, controller) {
3375
+ for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
3376
+ }
3377
+ function createNdJsonMessageStream(agentCommand, output, input, maxMessageBytes, onReadError) {
3378
+ const textEncoder = new TextEncoder();
3379
+ const textDecoder = new TextDecoder();
3380
+ return {
3381
+ readable: new ReadableStream({ async start(controller) {
3382
+ let content = "";
3383
+ let retainedBytes = 0;
3384
+ const reader = input.getReader();
3385
+ try {
3386
+ while (true) {
3387
+ const { value, done } = await reader.read();
3388
+ if (done) break;
3389
+ if (maxMessageBytes !== void 0) retainedBytes = countLineBytes(value, retainedBytes, maxMessageBytes);
3390
+ content += textDecoder.decode(value, { stream: true });
3391
+ const lines = content.split("\n");
3392
+ content = lines.pop() || "";
3393
+ enqueueNdJsonLines(agentCommand, lines, controller);
3394
+ }
3395
+ controller.close();
3396
+ } catch (err) {
3397
+ const error = err instanceof Error ? err : new Error(String(err));
3398
+ controller.error(error);
3399
+ onReadError?.(error);
3400
+ } finally {
3401
+ reader.releaseLock();
3402
+ }
3403
+ } }),
3404
+ writable: new WritableStream({ async write(message) {
3405
+ const content = JSON.stringify(message) + "\n";
3406
+ const writer = output.getWriter();
3407
+ try {
3408
+ await writer.write(textEncoder.encode(content));
3409
+ } finally {
3410
+ writer.releaseLock();
3411
+ }
3412
+ } })
3413
+ };
3414
+ }
3415
+ //#endregion
3313
3416
  //#region src/acp/session-control-errors.ts
3314
3417
  const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = /* @__PURE__ */ new Set([-32601, -32602]);
3315
3418
  function asRecord$1(value) {
@@ -3399,6 +3502,7 @@ function waitMs(ms) {
3399
3502
  setTimeout(resolve, Math.max(0, ms));
3400
3503
  });
3401
3504
  }
3505
+ function onStreamError() {}
3402
3506
  var TerminalManager = class {
3403
3507
  cwd;
3404
3508
  permissionMode;
@@ -3464,6 +3568,8 @@ var TerminalManager = class {
3464
3568
  };
3465
3569
  proc.stdout.on("data", appendOutput);
3466
3570
  proc.stderr.on("data", appendOutput);
3571
+ proc.stdout.on("error", onStreamError);
3572
+ proc.stderr.on("error", onStreamError);
3467
3573
  proc.once("exit", (exitCode, signal) => {
3468
3574
  terminal.exitCode = exitCode;
3469
3575
  terminal.signal = signal;
@@ -3638,7 +3744,7 @@ var TerminalManager = class {
3638
3744
  } catch {
3639
3745
  return;
3640
3746
  }
3641
- await this.waitForCleanupAfterSignal(terminal);
3747
+ await this.waitForFinalCleanup(terminal);
3642
3748
  }
3643
3749
  async signalProcess(terminal, signal) {
3644
3750
  const pid = terminal.process.pid;
@@ -3655,10 +3761,10 @@ var TerminalManager = class {
3655
3761
  async signalWindowsProcessGroup(terminal, pid, signal) {
3656
3762
  await this.captureDescendantPids(terminal, pid);
3657
3763
  if (this.isRunning(terminal)) {
3658
- await killWindowsProcessTree(pid, signal);
3764
+ await killWindowsProcessTree(pid, signal, terminal.processHelperTimeoutMs);
3659
3765
  return;
3660
3766
  }
3661
- for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal);
3767
+ for (const descendantPid of terminal.descendantPids) await killWindowsProcessTree(descendantPid, signal, terminal.processHelperTimeoutMs);
3662
3768
  }
3663
3769
  async signalPosixProcessGroup(terminal, pid, signal) {
3664
3770
  await this.captureDescendantPids(terminal, pid);
@@ -3672,6 +3778,9 @@ var TerminalManager = class {
3672
3778
  if (!this.isRunning(terminal)) await terminal.processGroupSnapshotPromise?.catch(() => {});
3673
3779
  for (const descendantPid of await listDescendantPids(pid, terminal.processHelperTimeoutMs)) terminal.descendantPids.add(descendantPid);
3674
3780
  }
3781
+ async waitForFinalCleanup(terminal) {
3782
+ if (!await this.waitForCleanupAfterSignal(terminal) && process.platform === "win32") throw new Error("Terminal process cleanup did not finish after SIGKILL");
3783
+ }
3675
3784
  async waitForCleanupAfterSignal(terminal) {
3676
3785
  return await Promise.race([this.waitForTerminalAndTrackedDescendants(terminal).then(() => true), waitMs(this.killGraceMs).then(() => false)]);
3677
3786
  }
@@ -3803,25 +3912,19 @@ async function runWindowsProcessListCommand(timeoutMs) {
3803
3912
  windowsHide: true
3804
3913
  });
3805
3914
  }
3806
- async function killWindowsProcessTree(pid, signal) {
3915
+ async function killWindowsProcessTree(pid, signal, timeoutMs = PROCESS_HELPER_TIMEOUT_MS) {
3807
3916
  const args = [
3808
3917
  "/pid",
3809
3918
  String(pid),
3810
3919
  "/t"
3811
3920
  ];
3812
3921
  if (signal === "SIGKILL") args.push("/f");
3813
- await new Promise((resolve) => {
3814
- const child = spawn("taskkill", args, {
3815
- stdio: [
3816
- "ignore",
3817
- "ignore",
3818
- "ignore"
3819
- ],
3922
+ try {
3923
+ await runTimedExecFile("taskkill", args, {
3924
+ timeoutMs,
3820
3925
  windowsHide: true
3821
3926
  });
3822
- child.once("error", () => resolve());
3823
- child.once("close", () => resolve());
3824
- });
3927
+ } catch {}
3825
3928
  }
3826
3929
  function sendSignal(pid, signal) {
3827
3930
  try {
@@ -3996,6 +4099,17 @@ function snapshotPermissionPolicy(policy) {
3996
4099
  ...policy.defaultAction ? { defaultAction: policy.defaultAction } : {}
3997
4100
  };
3998
4101
  }
4102
+ function snapshotProcessLaunchScope(scope) {
4103
+ if (!scope || scope.kind === "client") return Object.freeze({ kind: "client" });
4104
+ if (scope.kind === "runtime-session") return Object.freeze({
4105
+ kind: "runtime-session",
4106
+ sessionKey: scope.sessionKey
4107
+ });
4108
+ return Object.freeze({
4109
+ kind: "runtime-probe",
4110
+ agent: scope.agent
4111
+ });
4112
+ }
3999
4113
  function childProcessIsRunning(agent) {
4000
4114
  if (!agent) return false;
4001
4115
  return agent.exitCode == null && agent.signalCode == null && !agent.killed;
@@ -4017,56 +4131,6 @@ function installSdkConsoleErrorSuppression() {
4017
4131
  console.error = originalConsoleError;
4018
4132
  };
4019
4133
  }
4020
- function enqueueNdJsonLine(agentCommand, line, controller) {
4021
- const trimmedLine = line.trim();
4022
- if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) return;
4023
- try {
4024
- const message = parseAcpJsonMessageLine(trimmedLine);
4025
- if (message) controller.enqueue(message);
4026
- } catch (err) {
4027
- console.error("Failed to parse JSON message:", trimmedLine, err);
4028
- }
4029
- }
4030
- function parseAcpJsonMessageLine(line) {
4031
- const message = JSON.parse(line);
4032
- return isAcpMessageObject(message) ? message : void 0;
4033
- }
4034
- function enqueueNdJsonLines(agentCommand, lines, controller) {
4035
- for (const line of lines) enqueueNdJsonLine(agentCommand, line, controller);
4036
- }
4037
- function createNdJsonMessageStream(agentCommand, output, input) {
4038
- const textEncoder = new TextEncoder();
4039
- const textDecoder = new TextDecoder();
4040
- return {
4041
- readable: new ReadableStream({ async start(controller) {
4042
- let content = "";
4043
- const reader = input.getReader();
4044
- try {
4045
- while (true) {
4046
- const { value, done } = await reader.read();
4047
- if (done) break;
4048
- if (!value) continue;
4049
- content += textDecoder.decode(value, { stream: true });
4050
- const lines = content.split("\n");
4051
- content = lines.pop() || "";
4052
- enqueueNdJsonLines(agentCommand, lines, controller);
4053
- }
4054
- } finally {
4055
- reader.releaseLock();
4056
- controller.close();
4057
- }
4058
- } }),
4059
- writable: new WritableStream({ async write(message) {
4060
- const content = JSON.stringify(message) + "\n";
4061
- const writer = output.getWriter();
4062
- try {
4063
- await writer.write(textEncoder.encode(content));
4064
- } finally {
4065
- writer.releaseLock();
4066
- }
4067
- } })
4068
- };
4069
- }
4070
4134
  var AcpClient = class {
4071
4135
  options;
4072
4136
  connection;
@@ -4103,6 +4167,7 @@ var AcpClient = class {
4103
4167
  this.options = {
4104
4168
  ...options,
4105
4169
  cwd: asAbsoluteCwd(options.cwd),
4170
+ agentProcessEnv: options.agentProcessEnv ? { ...options.agentProcessEnv } : void 0,
4106
4171
  authPolicy: options.authPolicy ?? "skip",
4107
4172
  permissionPolicy: snapshotPermissionPolicy(options.permissionPolicy),
4108
4173
  elicitationModes: normalizeElicitationModes(options.elicitationModes)
@@ -4207,29 +4272,41 @@ var AcpClient = class {
4207
4272
  async start() {
4208
4273
  if (this.connection && this.agent && isChildProcessRunning(this.agent)) return;
4209
4274
  if (this.connection || this.agent) await this.close();
4275
+ const maxMessageBytes = readMaxAcpMessageBytes();
4210
4276
  const launch = await this.resolveAgentLaunchPlan();
4211
4277
  this.logAgentLaunch(launch);
4212
4278
  await this.ensureLaunchSupport(launch);
4213
- const child = await this.spawnAgentProcess(launch);
4279
+ const { child, process: startedProcess } = await this.spawnAgentProcess(launch);
4214
4280
  this.closing = false;
4215
- this.agentStartedAt = isoNow$1();
4281
+ this.agentStartedAt = startedProcess.startedAt;
4216
4282
  this.lastAgentExit = void 0;
4217
- this.lastKnownPid = child.pid ?? void 0;
4218
- this.attachAgentLifecycleObservers(child);
4283
+ this.lastKnownPid = startedProcess.pid;
4219
4284
  const startupStderr = [];
4220
4285
  child.stderr.on("data", (chunk) => {
4221
4286
  this.captureStartupStderr(startupStderr, chunk);
4222
4287
  if (!this.options.verbose) return;
4223
4288
  process.stderr.write(chunk);
4224
4289
  });
4290
+ const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
4291
+ try {
4292
+ await this.admitAndObserveSpawnedProcess(child, startedProcess);
4293
+ const admissionExit = startupFailure.getError();
4294
+ if (admissionExit) throw admissionExit;
4295
+ } catch (error) {
4296
+ startupFailure.dispose();
4297
+ throw error;
4298
+ }
4225
4299
  const input = Writable.toWeb(child.stdin);
4226
4300
  const output = Readable.toWeb(child.stdout);
4227
- const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output));
4228
- const connection = this.createConnection(stream, launch);
4301
+ let connection;
4302
+ const stream = this.createTappedStream(createNdJsonMessageStream(this.options.agentCommand, input, output, maxMessageBytes, (error) => {
4303
+ this.rejectPendingConnectionRequests(error);
4304
+ connection?.close?.(error);
4305
+ }));
4306
+ connection = this.createConnection(stream, launch);
4229
4307
  connection.signal.addEventListener("abort", () => {
4230
4308
  this.recordAgentExit("connection_close", child.exitCode ?? null, child.signalCode ?? null);
4231
4309
  }, { once: true });
4232
- const startupFailure = this.createStartupFailureWatcher(child, startupStderr);
4233
4310
  await this.initializeAgentConnection({
4234
4311
  child,
4235
4312
  connection,
@@ -4253,7 +4330,7 @@ var AcpClient = class {
4253
4330
  geminiAcp: isGeminiAcpCommand(spawnCommand, args),
4254
4331
  copilotAcp: isCopilotAcpCommand(spawnCommand, args),
4255
4332
  claudeAcp: isClaudeAcpCommand(spawnCommand, args),
4256
- spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials, this.options.sessionOptions?.env)
4333
+ spawnOptions: buildAgentSpawnOptions(this.options.cwd, this.options.authCredentials, this.options.sessionOptions?.env, this.options.agentProcessEnv)
4257
4334
  };
4258
4335
  }
4259
4336
  logAgentLaunch(plan) {
@@ -4279,16 +4356,57 @@ var AcpClient = class {
4279
4356
  }
4280
4357
  async spawnAgentProcess(plan) {
4281
4358
  const spawnCommand = buildAgentSpawnCommand(plan.spawnCommand, plan.args, process.platform, plan.spawnOptions.env);
4282
- const spawnedChild = spawn(spawnCommand.command, spawnCommand.args, {
4283
- ...plan.spawnOptions,
4284
- windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments
4359
+ const launch = Object.freeze({
4360
+ launchId: randomUUID(),
4361
+ scope: snapshotProcessLaunchScope(this.options.processLaunchScope),
4362
+ command: spawnCommand.command,
4363
+ args: Object.freeze([...spawnCommand.args]),
4364
+ cwd: this.options.cwd
4285
4365
  });
4366
+ await this.options.processLifecycle?.onBeforeSpawn?.(launch);
4367
+ let spawnedChild;
4286
4368
  try {
4369
+ spawnedChild = spawn(spawnCommand.command, spawnCommand.args, {
4370
+ ...plan.spawnOptions,
4371
+ windowsVerbatimArguments: spawnCommand.windowsVerbatimArguments
4372
+ });
4287
4373
  await waitForSpawn$1(spawnedChild);
4288
4374
  } catch (error) {
4289
- throw new AgentSpawnError(this.options.agentCommand, error);
4375
+ const spawnError = new AgentSpawnError(this.options.agentCommand, error);
4376
+ this.notifyProcessSpawnFailure(launch, spawnError);
4377
+ throw spawnError;
4378
+ }
4379
+ const child = requireAgentStdio(spawnedChild);
4380
+ const pid = child.pid;
4381
+ if (pid === void 0) {
4382
+ const spawnError = new AgentSpawnError(this.options.agentCommand, /* @__PURE__ */ new Error("spawned agent process did not expose a PID"));
4383
+ this.notifyProcessSpawnFailure(launch, spawnError);
4384
+ await this.terminateAgentProcess(child);
4385
+ throw spawnError;
4386
+ }
4387
+ return {
4388
+ child,
4389
+ process: Object.freeze({
4390
+ ...launch,
4391
+ pid,
4392
+ startedAt: isoNow$1()
4393
+ })
4394
+ };
4395
+ }
4396
+ async admitAndObserveSpawnedProcess(child, process) {
4397
+ let releaseExitNotification = () => {};
4398
+ const exitNotificationBarrier = new Promise((resolve) => {
4399
+ releaseExitNotification = resolve;
4400
+ });
4401
+ this.attachAgentLifecycleObservers(child, process, exitNotificationBarrier);
4402
+ try {
4403
+ await this.options.processLifecycle?.onSpawned?.(process);
4404
+ } catch (error) {
4405
+ await this.terminateAgentProcess(child);
4406
+ throw error;
4407
+ } finally {
4408
+ releaseExitNotification();
4290
4409
  }
4291
- return requireAgentStdio(spawnedChild);
4292
4410
  }
4293
4411
  createConnection(stream, launch) {
4294
4412
  const app = client({ name: "acpx" }).onNotification(methods.client.session.update, async ({ params }) => {
@@ -4347,7 +4465,7 @@ var AcpClient = class {
4347
4465
  }
4348
4466
  async handleInitializeFailure(params, error) {
4349
4467
  params.startupFailure.dispose();
4350
- const normalizedError = await this.normalizeInitializeError(error, params.child, params.startupStderr);
4468
+ const normalizedError = error instanceof AcpMessageLimitError ? error : await this.normalizeInitializeError(error, params.child, params.startupStderr);
4351
4469
  try {
4352
4470
  params.child.kill();
4353
4471
  } catch {}
@@ -4361,9 +4479,8 @@ var AcpClient = class {
4361
4479
  const onAcpMessage = () => this.eventHandlers.onAcpMessage;
4362
4480
  const onAcpOutputMessage = () => this.eventHandlers.onAcpOutputMessage;
4363
4481
  const elicitationRequestIds = /* @__PURE__ */ new Set();
4364
- const bindPromptOwner = (owner) => {
4365
- this.bindPromptOwner(owner);
4366
- };
4482
+ const bindPromptOwner = (owner) => this.bindPromptOwner(owner);
4483
+ const onPromptRequestWritten = (active, owner) => this.onPromptRequestWritten(active, owner);
4367
4484
  const shouldSuppressInboundReplaySessionUpdate = (message) => {
4368
4485
  return this.suppressReplaySessionUpdateMessages && isSessionUpdateNotification(message);
4369
4486
  };
@@ -4397,7 +4514,7 @@ var AcpClient = class {
4397
4514
  } }),
4398
4515
  writable: new WritableStream({ async write(message) {
4399
4516
  const promptOwner = promptRequestOwner(message);
4400
- if (promptOwner) bindPromptOwner(promptOwner);
4517
+ const activePrompt = promptOwner ? bindPromptOwner(promptOwner) : void 0;
4401
4518
  const id = responseId(message);
4402
4519
  if (!(id !== void 0 && elicitationRequestIds.delete(id))) {
4403
4520
  onAcpOutputMessage()?.("outbound", message);
@@ -4409,6 +4526,7 @@ var AcpClient = class {
4409
4526
  } finally {
4410
4527
  writer.releaseLock();
4411
4528
  }
4529
+ if (activePrompt && promptOwner) onPromptRequestWritten(activePrompt, promptOwner);
4412
4530
  } })
4413
4531
  };
4414
4532
  }
@@ -4498,17 +4616,17 @@ var AcpClient = class {
4498
4616
  this.suppressSessionUpdates = previous.suppressSessionUpdates;
4499
4617
  this.suppressReplaySessionUpdateMessages = previous.suppressReplaySessionUpdateMessages;
4500
4618
  }
4501
- async prompt(sessionId, prompt, onRequestStarted, onElicitation) {
4619
+ async prompt(sessionId, prompt, onRequestWritten, onElicitation) {
4502
4620
  const connection = this.getConnection();
4503
4621
  const normalizedPrompt = this.normalizePromptForAgent(prompt);
4504
4622
  const restoreConsoleError = this.options.suppressSdkConsoleErrors ? installSdkConsoleErrorSuppression() : void 0;
4505
- const activePrompt = this.beginActivePrompt(sessionId, onElicitation);
4623
+ const activePrompt = this.beginActivePrompt(sessionId, onRequestWritten, onElicitation);
4506
4624
  let promptPromise;
4507
4625
  try {
4508
4626
  promptPromise = this.runConnectionRequest(() => connection.prompt({
4509
4627
  sessionId,
4510
4628
  prompt: normalizedPrompt
4511
- }), onRequestStarted, () => !connection.signal?.aborted);
4629
+ }));
4512
4630
  } catch (error) {
4513
4631
  this.clearActivePrompt(activePrompt);
4514
4632
  restoreConsoleError?.();
@@ -4528,11 +4646,12 @@ var AcpClient = class {
4528
4646
  this.promptPermissionFailures.delete(sessionId);
4529
4647
  }
4530
4648
  }
4531
- beginActivePrompt(sessionId, elicitationHandler) {
4649
+ beginActivePrompt(sessionId, onRequestWritten, elicitationHandler) {
4532
4650
  const previous = this.activePrompt;
4533
4651
  this.cancellingSessionIds.delete(sessionId);
4534
4652
  const active = {
4535
4653
  sessionId,
4654
+ onRequestWritten,
4536
4655
  elicitationHandler,
4537
4656
  elicitationController: new AbortController()
4538
4657
  };
@@ -4546,6 +4665,13 @@ var AcpClient = class {
4546
4665
  return candidate.requestId === void 0 && candidate.sessionId === owner.sessionId;
4547
4666
  });
4548
4667
  if (active) active.requestId = owner.requestId;
4668
+ return active;
4669
+ }
4670
+ onPromptRequestWritten(active, owner) {
4671
+ if (active.requestId !== owner.requestId || active.sessionId !== owner.sessionId) return;
4672
+ try {
4673
+ Promise.resolve(active.onRequestWritten?.()).catch(() => {});
4674
+ } catch {}
4549
4675
  }
4550
4676
  clearActivePrompt(active) {
4551
4677
  if (this.activePrompt === active) this.activePrompt = void 0;
@@ -4804,6 +4930,7 @@ var AcpClient = class {
4804
4930
  }
4805
4931
  createStartupFailureWatcher(child, startupStderr) {
4806
4932
  let settled = false;
4933
+ let failure;
4807
4934
  let rejectPromise;
4808
4935
  const cleanup = () => {
4809
4936
  child.off("error", onError);
@@ -4814,7 +4941,10 @@ var AcpClient = class {
4814
4941
  if (settled) return;
4815
4942
  settled = true;
4816
4943
  cleanup();
4817
- if (error) rejectPromise(error);
4944
+ if (error) {
4945
+ failure = error;
4946
+ rejectPromise(error);
4947
+ }
4818
4948
  };
4819
4949
  const createError = (params) => new AgentStartupError({
4820
4950
  agentCommand: this.options.agentCommand,
@@ -4838,14 +4968,18 @@ var AcpClient = class {
4838
4968
  signal
4839
4969
  }));
4840
4970
  };
4971
+ const promise = new Promise((_resolve, reject) => {
4972
+ rejectPromise = reject;
4973
+ child.once("error", onError);
4974
+ child.once("exit", onExit);
4975
+ child.once("close", onClose);
4976
+ if (child.exitCode !== null || child.signalCode !== null) onExit(child.exitCode, child.signalCode);
4977
+ });
4978
+ promise.catch(() => {});
4841
4979
  return {
4842
- promise: new Promise((_resolve, reject) => {
4843
- rejectPromise = reject;
4844
- child.once("error", onError);
4845
- child.once("exit", onExit);
4846
- child.once("close", onClose);
4847
- }),
4848
- dispose: () => finish()
4980
+ promise,
4981
+ dispose: () => finish(),
4982
+ getError: () => failure
4849
4983
  };
4850
4984
  }
4851
4985
  async normalizeInitializeError(error, child, startupStderr) {
@@ -5025,10 +5159,19 @@ var AcpClient = class {
5025
5159
  recorded: true
5026
5160
  };
5027
5161
  }
5028
- attachAgentLifecycleObservers(child) {
5029
- child.once("exit", (exitCode, signal) => {
5162
+ attachAgentLifecycleObservers(child, startedProcess, exitNotificationBarrier) {
5163
+ const onExit = (exitCode, signal) => {
5164
+ const exitedAt = isoNow$1();
5030
5165
  this.recordAgentExit("process_exit", exitCode, signal);
5031
- });
5166
+ exitNotificationBarrier.then(() => {
5167
+ this.notifyProcessExit(startedProcess, exitCode, signal, exitedAt);
5168
+ });
5169
+ };
5170
+ child.once("exit", onExit);
5171
+ if (child.exitCode !== null || child.signalCode !== null) {
5172
+ child.off("exit", onExit);
5173
+ onExit(child.exitCode, child.signalCode);
5174
+ }
5032
5175
  child.once("close", (exitCode, signal) => {
5033
5176
  this.recordAgentExit("process_close", exitCode, signal);
5034
5177
  });
@@ -5036,6 +5179,43 @@ var AcpClient = class {
5036
5179
  this.recordAgentExit("pipe_close", child.exitCode ?? null, child.signalCode ?? null);
5037
5180
  });
5038
5181
  }
5182
+ notifyProcessSpawnFailure(launch, error) {
5183
+ const handler = this.options.processLifecycle?.onSpawnFailed;
5184
+ if (!handler) return;
5185
+ const event = Object.freeze({
5186
+ ...launch,
5187
+ error,
5188
+ failedAt: isoNow$1()
5189
+ });
5190
+ try {
5191
+ Promise.resolve(handler(event)).catch((observerError) => {
5192
+ this.logProcessLifecycleError("onSpawnFailed", observerError);
5193
+ });
5194
+ } catch (observerError) {
5195
+ this.logProcessLifecycleError("onSpawnFailed", observerError);
5196
+ }
5197
+ }
5198
+ notifyProcessExit(startedProcess, exitCode, signal, exitedAt) {
5199
+ const handler = this.options.processLifecycle?.onExit;
5200
+ if (!handler) return;
5201
+ const event = Object.freeze({
5202
+ ...startedProcess,
5203
+ exitCode,
5204
+ signal,
5205
+ exitedAt
5206
+ });
5207
+ try {
5208
+ Promise.resolve(handler(event)).catch((error) => {
5209
+ this.logProcessLifecycleError("onExit", error);
5210
+ });
5211
+ } catch (error) {
5212
+ this.logProcessLifecycleError("onExit", error);
5213
+ }
5214
+ }
5215
+ logProcessLifecycleError(hook, error) {
5216
+ const message = error instanceof Error ? error.message : String(error);
5217
+ this.log(`process lifecycle ${hook} hook failed: ${message}`);
5218
+ }
5039
5219
  recordAgentExit(reason, exitCode, signal) {
5040
5220
  if (this.lastAgentExit) return;
5041
5221
  this.lastAgentExit = {
@@ -5055,7 +5235,7 @@ var AcpClient = class {
5055
5235
  if (error) this.promptPermissionFailures.delete(sessionId);
5056
5236
  return error;
5057
5237
  }
5058
- async runConnectionRequest(run, onRequestStarted, canStartRequest = () => true) {
5238
+ async runConnectionRequest(run) {
5059
5239
  return await new Promise((resolve, reject) => {
5060
5240
  const pending = {
5061
5241
  settled: false,
@@ -5070,14 +5250,9 @@ var AcpClient = class {
5070
5250
  this.pendingConnectionRequests.add(pending);
5071
5251
  Promise.resolve().then(async () => {
5072
5252
  if (pending.settled) return { started: false };
5073
- const requestCanStart = canStartRequest();
5074
- const request = run();
5075
- if (requestCanStart) try {
5076
- Promise.resolve(onRequestStarted?.()).catch(() => {});
5077
- } catch {}
5078
5253
  return {
5079
5254
  started: true,
5080
- value: await request
5255
+ value: await run()
5081
5256
  };
5082
5257
  }).then((outcome) => {
5083
5258
  if (outcome.started) finish(() => resolve(outcome.value));
@@ -6469,9 +6644,24 @@ async function withConnectedSession(options) {
6469
6644
  //#region src/runtime/engine/prompt-turn.ts
6470
6645
  const SESSION_REPLY_IDLE_MS = 1e3;
6471
6646
  const SESSION_REPLY_DRAIN_TIMEOUT_MS = 5e3;
6647
+ function responseMetaField(meta) {
6648
+ return meta === void 0 ? {} : { _meta: meta };
6649
+ }
6650
+ function recoveredSessionResult(response, conversation, promptMessageId) {
6651
+ recordPromptResponseUsage(conversation, response?.usage, promptMessageId);
6652
+ return {
6653
+ stopReason: "end_turn",
6654
+ source: "session",
6655
+ ...responseMetaField(response?._meta)
6656
+ };
6657
+ }
6472
6658
  async function runPromptTurn(params) {
6659
+ let settledResponse;
6473
6660
  try {
6474
- const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestStarted, params.onElicitation);
6661
+ const promptPromise = params.client.prompt(params.sessionId, params.prompt, params.onPromptRequestWritten, params.onElicitation);
6662
+ promptPromise.then((response) => {
6663
+ settledResponse = response;
6664
+ }, () => {});
6475
6665
  await params.onPromptStarted?.();
6476
6666
  const response = await withTimeout(promptPromise, params.timeoutMs);
6477
6667
  await params.client.waitForSessionUpdatesIdle?.({
@@ -6481,7 +6671,8 @@ async function runPromptTurn(params) {
6481
6671
  recordPromptResponseUsage(params.conversation, response.usage, params.promptMessageId);
6482
6672
  return {
6483
6673
  stopReason: response.stopReason,
6484
- source: "rpc"
6674
+ source: "rpc",
6675
+ ...responseMetaField(response._meta)
6485
6676
  };
6486
6677
  } catch (error) {
6487
6678
  if (!(error instanceof TimeoutError) || !params.promptMessageId) throw error;
@@ -6489,10 +6680,7 @@ async function runPromptTurn(params) {
6489
6680
  idleMs: SESSION_REPLY_IDLE_MS,
6490
6681
  timeoutMs: SESSION_REPLY_DRAIN_TIMEOUT_MS
6491
6682
  }).catch(() => {});
6492
- if (hasAgentReplyAfterPrompt(params.conversation, params.promptMessageId)) return {
6493
- stopReason: "end_turn",
6494
- source: "session"
6495
- };
6683
+ if (hasAgentReplyAfterPrompt(params.conversation, params.promptMessageId)) return recoveredSessionResult(settledResponse, params.conversation, params.promptMessageId);
6496
6684
  throw error;
6497
6685
  }
6498
6686
  }
@@ -6552,4 +6740,4 @@ var LiveSessionCheckpoint = class {
6552
6740
  //#endregion
6553
6741
  export { formatPerfMetric as $, AgentSpawnError as $t, RequestedModelUnsupportedError as A, DEFAULT_AGENT_NAME as At, absolutePath as B, extractAcpError as Bt, sessionOptionsFromRecord as C, parsePromptSource as Ct, AcpClient as D, TimeoutError as Dt, reconcileAgentSessionId as E, InterruptedError as Et, runTimedExecFile as F, resolveCanonicalAgentName as Ft, listSessions as G, NON_INTERACTIVE_PERMISSION_POLICIES as Gt, findSession as H, toAcpErrorPayload as Ht, splitCommandLine as I, exitCodeForOutputErrorCode as It, pruneSessions as J, OUTPUT_FORMATS as Jt, listSessionsForAgent as K, OUTPUT_ERROR_CODES as Kt, getAcpxVersion as L, formatErrorMessage as Lt, modelStateFromConfigOptions as M, normalizeAgentName$1 as Mt, normalizeAgentCommandInput as N, resolveAgentArgv as Nt, REQUESTED_MODEL_UNSUPPORTED_ERROR_CODE as O, withInterrupt as Ot, renderArgvIdentity as P, resolveAgentCommand as Pt, assertPersistedKeyPolicy as Q, AcpxOperationalError as Qt, permissionModeSatisfies as R, isRetryablePromptError as Rt, persistSessionOptions as S, mergePromptSourceWithText as St, applyLifecycleSnapshotToRecord as T, textPrompt as Tt, findSessionByDirectoryWalk as U, AUTH_POLICIES as Ut, findGitRepositoryRoot as V, isAcpResourceNotFoundError as Vt, isoNow$2 as W, EXIT_CODES as Wt, writeSessionRecord as X, PERMISSION_POLICY_ACTIONS as Xt, resolveSessionRecord as Y, PERMISSION_MODES as Yt, createAtomicWriteTempPath as Z, SESSION_RECORD_SCHEMA as Zt, recordPromptSubmission as _, isAcpJsonRpcMessage as _t, applyConfigOptionSelection as a, setPerfGauge as at, advertisedModelState as b, PromptInputValidationError as bt, applyRequestedModelIfAdvertised as c, serializeSessionRecordForDisk as ct, setDesiredModeId as d, defaultSessionEventLog as dt, QueueConnectionError as en, getPerfMetricsSnapshot as et, syncAdvertisedModelState as f, sessionBaseDir$1 as ft, recordClientOperation as g, extractSessionUpdateNotification as gt, createSessionConversation as h, sessionEventSegmentPath as ht, connectAndLoadSession as i, resetPerfMetrics as it, isRequestedModelUnsupportedError as j, listBuiltInAgents as jt, REQUESTED_MODEL_UNSUPPORTED_REASONS as k, withTimeout as kt, currentModelIdFromSetModelResponse as l, normalizeRuntimeSessionId as lt, cloneSessionConversation as m, sessionEventLockPath as mt, runPromptTurn as n, measurePerf as nt, applyConfigOptionsToRecord as o, startPerfTimer as ot, cloneSessionAcpxState as p, sessionEventActivePath as pt, normalizeName as q, OUTPUT_ERROR_ORIGINS as qt, withConnectedSession as r, recordPerfDuration as rt, applyModelSelection as s, parseSessionRecord as st, LiveSessionCheckpoint as t, QueueProtocolError as tn, incrementPerfCounter as tt, setCurrentModelId as u, DEFAULT_EVENT_SEGMENT_MAX_BYTES as ut, recordSessionUpdate as v, parseJsonRpcErrorMessage as vt, applyConversation as w, promptToDisplayText as wt, mergeSessionOptions as x, isPromptInput as xt, trimConversationForRuntime as y, parsePromptStopReason as yt, DEFAULT_HISTORY_LIMIT as z, normalizeOutputError as zt };
6554
6742
 
6555
- //# sourceMappingURL=live-checkpoint-Gw2oGjhe.js.map
6743
+ //# sourceMappingURL=live-checkpoint-CYRs-D9t.js.map