@swmansion/argent 0.22.1-next.5 → 0.22.1-next.6

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/dist/cli-cmds.mjs CHANGED
@@ -2675,11 +2675,13 @@ async function applyClientFileDirectives(result) {
2675
2675
  var ToolInvocationError = class extends Error {
2676
2676
  errorCode;
2677
2677
  errorKind;
2678
+ issues;
2678
2679
  constructor(message, signal) {
2679
2680
  super(message);
2680
2681
  this.name = "ToolInvocationError";
2681
2682
  this.errorCode = signal?.errorCode;
2682
2683
  this.errorKind = signal?.errorKind;
2684
+ this.issues = signal?.issues;
2683
2685
  }
2684
2686
  };
2685
2687
  function authHeaders2(token) {
@@ -2727,6 +2729,10 @@ async function consumeToolStream(body, onProgress) {
2727
2729
  const { result: data } = await applyClientFileDirectives(final.data);
2728
2730
  return { data, note: final.note };
2729
2731
  }
2732
+ function errorBodyMessage(body) {
2733
+ if (Array.isArray(body.issues) && typeof body.message === "string") return body.message;
2734
+ return body.error ?? body.message;
2735
+ }
2730
2736
  function createToolsClient(options = {}) {
2731
2737
  let cached2 = null;
2732
2738
  async function baseUrl() {
@@ -2781,13 +2787,11 @@ function createToolsClient(options = {}) {
2781
2787
  }
2782
2788
  const json = await res.json().catch(() => ({}));
2783
2789
  if (!res.ok) {
2784
- throw new ToolInvocationError(
2785
- json.error ?? json.message ?? `${res.status} ${res.statusText}`,
2786
- {
2787
- errorCode: json.error_code,
2788
- errorKind: json.error_kind
2789
- }
2790
- );
2790
+ throw new ToolInvocationError(errorBodyMessage(json) ?? `${res.status} ${res.statusText}`, {
2791
+ errorCode: json.error_code,
2792
+ errorKind: json.error_kind,
2793
+ issues: Array.isArray(json.issues) ? json.issues : void 0
2794
+ });
2791
2795
  }
2792
2796
  const { result: data } = await applyClientFileDirectives(json.data);
2793
2797
  return { data, note: json.note };
@@ -7206,7 +7210,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
7206
7210
  var SESSION_ID2 = randomUUID5();
7207
7211
  function readCliVersion() {
7208
7212
  if (true) {
7209
- return "0.22.1-next.5";
7213
+ return "0.22.1-next.6";
7210
7214
  }
7211
7215
  return "0.0.0";
7212
7216
  }
@@ -8009,7 +8013,11 @@ function findMissingRequired(payload, schema) {
8009
8013
  }
8010
8014
  return names.filter((name) => !Object.hasOwn(payload, name));
8011
8015
  }
8012
- function describeServerValidationFailure(err, payload, schema) {
8016
+ function serverIssueList(err) {
8017
+ const carried = err?.issues;
8018
+ if (Array.isArray(carried)) {
8019
+ return carried.length > 0 && carried.every(isValidationIssue) ? carried : null;
8020
+ }
8013
8021
  const message = err instanceof Error ? err.message : typeof err === "string" ? err : null;
8014
8022
  if (message === null) return null;
8015
8023
  let parsed;
@@ -8019,7 +8027,11 @@ function describeServerValidationFailure(err, payload, schema) {
8019
8027
  return null;
8020
8028
  }
8021
8029
  if (!Array.isArray(parsed) || parsed.length === 0) return null;
8022
- if (!parsed.every(isValidationIssue)) return null;
8030
+ return parsed.every(isValidationIssue) ? parsed : null;
8031
+ }
8032
+ function describeServerValidationFailure(err, payload, schema) {
8033
+ const parsed = serverIssueList(err);
8034
+ if (parsed === null) return null;
8023
8035
  const properties = schema?.properties ?? {};
8024
8036
  const addressesThisTool = (issue) => issue.path.length === 0 || typeof issue.path[0] === "string" && Object.hasOwn(properties, issue.path[0]);
8025
8037
  if (!parsed.every(addressesThisTool)) return null;
@@ -16428,7 +16428,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16428
16428
  var SESSION_ID = randomUUID4();
16429
16429
  function readCliVersion() {
16430
16430
  if (true) {
16431
- return "0.22.1-next.5";
16431
+ return "0.22.1-next.6";
16432
16432
  }
16433
16433
  return "0.0.0";
16434
16434
  }
@@ -16855,6 +16855,12 @@ async function applyClientFileDirectives(result) {
16855
16855
  return { result: rewritten, written };
16856
16856
  }
16857
16857
 
16858
+ // ../argent-tools-client/src/tools-client.ts
16859
+ function errorBodyMessage(body) {
16860
+ if (Array.isArray(body.issues) && typeof body.message === "string") return body.message;
16861
+ return body.error ?? body.message;
16862
+ }
16863
+
16858
16864
  // ../argent-tools-client/src/artifacts.ts
16859
16865
  import { copyFile, mkdir as mkdir4, readFile as readFile4, realpath, rm as rm3, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
16860
16866
  import { constants as fsConstants } from "node:fs";
@@ -18685,7 +18691,7 @@ async function startMcpServer(options) {
18685
18691
  fetchTimeoutMs: meta2?.longRunning ? null : FETCH_TIMEOUT_MS
18686
18692
  });
18687
18693
  const json = await res.json();
18688
- if (!res.ok) throw new Error(json.error ?? json.message ?? res.statusText);
18694
+ if (!res.ok) throw new Error(errorBodyMessage(json) ?? res.statusText);
18689
18695
  const { result: data } = await applyClientFileDirectives(json.data);
18690
18696
  return { result: data, outputHint: meta2?.outputHint, note: json.note };
18691
18697
  }
@@ -16063,6 +16063,63 @@ function terminatingSignalCause(message) {
16063
16063
  error_kind: "unknown"
16064
16064
  });
16065
16065
  }
16066
+ function valueAtPath(root, path41) {
16067
+ let current = root;
16068
+ for (const key2 of path41) {
16069
+ if (current === null || typeof current !== "object") return void 0;
16070
+ if (!Object.hasOwn(current, key2)) return void 0;
16071
+ current = current[key2];
16072
+ }
16073
+ return current;
16074
+ }
16075
+ function describeParamIssues(error52, params) {
16076
+ const allKeys = params !== null && typeof params === "object" && !Array.isArray(params) ? Object.keys(params) : [];
16077
+ const supplied = allKeys.slice(0, 24);
16078
+ const truncated = allKeys.length > supplied.length;
16079
+ const parts2 = error52.issues.map((issue2) => {
16080
+ const at = issue2.path.length > 0 ? issue2.path.join(".") : "(root)";
16081
+ if (issue2.code === "custom") {
16082
+ return issue2.path.length > 0 ? `\`${at}\`: ${issue2.message}` : issue2.message;
16083
+ }
16084
+ if (valueAtPath(params, issue2.path) === void 0) {
16085
+ const expected = issue2.expected;
16086
+ const kind = typeof expected === "string" ? ` (${expected})` : "";
16087
+ return `\`${at}\` is required${kind} and was not provided`;
16088
+ }
16089
+ if (issue2.code === "unrecognized_keys") {
16090
+ const keys = issue2.keys ?? [];
16091
+ const at2 = issue2.path.length > 0 ? `${issue2.path.join(".")}.` : "";
16092
+ return `unknown parameter${keys.length === 1 ? "" : "s"} ${keys.map((k) => `\`${at2}${k}\``).join(", ")}`;
16093
+ }
16094
+ if (issue2.code === "invalid_union") {
16095
+ const branches = issue2.errors ?? [];
16096
+ const alternatives = [];
16097
+ const seen = /* @__PURE__ */ new Set();
16098
+ let moreAlternatives = false;
16099
+ for (const branch of branches) {
16100
+ for (const inner of branch) {
16101
+ const innerAt = inner.path.length > 0 ? `${at}.${inner.path.join(".")}: ` : "";
16102
+ const text = `${innerAt}${inner.message}`;
16103
+ if (seen.has(text)) continue;
16104
+ if (alternatives.length >= MAX_UNION_ALTERNATIVES) {
16105
+ moreAlternatives = true;
16106
+ break;
16107
+ }
16108
+ seen.add(text);
16109
+ alternatives.push(text);
16110
+ }
16111
+ if (moreAlternatives) break;
16112
+ }
16113
+ if (alternatives.length > 0) {
16114
+ return `\`${at}\`: ${alternatives.join("; or ")}${moreAlternatives ? "; or \u2026" : ""}`;
16115
+ }
16116
+ }
16117
+ return `\`${at}\`: ${issue2.message}`;
16118
+ });
16119
+ const sent = supplied.length > 0 ? ` You sent: ${supplied.map((k) => `\`${k}\``).join(", ")}${truncated ? ", \u2026" : ""}.` : "";
16120
+ const body = parts2.length > 0 ? `${parts2.map((p) => p.replace(/\.$/, "")).join("; ")}.` : "";
16121
+ return `${body}${sent}`.trim() || "invalid parameters";
16122
+ }
16066
16123
  function formatInteractionMessage(format, fallback) {
16067
16124
  try {
16068
16125
  return format() ?? fallback;
@@ -16070,7 +16127,7 @@ function formatInteractionMessage(format, fallback) {
16070
16127
  return fallback;
16071
16128
  }
16072
16129
  }
16073
- var import_node_crypto2, Registry;
16130
+ var import_node_crypto2, Registry, MAX_UNION_ALTERNATIVES;
16074
16131
  var init_registry = __esm({
16075
16132
  "../registry/src/registry.ts"() {
16076
16133
  "use strict";
@@ -16151,7 +16208,15 @@ var init_registry = __esm({
16151
16208
  if (definition.zodSchema) {
16152
16209
  const parsed = definition.zodSchema.safeParse(params ?? {});
16153
16210
  if (!parsed.success) {
16154
- throw new Error(`Invalid params for tool "${id}": ${parsed.error.message}`);
16211
+ throw new FailureError(
16212
+ `Invalid params for tool "${id}": ${describeParamIssues(parsed.error, params)}`,
16213
+ {
16214
+ error_code: FAILURE_CODES.TOOL_INPUT_INVALID,
16215
+ failure_stage: "tool_params_parse",
16216
+ failure_area: "registry",
16217
+ error_kind: "validation"
16218
+ }
16219
+ );
16155
16220
  }
16156
16221
  effectiveParams = parsed.data;
16157
16222
  }
@@ -16388,6 +16453,7 @@ var init_registry = __esm({
16388
16453
  this._transition(node, cause ? "ERROR" /* ERROR */ : "IDLE" /* IDLE */, cause);
16389
16454
  }
16390
16455
  };
16456
+ MAX_UNION_ALTERNATIVES = 12;
16391
16457
  }
16392
16458
  });
16393
16459
 
@@ -95668,7 +95734,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
95668
95734
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
95669
95735
  function readCliVersion() {
95670
95736
  if (true) {
95671
- return "0.22.1-next.5";
95737
+ return "0.22.1-next.6";
95672
95738
  }
95673
95739
  return "0.0.0";
95674
95740
  }
@@ -113875,14 +113941,23 @@ async function resolveFileInputs(def, body, lookupUpload) {
113875
113941
  };
113876
113942
  const specs = def.fileInputs;
113877
113943
  if (!specs || specs.length === 0 || typeof body !== "object" || body === null) {
113878
- return { args: body ?? {}, fileInputs: void 0, cleanup };
113944
+ return {
113945
+ args: body ?? {},
113946
+ fileInputs: void 0,
113947
+ derivedTargets: [],
113948
+ cleanup
113949
+ };
113879
113950
  }
113880
113951
  const args = { ...body };
113881
113952
  let resolved;
113953
+ const derivedTargets = [];
113882
113954
  try {
113883
113955
  for (const spec of specs) {
113884
113956
  const value = args[spec.target];
113885
113957
  if (!isFileInputWire(value)) continue;
113958
+ if (spec.path !== `\${${spec.target}}` && !derivedTargets.includes(spec.target)) {
113959
+ derivedTargets.push(spec.target);
113960
+ }
113886
113961
  if (spec.unwrapWhenSet !== void 0 && isParamSet(args[spec.unwrapWhenSet])) {
113887
113962
  args[spec.target] = value.path;
113888
113963
  continue;
@@ -113899,7 +113974,7 @@ async function resolveFileInputs(def, body, lookupUpload) {
113899
113974
  await cleanup();
113900
113975
  throw err;
113901
113976
  }
113902
- return { args, fileInputs: resolved, cleanup };
113977
+ return { args, fileInputs: resolved, derivedTargets, cleanup };
113903
113978
  }
113904
113979
 
113905
113980
  // ../tool-server/src/utils/debugger/device-alias.ts
@@ -114190,6 +114265,14 @@ function isToolExposed(def) {
114190
114265
  function findDependencyMissing(err) {
114191
114266
  return findErrorInCauseChain(err, DependencyMissingError);
114192
114267
  }
114268
+ function omitKeys(args, keys) {
114269
+ if (keys.length === 0 || args === null || typeof args !== "object" || Array.isArray(args)) {
114270
+ return args;
114271
+ }
114272
+ const copy = { ...args };
114273
+ for (const key2 of keys) delete copy[key2];
114274
+ return copy;
114275
+ }
114193
114276
  function errorSignalFields(err) {
114194
114277
  const signal = getFailureSignal(err);
114195
114278
  return signal ? { error_code: signal.error_code, error_kind: signal.error_kind } : {};
@@ -114529,6 +114612,7 @@ function createHttpApp(registry2, options) {
114529
114612
  }
114530
114613
  let bodyArgs;
114531
114614
  let resolvedFileInputs;
114615
+ let derivedTargets;
114532
114616
  try {
114533
114617
  const resolved = await resolveFileInputs(def, req.body, (id) => {
114534
114618
  const entry = uploads.get(id);
@@ -114537,6 +114621,7 @@ function createHttpApp(registry2, options) {
114537
114621
  });
114538
114622
  bodyArgs = resolved.args;
114539
114623
  resolvedFileInputs = resolved.fileInputs;
114624
+ derivedTargets = resolved.derivedTargets;
114540
114625
  res.once("close", () => void resolved.cleanup());
114541
114626
  } catch (err) {
114542
114627
  if (err instanceof FileInputError) {
@@ -114560,7 +114645,11 @@ function createHttpApp(registry2, options) {
114560
114645
  req.body,
114561
114646
  { invalid_params: deriveInvalidParams(parseResult.error, declared) }
114562
114647
  );
114563
- res.status(400).json({ error: parseResult.error.message });
114648
+ res.status(400).json({
114649
+ error: parseResult.error.message,
114650
+ message: describeParamIssues(parseResult.error, omitKeys(bodyArgs, derivedTargets)),
114651
+ issues: parseResult.error.issues
114652
+ });
114564
114653
  return;
114565
114654
  }
114566
114655
  parsedData = parseResult.data;
@@ -114701,6 +114790,10 @@ function createHttpApp(registry2, options) {
114701
114790
  res.status(400).json({ error: invalidInputErr.message, ...errorSignalFields(err) });
114702
114791
  return;
114703
114792
  }
114793
+ if (getFailureSignal(err)?.error_code === FAILURE_CODES.TOOL_INPUT_INVALID) {
114794
+ res.status(400).json({ error: formatErrorForAgent(err), ...errorSignalFields(err) });
114795
+ return;
114796
+ }
114704
114797
  const notImplementedErr = findErrorInCauseChain(err, NotImplementedOnPlatformError);
114705
114798
  if (notImplementedErr) {
114706
114799
  res.status(501).json({
@@ -123254,6 +123347,7 @@ init_zod();
123254
123347
 
123255
123348
  // ../tool-server/src/utils/sub-invoke.ts
123256
123349
  var import_node_crypto7 = require("node:crypto");
123350
+ init_src();
123257
123351
  async function invokeSubTool(registry2, ctx, toolId, args) {
123258
123352
  const signal = ctx?.signal;
123259
123353
  const recordChildInvocation = ctx?.recordChildInvocation;
@@ -123272,6 +123366,14 @@ async function invokeSubTool(registry2, ctx, toolId, args) {
123272
123366
  release();
123273
123367
  }
123274
123368
  }
123369
+ function describeNestedParamError(registry2, err, toolId, dispatchedArgs, authoredArgs) {
123370
+ if (getFailureSignal(err)?.error_code !== FAILURE_CODES.TOOL_INPUT_INVALID) return void 0;
123371
+ const zodSchema76 = registry2.getTool(toolId)?.zodSchema;
123372
+ if (!zodSchema76) return void 0;
123373
+ const parsed = zodSchema76.safeParse(dispatchedArgs ?? {});
123374
+ if (parsed.success) return void 0;
123375
+ return `Invalid params for tool "${toolId}": ${describeParamIssues(parsed.error, authoredArgs)}`;
123376
+ }
123275
123377
 
123276
123378
  // ../tool-server/src/tools/await-ui-element/index.ts
123277
123379
  init_zod();
@@ -129750,8 +129852,8 @@ Stops on the first error (or unmet await-ui-element condition) and returns parti
129750
129852
  throw err;
129751
129853
  }
129752
129854
  }
129855
+ const toolArgs = { ...step.args, udid };
129753
129856
  try {
129754
- const toolArgs = { ...step.args, udid };
129755
129857
  const result = await invokeSubTool(registry2, ctx, step.tool, toolArgs);
129756
129858
  if (isUnmetUiWaitResult(step.tool, result)) {
129757
129859
  const note = result.note;
@@ -129763,9 +129865,16 @@ Stops on the first error (or unmet await-ui-element condition) and returns parti
129763
129865
  }
129764
129866
  results.push({ tool: step.tool, result });
129765
129867
  } catch (err) {
129868
+ const reframed = describeNestedParamError(
129869
+ registry2,
129870
+ err,
129871
+ step.tool,
129872
+ toolArgs,
129873
+ step.args ?? {}
129874
+ );
129766
129875
  results.push({
129767
129876
  tool: step.tool,
129768
- error: err instanceof Error ? err.message : String(err)
129877
+ error: reframed ?? (err instanceof Error ? err.message : String(err))
129769
129878
  });
129770
129879
  break;
129771
129880
  }
@@ -145870,7 +145979,9 @@ var zodSchema63 = external_exports.object({
145870
145979
  project_root: external_exports.string().describe(
145871
145980
  "Absolute path to the project root of the flow being recorded \u2014 the same value passed to flow-start-recording. Together with `name` it identifies which recording this step belongs to."
145872
145981
  ),
145873
- command: external_exports.string().describe('MCP tool name (e.g. "gesture-tap", "screenshot", "launch-app")'),
145982
+ command: external_exports.string().describe(
145983
+ 'MCP tool name (e.g. "gesture-tap", "screenshot", "launch-app") \u2014 a TOOL, not a flow directive. A flow-file directive name ("tap", "launch", "run", "type", "await", "assert", "pinch", "echo", "wait", "long-press", "scroll-to", "snapshot", "when") is answered with guidance, and nothing runs or is recorded: most name the tool that records the directive, while "wait", "long-press", "scroll-to", "snapshot" and "when" have no recording tool at all and are answered with what to do instead. A recording tool (flow-add-step, flow-add-echo, flow-start-recording, flow-finish-recording) is refused the same way, each for its own reason \u2014 nesting one would erase this flow at replay, end the take, or write the step twice.'
145984
+ ),
145874
145985
  args: external_exports.string().optional().describe(
145875
145986
  `Tool arguments as a JSON string, e.g. '{"udid": "ABC", "x": 0.5, "y": 0.3}'. Omit for tools with no arguments.`
145876
145987
  ),
@@ -146070,6 +146181,77 @@ async function captureTapSelector(registry2, session, udid, point) {
146070
146181
  };
146071
146182
  }
146072
146183
  }
146184
+ async function activeFlowState(session) {
146185
+ if (session.persist === "host") {
146186
+ try {
146187
+ session.flow = parseFlow(await fs48.readFile(session.filePath, "utf8"));
146188
+ } catch (err) {
146189
+ return {
146190
+ stepCount: session.flow.steps.length,
146191
+ note: `The persisted flow could not be read and parsed (${err instanceof Error ? err.message : String(err)}); the step count is from the last valid in-memory snapshot.`
146192
+ };
146193
+ }
146194
+ }
146195
+ return { stepCount: session.flow.steps.length };
146196
+ }
146197
+ async function recordNothing(session, guidance) {
146198
+ const { stepCount, note } = await activeFlowState(session);
146199
+ return {
146200
+ message: `${guidance} Nothing was executed and no step was recorded.${note ? ` ${note}` : ""}`,
146201
+ toolResult: void 0,
146202
+ stepCount,
146203
+ savedTo: session.filePath
146204
+ };
146205
+ }
146206
+ var DIRECTIVE_COMMAND_HINTS = {
146207
+ tap: { tool: "gesture-tap", rewritten: true },
146208
+ launch: {
146209
+ tool: "restart-app",
146210
+ rewritten: true,
146211
+ rewriteCondition: "when it carries only the bundle id (a call with an extra arg, e.g. an Android `activity`, is kept as a raw `tool: restart-app` step to convert during polish)"
146212
+ },
146213
+ run: {
146214
+ tool: "flow-execute",
146215
+ rewritten: true,
146216
+ rewriteCondition: "when the target resolves as a sibling flow in this recording's folder \u2014 a `name` that does not is kept as a raw `tool: flow-execute` step, and so is every target in a REMOTE recording (`run:` composition is host-resolved, so the host cannot validate the client's siblings); a `flow_path` that is not a sibling is refused outright and records nothing"
146217
+ },
146218
+ type: { tool: "keyboard", rewritten: false },
146219
+ await: { tool: AWAIT_UI_ELEMENT_TOOL_ID, rewritten: false },
146220
+ assert: { tool: AWAIT_UI_ELEMENT_TOOL_ID, rewritten: false },
146221
+ pinch: { tool: "gesture-pinch", rewritten: false }
146222
+ };
146223
+ var NESTED_RECORDER_TOOLS = {
146224
+ "flow-add-echo": "`flow-add-echo` records a step itself, so it must be called DIRECTLY, not through flow-add-step \u2014 nesting it would write the echo AND a `tool: flow-add-echo` step that fails on every replay.",
146225
+ "flow-add-step": "flow-add-step cannot record itself. Pass the MCP tool you want to execute as `command`.",
146226
+ "flow-start-recording": "`flow-start-recording` truncates the flow it names. Recording it as a step would erase this flow at replay; call it directly when you want to start a recording.",
146227
+ "flow-finish-recording": "`flow-finish-recording` ends the recording, so it cannot also be a step in it. Call it directly when the walkthrough is complete."
146228
+ };
146229
+ function isToolNotFound(err, command) {
146230
+ return err instanceof ToolNotFoundError && err.toolId === command;
146231
+ }
146232
+ function directiveCommandHint(command) {
146233
+ if (command === "echo") {
146234
+ return `"echo" is a flow directive, not a tool. Call \`flow-add-echo\` DIRECTLY \u2014 not through flow-add-step, which would run it as a nested tool AND record a \`tool: flow-add-echo\` step that fails on every replay.`;
146235
+ }
146236
+ if (command === "wait") {
146237
+ return `"wait" is a flow directive, not a tool, and there is no tool that records one \u2014 a fixed sleep is not a readiness signal. Record the thing you are actually waiting for with \`${AWAIT_UI_ELEMENT_TOOL_ID}\` instead.`;
146238
+ }
146239
+ if (command === "long-press") {
146240
+ return `"long-press" is a flow directive, not a tool, and no tool records one \u2014 there is no gesture-long-press. Record the rest of the path, then add the \`long-press:\` step by hand during polish and prove it with the replay.`;
146241
+ }
146242
+ if (command === "scroll-to") {
146243
+ return `"scroll-to" is a flow directive, not a tool, and no tool records one \u2014 it SEARCHES, scrolling until the target is visible, which no single recorded gesture reproduces. Record the movement with \`gesture-swipe\` (\`gesture-scroll\` on chromium) if the path needs it, then add the \`scroll-to:\` step by hand during polish and prove it with the replay.`;
146244
+ }
146245
+ if (command === "snapshot") {
146246
+ return `"snapshot" is a flow directive, not a tool, and no tool records one \u2014 it compares the screen against a stored baseline, which \`screenshot-diff\` does not manage. Add the \`snapshot:\` step by hand during polish, then adopt its baseline with a run that sets updateBaselines, and review the PNG before committing it.`;
146247
+ }
146248
+ if (command === "when") {
146249
+ return `"when" is a flow directive, not a tool, and no tool records one \u2014 it GUARDS the steps nested under it, so there is no action of its own to run. Record those steps, then wrap them in the \`when:\` block by hand during polish and prove both branches with the replay.`;
146250
+ }
146251
+ const hint = Object.hasOwn(DIRECTIVE_COMMAND_HINTS, command) ? DIRECTIVE_COMMAND_HINTS[command] : void 0;
146252
+ if (!hint) return void 0;
146253
+ return `"${command}" is a flow directive, not a tool. Record it by calling \`${hint.tool}\` through flow-add-step` + (hint.rewritten ? ` \u2014 the recorder rewrites it into the \`${command}:\` step ${hint.rewriteCondition ?? "for you"}. Where the call is recorded at all, a \`delayMs\` on it opts out of the rewrite: the step is then kept in its raw \`tool: ${hint.tool}\` form (a replay delay has no directive form), so leave \`delayMs\` off if you want the \`${command}:\` step.` : `. It is stored as a raw \`tool: ${hint.tool}\` step; converting it to \`${command}:\` is part of the polish pass.`);
146254
+ }
146073
146255
  var RUN_TARGET_COMMAND = "flow-execute";
