agentbox-sdk 0.1.319 → 0.1.323

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.
@@ -1,5 +1,5 @@
1
- import { o as AgentProviderName, h as AgentOptions, t as AgentRunConfig, s as AgentRun, r as AgentResult, a3 as RawAgentEvent, b as AgentAttachRequest, y as AttachedRun, aa as SetupLayout } from '../types-DG4J_zMT.js';
2
- export { a as AgentApprovalMode, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexProviderOptions, N as DataContent, O as EmbeddedSkillConfig, P as FilePart, Q as ImagePart, X as OpenCodeAgentOptions, Y as OpenCodePluginConfig, Z as OpenCodePluginEvent, _ as OpenCodePluginHookConfig, $ as OpenCodeProviderOptions, a0 as OpenRouterPlugin, a5 as RepoSkillConfig, ac as TextPart, ag as UserContent, ah as UserContentPart } from '../types-DG4J_zMT.js';
1
+ import { o as AgentProviderName, h as AgentOptions, t as AgentRunConfig, s as AgentRun, r as AgentResult, a3 as RawAgentEvent, b as AgentAttachRequest, y as AttachedRun, aa as SetupLayout } from '../types-t01PuLUJ.js';
2
+ export { a as AgentApprovalMode, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexProviderOptions, N as DataContent, O as EmbeddedSkillConfig, P as FilePart, Q as ImagePart, X as OpenCodeAgentOptions, Y as OpenCodePluginConfig, Z as OpenCodePluginEvent, _ as OpenCodePluginHookConfig, $ as OpenCodeProviderOptions, a0 as OpenRouterPlugin, a5 as RepoSkillConfig, ac as TextPart, ag as UserContent, ah as UserContentPart } from '../types-t01PuLUJ.js';
3
3
  import { S as Sandbox } from '../Sandbox-DcKAU-E3.js';
4
4
  export { AgentProvider } from '../enums.js';
5
5
  import 'e2b';
@@ -42,6 +42,23 @@ declare class Agent<P extends AgentProviderName = AgentProviderName> {
42
42
  * work.
43
43
  */
44
44
  setup(): Promise<void>;
45
+ /**
46
+ * Stop the long-lived provider CLI server booted by {@link Agent.setup}
47
+ * (claude-code relay daemon, codex app-server, opencode `serve`).
48
+ *
49
+ * agentbox NEVER kills a running server on its own — not on run
50
+ * completion, not on `abort()`, and not when {@link Agent.setup} detects
51
+ * a changed config/credential set. This method is the single, explicit,
52
+ * developer-driven teardown. Call it to free a server's resources, or to
53
+ * apply a changed config: after `killServer()` the next {@link setup}
54
+ * cold-starts a fresh server with the new configuration.
55
+ *
56
+ * Best-effort and idempotent: a no-op when no server is running, or when
57
+ * the provider has no shared server for the current mode (host-mode
58
+ * claude-code runs the SDK in-process; local codex spawns a fresh
59
+ * app-server per run, torn down with the run).
60
+ */
61
+ killServer(): Promise<void>;
45
62
  stream(runConfig: AgentRunConfig): AgentRun;
46
63
  run(runConfig: AgentRunConfig): Promise<AgentResult>;
47
64
  rawEvents(runConfig: AgentRunConfig): AsyncIterable<RawAgentEvent>;
@@ -2,7 +2,7 @@ import {
2
2
  Agent,
3
3
  agentboxRoot,
4
4
  getAgentLayout
5
- } from "../chunk-2IHQY3WC.js";
5
+ } from "../chunk-XONT46YC.js";
6
6
  import "../chunk-775FIGGL.js";
