@aiden-ade/sandbox-agent 0.1.41 → 0.1.42

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 (2) hide show
  1. package/dist/index.cjs +165 -115
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -13514,6 +13514,16 @@ function resolveBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment
13514
13514
  cacheResolvedCli(command, resolved);
13515
13515
  return resolved ?? void 0;
13516
13516
  }
13517
+ function revalidateBackendRuntimeCommand(backendKind, env = getDaemonCliEnvironment()) {
13518
+ if (!backendKind) return void 0;
13519
+ const command = BACKEND_CLI_COMMANDS[backendKind];
13520
+ if (!command) return void 0;
13521
+ resolvedCliCache.delete(command);
13522
+ const spec = Object.values(PROVIDER_CLI_COMMANDS).find((entry) => entry.command === command);
13523
+ const envOverride = spec ? spec.envVars.map((name) => env[name]?.trim()).find(Boolean) : void 0;
13524
+ if (envOverride) resolvedCliCache.delete(envOverride);
13525
+ return resolveBackendRuntimeCommand(backendKind, env);
13526
+ }
13517
13527
 
13518
13528
  // src/computer-readiness.ts
13519
13529
  var import_node_fs5 = require("fs");
@@ -15282,11 +15292,12 @@ var import_os3 = require("os");
15282
15292
  var import_path3 = require("path");
15283
15293
  var import_readline = require("readline");
15284
15294
  var import_child_process2 = require("child_process");
15295
+ var import_child_process3 = require("child_process");
15285
15296
  var import_crypto2 = require("crypto");
15286
15297
  var import_fs4 = require("fs");
15287
15298
  var import_os4 = require("os");
15288
15299
  var import_path4 = require("path");
15289
- var import_child_process3 = require("child_process");
15300
+ var import_child_process4 = require("child_process");
15290
15301
  var import_util10 = require("util");
15291
15302
  var import_fs5 = require("fs");
15292
15303
  var import_os5 = require("os");
@@ -15876,6 +15887,7 @@ function specForErrorKind(errorKind) {
15876
15887
  const spec = ERROR_SPECS[errorKind];
15877
15888
  return { message: spec.message, errorKind, recoveryClass: spec.recoveryClass };
15878
15889
  }
15890
+ var RESUME_SESSION_UNAVAILABLE_MESSAGE = ERROR_SPECS.resume_failed.message;
15879
15891
  function isLikelyProviderAuthError(stderr) {
15880
15892
  const lower = stderr.toLowerCase();
15881
15893
  return lower.includes("api key") || lower.includes("api_key") || lower.includes("invalid api key") || lower.includes("api key invalid") || lower.includes("unauthorized") || lower.includes("authentication failed") || lower.includes("authentication error") || lower.includes("provider settings") || lower.includes("invalid token") || lower.includes("token expired");
@@ -16031,6 +16043,54 @@ function spawnCli(command, args, context) {
16031
16043
  detached: process.platform !== "win32"
16032
16044
  });
16033
16045
  }
