@swmansion/argent 0.27.1-next.17 → 0.27.1-next.18

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,7 +1,7 @@
1
1
  {
2
2
  "packageName": "com.argent.androiddevtools",
3
3
  "instrumentationRunner": "com.argent.androiddevtools/.SnapshotInstrumentation",
4
- "versionName": "0.1.0",
5
- "versionCode": 1,
4
+ "versionName": "0.2.0",
5
+ "versionCode": 2,
6
6
  "installFlags": ["-r", "-t"]
7
7
  }
Binary file
Binary file
Binary file
Binary file
package/dist/cli-cmds.mjs CHANGED
@@ -21916,7 +21916,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
21916
21916
  var SESSION_ID2 = randomUUID5();
21917
21917
  function readCliVersion() {
21918
21918
  if (true) {
21919
- return "0.27.1-next.17";
21919
+ return "0.27.1-next.18";
21920
21920
  }
21921
21921
  return "0.0.0";
21922
21922
  }
@@ -23267,8 +23267,11 @@ file, or a device it cannot resolve. A transport failure, a rejection the server
23267
23267
  does not mark as validation, or a reply that is not a report stops the batch and
23268
23268
  counts the remaining flows skipped.
23269
23269
 
23270
- Runs require the auto-started local tool server;
23271
- ARGENT_TOOLS_URL and \`argent link\` routing are not supported.
23270
+ The CLI sends a run to its tool-server: the local one that starts
23271
+ automatically, or the one that \`argent link\` or ARGENT_TOOLS_URL names. With
23272
+ either, the CLI uploads the flow file, and the tool-server rejects a flow with
23273
+ run:, script: or snapshot: steps, or with tool: steps that take a file or
23274
+ record a flow, before the first step. With neither, all step kinds run.
23272
23275
 
23273
23276
  Subcommands:
23274
23277
  run <flow|flow.yaml|dir> Run a saved flow by name, a YAML file by path, or
@@ -23712,13 +23715,6 @@ async function collectFlowFiles(dir, recursive) {
23712
23715
  await walk(dir, "");
23713
23716
  return found.sort();
23714
23717
  }
23715
- async function requireLocalToolServer() {
23716
- const routing = await getResolvedToolsUrl();
23717
- if (routing.source === "none") return void 0;
23718
- const recovery = routing.source === "env" ? routing.shadowedLink ? `Unset ARGENT_TOOLS_URL and run \`argent unlink\`, then try again \u2014 a link to ${routing.shadowedLink.url} is also configured and takes over once the env var is unset.` : "Unset ARGENT_TOOLS_URL and try again." : "Run `argent unlink` and try again.";
23719
- return `argent flow run requires the auto-started local tool server; ${routing.source} routing is configured.
23720
- ${recovery}`;
23721
- }
23722
23718
  function buildRunPayload(flowPath, projectRoot, args) {
23723
23719
  const payload = {
23724
23720
  flow_path: flowPath,
@@ -23734,16 +23730,51 @@ function buildRunPayload(flowPath, projectRoot, args) {
23734
23730
  function writeJsonStreamRecord(record2) {
23735
23731
  console.log(JSON.stringify(record2));
23736
23732
  }
23733
+ var ToolServerUnreachableError = class extends Error {
23734
+ errorCode = "TOOL_SERVER_UNREACHABLE";
23735
+ errorKind = "network";
23736
+ };
23737
+ var UNREACHABLE_VERDICT = "not run (tool-server unreachable)";
23738
+ var CONNECT_FAILURES = /* @__PURE__ */ new Map([
23739
+ ["ECONNREFUSED", "connection refused"],
23740
+ ["ENOTFOUND", "host not found"],
23741
+ ["EAI_AGAIN", "host not found"],
23742
+ ["ETIMEDOUT", "timed out"],
23743
+ ["UND_ERR_CONNECT_TIMEOUT", "timed out"],
23744
+ ["EHOSTUNREACH", "host unreachable"],
23745
+ ["ENETUNREACH", "host unreachable"]
23746
+ ]);
23747
+ async function toolServerUnreachable(err) {
23748
+ if (!(err instanceof TypeError)) return void 0;
23749
+ const cause = err.cause;
23750
+ const reason = typeof cause?.code === "string" ? CONNECT_FAILURES.get(cause.code) : void 0;
23751
+ const syscall = cause?.syscall;
23752
+ if (!reason || syscall !== void 0 && syscall !== "connect" && syscall !== "getaddrinfo") {
23753
+ return void 0;
23754
+ }
23755
+ const routing = await getResolvedToolsUrl();
23756
+ if (routing.source === "none") return void 0;
23757
+ const recovery = routing.source === "link" ? "Start the tool-server on the linked machine, or run `argent unlink`." : routing.shadowedLink ? `Start that tool-server, or unset ARGENT_TOOLS_URL and run \`argent unlink\` to use the local one. A link to ${routing.shadowedLink.url} is also configured and takes over once the env var is unset.` : "Start that tool-server, or unset ARGENT_TOOLS_URL to use the local one.";
23758
+ const setBy = routing.source === "env" ? "ARGENT_TOOLS_URL" : "argent link";
23759
+ return new ToolServerUnreachableError(
23760
+ `Could not reach the tool-server at ${routing.url} (set by ${setBy}): ${reason}.
23761
+ ${recovery}`
23762
+ );
23763
+ }
23737
23764
  function failureSignal2(err) {
23738
- if (!(err instanceof ToolInvocationError)) return {};
23765
+ if (!(err instanceof ToolInvocationError) && !(err instanceof ToolServerUnreachableError)) {
23766
+ return {};
23767
+ }
23739
23768
  return {
23740
23769
  ...err.errorCode ? { error_code: err.errorCode } : {},
23741
23770
  ...err.errorKind ? { error_kind: err.errorKind } : {}
23742
23771
  };
23743
23772
  }
23773
+ function errorRecord(err, message = err instanceof Error ? err.message : String(err)) {
23774
+ return { event: "error", error: message, ...failureSignal2(err) };
23775
+ }
23744
23776
  function writeJsonStreamError(err) {
23745
- const message = err instanceof Error ? err.message : String(err);
23746
- writeJsonStreamRecord({ event: "error", error: message, ...failureSignal2(err) });
23777
+ writeJsonStreamRecord(errorRecord(err));
23747
23778
  }
23748
23779
  async function exportAndResolveArtifacts(report, outputDir, flowPath, baseUrl) {
23749
23780
  if (outputDir) {
@@ -23769,22 +23800,23 @@ function isFlowReport(data) {
23769
23800
  return Array.isArray(steps) && steps.every((step) => !!step && typeof step === "object");
23770
23801
  }
23771
23802
  async function runFlowDirectory(dir, args, projectRoot, options) {
23803
+ const printError = (message, err = message) => {
23804
+ console.error(args.json ? JSON.stringify(errorRecord(err, message)) : message);
23805
+ };
23806
+ const reject = (message) => {
23807
+ printError(message);
23808
+ return exitAfterFlush(2);
23809
+ };
23772
23810
  let flows;
23773
23811
  try {
23774
23812
  flows = await collectFlowFiles(dir, args.recursive);
23775
23813
  } catch {
23776
- console.error(`Could not read flow directory: ${dir}`);
23777
- return exitAfterFlush(2);
23814
+ return reject(`Could not read flow directory: ${dir}`);
23778
23815
  }
23779
23816
  if (flows.length === 0) {
23780
- console.error(`No flows found in ${dir}`);
23781
- if (!args.recursive) console.error("Pass -r/--recursive to include subdirectories.");
23782
- return exitAfterFlush(2);
23783
- }
23784
- const refusal = await requireLocalToolServer();
23785
- if (refusal) {
23786
- console.error(refusal);
23787
- return exitAfterFlush(2);
23817
+ return reject(
23818
+ `No flows found in ${dir}` + (args.recursive ? "" : "\nPass -r/--recursive to include subdirectories.")
23819
+ );
23788
23820
  }
23789
23821
  const { callTool, baseUrl } = createToolsClient({ paths: options.paths });
23790
23822
  const outputBase = args.output ? path14.resolve(args.output) : void 0;
@@ -23792,6 +23824,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
23792
23824
  const failures = [];
23793
23825
  const batchStartedAt = Date.now();
23794
23826
  let stopped = false;
23827
+ let unreachable = false;
23795
23828
  for (const [i2, rel] of flows.entries()) {
23796
23829
  if (!args.json) console.log(`[${i2 + 1}/${flows.length}] ${rel}`);
23797
23830
  if (stopped) {
@@ -23805,23 +23838,26 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
23805
23838
  try {
23806
23839
  const resp = await callTool("flow-execute", buildRunPayload(flowPath, projectRoot, args));
23807
23840
  if (isFlowReport(resp.data)) report = resp.data;
23808
- } catch (err) {
23841
+ } catch (thrown) {
23842
+ const unreachableErr = await toolServerUnreachable(thrown);
23843
+ const err = unreachableErr ?? thrown;
23809
23844
  const message = err instanceof Error ? err.message : String(err);
23810
23845
  const toolErr = err instanceof ToolInvocationError ? err : void 0;
23811
23846
  const rejectedThisFlowOnly = toolErr?.errorKind === "validation";
23812
- const verdict = rejectedThisFlowOnly ? rejectionVerdict(toolErr?.errorCode) : "did not finish (run error)";
23847
+ const verdict = unreachableErr ? UNREACHABLE_VERDICT : rejectedThisFlowOnly ? rejectionVerdict(toolErr?.errorCode) : "did not finish (run error)";
23813
23848
  if (!args.json) console.log(` ${STATUS_GLYPH.error} ${verdict}`);
23814
- console.error(message);
23849
+ printError(message, err);
23815
23850
  results.push({ path: rel, status: "fail", error: message, ...failureSignal2(err) });
23816
23851
  failures.push({ path: rel, headline: verdict, detail: message, rerun });
23817
23852
  if (!rejectedThisFlowOnly) stopped = true;
23853
+ if (unreachableErr) unreachable = true;
23818
23854
  continue;
23819
23855
  }
23820
23856
  if (!report) {
23821
23857
  const message = `"${rel}" did not produce a run report.`;
23822
23858
  const verdict = "did not finish (no run report)";
23823
23859
  if (!args.json) console.log(` ${STATUS_GLYPH.error} ${verdict}`);
23824
- console.error(message);
23860
+ printError(message);
23825
23861
  results.push({ path: rel, status: "fail", error: message });
23826
23862
  failures.push({ path: rel, headline: verdict, detail: message, rerun });
23827
23863
  stopped = true;
@@ -23856,7 +23892,7 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
23856
23892
  console.log(`
23857
23893
  ${renderBatchSummary(counts, durationMs)}`);
23858
23894
  }
23859
- return exitAfterFlush(counts.failed === 0 ? 0 : 1);
23895
+ return exitAfterFlush(unreachable ? 2 : counts.failed === 0 ? 0 : 1);
23860
23896
  }
23861
23897
  async function flow(argv, options) {
23862
23898
  const [sub, ...rest] = argv;
@@ -23879,38 +23915,42 @@ async function flow(argv, options) {
23879
23915
  console.error(`Unknown flow subcommand "${sub}". Run \`argent flow --help\`.`);
23880
23916
  return exitAfterFlush(2);
23881
23917
  }
23882
- const jsonStream = rest.some(
23883
- (tok) => tok === "--json-stream" || tok.startsWith("--json-stream=")
23884
- );
23918
+ const flagGiven = (flag) => rest.some((tok) => tok === flag || tok.startsWith(`${flag}=`));
23919
+ const jsonStream = flagGiven("--json-stream");
23920
+ const json2 = !jsonStream && flagGiven("--json");
23885
23921
  if (rest.includes("--help") || rest.includes("-h")) {
23886
23922
  printHelp(jsonStream);
23887
23923
  return;
23888
23924
  }
23889
- const fail = (message, code, err = message) => {
23890
- if (jsonStream) writeJsonStreamError(err);
23891
- console.error(message);
23892
- return exitAfterFlush(code);
23925
+ const usageError = (message, prose) => {
23926
+ if (json2) {
23927
+ console.error(JSON.stringify(errorRecord(message)));
23928
+ return exitAfterFlush(2);
23929
+ }
23930
+ if (jsonStream) writeJsonStreamError(message);
23931
+ console.error(prose);
23932
+ printHelp(jsonStream);
23933
+ return exitAfterFlush(2);
23893
23934
  };
23894
23935
  let args;
23895
23936
  try {
23896
23937
  args = parseRunArgs(rest);
23897
23938
  } catch (err) {
23898
23939
  if (err instanceof FlagParseException) {
23899
- if (jsonStream) writeJsonStreamError(err);
23900
- console.error(`Error: ${err.message}
23940
+ return usageError(err.message, `Error: ${err.message}
23901
23941
  `);
23902
- printHelp(jsonStream);
23903
- return exitAfterFlush(2);
23904
23942
  }
23905
23943
  throw err;
23906
23944
  }
23907
23945
  if (!args.flowRef) {
23908
23946
  const message = "argent flow run <flow|flow.yaml|dir> requires a flow name, a YAML file path, or a directory path.";
23909
- if (jsonStream) writeJsonStreamError(message);
23910
- console.error(message);
23911
- printHelp(jsonStream);
23912
- return exitAfterFlush(2);
23947
+ return usageError(message, message);
23913
23948
  }
23949
+ const fail = (message, code, err = message) => {
23950
+ if (jsonStream) writeJsonStreamError(err);
23951
+ console.error(args.json ? JSON.stringify(errorRecord(err, message)) : message);
23952
+ return exitAfterFlush(code);
23953
+ };
23914
23954
  const projectRoot = process.cwd();
23915
23955
  const { suppliedPath, fromName } = resolveFlowRef(args.flowRef);
23916
23956
  if (suppliedPath.split(/[\\/]+/).includes("..")) {
@@ -24019,8 +24059,6 @@ ${recovery}`,
24019
24059
  2
24020
24060
  );
24021
24061
  }
24022
- const refusal = await requireLocalToolServer();
24023
- if (refusal) return fail(refusal, 2);
24024
24062
  const { callTool, baseUrl } = createToolsClient({ paths: options.paths });
24025
24063
  const payload = buildRunPayload(flowPath, projectRoot, args);
24026
24064
  let liveSteps = 0;
@@ -24052,12 +24090,14 @@ ${recovery}`,
24052
24090
  );
24053
24091
  report = resp.data;
24054
24092
  } catch (err) {
24093
+ const unreachableErr = await toolServerUnreachable(err);
24055
24094
  if (!args.json && !args.jsonStream) {
24056
24095
  if (liveSteps === 0) console.log(`Flow "${flowName}"`);
24057
24096
  console.log(
24058
- ` ${STATUS_GLYPH.error} ` + (err instanceof ToolInvocationError && err.errorKind === "validation" ? rejectionVerdict(err.errorCode) : "did not finish (run error)")
24097
+ ` ${STATUS_GLYPH.error} ` + (unreachableErr ? UNREACHABLE_VERDICT : err instanceof ToolInvocationError && err.errorKind === "validation" ? rejectionVerdict(err.errorCode) : "did not finish (run error)")
24059
24098
  );
24060
24099
  }
24100
+ if (unreachableErr) return fail(unreachableErr.message, 2, unreachableErr);
24061
24101
  return fail(err instanceof Error ? err.message : String(err), 1, err);
24062
24102
  }
24063
24103
  if (!isFlowReport(report)) {
@@ -16730,7 +16730,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
16730
16730
  var SESSION_ID = randomUUID4();
16731
16731
  function readCliVersion() {
16732
16732
  if (true) {
16733
- return "0.27.1-next.17";
16733
+ return "0.27.1-next.18";
16734
16734
  }
16735
16735
  return "0.0.0";
16736
16736
  }
@@ -225,7 +225,7 @@ export interface ArgentToolArgs {
225
225
  project_root: string;
226
226
  /** Path to the flow .yaml as readable by the tool-server. Internal — the argent client derives it from project_root and name automatically; leave unset. */
227
227
  flow_file?: string;
228
- /** Omit when name is set. Absolute path to a co-located flow .yaml on the client and tool server's shared filesystem. This must be supplied through the file-input boundary. For remote execution, pass name + project_root instead. */
228
+ /** Omit when name is set. Absolute path to a flow .yaml on the client. Over a link (argent link or ARGENT_TOOLS_URL), the argent client uploads the file and the tool-server runs the uploaded copy. An uploaded flow must be self-contained: no run:, script: or snapshot: steps, and no tool: steps that take a file or record a flow. Without a link, the tool-server reads the file in place and all step kinds run. */
229
229
  flow_path?: string;
230
230
  /** Device id to run against (iOS UDID, Android/Vega serial, Chromium id) — the id list-devices reports. Auto-detected when omitted, but only when exactly one booted device matches (optionally narrowed by `platform`); with several booted the run fails and lists them, so pass this explicitly whenever more than one device is up. */
231
231
  device?: string;
@@ -249,7 +249,7 @@ export interface ArgentToolArgs {
249
249
  project_root: string;
250
250
  /** Path to the flow .yaml as readable by the tool-server. Internal — the argent client derives it from project_root and name automatically; leave unset. */
251
251
  flow_file?: string;
252
- /** Omit when name is set. Absolute path to a co-located flow .yaml on the client and tool server's shared filesystem. This must be supplied through the file-input boundary. Pass the same flow source here as to flow-execute, so the prerequisite you read belongs to the flow that will run; for remote reads, pass name + project_root instead. */
252
+ /** Omit when name is set. Absolute path to a flow .yaml on the client. Over a link (argent link or ARGENT_TOOLS_URL), the argent client uploads the file and the tool-server reads the uploaded copy; without a link, the tool-server reads the file in place. Pass the path through the argent client; a raw path that the file-input boundary did not verify is rejected. Pass the same flow source here as to flow-execute, so the prerequisite you read belongs to the flow that will run. */
253
253
  flow_path?: string;
254
254
  };
255
255
  "flow-start-recording": {
@@ -1108,6 +1108,18 @@ var init_artifacts = __esm({
1108
1108
  function isFileInputWire(value) {
1109
1109
  return !!value && typeof value === "object" && value[FILE_INPUT_MARKER] === true && typeof value.path === "string";
1110
1110
  }
1111
+ function interpolateFileInputPath(template, args) {
1112
+ let missing = false;
1113
+ const out = template.replace(/\$\{([A-Za-z0-9_]+)\}/g, (_m, name) => {
1114
+ const v = args[name];
1115
+ if (typeof v !== "string" || v.length === 0) {
1116
+ missing = true;
1117
+ return "";
1118
+ }
1119
+ return v;
1120
+ });
1121
+ return missing ? null : out;
1122
+ }
1111
1123
  var FILE_INPUT_MARKER, FLOW_NAME_CHARSET, FLOW_NAME_PATTERN, FLOW_FILE_NAME_PATTERN, SCRIPT_FILE_NAME_PATTERN, CLIENT_FILE_MARKER;
1112
1124
  var init_file_inputs = __esm({
1113
1125
  "../registry/src/file-inputs.ts"() {
@@ -79147,16 +79159,16 @@ var require_path_is_absolute = __commonJS({
79147
79159
  function posix6(path51) {
79148
79160
  return path51.charAt(0) === "/";
79149
79161
  }
79150
- function win32(path51) {
79162
+ function win322(path51) {
79151
79163
  var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
79152
79164
  var result = splitDeviceRe.exec(path51);
79153
79165
  var device = result[1] || "";
79154
79166
  var isUnc = Boolean(device && device.charAt(1) !== ":");
79155
79167
  return Boolean(result[2] || isUnc);
79156
79168
  }
79157
- module2.exports = process.platform === "win32" ? win32 : posix6;
79169
+ module2.exports = process.platform === "win32" ? win322 : posix6;
79158
79170
  module2.exports.posix = posix6;
79159
- module2.exports.win32 = win32;
79171
+ module2.exports.win32 = win322;
79160
79172
  }
79161
79173
  });
79162
79174
 
@@ -94814,7 +94826,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
94814
94826
  var SESSION_ID = (0, import_node_crypto3.randomUUID)();
94815
94827
  function readCliVersion() {
94816
94828
  if (true) {
94817
- return "0.27.1-next.17";
94829
+ return "0.27.1-next.18";
94818
94830
  }
94819
94831
  return "0.0.0";
94820
94832
  }
@@ -113595,26 +113607,30 @@ init_android_binary();
113595
113607
  // ../tool-server/src/utils/android-helper-install.ts
113596
113608
  init_adb();
113597
113609
  async function probeInstalledVersion(serial, packageName) {
113598
- let out;
113599
113610
  try {
113600
- out = await adbShell(serial, `cmd package list packages --show-versioncode ${packageName}`, {
113601
- timeoutMs: 5e3
113602
- });
113603
- } catch {
113604
- try {
113605
- out = await adbShell(serial, `pm list packages ${packageName}`, { timeoutMs: 5e3 });
113606
- } catch {
113607
- return { installed: false, versionCode: null };
113611
+ const out2 = await adbShell(
113612
+ serial,
113613
+ `cmd package list packages --show-versioncode ${packageName}`,
113614
+ { timeoutMs: 5e3 }
113615
+ );
113616
+ for (const line of out2.split("\n")) {
113617
+ const match = line.trim().match(/^package:([^\s]+)\s+versionCode:(\d+)$/);
113618
+ if (match?.[1] === packageName) {
113619
+ return { installed: true, versionCode: parseInt(match[2], 10) };
113620
+ }
113608
113621
  }
113622
+ } catch {
113609
113623
  }
113610
- for (const line of out.split("\n")) {
113611
- const match = line.trim().match(/^package:([^\s]+)(?:\s+versionCode:(\d+))?$/);
113612
- if (!match) continue;
113613
- if (match[1] !== packageName) continue;
113614
- const versionCode = match[2] ? parseInt(match[2], 10) : null;
113615
- return { installed: true, versionCode: Number.isFinite(versionCode) ? versionCode : null };
113624
+ let out;
113625
+ try {
113626
+ out = await adbShell(serial, `dumpsys package ${packageName}`, { timeoutMs: 5e3 });
113627
+ } catch {
113628
+ return { installed: false, versionCode: null };
113616
113629
  }
113617
- return { installed: false, versionCode: null };
113630
+ const block = out.split(`Package [${packageName}]`)[1];
113631
+ if (block === void 0) return { installed: false, versionCode: null };
113632
+ const versionCode = block.match(/versionCode=(\d+)/);
113633
+ return { installed: true, versionCode: versionCode ? parseInt(versionCode[1], 10) : null };
113618
113634
  }
113619
113635
  async function ensureAndroidDevtoolsInstalled(serial, options = {}) {
113620
113636
  const manifest = helperManifest();
@@ -114906,6 +114922,14 @@ async function resolveOne(spec, wire, tempDirs, lookupUpload) {
114906
114922
  `Path "${wire.path}" does not exist on the tool-server host and no upload was provided. Update argent to a version that supports uploads for remote sessions.`
114907
114923
  );
114908
114924
  }
114925
+ if (spec.kind === "file" && typeof wire.content === "string") {
114926
+ const { filePath, dir } = await materializeUpload(wire);
114927
+ tempDirs.push(dir);
114928
+ return {
114929
+ value: filePath,
114930
+ meta: { clientPath: wire.path, presentOnHost: probe3.present, viaUpload: true }
114931
+ };
114932
+ }
114909
114933
  if (meta3.presentOnHost) {
114910
114934
  return { value: wire.path, meta: meta3 };
114911
114935
  }
@@ -114914,11 +114938,6 @@ async function resolveOne(spec, wire, tempDirs, lookupUpload) {
114914
114938
  `Directory "${wire.path}" does not exist on the tool-server host. This tool reads a directory tree from the tool-server's filesystem, which cannot be uploaded with the call \u2014 when the tool-server runs on a different machine, pass a path that exists on that machine (e.g. the server-side checkout of the project).`
114915
114939
  );
114916
114940
  }
114917
- if (typeof wire.content === "string") {
114918
- const { filePath, dir } = await materializeUpload(wire);
114919
- tempDirs.push(dir);
114920
- return { value: filePath, meta: { ...meta3, viaUpload: true } };
114921
- }
114922
114941
  if (wire.contentOmitted === "size-limit") {
114923
114942
  throw new FileInputError(
114924
114943
  `File "${wire.path}" is ${formatBytes(wire.size)} \u2014 larger than the ${formatBytes(MAX_UPLOAD_BYTES)} file-input transfer limit, so the client did not upload it, and it was not found on the tool-server host. Copy the file to the tool-server machine and pass that path, or use a smaller file.`
@@ -147148,7 +147167,22 @@ function selectRotateCandidate(candidates) {
147148
147167
 
147149
147168
  // ../tool-server/src/tools/flows/flow-utils.ts
147150
147169
  var FLOWS_DIR_NAME = path38.join(".argent", "flows");
147170
+ function windowsPathRefusal(label, value) {
147171
+ if (process.platform === "win32" || path38.isAbsolute(value) || !path38.win32.isAbsolute(value)) {
147172
+ return void 0;
147173
+ }
147174
+ return `${label} "${value}" is a Windows path. A tool-server on macOS or Linux cannot open Windows paths. Use argent on the computer that runs the tool-server, or start the tool-server on the Windows computer.`;
147175
+ }
147151
147176
  function assertValidProjectRoot(root) {
147177
+ const windows = windowsPathRefusal("project_root", root);
147178
+ if (windows) {
147179
+ throw new FailureError(windows, {
147180
+ error_code: FAILURE_CODES.FLOW_PROJECT_ROOT_INVALID,
147181
+ failure_stage: "flow_project_root_set",
147182
+ failure_area: "tool_server",
147183
+ error_kind: "validation"
147184
+ });
147185
+ }
147152
147186
  if (!path38.isAbsolute(root)) {
147153
147187
  throw new FailureError(
147154
147188
  `project_root must be an absolute path (got "${root}"). Pass the absolute path to the project root directory \u2014 the same cwd the calling agent is working in.`,
@@ -151775,6 +151809,7 @@ var NESTED_RECORDER_TOOLS = {
151775
151809
  "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.",
151776
151810
  "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."
151777
151811
  };
151812
+ var RECORDING_TOOL_IDS = new Set(Object.keys(NESTED_RECORDER_TOOLS));
151778
151813
  function isToolNotFound(err, command) {
151779
151814
  return err instanceof ToolNotFoundError && err.toolId === command;
151780
151815
  }
@@ -156152,7 +156187,7 @@ var zodSchema68 = external_exports.object({
156152
156187
  "Path to the flow .yaml as readable by the tool-server. Internal \u2014 the argent client derives it from project_root and name automatically; leave unset."
156153
156188
  ),
156154
156189
  flow_path: external_exports.string().optional().describe(
156155
- "Omit when name is set. Absolute path to a co-located flow .yaml on the client and tool server's shared filesystem. This must be supplied through the file-input boundary. For remote execution, pass name + project_root instead."
156190
+ "Omit when name is set. Absolute path to a flow .yaml on the client. Over a link (argent link or ARGENT_TOOLS_URL), the argent client uploads the file and the tool-server runs the uploaded copy. An uploaded flow must be self-contained: no run:, script: or snapshot: steps, and no tool: steps that take a file or record a flow. Without a link, the tool-server reads the file in place and all step kinds run."
156156
156191
  ),
156157
156192
  device: external_exports.string().optional().describe(
156158
156193
  "Device id to run against (iOS UDID, Android/Vega serial, Chromium id) \u2014 the id list-devices reports. Auto-detected when omitted, but only when exactly one booted device matches (optionally narrowed by `platform`); with several booted the run fails and lists them, so pass this explicitly whenever more than one device is up."
@@ -156532,42 +156567,69 @@ function findRetiredToolArg(registry2, steps) {
156532
156567
  function retiredArgReason(use) {
156533
156568
  return `${use.where} as written (echo included) passes ${use.tool}'s retired \`${use.key}\` key${use.guidance ? `: ${use.guidance}` : ""}`;
156534
156569
  }
156535
- function assertUploadSelfContained(flow) {
156536
- for (const { step } of walkSteps(flow.steps)) {
156570
+ var UPLOAD_STAGE_BY_KIND = {
156571
+ run: "flow_upload_run_composition",
156572
+ script: "flow_upload_script_step",
156573
+ snapshot: "flow_upload_snapshot_baseline",
156574
+ nested: "flow_upload_nested_flow",
156575
+ toolFile: "flow_upload_tool_file_input",
156576
+ recording: "flow_upload_recording_tool"
156577
+ };
156578
+ function nestedFlowRef(args) {
156579
+ if (typeof args.name === "string") return ` (name: ${args.name})`;
156580
+ if (typeof args.flow_path === "string") return ` (flow_path: ${args.flow_path})`;
156581
+ return "";
156582
+ }
156583
+ function toolStepFilePaths(registry2, tool, args) {
156584
+ const paths = [];
156585
+ for (const spec of registry2.getTool(tool)?.fileInputs ?? []) {
156586
+ if (spec.skipWhenSet !== void 0 && args[spec.skipWhenSet] !== void 0) continue;
156587
+ const filled = interpolateFileInputPath(spec.path, args);
156588
+ if (filled !== null) paths.push(filled);
156589
+ }
156590
+ return paths;
156591
+ }
156592
+ function assertUploadSelfContained(registry2, flow) {
156593
+ const offending = [];
156594
+ for (const { step, where } of walkSteps(flow.steps)) {
156537
156595
  if (step.kind === "run") {
156538
- throw new FailureError(
156539
- `This flow uses run: composition ("run: ${step.flow}"), which requires a co-located client and tool server \u2014 an uploaded flow's referenced files are not available on this host.`,
156540
- {
156541
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
156542
- failure_stage: "flow_upload_run_composition",
156543
- failure_area: "tool_server",
156544
- error_kind: "validation"
156545
- }
156546
- );
156547
- }
156548
- if (step.kind === "script") {
156549
- throw new FailureError(
156550
- `This flow uses a script step ("script: { path: ${step.path} }"), whose .mjs file lives beside the flow's file on the CLIENT \u2014 an uploaded flow carries only its own YAML, so the script is not on this host and never could be. Use name + project_root with a co-located client and tool server for flows that run scripts.`,
156551
- {
156552
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
156553
- failure_stage: "flow_upload_script_step",
156554
- failure_area: "tool_server",
156555
- error_kind: "validation"
156556
- }
156557
- );
156558
- }
156559
- if (step.kind === "snapshot") {
156560
- throw new FailureError(
156561
- `This flow uses a snapshot step ("snapshot: ${step.name}"), whose baselines live beside the flow's file \u2014 an uploaded flow materializes to a fresh temp directory each call, so a plain snapshot can never find a baseline and updateBaselines (--update-baselines) writes PNGs no later run can read. Use name + project_root with a co-located client and tool server for snapshot flows.`,
156562
- {
156563
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
156564
- failure_stage: "flow_upload_snapshot_baseline",
156565
- failure_area: "tool_server",
156566
- error_kind: "validation"
156567
- }
156568
- );
156596
+ offending.push({ kind: "run", line: `${where}: run: ${step.flow}` });
156597
+ } else if (step.kind === "script") {
156598
+ offending.push({ kind: "script", line: `${where}: script: { path: ${step.path} }` });
156599
+ } else if (step.kind === "snapshot") {
156600
+ offending.push({ kind: "snapshot", line: `${where}: snapshot: ${step.name}` });
156601
+ } else if (step.kind === "tool" && step.name === "flow-execute") {
156602
+ offending.push({
156603
+ kind: "nested",
156604
+ line: `${where}: tool: flow-execute${nestedFlowRef(step.args)}`
156605
+ });
156606
+ } else if (step.kind === "tool" && RECORDING_TOOL_IDS.has(step.name)) {
156607
+ offending.push({
156608
+ kind: "recording",
156609
+ line: `${where}: tool: ${step.name} (records a flow)`
156610
+ });
156611
+ } else if (step.kind === "tool") {
156612
+ const paths = toolStepFilePaths(registry2, step.name, step.args);
156613
+ if (paths.length > 0) {
156614
+ offending.push({
156615
+ kind: "toolFile",
156616
+ line: `${where}: tool: ${step.name} (${paths.join(", ")})`
156617
+ });
156618
+ }
156569
156619
  }
156570
156620
  }
156621
+ if (offending.length === 0) return;
156622
+ throw new FailureError(
156623
+ `This flow is not self-contained, and it arrived as an upload. The steps below read or write project files, which stay on the client:
156624
+ ` + offending.map((o) => ` - ${o.line}`).join("\n") + `
156625
+ Run the flow on the computer that runs the tool-server, with no link and no ARGENT_TOOLS_URL, so that the tool-server reads the files in place.`,
156626
+ {
156627
+ error_code: FAILURE_CODES.FLOW_FILE_INVALID,
156628
+ failure_stage: UPLOAD_STAGE_BY_KIND[offending[0].kind],
156629
+ failure_area: "tool_server",
156630
+ error_kind: "validation"
156631
+ }
156632
+ );
156571
156633
  }
156572
156634
  function createRunFlowTool(registry2) {
156573
156635
  return {
@@ -156597,7 +156659,7 @@ Returns a per-step report: the first failure stops the run and the rest report a
156597
156659
  const canonicalPath = await canonicalFlowPath2(filePath);
156598
156660
  const flowsDir = path46.dirname(canonicalPath);
156599
156661
  const flow = parseFlow(await fs62.readFile(canonicalPath, "utf8"));
156600
- if (viaUpload) assertUploadSelfContained(flow);
156662
+ if (viaUpload) assertUploadSelfContained(registry2, flow);
156601
156663
  const retiredArg = findRetiredToolArg(registry2, flow.steps);
156602
156664
  if (retiredArg) {
156603
156665
  throw new FailureError(`Flow "${flowName}" ${retiredArgReason(retiredArg)}`, {
@@ -156800,7 +156862,7 @@ async function resolveAppPath(specPath, flowDir) {
156800
156862
  async function bootChromiumForFlow(spec, flowDir, viaUpload) {
156801
156863
  if (viaUpload && !path46.isAbsolute(spec.path)) {
156802
156864
  throw new FailureError(
156803
- `A relative chromium app path ("${spec.path}") resolves against the flow file's directory, which requires a co-located client and tool server \u2014 an uploaded flow has no real flow directory on this host. Use an absolute tool-server path instead.`,
156865
+ `A relative chromium app path ("${spec.path}") resolves against the flow file's directory, which requires a run with no link and no ARGENT_TOOLS_URL \u2014 an uploaded flow has no real flow directory on this host. Use an absolute tool-server path instead.`,
156804
156866
  {
156805
156867
  error_code: FAILURE_CODES.FLOW_FILE_INVALID,
156806
156868
  failure_stage: "flow_upload_chromium_app_path",
@@ -157349,6 +157411,55 @@ async function execLeafStep(state3, step, index, scope) {
157349
157411
  function errMsg4(err) {
157350
157412
  return err instanceof Error ? err.message : String(err);
157351
157413
  }
157414
+ function assertFlowPathShape(flowPath) {
157415
+ const windows = windowsPathRefusal("flow_path", flowPath);
157416
+ if (windows) {
157417
+ throw new FailureError(windows, {
157418
+ error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157419
+ failure_stage: "flow_path_absolute",
157420
+ failure_area: "tool_server",
157421
+ error_kind: "validation"
157422
+ });
157423
+ }
157424
+ if (!path46.isAbsolute(flowPath)) {
157425
+ throw new FailureError(
157426
+ `Invalid flow_path "${flowPath}": flow paths must be absolute \u2014 a relative path is resolved against the tool server's working directory, not the caller's. Pass the absolute path to the flow's YAML.`,
157427
+ {
157428
+ error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157429
+ failure_stage: "flow_path_absolute",
157430
+ failure_area: "tool_server",
157431
+ error_kind: "validation"
157432
+ }
157433
+ );
157434
+ }
157435
+ if (flowPath.split(/[\\/]+/).includes("..")) {
157436
+ throw new FailureError(
157437
+ `Invalid flow_path "${flowPath}": flow paths must not contain ".." segments \u2014 a ".." after a symlinked directory can name a different file than the spelling suggests, and the argent client always sends fully resolved paths. Pass the fully resolved absolute path to the flow's YAML.`,
157438
+ {
157439
+ error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157440
+ failure_stage: "flow_path_dotdot",
157441
+ failure_area: "tool_server",
157442
+ error_kind: "validation"
157443
+ }
157444
+ );
157445
+ }
157446
+ }
157447
+ function flowNameOf(clientPath) {
157448
+ const clientExt = path46.extname(clientPath);
157449
+ const bareExtension = path46.basename(clientPath).toLowerCase() === ".yaml";
157450
+ if (!bareExtension && clientExt !== ".yaml") {
157451
+ const detail = clientExt.toLowerCase() === ".yaml" ? `flow files must use the lowercase .yaml extension, not "${clientExt}".` : `flow files must use the .yaml extension.`;
157452
+ throw new FailureError(`Invalid flow_path "${clientPath}": ${detail}`, {
157453
+ error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157454
+ failure_stage: "flow_path_extension",
157455
+ failure_area: "tool_server",
157456
+ error_kind: "validation"
157457
+ });
157458
+ }
157459
+ const flowName = bareExtension ? "" : path46.basename(clientPath, ".yaml");
157460
+ assertSafeFlowName(flowName);
157461
+ return flowName;
157462
+ }
157352
157463
  async function resolveFlowSource(params, fileInput, flowPathInput) {
157353
157464
  if (params.name === void 0 === (params.flow_path === void 0)) {
157354
157465
  throw new FailureError("Pass exactly one flow source: name or flow_path.", {
@@ -157361,15 +157472,9 @@ async function resolveFlowSource(params, fileInput, flowPathInput) {
157361
157472
  assertValidProjectRoot(params.project_root);
157362
157473
  if (params.flow_path !== void 0) {
157363
157474
  if (flowPathInput?.viaUpload) {
157364
- throw new FailureError(
157365
- `Invalid flow_path "${flowPathInput.clientPath}": explicit flow paths require a co-located client and tool server with a shared filesystem, and this one arrived as an upload \u2014 sibling run: files, baselines, and baseline write-back all resolve beside the copy this server materialized, alone in a temp directory. Pass name + project_root to run a self-contained flow from a remote client; name uploads the same way, so a flow with run:, script: or snapshot: steps needs the client and tool server on one filesystem.`,
157366
- {
157367
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157368
- failure_stage: "flow_path_shared_filesystem",
157369
- failure_area: "tool_server",
157370
- error_kind: "validation"
157371
- }
157372
- );
157475
+ const clientPath2 = flowPathInput.clientPath;
157476
+ assertFlowPathShape(clientPath2);
157477
+ return { filePath: params.flow_path, flowName: flowNameOf(clientPath2), viaUpload: true };
157373
157478
  }
157374
157479
  const isVerifiedHostPath = flowPathInput?.presentOnHost === true && flowPathInput.statVerified === true && path46.resolve(params.flow_path) === path46.resolve(flowPathInput.clientPath);
157375
157480
  if (!isVerifiedHostPath) {
@@ -157383,42 +157488,9 @@ async function resolveFlowSource(params, fileInput, flowPathInput) {
157383
157488
  }
157384
157489
  );
157385
157490
  }
157386
- if (!path46.isAbsolute(params.flow_path)) {
157387
- throw new FailureError(
157388
- `Invalid flow_path "${params.flow_path}": flow paths must be absolute \u2014 a relative path is resolved against the tool server's working directory, not the caller's. Pass the absolute path to the flow's YAML.`,
157389
- {
157390
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157391
- failure_stage: "flow_path_absolute",
157392
- failure_area: "tool_server",
157393
- error_kind: "validation"
157394
- }
157395
- );
157396
- }
157397
- if (params.flow_path.split(/[\\/]+/).includes("..")) {
157398
- throw new FailureError(
157399
- `Invalid flow_path "${params.flow_path}": flow paths must not contain ".." segments \u2014 a ".." after a symlinked directory can name a different file than the spelling suggests, and the argent client always sends fully resolved paths. Pass the fully resolved absolute path to the flow's YAML.`,
157400
- {
157401
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157402
- failure_stage: "flow_path_dotdot",
157403
- failure_area: "tool_server",
157404
- error_kind: "validation"
157405
- }
157406
- );
157407
- }
157491
+ assertFlowPathShape(params.flow_path);
157408
157492
  const clientPath = flowPathInput.clientPath;
157409
- const clientExt = path46.extname(clientPath);
157410
- const bareExtension = path46.basename(clientPath).toLowerCase() === ".yaml";
157411
- if (!bareExtension && clientExt !== ".yaml") {
157412
- const detail = clientExt.toLowerCase() === ".yaml" ? `flow files must use the lowercase .yaml extension, not "${clientExt}".` : `flow files must use the .yaml extension.`;
157413
- throw new FailureError(`Invalid flow_path "${clientPath}": ${detail}`, {
157414
- error_code: FAILURE_CODES.FLOW_FILE_INVALID,
157415
- failure_stage: "flow_path_extension",
157416
- failure_area: "tool_server",
157417
- error_kind: "validation"
157418
- });
157419
- }
157420
- const flowName2 = bareExtension ? "" : path46.basename(clientPath, ".yaml");
157421
- assertSafeFlowName(flowName2);
157493
+ const flowName2 = flowNameOf(clientPath);
157422
157494
  const suppliedBase = path46.basename(clientPath);
157423
157495
  const spelling2 = await classifyOnDiskSpelling(path46.dirname(params.flow_path), suppliedBase);
157424
157496
  if (spelling2.state !== "listed") {
@@ -157481,7 +157553,7 @@ var zodSchema69 = external_exports.object({
157481
157553
  "Path to the flow .yaml as readable by the tool-server. Internal \u2014 the argent client derives it from project_root and name automatically; leave unset."
157482
157554
  ),
157483
157555
  flow_path: external_exports.string().optional().describe(
157484
- "Omit when name is set. Absolute path to a co-located flow .yaml on the client and tool server's shared filesystem. This must be supplied through the file-input boundary. Pass the same flow source here as to flow-execute, so the prerequisite you read belongs to the flow that will run; for remote reads, pass name + project_root instead."
157556
+ "Omit when name is set. Absolute path to a flow .yaml on the client. Over a link (argent link or ARGENT_TOOLS_URL), the argent client uploads the file and the tool-server reads the uploaded copy; without a link, the tool-server reads the file in place. Pass the path through the argent client; a raw path that the file-input boundary did not verify is rejected. Pass the same flow source here as to flow-execute, so the prerequisite you read belongs to the flow that will run."
157485
157557
  )
157486
157558
  }).superRefine((params, ctx) => {
157487
157559
  if (params.name === void 0 === (params.flow_path === void 0)) {
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.27.1-next.17",
3
+ "version": "0.27.1-next.18",
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",
@@ -266,7 +266,7 @@ Resolve every hit and confirm:
266
266
 
267
267
  Run `flow-execute` on the complete YAML with the absolute project root. For a fragment, verify its prerequisite before setting `prerequisiteAcknowledged: true`.
268
268
 
269
- `flow-execute` takes exactly one flow source: `name`, for a flow saved under `.argent/flows/`, or `flow_path`, an absolute path to any flow `.yaml`. `run:` targets and baselines resolve on the tool server's filesystem, beside the YAML it actually reads. `flow_path` therefore requires the agent and the tool server to share a filesystem and is refused when they do not. `name` still runs remotely, but the server receives only that one YAML in a fresh temp directory. It checks the whole flow first and refuses a `run:`, `script:`, or `snapshot:` step at any depth, naming the missing co-location rather than a missing fragment, script, or baseline. Replay self-contained flows remotely; a composing, scripting, or snapshotting flow needs one shared filesystem.
269
+ `flow-execute` takes exactly one flow source: `name`, for a flow saved under `.argent/flows/`, or `flow_path`, an absolute path to any flow `.yaml`. Over `argent link` or `ARGENT_TOOLS_URL`, either source sends only that one YAML file, and the tool server runs that copy. The server checks the whole flow before it runs the first step. It refuses a flow with a `run:`, `script:`, or `snapshot:` step, or with a `tool:` step that takes a file (such as `screenshot-diff`, `reinstall-app`, or `flow-execute`) or records a flow, at any depth. The error lists each such step. Replay only a self-contained flow over a link. A flow that composes, runs a script, or takes a snapshot runs only with no link and no `ARGENT_TOOLS_URL`, on the computer that runs the tool server.
270
270
 
271
271
  Manual rescue invalidates the pass. An `errored` step was never evaluated: an `idle` wait whose tree source could not be read, a step that threw, an unresolvable `run:` target, or a `launch:` that did not start the app. Read the reason — most name the environment, but a failed `launch:` is a verdict about the app. Unconfirmed focus is not in this class at all: the replay focus poll has no failure return, so a `type:` step whose focus was never confirmed is scored a **pass**, and only the value check after typing catches it.
272
272