7
7
  import {
8
8
  AGENT_RESERVED_PORTS,
@@ -251,9 +251,33 @@ function resolveSandboxResources(resources) {
251
251
  }
252
252
 
253
253
  // src/sandboxes/providers/daytona.ts
254
+ var STARTABLE_STATES = /* @__PURE__ */ new Set(["stopped", "archived"]);
255
+ var TERMINAL_STATES = /* @__PURE__ */ new Set([
256
+ "error",
257
+ "build_failed",
258
+ "destroyed",
259
+ "destroying"
260
+ ]);
261
+ var ATTACH_SETTLE_TIMEOUT_MS = 12e4;
262
+ var ATTACH_POLL_INTERVAL_MS = 2e3;
263
+ function isStateChangeInProgressError(err) {
264
+ const e = err;
265
+ if (!e) return false;
266
+ return e.statusCode === 409 || e.name === "DaytonaConflictError" || /state change in progress/i.test(e.message ?? "");
267
+ }
254
268
  var DaytonaSandboxAdapter = class extends SandboxAdapter {
255
269
  client;
256
270
  sandbox;
271
+ /**
272
+ * Per-sandbox preview access token, captured from `getPreviewLink`.
273
+ * Daytona's preview proxy now requires this token to reach a sandbox's
274
+ * ports: unauthenticated requests get 307-redirected to an Auth0 login
275
+ * (which surfaces as a 307 on WebSocket upgrades and a 404 on plain GETs).
276
+ * The token is sandbox-level and stable across ports, so caching the most
277
+ * recent one is sufficient — every consumer calls `getPreviewLink` to build
278
+ * the URL right before reading `previewHeaders`. See {@link previewHeaders}.
279
+ */
280
+ previewToken;
257
281
  constructor(options) {
258
282
  super(options);
259
283
  this.client = new Daytona({
@@ -282,20 +306,54 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
282
306
  throw new Error(`Daytona sandbox ${id} not found`);
283
307
  }
284
308
  this.sandbox = existing;
285
- const state = existing.state ?? "unknown";
286
- const isWarm = state === "started";
287
- if (!isWarm) {
288
- await existing.start();
309
+ this.isWarmFlag = existing.state === "started";
310
+ await this.ensureStarted(existing);
311
+ }
312
+ /**
313
+ * Bring a sandbox to the `started` state, tolerating in-flight transitions.
314
+ *
315
+ * `start()` only works from a resting state (`stopped`/`archived`); calling
316
+ * it while the sandbox is creating/starting/restoring/snapshotting/etc. — or
317
+ * racing another caller that's already starting it — makes Daytona 409 with
318
+ * "Sandbox state change in progress". So we poll: start from a resting
319
+ * state, wait out a transition, fail fast on a terminal state, and treat a
320
+ * 409 as "someone else is mid-transition" and keep waiting.
321
+ */
322
+ async ensureStarted(sandbox) {
323
+ let current = sandbox;
324
+ const deadline = Date.now() + ATTACH_SETTLE_TIMEOUT_MS;
325
+ for (; ; ) {
326
+ const state = current.state ?? "unknown";
327
+ if (state === "started") return;
328
+ if (TERMINAL_STATES.has(state)) {
329
+ throw new Error(
330
+ `Daytona sandbox ${current.id} is in a terminal state: ${state}`
331
+ );
332
+ }
333
+ if (STARTABLE_STATES.has(state)) {
334
+ try {
335
+ await current.start();
336
+ return;
337
+ } catch (err) {
338
+ if (!isStateChangeInProgressError(err)) throw err;
339
+ }
340
+ }
341
+ if (Date.now() >= deadline) {
342
+ throw new Error(
343
+ `Timed out waiting for Daytona sandbox ${current.id} to start (state=${state})`
344
+ );
345
+ }
346
+ await sleep(ATTACH_POLL_INTERVAL_MS);
347
+ current = await this.client.get(current.id);
348
+ this.sandbox = current;
289
349
  }
290
- this.isWarmFlag = isWarm;
291
350
  }
292
351
  async provision() {
293
352
  const existing = await this.findMatchingSandbox();
294
353
  if (existing) {
295
354
  this.sandbox = existing;
296
- const isWarm = existing.state === "started";
297
- await existing.start();
298
- this.isWarmFlag = isWarm;
355
+ this.isWarmFlag = existing.state === "started";
356
+ await this.ensureStarted(existing);
299
357
  return;
300
358
  }
301
359
  const labels = this.getLabels();
@@ -494,12 +552,26 @@ var DaytonaSandboxAdapter = class extends SandboxAdapter {
494
552
  }
495
553
  async openPort(port) {
496
554
  this.requireProvisioned();
497
- await this.requireSandbox().getPreviewLink(port);
555
+ const preview = await this.requireSandbox().getPreviewLink(port);
556
+ this.previewToken = preview.token;
557
+ }
558
+ /**
559
+ * Headers callers must attach to HTTP/WebSocket requests against this
560
+ * sandbox's preview URLs. Daytona private sandboxes gate their preview
561
+ * proxy behind `x-daytona-preview-token`; without it the proxy 307-redirects
562
+ * to Auth0, which breaks every provider (claude-code `/start` 404, codex WS
563
+ * "307", opencode `/session` 404). The token is captured lazily from
564
+ * `getPreviewLink`/`openPort`, both of which every consumer calls to build
565
+ * the URL immediately before reading these headers.
566
+ */
567
+ get previewHeaders() {
568
+ return this.previewToken ? { "x-daytona-preview-token": this.previewToken } : {};
498
569
  }
499
570
  async getPreviewLink(port) {
500
571
  this.requireProvisioned();
501
572
  const sandbox = this.requireSandbox();
502
573
  const preview = await sandbox.getPreviewLink(port);
574
+ this.previewToken = preview.token;
503
575
  return preview.url;
504
576
  }
505
577
  async uploadFile(content, targetPath) {
@@ -1244,16 +1244,23 @@ async function preflightSetup(target, setupId, daemon) {
1244
1244
  );
1245
1245
  }
1246
1246
  async function markSetupComplete(target, setupId) {
1247
- await time(
1248
- debugSetup,
1249
- `markSetupComplete ${target.provider}`,
1250
- () => target.runCommand(
1251
- [
1252
- `mkdir -p ${shellQuote(target.layout.rootDir)}`,
1253
- `printf '%s' ${shellQuote(setupId)} > ${shellQuote(path5.posix.join(target.layout.rootDir, SETUP_ID_FILENAME))}`
1254
- ].join(" && ")
1255
- )
1256
- );
1247
+ await time(debugSetup, `markSetupComplete ${target.provider}`, async () => {
1248
+ const setupIdFile = path5.posix.join(
1249
+ target.layout.rootDir,
1250
+ SETUP_ID_FILENAME
1251
+ );
1252
+ const result = await target.uploadAndRun(
1253
+ [{ path: setupIdFile, content: setupId }],
1254
+ "true"
1255
+ );
1256
+ if (result.exitCode !== 0) {
1257
+ const detail = result.combinedOutput?.trim();
1258
+ throw new Error(
1259
+ `markSetupComplete failed (${result.exitCode})${detail ? `
1260
+ ${detail}` : ""}`
1261
+ );
1262
+ }
1263
+ });
1257
1264
  }
1258
1265
  function buildInstallScript(rootDir, installCommandsByKey) {
1259
1266
  const commandsB64 = Buffer.from(
@@ -1641,6 +1648,8 @@ var DAEMON_PORT = 43180;
1641
1648
  var DAEMON_PATH = "/tmp/agentbox/claude-code/daemon.mjs";
1642
1649
  var DAEMON_LOG_PATH = "/tmp/agentbox/claude-code/daemon.log";
1643
1650
  var DAEMON_PID_PATH = "/tmp/agentbox/claude-code/daemon.pid";
1651
+ var DAEMON_READY_TIMEOUT_MS = 3e4;
1652
+ var DAEMON_READY_POLL_INTERVAL_MS = 250;
1644
1653
  function claudeConfigDir(options) {
1645
1654
  return path8.join(
1646
1655
  agentboxRoot(AgentProvider.ClaudeCode, Boolean(options.sandbox)),
@@ -2078,16 +2087,44 @@ async function ensureClaudeCodeDaemonUncached(options, env) {
2078
2087
  `Could not start claude-code daemon: ${launch.stderr || launch.combinedOutput || "(no output)"}`
2079
2088
  );
2080
2089
  }
2090
+ const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
2091
+ let lastProbe = "";
2092
+ while (Date.now() < deadline) {
2093
+ const ready = await sandbox.run(
2094
+ `curl -fsS --max-time 1 http://127.0.0.1:${DAEMON_PORT}/__version 2>/dev/null`,
2095
+ { cwd: options.cwd, timeoutMs: 1e4 }
2096
+ );
2097
+ lastProbe = ready.combinedOutput.trim();
2098
+ if (ready.exitCode === 0 && lastProbe === DAEMON_PROTOCOL_VERSION) {
2099
+ return;
2100
+ }
2101
+ await sleep(DAEMON_READY_POLL_INTERVAL_MS);
2102
+ }
2103
+ const logTail = await sandbox.run(`tail -n 20 ${shellQuote(DAEMON_LOG_PATH)} 2>/dev/null`, {
2104
+ cwd: options.cwd
2105
+ }).catch(() => void 0);
2106
+ throw new Error(
2107
+ `claude-code daemon did not become ready within ${DAEMON_READY_TIMEOUT_MS}ms` + (lastProbe ? ` (last /__version response: ${lastProbe})` : "") + (logTail?.combinedOutput ? `
2108
+ ${logTail.combinedOutput}` : "")
2109
+ );
2081
2110
  });
2082
2111
  }
2083
2112
  var DAEMON_FIRST_REQUEST_RETRY_BUDGET_MS = 3e4;
2084
2113
  var DAEMON_FIRST_REQUEST_RETRY_INTERVAL_MS = 250;
2114
+ var TRANSIENT_PROXY_STATUSES = /* @__PURE__ */ new Set([404, 502, 503, 504]);
2085
2115
  async function fetchWithDaemonRetry(input, init) {
2086
2116
  const deadline = Date.now() + DAEMON_FIRST_REQUEST_RETRY_BUDGET_MS;
2087
2117
  let lastError;
2118
+ let lastResponse;
2088
2119
  while (Date.now() < deadline) {
2089
2120
  try {
2090
- return await fetch(input, init);
2121
+ const response = await fetch(input, init);
2122
+ if (TRANSIENT_PROXY_STATUSES.has(response.status)) {
2123
+ lastResponse = response;
2124
+ await sleep(DAEMON_FIRST_REQUEST_RETRY_INTERVAL_MS);
2125
+ continue;
2126
+ }
2127
+ return response;
2091
2128
  } catch (error) {
2092
2129
  lastError = error;
2093
2130
  const aborted = error?.name === "AbortError";
@@ -2097,6 +2134,9 @@ async function fetchWithDaemonRetry(input, init) {
2097
2134
  await sleep(DAEMON_FIRST_REQUEST_RETRY_INTERVAL_MS);
2098
2135
  }
2099
2136
  }
2137
+ if (lastResponse) {
2138
+ return lastResponse;
2139
+ }
2100
2140
  throw lastError ?? new Error("claude-code daemon request timed out");
2101
2141
  }
2102
2142
  async function* parseNdjsonStream(body) {
@@ -2141,6 +2181,24 @@ var ClaudeCodeAgentAdapter = class {
2141
2181
  * match what we'd produce, we skip the artifact upload AND the daemon
2142
2182
  * boot entirely.
2143
2183
  */
2184
+ /**
2185
+ * Explicit, developer-invoked teardown of the in-sandbox claude-code
2186
+ * relay daemon (see {@link AgentProviderAdapter.killServer}). agentbox
2187
+ * never calls this on its own. Best-effort and idempotent: a no-op when
2188
+ * the daemon isn't running or no sandbox is configured. After it returns
2189
+ * the next `setup()` re-spawns the daemon (its `/__version` probe fails).
2190
+ */
2191
+ async killServer(request) {
2192
+ const sandbox = request.options.sandbox;
2193
+ if (!sandbox) return;
2194
+ await sandbox.run(
2195
+ [
2196
+ `if [ -f ${shellQuote(DAEMON_PID_PATH)} ]; then kill -TERM "$(cat ${shellQuote(DAEMON_PID_PATH)})" 2>/dev/null || true; rm -f ${shellQuote(DAEMON_PID_PATH)}; fi`,
2197
+ `fuser -k -n tcp ${DAEMON_PORT} 2>/dev/null || true`
2198
+ ].join("; "),
2199
+ { cwd: request.options.cwd, timeoutMs: 1e4 }
2200
+ ).catch(() => void 0);
2201
+ }
2144
2202
  async setup(request) {
2145
2203
  await time(debugClaude, "claude-code setup()", async () => {
2146
2204
  const options = request.options;
@@ -3533,10 +3591,34 @@ async function buildCodexInputItems(options, inputParts) {
3533
3591
  );
3534
3592
  return inputItems;
3535
3593
  }
3594
+ async function killCodexAppServer(request) {
3595
+ const { options } = request;
3596
+ const sandbox = options.sandbox;
3597
+ if (!sandbox) return;
3598
+ const sharedTarget = await createSetupTarget(
3599
+ request.provider,
3600
+ REMOTE_CODEX_APP_SERVER_ID,
3601
+ options
3602
+ );
3603
+ const pidFilePath = path9.posix.join(
3604
+ sharedTarget.layout.rootDir,
3605
+ "codex-app-server.pid"
3606
+ );
3607
+ await sandbox.run(
3608
+ [
3609
+ `if [ -f ${shellQuote(pidFilePath)} ]; then kill "$(cat ${shellQuote(pidFilePath)})" 2>/dev/null || true; rm -f ${shellQuote(pidFilePath)}; fi`,
3610
+ `fuser -k -n tcp ${REMOTE_CODEX_APP_SERVER_PORT} 2>/dev/null || true`
3611
+ ].join("; "),
3612
+ { cwd: options.cwd, timeoutMs: 1e4 }
3613
+ ).catch(() => void 0);
3614
+ }
3536
3615
  var CodexAgentAdapter = class {
3537
3616
  async setup(request) {
3538
3617
  await setupCodex(request);
3539
3618
  }
3619
+ async killServer(request) {
3620
+ await killCodexAppServer(request);
3621
+ }
3540
3622
  async execute(request, sink) {
3541
3623
  const executeStartedAt = Date.now();
3542
3624
  debugCodex("execute() start runId=%s", request.runId);
@@ -4112,6 +4194,12 @@ async function ensureSandboxOpenCodeServer(request) {
4112
4194
  debugOpencode("opencode setup() preflight hit \u2014 skipping");
4113
4195
  return;
4114
4196
  }
4197
+ if (await isSandboxOpenCodeServerHealthy(sandbox, options.cwd, port)) {
4198
+ debugOpencode(
4199
+ "opencode server already running but setup drifted \u2014 reusing it without restart; call agent.killServer() to apply the new config"
4200
+ );
4201
+ return;
4202
+ }
4115
4203
  const commonEnv = {
4116
4204
  OPENCODE_CONFIG: configPath,
4117
4205
  OPENCODE_CONFIG_DIR: target.layout.opencodeDir,
@@ -4147,42 +4235,79 @@ async function ensureSandboxOpenCodeServer(request) {
4147
4235
  `disown 2>/dev/null || true`
4148
4236
  ].join(" ")})`
4149
4237
  ].join(" && ");
4150
- await killSandboxOpenCodeServer(sandbox, pidFilePath, options.cwd, port);
4151
- const launchResult = await time(
4238
+ const OPENCODE_MAX_LAUNCH_ATTEMPTS = 4;
4239
+ const OPENCODE_RELAUNCH_BACKOFF_MS = 1e3;
4240
+ const readyDeadline = Date.now() + SANDBOX_OPENCODE_READY_TIMEOUT_MS;
4241
+ const pidAlive = `kill -0 "$(cat ${shellQuote(pidFilePath)} 2>/dev/null)" 2>/dev/null`;
4242
+ let lastLog = "";
4243
+ const becameReady = await time(
4152
4244
  debugOpencode,
4153
- "spawn opencode serve",
4154
- () => sandbox.run(launchCommand, {
4155
- cwd: options.cwd,
4156
- env: serveEnv,
4157
- timeoutMs: 2e4
4158
- })
4159
- );
4160
- if (launchResult.exitCode !== 0) {
4161
- await target.cleanup().catch(() => void 0);
4162
- throw new Error(
4163
- `Could not start OpenCode server: ${launchResult.combinedOutput || launchResult.stderr}`
4164
- );
4165
- }
4166
- await time(debugOpencode, "poll opencode until ready", async () => {
4167
- const readyDeadline = Date.now() + SANDBOX_OPENCODE_READY_TIMEOUT_MS;
4168
- let attempt = 0;
4169
- while (Date.now() < readyDeadline) {
4170
- attempt++;
4171
- const probe = await sandbox.run(
4172
- `curl -fsS http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
4173
- { cwd: options.cwd, timeoutMs: 5e3 }
4174
- );
4175
- if (probe.exitCode === 0) {
4176
- debugOpencode("ready after %d probe attempt(s)", attempt);
4177
- return;
4245
+ "launch + poll opencode until ready",
4246
+ async () => {
4247
+ for (let attempt = 1; attempt <= OPENCODE_MAX_LAUNCH_ATTEMPTS && Date.now() < readyDeadline; attempt++) {
4248
+ await killSandboxOpenCodeServer(
4249
+ sandbox,
4250
+ pidFilePath,
4251
+ options.cwd,
4252
+ port
4253
+ );
4254
+ const launchResult = await sandbox.run(launchCommand, {
4255
+ cwd: options.cwd,
4256
+ env: serveEnv,
4257
+ timeoutMs: 4e4
4258
+ });
4259
+ if (launchResult.exitCode !== 0) {
4260
+ await target.cleanup().catch(() => void 0);
4261
+ throw new Error(
4262
+ `Could not start OpenCode server: ${launchResult.combinedOutput || launchResult.stderr}`
4263
+ );
4264
+ }
4265
+ while (Date.now() < readyDeadline) {
4266
+ const probe = await sandbox.run(
4267
+ `curl -fsS http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
4268
+ { cwd: options.cwd, timeoutMs: 5e3 }
4269
+ );
4270
+ if (probe.exitCode === 0) {
4271
+ debugOpencode("ready on attempt %d", attempt);
4272
+ return true;
4273
+ }
4274
+ const alive = await sandbox.run(pidAlive, {
4275
+ cwd: options.cwd,
4276
+ timeoutMs: 5e3
4277
+ });
4278
+ if (alive.exitCode !== 0) {
4279
+ lastLog = (await sandbox.run(`tail -n 40 ${shellQuote(logFilePath)} 2>/dev/null`, {
4280
+ cwd: options.cwd
4281
+ }).catch(() => void 0))?.combinedOutput?.trim() ?? lastLog;
4282
+ debugOpencode(
4283
+ "opencode died on attempt %d/%d; relaunching. log:\n%s",
4284
+ attempt,
4285
+ OPENCODE_MAX_LAUNCH_ATTEMPTS,
4286
+ lastLog
4287
+ );
4288
+ break;
4289
+ }
4290
+ await sleep(500);
4291
+ }
4292
+ if (Date.now() >= readyDeadline) break;
4293
+ await sleep(OPENCODE_RELAUNCH_BACKOFF_MS);
4178
4294
  }
4179
- await sleep(500);
4295
+ return false;
4296
+ }
4297
+ );
4298
+ if (!becameReady) {
4299
+ if (!lastLog) {
4300
+ lastLog = (await sandbox.run(`tail -n 40 ${shellQuote(logFilePath)} 2>/dev/null`, {
4301
+ cwd: options.cwd
4302
+ }).catch(() => void 0))?.combinedOutput?.trim() ?? "";
4180
4303
  }
4181
4304
  await target.cleanup().catch(() => void 0);
4182
4305
  throw new Error(
4183
- `OpenCode server did not become ready within ${SANDBOX_OPENCODE_READY_TIMEOUT_MS}ms.`
4306
+ `OpenCode server did not become ready within ${SANDBOX_OPENCODE_READY_TIMEOUT_MS}ms.` + (lastLog ? `
4307
+ opencode log:
4308
+ ${lastLog}` : "")
4184
4309
  );
4185
- });
4310
+ }
4186
4311
  await markSetupComplete(target, setupId);
4187
4312
  });
4188
4313
  }
@@ -4234,7 +4359,13 @@ async function ensureLocalOpenCodeServer(request) {
4234
4359
  debugOpencode("local opencode server up-to-date \u2014 reusing");
4235
4360
  return;
4236
4361
  }
4237
- debugOpencode("local opencode server drifted/absent \u2014 (re)spawning");
4362
+ if (await isLocalOpenCodeServerHealthy()) {
4363
+ debugOpencode(
4364
+ "local opencode server already running but setup drifted \u2014 reusing it without restart; call agent.killServer() to apply the new config"
4365
+ );
4366
+ return;
4367
+ }
4368
+ debugOpencode("local opencode server absent \u2014 spawning");
4238
4369
  await applyDifferentialSetup(target, allArtifacts, installCommands);
4239
4370
  await killLocalOpenCodeServer();
4240
4371
  spawnCommand({
@@ -4267,6 +4398,45 @@ async function setupOpenCode(request) {
4267
4398
  }
4268
4399
  await ensureLocalOpenCodeServer(request);
4269
4400
  }
4401
+ async function isSandboxOpenCodeServerHealthy(sandbox, cwd, port) {
4402
+ const probe = await sandbox.run(
4403
+ `curl -fsS --max-time 2 http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
4404
+ { cwd, timeoutMs: 5e3 }
4405
+ ).catch(() => void 0);
4406
+ return probe?.exitCode === 0;
4407
+ }
4408
+ async function isLocalOpenCodeServerHealthy() {
4409
+ try {
4410
+ const res = await fetch(
4411
+ `http://127.0.0.1:${LOCAL_OPENCODE_PORT}/global/health`
4412
+ );
4413
+ return res.ok;
4414
+ } catch {
4415
+ return false;
4416
+ }
4417
+ }
4418
+ async function killOpenCodeServer(request) {
4419
+ const { options } = request;
4420
+ if (options.sandbox) {
4421
+ const target = await createSetupTarget(
4422
+ request.provider,
4423
+ SHARED_OPENCODE_TARGET_ID,
4424
+ options
4425
+ );
4426
+ const pidFilePath = path10.posix.join(
4427
+ target.layout.rootDir,
4428
+ "opencode-serve.pid"
4429
+ );
4430
+ await killSandboxOpenCodeServer(
4431
+ options.sandbox,
4432
+ pidFilePath,
4433
+ options.cwd,
4434
+ SANDBOX_OPENCODE_PORT
4435
+ );
4436
+ return;
4437
+ }
4438
+ await killLocalOpenCodeServer();
4439
+ }
4270
4440
  async function buildOpenCodeRuntime(options) {
4271
4441
  if (options.sandbox) {
4272
4442
  const sandbox = options.sandbox;
@@ -4288,6 +4458,9 @@ var OpenCodeAgentAdapter = class {
4288
4458
  async setup(request) {
4289
4459
  await setupOpenCode(request);
4290
4460
  }
4461
+ async killServer(request) {
4462
+ await killOpenCodeServer(request);
4463
+ }
4291
4464
  async execute(request, sink) {
4292
4465
  const executeStartedAt = Date.now();
4293
4466
  debugOpencode("execute() start runId=%s", request.runId);
@@ -5276,6 +5449,30 @@ var Agent = class {
5276
5449
  throw error;
5277
5450
  }
5278
5451
  }
5452
+ /**
5453
+ * Stop the long-lived provider CLI server booted by {@link Agent.setup}
5454
+ * (claude-code relay daemon, codex app-server, opencode `serve`).
5455
+ *
5456
+ * agentbox NEVER kills a running server on its own — not on run
5457
+ * completion, not on `abort()`, and not when {@link Agent.setup} detects
5458
+ * a changed config/credential set. This method is the single, explicit,
5459
+ * developer-driven teardown. Call it to free a server's resources, or to
5460
+ * apply a changed config: after `killServer()` the next {@link setup}
5461
+ * cold-starts a fresh server with the new configuration.
5462
+ *
5463
+ * Best-effort and idempotent: a no-op when no server is running, or when
5464
+ * the provider has no shared server for the current mode (host-mode
5465
+ * claude-code runs the SDK in-process; local codex spawns a fresh
5466
+ * app-server per run, torn down with the run).
5467
+ */
5468
+ async killServer() {
5469
+ debugAgent("killServer() provider=%s", this.provider);
5470
+ await this.adapter.killServer({
5471
+ provider: this.provider,
5472
+ options: this.options
5473
+ });
5474
+ this.setupPromise = void 0;
5475
+ }
5279
5476
  stream(runConfig) {
5280
5477
  if (runConfig.resumeSessionId && runConfig.forkSessionId) {
5281
5478
  throw new Error(
@@ -1,4 +1,4 @@
1
- export { A as AISDKEvent, R as MessageCompletedEvent, S as MessageInjectedEvent, T as MessageStartedEvent, U as NormalizedAgentEvent, V as NormalizedAgentEventBase, W as NormalizedAgentEventType, a1 as PermissionRequestedEvent, a2 as PermissionResolvedEvent, a3 as RawAgentEvent, a4 as ReasoningDeltaEvent, a6 as RunCancelledEvent, a7 as RunCompletedEvent, a8 as RunErrorEvent, a9 as RunStartedEvent, ab as TextDeltaEvent, ad as ToolCallCompletedEvent, ae as ToolCallDeltaEvent, af as ToolCallStartedEvent, ai as createNormalizedEvent, aj as normalizeRawAgentEvent, ak as toAISDKEvent, al as toAISDKStream } from '../types-DG4J_zMT.js';
1
+ export { A as AISDKEvent, R as MessageCompletedEvent, S as MessageInjectedEvent, T as MessageStartedEvent, U as NormalizedAgentEvent, V as NormalizedAgentEventBase, W as NormalizedAgentEventType, a1 as PermissionRequestedEvent, a2 as PermissionResolvedEvent, a3 as RawAgentEvent, a4 as ReasoningDeltaEvent, a6 as RunCancelledEvent, a7 as RunCompletedEvent, a8 as RunErrorEvent, a9 as RunStartedEvent, ab as TextDeltaEvent, ad as ToolCallCompletedEvent, ae as ToolCallDeltaEvent, af as ToolCallStartedEvent, ai as createNormalizedEvent, aj as normalizeRawAgentEvent, ak as toAISDKEvent, al as toAISDKStream } from '../types-t01PuLUJ.js';
2
2
  import { AgentProvider } from '../enums.js';
3
3
  import '../Sandbox-DcKAU-E3.js';
4
4
  import 'e2b';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AISDKEvent, a as AgentApprovalMode, b as AgentAttachRequest, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, h as AgentOptions, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, o as AgentProviderName, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, r as AgentResult, s as AgentRun, t as AgentRunConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, y as AttachedRun, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexProviderOptions, N as DataContent, O as EmbeddedSkillConfig, P as FilePart, Q as ImagePart, R as MessageCompletedEvent, S as MessageInjectedEvent, T as MessageStartedEvent, U as NormalizedAgentEvent, V as NormalizedAgentEventBase, W as NormalizedAgentEventType, X as OpenCodeAgentOptions, Y as OpenCodePluginConfig, Z as OpenCodePluginEvent, _ as OpenCodePluginHookConfig, $ as OpenCodeProviderOptions, a0 as OpenRouterPlugin, a1 as PermissionRequestedEvent, a2 as PermissionResolvedEvent, a3 as RawAgentEvent, a4 as ReasoningDeltaEvent, a5 as RepoSkillConfig, a6 as RunCancelledEvent, a7 as RunCompletedEvent, a8 as RunErrorEvent, a9 as RunStartedEvent, aa as SetupLayout, ab as TextDeltaEvent, ac as TextPart, ad as ToolCallCompletedEvent, ae as ToolCallDeltaEvent, af as ToolCallStartedEvent, ag as UserContent, ah as UserContentPart, ai as createNormalizedEvent, aj as normalizeRawAgentEvent, ak as toAISDKEvent, al as toAISDKStream } from './types-DG4J_zMT.js';
1
+ export { A as AISDKEvent, a as AgentApprovalMode, b as AgentAttachRequest, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, h as AgentOptions, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, o as AgentProviderName, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, r as AgentResult, s as AgentRun, t as AgentRunConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, y as AttachedRun, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexProviderOptions, N as DataContent, O as EmbeddedSkillConfig, P as FilePart, Q as ImagePart, R as MessageCompletedEvent, S as MessageInjectedEvent, T as MessageStartedEvent, U as NormalizedAgentEvent, V as NormalizedAgentEventBase, W as NormalizedAgentEventType, X as OpenCodeAgentOptions, Y as OpenCodePluginConfig, Z as OpenCodePluginEvent, _ as OpenCodePluginHookConfig, $ as OpenCodeProviderOptions, a0 as OpenRouterPlugin, a1 as PermissionRequestedEvent, a2 as PermissionResolvedEvent, a3 as RawAgentEvent, a4 as ReasoningDeltaEvent, a5 as RepoSkillConfig, a6 as RunCancelledEvent, a7 as RunCompletedEvent, a8 as RunErrorEvent, a9 as RunStartedEvent, aa as SetupLayout, ab as TextDeltaEvent, ac as TextPart, ad as ToolCallCompletedEvent, ae as ToolCallDeltaEvent, af as ToolCallStartedEvent, ag as UserContent, ah as UserContentPart, ai as createNormalizedEvent, aj as normalizeRawAgentEvent, ak as toAISDKEvent, al as toAISDKStream } from './types-t01PuLUJ.js';
2
2
  export { AGENT_RESERVED_PORTS, Agent, agentboxRoot, collectAllAgentReservedPorts, getAgentLayout } from './agents/index.js';
3
3
  export { A as AsyncCommandHandle, C as CommandEvent, a as CommandOptions, b as CommandResult, D as DaytonaProviderOptions, c as DaytonaSandboxOptions, E as E2bProviderOptions, d as E2bSandboxOptions, G as GitCloneOptions, L as LocalDockerProviderOptions, e as LocalDockerSandboxOptions, M as ModalProviderOptions, f as ModalSandboxOptions, S as Sandbox, g as SandboxDescriptor, h as SandboxListOptions, i as SandboxOptions, j as SandboxOptionsBase, k as SandboxOptionsMap, l as SandboxProviderName, m as SandboxRaw, n as SandboxRawMap, o as SandboxResourceSpec, T as TarballEntry, V as VercelGitSource, p as VercelProviderOptions, q as VercelSandboxOptions } from './Sandbox-DcKAU-E3.js';
4
4
  export { SandboxAdapter, buildGitCloneCommand } from './sandboxes/index.js';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  Agent,
3
3
  agentboxRoot,
4
4
  getAgentLayout
5
- } from "./chunk-2IHQY3WC.js";
5
+ } from "./chunk-XONT46YC.js";
6
6
  import {
7
7
  ProviderLogAssembler,
8
8
  createNormalizedEvent,
@@ -14,7 +14,7 @@ import {
14
14
  Sandbox,
15
15
  SandboxAdapter,
16
16
  buildGitCloneCommand
17
- } from "./chunk-HYHLKO3L.js";
17
+ } from "./chunk-TUBBWIOM.js";
18
18
  import {
19
19
  AGENT_RESERVED_PORTS,
20
20
  collectAllAgentReservedPorts
@@ -2,7 +2,7 @@ import {
2
2
  Sandbox,
3
3
  SandboxAdapter,
4
4
  buildGitCloneCommand
5
- } from "../chunk-HYHLKO3L.js";
5
+ } from "../chunk-TUBBWIOM.js";
6
6
  import "../chunk-AVXJMCBC.js";
7
7
  import "../chunk-NSJM57Z4.js";
8
8
  import {
@@ -599,6 +599,22 @@ interface AgentProviderAdapter<P extends AgentProviderName = AgentProviderName>
599
599
  * is already listening.
600
600
  */
601
601
  setup(request: AgentSetupRequest<P>): Promise<void>;
602
+ /**
603
+ * Stop the long-lived provider CLI server that {@link setup} boots
604
+ * (claude-code relay daemon, codex app-server, opencode `serve`).
605
+ *
606
+ * agentbox NEVER calls this on its own — not on run completion, not on
607
+ * abort, and not when {@link setup} sees a changed config/credential
608
+ * set. It exists purely so a developer can explicitly tear a server
609
+ * down (to reclaim resources, or to force the changed config to apply
610
+ * on the next cold {@link setup}).
611
+ *
612
+ * Best-effort and idempotent: a no-op when nothing is running, or when
613
+ * the provider has no shared server for the current mode (host-mode
614
+ * claude-code runs the SDK in-process; local codex spawns a fresh
615
+ * app-server per run).
616
+ */
617
+ killServer(request: AgentSetupRequest<P>): Promise<void>;
602
618
  execute(request: AgentExecutionRequest<P>, sink: AgentRunSink): Promise<() => Promise<void> | void>;
603
619
  /**
604
620
  * Stateless abort. Dial the in-sandbox provider server, issue the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbox-sdk",
3
- "version": "0.1.319",
3
+ "version": "0.1.323",
4
4
  "description": "Swappable coding agents and sandbox providers for Bun and TypeScript.",
5
5
  "license": "MIT",
6
6
  "repository": {