@rowan-agent/agent 0.35.1 → 0.35.3

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/runtime.js +48 -15
  2. package/package.json +1 -1
package/dist/runtime.js CHANGED
@@ -1767,6 +1767,17 @@ function resolveCoreToolPath(context, path = ".") {
1767
1767
  function toolResult(input) {
1768
1768
  return input.ok ? { ok: true, content: input.content } : { ok: false, content: input.content, error: input.error ?? "Tool execution failed" };
1769
1769
  }
1770
+ function isSchemaParseError(error) {
1771
+ return !(error instanceof Error) && Array.isArray(error?.errors);
1772
+ }
1773
+ function toolErrorMessage(error) {
1774
+ if (error instanceof Error) return error.message;
1775
+ if (isSchemaParseError(error) && error.errors.length > 0) {
1776
+ const { errors } = error;
1777
+ return `Invalid arguments: ${errors.map((item) => `${item.instancePath || "/"} ${item.message ?? "is invalid"}`).join("; ")}`;
1778
+ }
1779
+ return String(error);
1780
+ }
1770
1781
  async function executeRuntimeToolCall(input) {
1771
1782
  const tool = input.tools.find((candidate) => candidate.name === input.toolCall.name);
1772
1783
  if (!tool) {
@@ -1780,13 +1791,19 @@ async function executeRuntimeToolCall(input) {
1780
1791
  }
1781
1792
  let args;
1782
1793
  try {
1783
- const rawArgs = input.toolCall.arguments;
1794
+ let rawArgs = input.toolCall.arguments;
1795
+ if (typeof rawArgs === "string") {
1796
+ try {
1797
+ rawArgs = JSON.parse(rawArgs);
1798
+ } catch {
1799
+ }
1800
+ }
1784
1801
  args = validatorFor(tool.parameters).Parse(rawArgs);
1785
1802
  } catch (error) {
1786
1803
  const result = toolResult({
1787
1804
  ok: false,
1788
1805
  content: null,
1789
- error: error instanceof Error ? error.message : String(error)
1806
+ error: toolErrorMessage(error)
1790
1807
  });
1791
1808
  await input.observe?.({ type: "tool_end", toolName: tool.name, result });
1792
1809
  return result;
@@ -1963,7 +1980,7 @@ function createReadTool(context) {
1963
1980
  return toolResult({
1964
1981
  ok: false,
1965
1982
  content: null,
1966
- error: error instanceof Error ? error.message : String(error)
1983
+ error: toolErrorMessage(error)
1967
1984
  });
1968
1985
  }
1969
1986
  if (!fileStat.isFile()) {
@@ -1980,7 +1997,7 @@ function createReadTool(context) {
1980
1997
  return toolResult({
1981
1998
  ok: false,
1982
1999
  content: null,
1983
- error: error instanceof Error ? error.message : String(error)
2000
+ error: toolErrorMessage(error)
1984
2001
  });
1985
2002
  }
1986
2003
  const source = new TextDecoder().decode(bytes);
@@ -2095,7 +2112,7 @@ function createEditTool(context) {
2095
2112
  return toolResult({
2096
2113
  ok: false,
2097
2114
  content: null,
2098
- error: error instanceof Error ? error.message : String(error)
2115
+ error: toolErrorMessage(error)
2099
2116
  });
2100
2117
  }
2101
2118
  await writeFile(resolved.absolutePath, next, "utf8");
@@ -2632,9 +2649,6 @@ function createRouteToolResultContent(toolCall, error, executionContext) {
2632
2649
  ];
2633
2650
  }
2634
2651
  async function executePhaseWithModel(ctx) {
2635
- const executableToolNames = new Set(
2636
- ctx.context.tools.filter((tool) => tool.name !== PhaseRouteTool).map((tool) => tool.name)
2637
- );
2638
2652
  let output = {
2639
2653
  message: "",
2640
2654
  phase: ctx.phase.name,
@@ -2652,7 +2666,10 @@ async function executePhaseWithModel(ctx) {
2652
2666
  if (checkpoint && typeof checkpoint === "object" && checkpoint.kind === "tool_call_suspension") {
2653
2667
  const toolSuspension = checkpoint;
2654
2668
  executableToolCalls = toolSuspension.toolCalls.filter(
2655
- (toolCall) => executableToolNames.has(toolCall.name)
2669
+ (toolCall) => (
2670
+ // An unknown name still runs, so the model gets "not available" back instead of a silent end of turn.
2671
+ toolCall.name !== PhaseRouteTool
2672
+ )
2656
2673
  );
2657
2674
  output = {
2658
2675
  message: "",
@@ -2686,7 +2703,10 @@ async function executePhaseWithModel(ctx) {
2686
2703
  await ctx.messageManager.end(messageId);
2687
2704
  }
2688
2705
  executableToolCalls = collected.toolCalls.filter(
2689
- (toolCall) => executableToolNames.has(toolCall.name)
2706
+ (toolCall) => (
2707
+ // An unknown name still runs, so the model gets "not available" back instead of a silent end of turn.
2708
+ toolCall.name !== PhaseRouteTool
2709
+ )
2690
2710
  );
2691
2711
  roundContentBlocks = collected.contentBlocks;
2692
2712
  if (executableToolCalls.length === 0) {
@@ -2706,6 +2726,7 @@ async function executePhaseWithModel(ctx) {
2706
2726
  });
2707
2727
  await ctx.messageManager.end(messageId);
2708
2728
  }
2729
+ ctx.progressed = true;
2709
2730
  }
2710
2731
  }
2711
2732
  async function runTurn(fn) {
@@ -2729,6 +2750,7 @@ async function runPhaseLoop(config, state, registry) {
2729
2750
  if (resumingSuspendedRun) {
2730
2751
  state.status = "running";
2731
2752
  delete state.entryPhases;
2753
+ state.attempt = 0;
2732
2754
  }
2733
2755
  let previousPayload = resumingSuspendedRun ? state.continuation && Object.hasOwn(state.continuation, "previousPayload") ? state.continuation.previousPayload : state.initialPhasePayload : state.initialPhasePayload;
2734
2756
  let previousPhaseMsgId = resumingSuspendedRun ? state.continuation?.previousPhaseMessageId : void 0;
@@ -2973,6 +2995,7 @@ async function runPhaseLoop(config, state, registry) {
2973
2995
  }
2974
2996
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext, completedPhases };
2975
2997
  let output = await executePhase(runtime);
2998
+ if (runtime.progressed) state.attempt = 0;
2976
2999
  delete state.initialPhasePayload;
2977
3000
  delete state.entryPhases;
2978
3001
  let modelRouteDecision = resolveModelRouteDecision(output.toolCalls, registry, state);
@@ -10668,12 +10691,12 @@ var AgentRuntime = class _AgentRuntime {
10668
10691
  expectedRevision: revision,
10669
10692
  toolCallId,
10670
10693
  state: "failed",
10671
- result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` },
10694
+ result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available. Available tools: ${input.toolConfig.tools.map(({ name }) => name).join(", ")}.` },
10672
10695
  title: toolCall.name,
10673
10696
  kind: "other"
10674
10697
  });
10675
10698
  setRevision(failed.run.revision);
10676
- results2.push(protocolFailure(`Tool ${toolCall.name} is not available.`));
10699
+ results2.push(protocolFailure(`Tool ${toolCall.name} is not available. Available tools: ${input.toolConfig.tools.map(({ name }) => name).join(", ")}.`));
10677
10700
  continue;
10678
10701
  }
10679
10702
  const decision = preDecisions[i] ?? { allow: true };
@@ -10791,12 +10814,12 @@ var AgentRuntime = class _AgentRuntime {
10791
10814
  expectedRevision: revision,
10792
10815
  toolCallId,
10793
10816
  state: "failed",
10794
- result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available.` },
10817
+ result: { ok: false, content: null, error: `Tool ${toolCall.name} is not available. Available tools: ${input.toolConfig.tools.map(({ name }) => name).join(", ")}.` },
10795
10818
  title: toolCall.name,
10796
10819
  kind: "other"
10797
10820
  });
10798
10821
  setRevision(failed.run.revision);
10799
- results.push(protocolFailure(`Tool ${toolCall.name} is not available.`));
10822
+ results.push(protocolFailure(`Tool ${toolCall.name} is not available. Available tools: ${input.toolConfig.tools.map(({ name }) => name).join(", ")}.`));
10800
10823
  continue;
10801
10824
  }
10802
10825
  const pendingPres = await presentToolPending(durableTool, toolCallArgs(toolCall));
@@ -11167,7 +11190,11 @@ var AgentRuntime = class _AgentRuntime {
11167
11190
  throw error;
11168
11191
  }
11169
11192
  let result = await withAbort(
11170
- input.durableTool.execute(toolCallArgs(input.toolCall), context, effectiveSignal),
11193
+ // Invalid arguments are the model's to fix: a result it can read, not an indeterminate outcome.
11194
+ Promise.resolve(input.durableTool.execute(toolCallArgs(input.toolCall), context, effectiveSignal)).catch((error) => {
11195
+ if (isSchemaParseError(error)) return { ok: false, content: null, error: toolErrorMessage(error) };
11196
+ throw error;
11197
+ }),
11171
11198
  input.options?.signal
11172
11199
  );
11173
11200
  if (!("ok" in result)) {
@@ -11606,6 +11633,12 @@ function toJsonValue(value) {
11606
11633
  return value;
11607
11634
  }
11608
11635
  function toolCallArgs(toolCall) {
11636
+ if (typeof toolCall.arguments === "string") {
11637
+ try {
11638
+ return toJsonValue(JSON.parse(toolCall.arguments));
11639
+ } catch {
11640
+ }
11641
+ }
11609
11642
  return toJsonValue(toolCall.arguments);
11610
11643
  }
11611
11644
  function createMergedToolCall(toolCallId, status, current, fallbackTitle, fallbackKind = "other") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.35.1",
3
+ "version": "0.35.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",