16046
+ function isAlreadyDead(error) {
16047
+ return error?.code === "ESRCH";
16048
+ }
16049
+ function killProcessTree(pid, options = {}) {
16050
+ const { signal = "SIGTERM", child, onError } = options;
16051
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
16052
+ if (child) {
16053
+ try {
16054
+ child.kill(signal);
16055
+ } catch (err) {
16056
+ if (!isAlreadyDead(err)) onError?.("fallback", err);
16057
+ }
16058
+ }
16059
+ return;
16060
+ }
16061
+ if (process.platform === "win32") {
16062
+ try {
16063
+ const killer = (0, import_child_process3.spawn)("taskkill", ["/pid", String(pid), "/t", "/f"], {
16064
+ stdio: "ignore",
16065
+ windowsHide: true
16066
+ });
16067
+ killer.once("error", (err) => {
16068
+ if (child) {
16069
+ try {
16070
+ child.kill();
16071
+ } catch {
16072
+ }
16073
+ }
16074
+ onError?.("taskkill", err);
16075
+ });
16076
+ } catch (err) {
16077
+ onError?.("taskkill", err);
16078
+ }
16079
+ return;
16080
+ }
16081
+ try {
16082
+ process.kill(-pid, signal);
16083
+ } catch (groupErr) {
16084
+ if (!isAlreadyDead(groupErr)) onError?.("group", groupErr);
16085
+ if (child) {
16086
+ try {
16087
+ child.kill(signal);
16088
+ } catch (fallbackErr) {
16089
+ if (!isAlreadyDead(fallbackErr)) onError?.("fallback", fallbackErr);
16090
+ }
16091
+ }
16092
+ }
16093
+ }
16034
16094
  function emitSessionNotice(presenter, kind, message) {
16035
16095
  if (presenter.onNotice) {
16036
16096
  void presenter.onNotice(kind, message);
@@ -16095,6 +16155,12 @@ function formatCliSpawnError(command, error) {
16095
16155
  }
16096
16156
  return error;
16097
16157
  }
16158
+ function waitForSpawnOutcome(child) {
16159
+ return new Promise((resolve22) => {
16160
+ child.once("spawn", () => resolve22(null));
16161
+ child.once("error", (error) => resolve22(error));
16162
+ });
16163
+ }
16098
16164
  function createGenericCliBackend(options) {
16099
16165
  return {
16100
16166
  kind: options.kind,
@@ -16108,7 +16174,23 @@ function createGenericCliBackend(options) {
16108
16174
  };
16109
16175
  const prompt = options.augmentPrompt?.(context) ?? (context.config.mode === "plan" ? buildPlanModePrefix(buildPromptWithSystem(context.config, context.promptText)) : buildPromptWithSystem(context.config, context.promptText));
16110
16176
  const args = options.buildArgs?.(context, prompt) ?? options.args.map((arg) => arg === "{{prompt}}" ? prompt : arg);
16111
- const child = spawnCli(options.command, args, context);
16177
+ let command = options.command;
16178
+ let child = spawnCli(command, args, context);
16179
+ if (context.revalidateCommand) {
16180
+ const spawnError = await waitForSpawnOutcome(child);
16181
+ if (spawnError) {
16182
+ const fresh = spawnError.code === "ENOENT" ? context.revalidateCommand()?.trim() || null : null;
16183
+ if (fresh && fresh !== command) {
16184
+ console.warn(
16185
+ `[${options.kind}] Spawn failed ENOENT for "${command}" \u2014 re-resolved to "${fresh}", retrying once`
16186
+ );
16187
+ command = fresh;
16188
+ child = spawnCli(command, args, context);
16189
+ } else {
16190
+ throw formatCliSpawnError(command, spawnError);
16191
+ }
16192
+ }
16193
+ }
16112
16194
  state.process = child;
16113
16195
  state.lastRawOutputAtMs = Date.now();
16114
16196
  context.onProcessSpawned?.(child);
@@ -16119,8 +16201,8 @@ function createGenericCliBackend(options) {
16119
16201
  const safeArgs = args.map(
16120
16202
  (a, i) => i > 0 && args[i - 1] === "--system-prompt" ? `"<system-prompt ${a.length} chars>"` : a
16121
16203
  );
16122
- console.info(`[${options.kind}] Spawning: ${options.command} ${safeArgs.join(" ")}`);
16123
- presenter.recordRawTranscript?.("system", `${options.command} ${args.join(" ")}`, {
16204
+ console.info(`[${options.kind}] Spawning: ${command} ${safeArgs.join(" ")}`);
16205
+ presenter.recordRawTranscript?.("system", `${command} ${args.join(" ")}`, {
16124
16206
  backendKind: options.kind
16125
16207
  });
16126
16208
  child.stdin.on("error", (err) => {
@@ -16152,14 +16234,7 @@ function createGenericCliBackend(options) {
16152
16234
  console.warn(
16153
16235
  `[${options.kind}] No stdout within ${options.startupTimeoutMs}ms of spawn \u2014 killing hung process`
16154
16236
  );
16155
- try {
16156
- if (process.platform === "win32") {
16157
- child.kill("SIGKILL");
16158
- } else if (child.pid) {
16159
- process.kill(-child.pid, "SIGKILL");
16160
- }
16161
- } catch {
16162
- }
16237
+ killProcessTree(child.pid, { signal: "SIGKILL", child });
16163
16238
  }, options.startupTimeoutMs);
16164
16239
  }
16165
16240
  stdoutRl.on("line", (line) => {
@@ -16202,16 +16277,7 @@ function createGenericCliBackend(options) {
16202
16277
  });
16203
16278
  child.on("exit", (code) => resolve22(code ?? 0));
16204
16279
  const onAbort = () => {
16205
- const pid = child.pid;
16206
- if (!pid) return;
16207
- try {
16208
- process.kill(-pid, "SIGTERM");
16209
- } catch {
16210
- try {
16211
- child.kill("SIGTERM");
16212
- } catch {
16213
- }
16214
- }
16280
+ killProcessTree(child.pid, { signal: "SIGTERM", child });
16215
16281
  };
16216
16282
  context.abortController.signal.addEventListener("abort", onAbort, { once: true });
16217
16283
  });
@@ -16247,14 +16313,7 @@ function createGenericCliBackend(options) {
16247
16313
  console.warn(
16248
16314
  `[${options.kind}] Idle timeout (${IDLE_MS}ms) waiting for ${idleTimeoutReason === "background_task" ? "a background task terminal event" : "a pending user answer"} \u2014 force-killing orphaned process`
16249
16315
  );
16250
- try {
16251
- if (process.platform === "win32") {
16252
- child.kill("SIGKILL");
16253
- } else if (child.pid) {
16254
- process.kill(-child.pid, "SIGKILL");
16255
- }
16256
- } catch {
16257
- }
16316
+ killProcessTree(child.pid, { signal: "SIGKILL", child });
16258
16317
  exitCode = await Promise.race([
16259
16318
  exitPromise,
16260
16319
  new Promise((resolve22) => setTimeout(() => resolve22(1), 5e3))
@@ -16270,14 +16329,7 @@ function createGenericCliBackend(options) {
16270
16329
  console.warn(
16271
16330
  `[${options.kind}] Process still alive ${GRACE_MS}ms after result event, killing`
16272
16331
  );
16273
- try {
16274
- if (process.platform === "win32") {
16275
- child.kill("SIGKILL");
16276
- } else if (child.pid) {
16277
- process.kill(-child.pid, "SIGKILL");
16278
- }
16279
- } catch {
16280
- }
16332
+ killProcessTree(child.pid, { signal: "SIGKILL", child });
16281
16333
  }
16282
16334
  } else {
16283
16335
  exitCode = raceResult;
@@ -16440,6 +16492,9 @@ function createGenericCliBackend(options) {
16440
16492
  classifiedErrorKind = detailed.errorKind;
16441
16493
  classifiedRecoveryClass = detailed.recoveryClass;
16442
16494
  }
16495
+ if (classifiedErrorKind === "resume_failed" && !requestedResumeId) {
16496
+ classifiedErrorKind = void 0;
16497
+ }
16443
16498
  }
16444
16499
  return {
16445
16500
  success: !failed,
@@ -16448,7 +16503,11 @@ function createGenericCliBackend(options) {
16448
16503
  planFilesCreated: [],
16449
16504
  iterations: Math.max(state.iterations, 1),
16450
16505
  error: classifiedError,
16451
- ...classifiedErrorKind ? { errorKind: classifiedErrorKind } : {},
16506
+ // Surface every recognized, machine-actionable kind (model_mismatch,
16507
+ // resume_failed, auth_*, …). The catch-all unknown_cli_error carries no routing
16508
+ // signal — nothing downstream keys on it — so leave errorKind absent for it,
16509
+ // keeping an unrelated failure untagged even when a resume id was set.
16510
+ ...classifiedErrorKind && classifiedErrorKind !== "unknown_cli_error" ? { errorKind: classifiedErrorKind } : {},
16452
16511
  ...classifiedRecoveryClass ? { recoveryClass: classifiedRecoveryClass } : {},
16453
16512
  providerSessionId: state.runtimeSessionId,
16454
16513
  runtimeSessionId: state.runtimeSessionId,
@@ -16906,7 +16965,7 @@ function claudeSessionLogExists(input) {
16906
16965
  if (!(0, import_fs4.existsSync)(projectDir)) return false;
16907
16966
  return (0, import_fs4.existsSync)((0, import_path4.join)(projectDir, `${input.sessionId}.jsonl`));
16908
16967
  }
16909
- var execFileAsync = (0, import_util10.promisify)(import_child_process3.execFile);
16968
+ var execFileAsync = (0, import_util10.promisify)(import_child_process4.execFile);
16910
16969
  var flagSupportCache = /* @__PURE__ */ new Map();
16911
16970
  function helpAdvertisesFlag(helpText, flag) {
16912
16971
  return helpText.includes(flag);
@@ -17641,18 +17700,7 @@ function armCodexExitGraceKill(state) {
17641
17700
  console.warn(
17642
17701
  "[codex_app_server] Process did not exit after turn completion; killing process group"
17643
17702
  );
17644
- const pid = child.pid;
17645
- if (pid) {
17646
- try {
17647
- process.kill(-pid, "SIGTERM");
17648
- return;
17649
- } catch {
17650
- }
17651
- }
17652
- try {
17653
- child.kill("SIGTERM");
17654
- } catch {
17655
- }
17703
+ killProcessTree(child.pid, { signal: "SIGTERM", child });
17656
17704
  }, CODEX_EXIT_GRACE_MS);
17657
17705
  timer.unref?.();
17658
17706
  state.exitGraceKillTimer = timer;
@@ -19231,17 +19279,7 @@ function handleOpencodeStructuredEvent(parsed, context, state) {
19231
19279
  void presenter.onError(message);
19232
19280
  state.error = message;
19233
19281
  }
19234
- const pid = state.process?.pid;
19235
- if (pid) {
19236
- try {
19237
- process.kill(-pid, "SIGTERM");
19238
- } catch {
19239
- try {
19240
- state.process?.kill("SIGTERM");
19241
- } catch {
19242
- }
19243
- }
19244
- }
19282
+ killProcessTree(state.process?.pid, { signal: "SIGTERM", child: state.process });
19245
19283
  return true;
19246
19284
  }
19247
19285
  default:
@@ -19791,6 +19829,8 @@ Prefer relative paths and keep your work scoped to this project.`
19791
19829
  runtimeCommand || "generic-cli",
19792
19830
  runtimeArgs
19793
19831
  );
19832
+ const revalidateRuntimeCommand = this.revalidateRuntimeCommand;
19833
+ const revalidateCommand = revalidateRuntimeCommand ? () => revalidateRuntimeCommand.call(this) : void 0;
19794
19834
  let lastResult = null;
19795
19835
  for (let attempt = 0; attempt <= _BaseMachineAgent.MAX_RETRIES; attempt++) {
19796
19836
  if (this.abortController?.signal.aborted) break;
@@ -19801,7 +19841,8 @@ Prefer relative paths and keep your work scoped to this project.`
19801
19841
  cwd,
19802
19842
  env,
19803
19843
  promptText,
19804
- onProcessSpawned: this.getOnProcessSpawned()
19844
+ onProcessSpawned: this.getOnProcessSpawned(),
19845
+ revalidateCommand
19805
19846
  });
19806
19847
  lastResult = {
19807
19848
  ...result,
@@ -19851,7 +19892,8 @@ ${runtimeConfig.task}` } : {}
19851
19892
  cwd,
19852
19893
  env,
19853
19894
  promptText: fallbackContext ? this.buildPromptText(runtimeConfig) : promptText,
19854
- onProcessSpawned: this.getOnProcessSpawned()
19895
+ onProcessSpawned: this.getOnProcessSpawned(),
19896
+ revalidateCommand
19855
19897
  });
19856
19898
  lastResult = {
19857
19899
  ...retryResult,
@@ -20271,38 +20313,32 @@ var CoreAgent = class _CoreAgent extends BaseMachineAgent {
20271
20313
  const pid = child.pid;
20272
20314
  if (!pid) return false;
20273
20315
  console.info("[CoreAgent] Killing agent process", { pid });
20274
- try {
20275
- if (process.platform === "win32") {
20276
- child.kill("SIGTERM");
20277
- } else {
20278
- process.kill(-pid, "SIGTERM");
20279
- }
20280
- } catch (err) {
20281
- if (err.code !== "ESRCH") {
20282
- console.warn("[CoreAgent] SIGTERM failed", { pid, err });
20283
- }
20284
- try {
20285
- child.kill("SIGTERM");
20286
- } catch {
20287
- }
20288
- }
20316
+ killProcessTree(pid, {
20317
+ signal: "SIGTERM",
20318
+ child,
20319
+ onError: (stage, err) => console.warn(`[CoreAgent] SIGTERM failed (${stage})`, { pid, err })
20320
+ });
20289
20321
  setTimeout(() => {
20290
20322
  if (child.killed || child.exitCode !== null) return;
20291
20323
  console.warn("[CoreAgent] Escalating to SIGKILL", { pid });
20292
- try {
20293
- if (process.platform === "win32") {
20294
- child.kill("SIGKILL");
20295
- } else {
20296
- process.kill(-pid, "SIGKILL");
20297
- }
20298
- } catch (err) {
20299
- if (err.code !== "ESRCH") {
20300
- console.warn("[CoreAgent] SIGKILL failed", { pid, err });
20301
- }
20302
- }
20324
+ killProcessTree(pid, {
20325
+ signal: "SIGKILL",
20326
+ child,
20327
+ onError: (stage, err) => console.warn(`[CoreAgent] SIGKILL failed (${stage})`, { pid, err })
20328
+ });
20303
20329
  }, _CoreAgent.SIGKILL_DELAY_MS);
20304
20330
  return true;
20305
20331
  }
20332
+ /**
20333
+ * Re-resolve this agent's CLI executable from scratch when a spawn fails
20334
+ * ENOENT. The daemon caches resolved CLI paths, so a provider binary that was
20335
+ * moved or reinstalled mid-session would otherwise fail every turn with
20336
+ * "CLI command not found" until the daemon restarts. Invalidating the cache
20337
+ * and re-resolving once lets the current run recover against the new location.
20338
+ */
20339
+ revalidateRuntimeCommand() {
20340
+ return revalidateBackendRuntimeCommand(this.runtime.backendKind) ?? null;
20341
+ }
20306
20342
  // ── Interactive tool response (WS relay → stdin) ───────────────────────
20307
20343
  /**
20308
20344
  * Send a tool_result to the CLI's stdin (stream-json format).
@@ -20655,6 +20691,8 @@ var EncryptedEventOutbox = class {
20655
20691
  inFlightEventId = null;
20656
20692
  ackTimer = null;
20657
20693
  retryTimer = null;
20694
+ /** Latch so a persistent disk failure logs once per streak, not per event. */
20695
+ persistFailureLogged = false;
20658
20696
  key;
20659
20697
  maxEncryptedBytes;
20660
20698
  maxIntermediateEventsPerRun;
@@ -20858,30 +20896,42 @@ var EncryptedEventOutbox = class {
20858
20896
  }
20859
20897
  }
20860
20898
  persist() {
20861
- if (this.entries.length === 0) {
20862
- (0, import_node_fs6.rmSync)(this.path, { force: true });
20863
- return;
20864
- }
20865
- (0, import_node_fs6.mkdirSync)((0, import_node_path5.dirname)(this.path), { recursive: true, mode: 448 });
20866
- const iv = (0, import_node_crypto.randomBytes)(12);
20867
- const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
20868
- const ciphertext = Buffer.concat([
20869
- cipher.update(JSON.stringify({ entries: this.entries }), "utf8"),
20870
- cipher.final()
20871
- ]);
20872
- const envelope = {
20873
- version: OUTBOX_VERSION,
20874
- iv: iv.toString("base64url"),
20875
- authTag: cipher.getAuthTag().toString("base64url"),
20876
- ciphertext: ciphertext.toString("base64url")
20877
- };
20878
- const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
20879
20899
  try {
20880
- (0, import_node_fs6.writeFileSync)(pendingPath, JSON.stringify(envelope), { mode: 384 });
20881
- (0, import_node_fs6.chmodSync)(pendingPath, 384);
20882
- (0, import_node_fs6.renameSync)(pendingPath, this.path);
20883
- } finally {
20884
- (0, import_node_fs6.rmSync)(pendingPath, { force: true });
20900
+ if (this.entries.length === 0) {
20901
+ (0, import_node_fs6.rmSync)(this.path, { force: true });
20902
+ this.persistFailureLogged = false;
20903
+ return;
20904
+ }
20905
+ (0, import_node_fs6.mkdirSync)((0, import_node_path5.dirname)(this.path), { recursive: true, mode: 448 });
20906
+ const iv = (0, import_node_crypto.randomBytes)(12);
20907
+ const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", this.key, iv);
20908
+ const ciphertext = Buffer.concat([
20909
+ cipher.update(JSON.stringify({ entries: this.entries }), "utf8"),
20910
+ cipher.final()
20911
+ ]);
20912
+ const envelope = {
20913
+ version: OUTBOX_VERSION,
20914
+ iv: iv.toString("base64url"),
20915
+ authTag: cipher.getAuthTag().toString("base64url"),
20916
+ ciphertext: ciphertext.toString("base64url")
20917
+ };
20918
+ const pendingPath = `${this.path}.pending-${process.pid}-${(0, import_node_crypto.randomBytes)(6).toString("hex")}`;
20919
+ try {
20920
+ (0, import_node_fs6.writeFileSync)(pendingPath, JSON.stringify(envelope), { mode: 384 });
20921
+ (0, import_node_fs6.chmodSync)(pendingPath, 384);
20922
+ (0, import_node_fs6.renameSync)(pendingPath, this.path);
20923
+ } finally {
20924
+ (0, import_node_fs6.rmSync)(pendingPath, { force: true });
20925
+ }
20926
+ this.persistFailureLogged = false;
20927
+ } catch (error) {
20928
+ if (!this.persistFailureLogged) {
20929
+ this.persistFailureLogged = true;
20930
+ const detail = error instanceof Error ? error.message : String(error);
20931
+ this.pushLog(
20932
+ `event_outbox_persist_failed error=${detail} pending_entries=${this.entries.length}`
20933
+ );
20934
+ }
20885
20935
  }
20886
20936
  }
20887
20937
  };
@@ -22527,7 +22577,7 @@ var RunStartGate = class {
22527
22577
  };
22528
22578
 
22529
22579
  // src/version.ts
22530
- var AGENT_VERSION = "0.1.41";
22580
+ var AGENT_VERSION = "0.1.42";
22531
22581
 
22532
22582
  // src/workspace-relocation.ts
22533
22583
  var import_node_child_process3 = require("child_process");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiden-ade/sandbox-agent",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "type": "module",
5
5
  "description": "Alan agent runtime — cloud sandbox and local daemon (alan-agent CLI)",
6
6
  "bin": {