@warble/claude-agent-sdk 0.17.0 → 0.19.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.
package/README.md CHANGED
@@ -111,7 +111,11 @@ owned by the current user, with no group or other permission bits (`chmod 600`).
111
111
  `[a-z0-9_]{1,32}`, `tools` is a non-empty list of `[a-z0-9_]{1,64}`, `instruction` is one line of at
112
112
  most 300 characters, and unknown keys are rejected. The server is passed to `query()` as a stdio
113
113
  `mcpServers` entry; the read-only guardrail then allows exactly `mcp__<name>__<tool>` for the listed
114
- tools and still refuses every other `mcp__*` tool, and the `wren`-only Bash rule is unchanged. Keep
114
+ tools and still refuses every other `mcp__*` tool, and the `wren`-only Bash rule is unchanged. In a
115
+ per-step-tier split the steps run as subagents with an exhaustive tool list, so every subagent that
116
+ has `Bash` (the data-access ones) also gets the listed host tools and the instruction line. That
117
+ includes the subagents of a setup or mutating component with data access: the guardrail allows the
118
+ listed host tools by exact name in every component, as it already did for the driver. Keep
115
119
  secrets out of this file — pass a token file path in `args` instead; the CLI never prints the file,
116
120
  its `args`, or the environment. Hybrid-staged plans refuse the flag. Without it, `chat` behaves
117
121
  exactly as before.
@@ -5,6 +5,12 @@ var DispatchError = class extends Error {
5
5
  this.name = "DispatchError";
6
6
  }
7
7
  };
8
+ function boundedMessage(message) {
9
+ const collapsed = message.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
10
+ const withoutPaths = collapsed.replace(/(?:^|\s)\/(?:[^\s/]+\/)*[^\s]*/g, " [redacted path]");
11
+ const safe = /\b(select|insert|update|delete|merge|alter|drop|create)\b/i.test(withoutPaths) ? "The callee did not complete the request." : withoutPaths;
12
+ return (safe || "The child run did not complete.").slice(0, 240);
13
+ }
8
14
 
9
15
  // src/codegen.ts
