agentbox-sdk 0.1.513 → 0.1.514

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -123,12 +123,26 @@ while the harness has ended its turn and the run stays open only for those
123
123
  tasks (or, with an empty set, for the wake-up the CLI queues for a task that
124
124
  finished mid-turn) — and settles the run on the follow-up turn's result
125
125
  instead of the first one. Once background work has been seen, a turn end
126
- settles the run only after a 15s grace with no new turn, and a final
126
+ settles the run when Claude emits `session_state_changed: idle` and the live
127
+ task set is empty. AgentBox enables this documented CLI event with
128
+ `CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1`; there is no extra model call or
129
+ fixed completion delay ([upstream opt-in documentation](https://github.com/anthropics/claude-agent-sdk-typescript/blob/main/CHANGELOG.md#0283)). Older/custom CLIs that never emit session state use
130
+ a 15s grace as a compatibility fallback. A final
127
131
  `background.tasks` with `tasks: []` and `waiting: false` precedes the settle.
128
132
  `backgroundTaskTimeoutMs` bounds the total time spent waiting across the run:
129
133
  default 30 minutes, `0` settles at the first turn end as before, `Infinity`
130
134
  waits forever. On expiry the tasks are stopped best-effort and the run
131
135
  completes with the last turn's text.
136
+ Tasks marked ambient by Claude (such as live-update watchers) do not keep the
137
+ run waiting. The SDK's full task snapshots take precedence over task start and
138
+ finish events, whose ordering relative to those snapshots is unspecified.
139
+
140
+ AgentBox does not inject a Stop-hook prompt or infer from the answer that a
141
+ running task is unwanted. A genuinely live polling helper keeps the run open
142
+ until it finishes, is stopped, or reaches the wait budget. Unlike the interactive
143
+ CLI, an AgentBox run owns the query lifetime: completing it closes the process.
144
+ Keep the streaming input open while waiting so background continuations retain
145
+ their hooks, permissions, and SDK MCP control channel.
132
146
 
133
147
  Codex owns command polling (`write_stdin`), yielded code-mode waits (`wait`),
134
148
  and subagent waits (`wait_agent`). AgentBox finishes an ordinary Codex run on
@@ -4,7 +4,7 @@ import {
4
4
  getAgentLayout,
5
5
  harnessCapabilities,
6
6
  resolveHarnessCommand
7
- } from "../chunk-XJWZR2NR.js";
7
+ } from "../chunk-D33LRRN7.js";
8
8
  import "../chunk-775FIGGL.js";
9
9
  import {
10
10
  AGENT_RESERVED_PORTS,
@@ -1859,17 +1859,21 @@ var BackgroundTaskTracker = class {
1859
1859
  pendingWakeups = /* @__PURE__ */ new Map();
1860
1860
  afterResult = false;
1861
1861
  seenBackgroundWork = false;
1862
+ hasTaskSnapshot = false;
1862
1863
  liveTasks() {
1863
- return [...this.tasks.values(), ...this.wakeups.values()];
1864
+ return [
1865
+ ...[...this.tasks.values()].filter(({ ambient }) => !ambient).map(({ task }) => task),
1866
+ ...this.wakeups.values()
1867
+ ];
1864
1868
  }
1865
1869
  /**
1866
1870
  * True once any background task or scheduled wakeup was live in this run.
1867
1871
  * The CLI queues a wake-up for every task that finishes and delivers it as
1868
1872
  * a new turn once the model is idle — including tasks that finished
1869
1873
  * mid-turn, whose queued turn starts right after that turn's `result`
1870
- * with nothing live and nothing observable in between. After background
1871
- * work has been seen, a `result` is therefore never the end of the run on
1872
- * its own; only the grace passing without a new turn is.
1874
+ * with nothing live in between. After background work has been seen, wait
1875
+ * for session_state_changed/idle with an empty live set. Older CLIs that
1876
+ * never emit session state use an idle grace as a compatibility fallback.
1873
1877
  */
1874
1878
  hasSeenBackgroundWork() {
1875
1879
  return this.seenBackgroundWork;
@@ -1911,6 +1915,7 @@ var BackgroundTaskTracker = class {
1911
1915
  const id = String(m.task_id ?? "");
1912
1916
  switch (m.subtype) {
1913
1917
  case "background_tasks_changed":
1918
+ this.hasTaskSnapshot = true;
1914
1919
  this.tasks.clear();
1915
1920
  for (const entry of asArray(m.tasks)) {
1916
1921
  const task = asRecord2(entry);
@@ -1918,13 +1923,13 @@ var BackgroundTaskTracker = class {
1918
1923
  }
1919
1924
  return false;
1920
1925
  case "task_started":
1921
- if (m.is_backgrounded === true && !m.owned_by_subagent) this.addTask(m);
1926
+ if (!this.hasTaskSnapshot && m.is_backgrounded === true && !m.owned_by_subagent) this.addTask(m);
1922
1927
  return false;
1923
1928
  case "task_notification":
1924
- this.tasks.delete(id);
1929
+ if (!this.hasTaskSnapshot) this.tasks.delete(id);
1925
1930
  return false;
1926
1931
  case "task_updated":
1927
- if (DONE_STATUSES.has(String(asRecord2(m.patch)?.status)))
1932
+ if (!this.hasTaskSnapshot && DONE_STATUSES.has(String(asRecord2(m.patch)?.status)))
1928
1933
  this.tasks.delete(id);
1929
1934
  return false;
1930
1935
  case "init":
@@ -1936,11 +1941,15 @@ var BackgroundTaskTracker = class {
1936
1941
  addTask(task) {
1937
1942
  const id = String(task.task_id ?? "");
1938
1943
  if (!id) return;
1939
- this.seenBackgroundWork = true;
1944
+ const ambient = task.ambient === true || task.skip_transcript === true;
1945
+ if (!ambient) this.seenBackgroundWork = true;
1940
1946
  this.tasks.set(id, {
1941
- id,
1942
- type: String(task.task_type ?? "task"),
1943
- description: String(task.description ?? "")
1947
+ ambient,
1948
+ task: {
1949
+ id,
1950
+ type: String(task.task_type ?? "task"),
1951
+ description: String(task.description ?? "")
1952
+ }
1944
1953
  });
1945
1954
  }
1946
1955
  ingestToolUses(m) {
@@ -2021,7 +2030,7 @@ var BackgroundWait = class {
2021
2030
  };
2022
2031
 
2023
2032
  // src/agents/providers/claude-code.ts
2024
- var DAEMON_PROTOCOL_VERSION = "5";
2033
+ var DAEMON_PROTOCOL_VERSION = "8";
2025
2034
  var DAEMON_PORT = 43180;
2026
2035
  var DAEMON_PATH = "/tmp/agentbox/claude-code/daemon.mjs";
2027
2036
  var DAEMON_LOG_PATH = "/tmp/agentbox/claude-code/daemon.log";
@@ -2130,7 +2139,7 @@ function createClaudeCodeDaemonScript() {
2130
2139
  return `import http from "node:http";
2131
2140
  import { execSync } from "node:child_process";
2132
2141
  import { existsSync, readFileSync } from "node:fs";
2133
- import { timingSafeEqual } from "node:crypto";
2142
+ import { randomUUID, timingSafeEqual } from "node:crypto";
2134
2143
  import { query, getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
2135
2144
 
2136
2145
  const VERSION = ${version};
@@ -2774,6 +2783,7 @@ var ClaudeCodeAgentAdapter = class {
2774
2783
  IS_SANDBOX: "1"
2775
2784
  };
2776
2785
  applyCliBackgroundWaitCeiling(env);
2786
+ env.CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS ??= "1";
2777
2787
  const customHeaders = request.options.customHeaders;
2778
2788
  if (customHeaders && Object.keys(customHeaders).length > 0) {
2779
2789
  const serialized = Object.entries(customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
@@ -2890,6 +2900,7 @@ ${serialized}` : serialized;
2890
2900
  { messageId: initialUuid }
2891
2901
  )
2892
2902
  );
2903
+ const tracker = new BackgroundTaskTracker();
2893
2904
  const permissionMessages = async function* () {
2894
2905
  for await (const item of parseNdjsonStream(response.body)) {
2895
2906
  const control = item;
@@ -2905,7 +2916,7 @@ ${serialized}` : serialized;
2905
2916
  if (!reply.ok) throw new Error(`Claude permission response failed: ${reply.status}`);
2906
2917
  }
2907
2918
  };
2908
- await consumeClaudeMessages(request, sink, permissionMessages(), executeStartedAt, cleanup, () => cancelled);
2919
+ await consumeClaudeMessages(request, sink, permissionMessages(), executeStartedAt, cleanup, () => cancelled, {}, tracker);
2909
2920
  return async () => void 0;
2910
2921
  }
2911
2922
  /**
@@ -2966,16 +2977,16 @@ ${serialized}` : serialized;
2966
2977
  }
2967
2978
  }
2968
2979
  };
2969
- async function consumeClaudeMessages(request, sink, messages, executeStartedAt, cleanup, wasCancelled = () => false, wait = {}) {
2980
+ async function consumeClaudeMessages(request, sink, messages, executeStartedAt, cleanup, wasCancelled = () => false, wait = {}, tracker = new BackgroundTaskTracker()) {
2970
2981
  let accumulatedText = "";
2971
2982
  let streamedThinkingChars = 0;
2972
2983
  let sawResult = false;
2984
+ let sawSessionState = false;
2973
2985
  let firstStreamEventLogged = false;
2974
2986
  let firstTextDeltaLogged = false;
2975
2987
  let lastTerminalReason;
2976
2988
  let lastIsError = false;
2977
2989
  const rawPayloads = [];
2978
- const tracker = new BackgroundTaskTracker();
2979
2990
  const timeoutMs = resolveBackgroundTaskTimeoutMs(request.options.backgroundTaskTimeoutMs);
2980
2991
  const graceMs = wait.graceMs ?? BACKGROUND_TASK_GRACE_MS;
2981
2992
  let pendingWait;
@@ -3047,7 +3058,10 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
3047
3058
  endWait();
3048
3059
  }
3049
3060
  emitTasks(tracker.liveTasks(), pendingWait !== void 0);
3050
- pendingWait?.setIdle(tracker.liveTasks().length === 0);
3061
+ const sessionState = message.type === "system" && message.subtype === "session_state_changed" ? message.state : void 0;
3062
+ if (sessionState) sawSessionState = true;
3063
+ pendingWait?.setIdle(!sawSessionState && tracker.liveTasks().length === 0);
3064
+ if (sessionState === "idle" && pendingWait && tracker.liveTasks().length === 0) break;
3051
3065
  if (message.type === "system") {
3052
3066
  const sub = message.subtype;
3053
3067
  if (sub === "init") {
@@ -3169,7 +3183,7 @@ async function consumeClaudeMessages(request, sink, messages, executeStartedAt,
3169
3183
  debugClaude("\u2605 turn ended with %d background task(s); waiting", live.length);
3170
3184
  endWait();
3171
3185
  pendingWait = new BackgroundWait(graceMs, Math.max(0, timeoutMs - waitedMs));
3172
- pendingWait.setIdle(live.length === 0);
3186
+ pendingWait.setIdle(!sawSessionState && live.length === 0);
3173
3187
  emitTasks(live, true);
3174
3188
  continue;
3175
3189
  }
@@ -3243,6 +3257,7 @@ async function executeNativeClaude(request, sink, wait = {}) {
3243
3257
  const prompt = new AsyncQueue();
3244
3258
  const sessionId = request.run.resumeSessionId ?? randomUUID();
3245
3259
  const controller = new AbortController();
3260
+ const tracker = new BackgroundTaskTracker();
3246
3261
  let handle;
3247
3262
  let processHandle;
3248
3263
  let stopped;
@@ -3262,6 +3277,7 @@ async function executeNativeClaude(request, sink, wait = {}) {
3262
3277
  prompt.push({ type: "user", uuid: messageId, message: { role: "user", content: mapToClaudeUserContent(input) }, parent_tool_use_id: null });
3263
3278
  const hostEnv = Object.fromEntries(Object.entries({ ...process.env, ...request.options.env }).filter((entry) => entry[1] !== void 0));
3264
3279
  applyCliBackgroundWaitCeiling(hostEnv);
3280
+ hostEnv.CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS ??= "1";
3265
3281
  if (request.options.customHeaders) {
3266
3282
  const headers = Object.entries(request.options.customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
3267
3283
  hostEnv.ANTHROPIC_CUSTOM_HEADERS = [hostEnv.ANTHROPIC_CUSTOM_HEADERS, headers].filter(Boolean).join("\n");
@@ -3344,7 +3360,7 @@ async function executeNativeClaude(request, sink, wait = {}) {
3344
3360
  stopTasks: async (ids) => {
3345
3361
  await Promise.all(ids.map((id) => live.stopTask(id).catch(() => void 0)));
3346
3362
  }
3347
- });
3363
+ }, tracker);
3348
3364
  } catch (error) {
3349
3365
  await stop();
3350
3366
  if (cancelled) sink.cancel();
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  getAgentLayout,
5
5
  harnessCapabilities,
6
6
  resolveHarnessCommand
7
- } from "./chunk-XJWZR2NR.js";
7
+ } from "./chunk-D33LRRN7.js";
8
8
  import {
9
9
  ProviderLogAssembler,
10
10
  createNormalizedEvent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbox-sdk",
3
- "version": "0.1.513",
3
+ "version": "0.1.514",
4
4
  "description": "Swappable coding agents and sandbox providers for Bun and TypeScript.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -59,7 +59,7 @@
59
59
  "bun": ">=1.1.0"
60
60
  },
61
61
  "dependencies": {
62
- "@anthropic-ai/claude-agent-sdk": "0.2.141",
62
+ "@anthropic-ai/claude-agent-sdk": "0.3.274",
63
63
  "@daytonaio/sdk": "^0.171.0",
64
64
  "@types/debug": "^4.1.13",
65
65
  "@vercel/sandbox": "2.0.0-beta.13",