146074
146256
  async function rewriteSiblingFlowPath(session, args) {
146075
146257
  const flowPath = args.flow_path;
@@ -146187,12 +146369,12 @@ function createFlowAddStepTool(registry2) {
146187
146369
  // Name the flow: recordings are concurrent, so several of these lines can
146188
146370
  // interleave in one log and "the recorded flow" would not say which.
146189
146371
  startedMsg: ({ params }) => `Adding ${params.command} step to flow ${params.name}`,
146190
- completedMsg: ({ params }) => `Added ${params.command} step to flow ${params.name}`,
146372
+ completedMsg: ({ params, result }) => result.recorded === void 0 ? `Recorded no ${params.command} step in flow ${params.name}` : `Added ${params.command} step to flow ${params.name}`,
146191
146373
  failedMsg: ({ params, failureSignal: failureSignal2 }) => `Failed to add ${params.command} step to flow ${params.name}: ${failureSignal2.error_code}`
146192
146374
  },
146193
146375
  description: `Execute a tool call and record it as a step in the flow named by \`name\` + \`project_root\` (the recording must already be open \u2014 see flow-start-recording). Use when recording a flow and you want to run and capture each action. A coordinate \`gesture-tap\` is recorded as a portable \`tap: { selector }\` step when the tapped element has stable text/identifier (otherwise coordinates are kept with a warning); a \`restart-app\` is recorded as a \`launch\` step (record one FIRST to make the flow a self-contained e2e flow; restart-app has no chromium support, so a chromium flow records as a fragment \u2014 add the \`launch: { chromium: <app path> }\` line to the YAML afterward, deleting the executionPrerequisite line if one was recorded: a flow that starts with a launch must not declare it).
146194
146376
  A recorded \`await-ui-element\` that PASSED is re-probed against the tree the RUNNER resolves \`await:\`/\`assert:\` directives against, which is NOT the tree the live call read; a wait that came back \`{ success: false }\` is not probed at all, and its warning says so; when the condition does not hold there the step is still recorded and \`message\` carries a warning to read before converting \u2014 whether the conversion actually breaks depends on WHY the two disagree, since a screen that moved on between the live wait and the re-probe reads the same way. If that tree could not be read at all, the warning says so instead: the conversion is UNKNOWN, not known-bad. The probe judges the selector exactly as recorded, so write the conversion in the strict map spelling (\`{ visible: { text: Continue } }\`, copying the step's \`selector:\`) \u2014 the bare-string spelling (\`{ visible: Continue }\`) re-parses as a loose selector that resolves identifier-first and falls back to text, which is a different check. \`message\` also warns when the live wait itself came back \`{ success: false }\` \u2014 that tool reports a failed wait by returning rather than throwing, so the step is recorded either way. That warning names the cause, because only one of them judges the condition: a genuine miss will stop the run at replay, while a wait whose tree source was unreadable, or one that was cancelled, observed nothing and leaves the condition UNKNOWN.
146195
- Returns { message, toolResult, stepCount, recorded, savedTo } on success \u2014 \`message\` is \`Step added to "<name>" flow\` plus any warning about what was recorded (read it; a warning never means the step was skipped). If it fails an error is returned and nothing is recorded.
146377
+ Returns { message, toolResult, stepCount, recorded, savedTo } on success \u2014 \`message\` is \`Step added to "<name>" flow\` plus any warning about what was recorded (read it; a warning never means the step was skipped). If it fails an error is returned and nothing is recorded. Two calls SUCCEED while recording nothing, and omit \`recorded\` to say so: a \`command\` naming a recording tool, and one naming a flow-file directive rather than a tool. Both answer with what to do instead \u2014 usually the call to make (the tool that records that directive, or the recording tool called directly), but \`wait\`, \`long-press\`, \`scroll-to\`, \`snapshot\` and \`when\` have no recording tool, so those name no call and say what to record or add by hand in its place. Either way nothing runs at the device and the take is left untouched \u2014 read \`recorded\`, not the status, to know whether a step was appended.
146196
146378
  If a step was recorded by mistake, remove it from the .yaml after \`flow-finish-recording\` rather than during the recording: against a remote client the in-memory copy is authoritative and every write serializes it over your edit, and in host mode a mid-recording edit renumbers the steps, which costs the finish the cross-tree verdicts anchored to them.`,
146197
146379
  // The recorded tool RUNS here, so this call lasts as long as whatever it
146198
146380
  // wraps, and the three it most often wraps declare this too. Without it the
@@ -146204,7 +146386,19 @@ If a step was recorded by mistake, remove it from the .yaml after \`flow-finish-
146204
146386
  services: () => ({}),
146205
146387
  async execute(_services, params, ctx) {
146206
146388
  const session = await requireRecordingSession(params.project_root, params.name);
146207
- const args = params.args ? JSON.parse(params.args) : {};
146389
+ const nested = Object.hasOwn(NESTED_RECORDER_TOOLS, params.command) ? NESTED_RECORDER_TOOLS[params.command] : void 0;
146390
+ if (nested) return recordNothing(session, nested);
146391
+ let args;
146392
+ try {
146393
+ args = params.args ? JSON.parse(params.args) : {};
146394
+ } catch (err) {
146395
+ if (registry2.getTool(params.command) === void 0) {
146396
+ const hint = directiveCommandHint(params.command);
146397
+ if (hint) return recordNothing(session, hint);
146398
+ }
146399
+ throw err;
146400
+ }
146401
+ const authoredArgs = { ...args };
146208
146402
  if (params.command === RUN_TARGET_COMMAND) await rewriteSiblingFlowPath(session, args);
146209
146403
  const isTap = params.command === "gesture-tap" && params.delayMs === void 0 && typeof args.udid === "string" && typeof args.x === "number" && typeof args.y === "number";
146210
146404
  let captured;
@@ -146214,7 +146408,27 @@ If a step was recorded by mistake, remove it from the .yaml after \`flow-finish-
146214
146408
  y: args.y
146215
146409
  });
146216
146410
  }
146217
- const toolResult = await invokeSubTool(registry2, ctx, params.command, args);
146411
+ let toolResult;
146412
+ try {
146413
+ toolResult = await invokeSubTool(registry2, ctx, params.command, args);
146414
+ } catch (err) {
146415
+ const hint = isToolNotFound(err, params.command) ? directiveCommandHint(params.command) : void 0;
146416
+ if (hint) return recordNothing(session, hint);
146417
+ const reframed = describeNestedParamError(
146418
+ registry2,
146419
+ err,
146420
+ params.command,
146421
+ args,
146422
+ authoredArgs
146423
+ );
146424
+ if (reframed === void 0) throw err;
146425
+ throw new FailureError(reframed, {
146426
+ error_code: FAILURE_CODES.TOOL_INPUT_INVALID,
146427
+ failure_stage: "flow_add_step_nested_params",
146428
+ failure_area: "tool_server",
146429
+ error_kind: "validation"
146430
+ });
146431
+ }
146218
146432
  let waitWarning;
146219
146433
  if (params.command === AWAIT_UI_ELEMENT_TOOL_ID) {
146220
146434
  if (isUnmetUiWaitResult(params.command, toolResult)) {
@@ -148756,8 +148970,10 @@ var zodSchema65 = external_exports.object({
148756
148970
  if (params.name === void 0 === (params.flow_path === void 0)) {
148757
148971
  ctx.addIssue({
148758
148972
  code: external_exports.ZodIssueCode.custom,
148759
- message: "Pass exactly one flow source: name or flow_path.",
148760
- path: ["flow_path"]
148973
+ message: params.name !== void 0 ? "Pass exactly one flow source: name or flow_path." : "Pass exactly one flow source: name or flow_path. flow-execute needs the flow's name in `name` \u2014 it resolves <project_root>/.argent/flows/<name>.yaml.",
148974
+ // The ROOT, not `flow_path`: the rule spans both source fields, and a
148975
+ // path would prefix the message with "`flow_path`:".
148976
+ path: []
148761
148977
  });
148762
148978
  }
148763
148979
  });
@@ -149847,7 +150063,8 @@ async function execLeafStep(state3, step, index, scope) {
149847
150063
  }
149848
150064
  return { ...base, status: "pass", tool: step.name, result, outputHint, args };
149849
150065
  } catch (err) {
149850
- return { ...base, status: "error", tool: step.name, reason: errMsg3(err) };
150066
+ const reframed = describeNestedParamError(registry2, err, step.name, args, step.args ?? {});
150067
+ return { ...base, status: "error", tool: step.name, reason: reframed ?? errMsg3(err) };
149851
150068
  }
149852
150069
  }
149853
150070
  default:
@@ -149995,8 +150212,10 @@ var zodSchema66 = external_exports.object({
149995
150212
  if (params.name === void 0 === (params.flow_path === void 0)) {
149996
150213
  ctx.addIssue({
149997
150214
  code: external_exports.ZodIssueCode.custom,
149998
- message: "Pass exactly one flow source: name or flow_path.",
149999
- path: ["flow_path"]
150215
+ message: params.name !== void 0 ? "Pass exactly one flow source: name or flow_path." : "Pass exactly one flow source: name or flow_path. flow-read-prerequisite needs the flow's name in `name` \u2014 it resolves <project_root>/.argent/flows/<name>.yaml.",
150216
+ // The ROOT, matching flow-execute: the rule spans both source fields,
150217
+ // so it must not be anchored on one of them.
150218
+ path: []
150000
150219
  });
150001
150220
  }
150002
150221
  });
@@ -150028,7 +150247,7 @@ Use when you need to check what app/simulator state is required before executing
150028
150247
  source (name or flow_path) you will pass to flow-execute, so the prerequisite you read is the contract of
150029
150248
  the flow that will actually run.
150030
150249
  Fails if the flow file does not exist.
150031
- Address the flow exactly as you will address it in flow-execute: name or flow_path, one and only one; supplying both or neither is rejected.`,
150250
+ Address the flow exactly as you will address it in flow-execute: name or flow_path, one and only one; supplying both or neither is rejected. The name goes in \`name\`, which resolves <project_root>/.argent/flows/<name>.yaml.`,
150032
150251
  zodSchema: zodSchema66,
150033
150252
  fileInputs: fileInputs4,
150034
150253
  services: () => ({}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.22.1-next.5",
3
+ "version": "0.22.1-next.6",
4
4
  "mcpName": "io.github.software-mansion/argent",
5
5
  "description": "MCP server for iOS Simulator and Android Emulator control",
6
6
  "license": "Apache-2.0",