10
16
  function ident(verb) {
@@ -138,7 +144,7 @@ function makeReadOnlyGuard(cfg: {
138
144
  const denials: Denial[] = [];
139
145
  const hooks: never[] = [];
140
146
  const canUseTool = async (toolName: string, input: Record<string, unknown>) => {
141
- if (toolName === "Read" || toolName === "Task" || toolName === "TodoWrite") {
147
+ if (toolName === "Read" || toolName === "Task" || toolName === "Agent" || toolName === "TodoWrite") {
142
148
  return { behavior: "allow" as const, updatedInput: input };
143
149
  }
144
150
  if (toolName === "Bash") {
@@ -2121,12 +2127,17 @@ import { resolve as resolvePath, sep as pathSep } from "path";
2121
2127
  function withinScope(abs, scopeAbs) {
2122
2128
  return abs === scopeAbs || abs.startsWith(scopeAbs.endsWith(pathSep) ? scopeAbs : scopeAbs + pathSep);
2123
2129
  }
2130
+ var OUT_OF_BAND_DENIAL = {
2131
+ behavior: "deny",
2132
+ message: "the embedding host answered this permission request out-of-band, which the guardrail cannot compose with"
2133
+ };
2124
2134
  function composeCanUseTool(embedder, floor) {
2125
2135
  if (embedder === void 0) return floor;
2126
2136
  return async (toolName, input, options) => {
2127
2137
  const fromEmbedder = await embedder(toolName, input, options);
2138
+ if (fromEmbedder === null) return OUT_OF_BAND_DENIAL;
2128
2139
  if (fromEmbedder.behavior !== "allow") return fromEmbedder;
2129
- return floor(toolName, fromEmbedder.updatedInput, options);
2140
+ return floor(toolName, fromEmbedder.updatedInput ?? input, options);
2130
2141
  };
2131
2142
  }
2132
2143
  function composeHooks(embedder, guardHooks) {
@@ -2191,7 +2202,7 @@ function makeReadOnlyGuard(cfg) {
2191
2202
  }
2192
2203
  return allow(input);
2193
2204
  }
2194
- if (toolName === "Task" || toolName === "TodoWrite") {
2205
+ if (toolName === "Task" || toolName === "Agent" || toolName === "TodoWrite") {
2195
2206
  return allow(input);
2196
2207
  }
2197
2208
  if (toolName === "Bash") {
@@ -2627,7 +2638,7 @@ var ChatEventMapper = class {
2627
2638
  return [{ t: "step_start", id: this.stepId, name: this.stepId, parent: null, depth: 0 }];
2628
2639
  }
2629
2640
  onAssistant(message) {
2630
- const content = message.message?.content;
2641
+ const content = isRecord(message.message) ? message.message["content"] : void 0;
2631
2642
  if (!Array.isArray(content)) return [];
2632
2643
  const parent = message.parent_tool_use_id ?? null;
2633
2644
  const depth = parent ? 1 : 0;
@@ -2648,7 +2659,7 @@ var ChatEventMapper = class {
2648
2659
  return events;
2649
2660
  }
2650
2661
  onUser(message) {
2651
- const content = message.message?.content;
2662
+ const content = isRecord(message.message) ? message.message["content"] : void 0;
2652
2663
  if (!Array.isArray(content)) return [];
2653
2664
  const events = [];
2654
2665
  for (const block of content) {
@@ -2720,6 +2731,20 @@ function applyHostMcp(options, hostMcp) {
2720
2731
  next.systemPrompt = `${options.systemPrompt}
2721
2732
  ${hostMcp.instruction}`;
2722
2733
  }
2734
+ if (options.agents !== void 0) {
2735
+ const hostTools = hostMcpToolNames(hostMcp);
2736
+ next.agents = Object.fromEntries(
2737
+ Object.entries(options.agents).map(([name, agent]) => {
2738
+ if (!agent.tools?.includes("Bash")) return [name, agent];
2739
+ return [name, {
2740
+ ...agent,
2741
+ tools: [...agent.tools, ...hostTools],
2742
+ ...hostMcp.instruction !== void 0 ? { prompt: `${agent.prompt}
2743
+ ${hostMcp.instruction}` } : {}
2744
+ }];
2745
+ })
2746
+ );
2747
+ }
2723
2748
  return next;
2724
2749
  }
2725
2750
  var DispatchSessionError = class extends DispatchError {
@@ -2948,18 +2973,19 @@ async function runHybridStaged(plan, cfg) {
2948
2973
  continue;
2949
2974
  }
2950
2975
  if (decision.kind === "repair") {
2951
- let lastFailureText = artifacts[decision.target.produces ?? decision.target.name] ?? "";
2976
+ const originalFailureText = boundedMessage(artifacts[decision.target.produces ?? decision.target.name] ?? "");
2977
+ let lastFailureText = originalFailureText;
2952
2978
  const { recovered, attempts } = await runRepairLoop(DEFAULT_MAX_REPAIR_ATTEMPTS, async () => {
2953
2979
  const attempt = await executeStep(step, artifacts, execCtx, true);
2954
2980
  outcomes[step.name] = attempt.outcome;
2955
2981
  artifacts[artifactKey(step)] = attempt.text;
2956
2982
  if (attempt.outcome === "success") finalText = attempt.text;
2957
- else lastFailureText = attempt.text;
2983
+ else lastFailureText = boundedMessage(attempt.text);
2958
2984
  return { failed: attempt.outcome === "failure" };
2959
2985
  });
2960
2986
  if (!recovered) {
2961
2987
  throw new DispatchError(
2962
- `repair step '${step.name}' did not recover '${decision.target.name}' after ${attempts} attempt(s); last failure: ${lastFailureText}`
2988
+ `repair step '${step.name}' did not recover '${decision.target.name}' after ${attempts} attempt(s); ${decision.target.name} failed: ${originalFailureText}; last failure: ${lastFailureText}`
2963
2989
  );
2964
2990
  }
2965
2991
  process.stderr.write(
@@ -3071,12 +3097,6 @@ function assertSupportedComponentInvocationLimits(input) {
3071
3097
  function byteLength(value) {
3072
3098
  return Buffer.byteLength(JSON.stringify(value), "utf8");
3073
3099
  }
3074
- function boundedMessage(message) {
3075
- const collapsed = message.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim();
3076
- const withoutPaths = collapsed.replace(/(?:^|\s)\/(?:[^\s/]+\/)*[^\s]*/g, " [redacted path]");
3077
- const safe = /\b(select|insert|update|delete|merge|alter|drop|create)\b/i.test(withoutPaths) ? "The callee did not complete the request." : withoutPaths;
3078
- return (safe || "The child run did not complete.").slice(0, 240);
3079
- }
3080
3100
  function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
3081
3101
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
3082
3102
  if (typeof value === "number") return Number.isFinite(value);
@@ -3517,7 +3537,13 @@ var ComponentInvocationRuntime = class {
3517
3537
  if (result2.outcome === "success") finalText = result2.text;
3518
3538
  return { failed: result2.outcome === "failure" };
3519
3539
  });
3520
- if (!repair.recovered) throw new DispatchError(`callee_failed: repair step '${call.name}' did not recover`);
3540
+ if (!repair.recovered) {
3541
+ const target = decision.target;
3542
+ const original = boundedMessage(artifacts[target.produces ?? target.name] ?? "");
3543
+ throw new DispatchError(
3544
+ `callee_failed: repair step '${call.name}' did not recover; ${target.name} failed: ${original}`
3545
+ );
3546
+ }
3521
3547
  continue;
3522
3548
  }
3523
3549
  }
@@ -3716,7 +3742,7 @@ function componentStepSystemPrompt(run) {
3716
3742
  function componentTools(component) {
3717
3743
  const configured = component.plan.options.tools;
3718
3744
  if (!Array.isArray(configured)) return ["Read"];
3719
- return configured.filter((name) => name !== "Task" && name !== "Write" && name !== "Edit");
3745
+ return configured.filter((name) => name !== "Task" && name !== "Agent" && name !== "Write" && name !== "Edit");
3720
3746
  }
3721
3747
  function createSdkComponentStepRunner(cfg = {}) {
3722
3748
  const denials = [];
@@ -3764,7 +3790,8 @@ function createSdkComponentStepRunner(cfg = {}) {
3764
3790
  if (!toolNames.has(name)) return originalCanUseTool(name, input, options2);
3765
3791
  if (!cfg.hostCanUseTool) return { behavior: "allow", updatedInput: input };
3766
3792
  const permission = await cfg.hostCanUseTool(name, input, options2);
3767
- if (permission.behavior === "allow") rememberApproval(name, permission.updatedInput);
3793
+ if (permission === null) return OUT_OF_BAND_DENIAL;
3794
+ if (permission.behavior === "allow") rememberApproval(name, permission.updatedInput ?? input);
3768
3795
  return permission;
3769
3796
  };
3770
3797
  const componentCalls = run.aliases.map((alias) => tool2(
@@ -3775,14 +3802,16 @@ function createSdkComponentStepRunner(cfg = {}) {
3775
3802
  const name = `mcp__${MCP_SERVER_NAME2}__${alias}`;
3776
3803
  let input = { request: args.request, input: args.input ?? {} };
3777
3804
  if (cfg.hostCanUseTool && !consumeApproval(name, input)) {
3805
+ const toolUseID = `${run.rootRunId}:${run.callId ?? "root"}:${run.step.name}:${alias}`;
3778
3806
  const permission = await cfg.hostCanUseTool(name, input, {
3779
3807
  signal: run.signal,
3780
- toolUseID: `${run.rootRunId}:${run.callId ?? "root"}:${run.step.name}:${alias}`
3808
+ toolUseID,
3809
+ requestId: toolUseID
3781
3810
  });
3782
- if (permission.behavior !== "allow") {
3811
+ if (permission === null || permission.behavior !== "allow") {
3783
3812
  throw new DispatchError("component invocation was denied by the embedding host");
3784
3813
  }
3785
- input = permission.updatedInput;
3814
+ input = permission.updatedInput ?? input;
3786
3815
  }
3787
3816
  const result2 = await run.invoke(alias, input);
3788
3817
  return {
@@ -4662,4 +4691,4 @@ export {
4662
4691
  ChatSession,
4663
4692
  createChatSession
4664
4693
  };
4665
- //# sourceMappingURL=chunk-74JAAZB7.js.map
4694
+ //# sourceMappingURL=chunk-J53WGCGG.js.map