@swmansion/argent 0.27.1-next.20 → 0.27.1-next.21

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
@@ -9390,6 +9390,7 @@ function isRecord(value) {
9390
9390
  }
9391
9391
  function collectFlowRequests(doc) {
9392
9392
  const runTargets = /* @__PURE__ */ new Set();
9393
+ const snapshots = /* @__PURE__ */ new Set();
9393
9394
  const seen = /* @__PURE__ */ new Set();
9394
9395
  const visit = (steps, depth) => {
9395
9396
  if (!Array.isArray(steps) || depth > MAX_BLOCK_NESTING || seen.has(steps)) return;
@@ -9401,11 +9402,17 @@ function collectFlowRequests(doc) {
9401
9402
  const target = completeRunExtension(run2);
9402
9403
  if (FLOW_FILE_NAME_PATTERN.test(path2.posix.basename(target))) runTargets.add(target);
9403
9404
  }
9405
+ const snapshot = isRecord(step.snapshot) ? step.snapshot.name : step.snapshot;
9406
+ if (typeof snapshot === "string" && FLOW_NAME_PATTERN.test(snapshot)) snapshots.add(snapshot);
9404
9407
  visit(step.steps, depth + 1);
9405
9408
  }
9406
9409
  };
9407
9410
  if (isRecord(doc)) visit(doc.steps, 0);
9408
- return { runTargets: [...runTargets] };
9411
+ return { runTargets: [...runTargets], snapshots: [...snapshots] };
9412
+ }
9413
+ function baselineKeyFor(canonicalPath, flowName) {
9414
+ const stem = path2.basename(canonicalPath, ".yaml");
9415
+ return FLOW_NAME_PATTERN.test(stem) ? stem : flowName;
9409
9416
  }
9410
9417
  function flowMemberKey(anchorDir, target) {
9411
9418
  return `${anchorDir}\0${target}`;
@@ -24461,7 +24468,17 @@ function parseLinkTarget(input) {
24461
24468
  // ../argent-tools-client/src/file-inputs.ts
24462
24469
  import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
24463
24470
  import { createReadStream, rmSync } from "node:fs";
24464
- import { mkdir as mkdir3, readFile as readFile3, rm as rm2, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
24471
+ import {
24472
+ chmod as chmod3,
24473
+ lstat,
24474
+ mkdir as mkdir3,
24475
+ readFile as readFile3,
24476
+ realpath as realpath2,
24477
+ rename as rename2,
24478
+ rm as rm2,
24479
+ stat as stat2,
24480
+ writeFile as writeFile3
24481
+ } from "node:fs/promises";
24465
24482
  import { constants, tmpdir } from "node:os";
24466
24483
  import * as path4 from "node:path";
24467
24484
 
@@ -24770,9 +24787,61 @@ function isAllowedClientFilePath(p) {
24770
24787
  function isClientFileDirective2(value) {
24771
24788
  return !!value && typeof value === "object" && value[CLIENT_FILE_MARKER2] === true && typeof value.path === "string" && typeof value.content === "string";
24772
24789
  }
24773
- async function applyClientFileDirectives(result) {
24790
+ async function replaceFile(target, bytes, mode) {
24791
+ const temp = path4.join(path4.dirname(target), `.baseline-${randomUUID3()}.tmp`);
24792
+ try {
24793
+ await writeFile3(temp, bytes, { flag: "wx" });
24794
+ if (mode !== void 0) await chmod3(temp, mode & 511);
24795
+ await rename2(temp, target);
24796
+ } catch (err) {
24797
+ await rm2(temp, { force: true });
24798
+ throw err;
24799
+ }
24800
+ }
24801
+ async function writeBaselineFile(file2, bytes, allowedDirs) {
24802
+ if (!path4.isAbsolute(file2) || path4.normalize(file2) !== file2 || file2.split(/[\\/]/).includes("..") || !file2.endsWith(".png") || !allowedDirs.includes(path4.dirname(file2))) {
24803
+ return allowedDirs.length === 0 ? "this call writes no baselines" : `${file2} is not a baseline of this call (${allowedDirs.map((d) => `${d}/<name>.png`).join(", ")})`;
24804
+ }
24805
+ if (bytes.length > MAX_CONTENT_BYTES) {
24806
+ return `${file2}: the baseline is larger than the 32 MiB cap on a file this client writes`;
24807
+ }
24808
+ const dir = path4.dirname(file2);
24809
+ await mkdir3(dir, { recursive: true });
24810
+ const link2 = await lstat(file2).catch(() => null);
24811
+ const real = link2 === null ? null : await realpath2(file2).catch(() => null);
24812
+ if (link2 !== null && real === null) return `${file2} is a symbolic link to a missing file`;
24813
+ if (real !== null) {
24814
+ if (!real.endsWith(".png")) return `${file2} links to a file that is not a PNG file`;
24815
+ if (path4.dirname(real) !== await realpath2(dir)) {
24816
+ return `${file2} links outside its baseline directory`;
24817
+ }
24818
+ }
24819
+ const existing = real === null ? null : await stat2(real);
24820
+ if (existing !== null && !existing.isFile()) return `${file2} is not a regular file`;
24821
+ await replaceFile(
24822
+ real ?? path4.join(await realpath2(dir), path4.basename(file2)),
24823
+ bytes,
24824
+ existing?.mode
24825
+ );
24826
+ return null;
24827
+ }
24828
+ async function applyClientFileDirectives(result, opts = {}) {
24774
24829
  const written = [];
24830
+ const failed = [];
24775
24831
  async function walk(value) {
24832
+ if (isClientFileDirective2(value) && value.encoding === "base64") {
24833
+ const error52 = await writeBaselineFile(
24834
+ value.path,
24835
+ Buffer.from(value.content, "base64"),
24836
+ opts.allowedDirs ?? []
24837
+ ).catch((err) => err instanceof Error ? err.message : String(err));
24838
+ if (error52 !== null) {
24839
+ failed.push({ path: value.path, error: error52 });
24840
+ return { path: value.path, error: error52 };
24841
+ }
24842
+ written.push(value.path);
24843
+ return value.path;
24844
+ }
24776
24845
  if (isClientFileDirective2(value)) {
24777
24846
  if (!isAllowedClientFilePath(value.path)) return null;
24778
24847
  try {
@@ -24785,7 +24854,9 @@ async function applyClientFileDirectives(result) {
24785
24854
  }
24786
24855
  }
24787
24856
  if (Array.isArray(value)) {
24788
- return Promise.all(value.map(walk));
24857
+ const out = [];
24858
+ for (const item of value) out.push(await walk(item));
24859
+ return out;
24789
24860
  }
24790
24861
  if (value && typeof value === "object") {
24791
24862
  const out = {};
@@ -24797,12 +24868,12 @@ async function applyClientFileDirectives(result) {
24797
24868
  return value;
24798
24869
  }
24799
24870
  const rewritten = await walk(result);
24800
- return { result: rewritten, written };
24871
+ return { result: rewritten, written, failed };
24801
24872
  }
24802
24873
 
24803
24874
  // ../argent-tools-client/src/flow-files.ts
24804
24875
  var import_yaml = __toESM(require_dist(), 1);
24805
- import { readFile as readFile4, realpath as realpath2, stat as stat3 } from "node:fs/promises";
24876
+ import { readdir as readdir4, readFile as readFile4, realpath as realpath3, stat as stat3 } from "node:fs/promises";
24806
24877
  import * as path5 from "node:path";
24807
24878
  var INLINE_MEMBERS_BYTES = 256 * 1024;
24808
24879
  var FLOW_FILES_LOG_ENV = "ARGENT_FLOW_FILES_LOG";
@@ -24837,7 +24908,7 @@ function savedFlowProject(file2) {
24837
24908
  }
24838
24909
  async function landing(spelled) {
24839
24910
  try {
24840
- const canonical = await realpath2(spelled);
24911
+ const canonical = await realpath3(spelled);
24841
24912
  return { canonical, fence: canonical };
24842
24913
  } catch (err) {
24843
24914
  const failure = err.code === "ENOENT" ? { missing: true } : { error: err.message };
@@ -24846,7 +24917,7 @@ async function landing(spelled) {
24846
24917
  for (; ; ) {
24847
24918
  const parent = path5.dirname(at);
24848
24919
  if (parent === at) return { canonical, fence: spelled, ...failure };
24849
- const real = await realpath2(parent).catch(() => null);
24920
+ const real = await realpath3(parent).catch(() => null);
24850
24921
  if (real !== null) {
24851
24922
  return { canonical, fence: path5.join(real, path5.basename(at)), ...failure };
24852
24923
  }
@@ -24866,7 +24937,7 @@ async function closureRoots(rootPath, rootCanonical, projectRoot) {
24866
24937
  }
24867
24938
  const real = [];
24868
24939
  for (const candidate of candidates) {
24869
- const resolved = await realpath2(candidate).catch(() => null);
24940
+ const resolved = await realpath3(candidate).catch(() => null);
24870
24941
  if (resolved !== null && !real.includes(resolved)) real.push(resolved);
24871
24942
  }
24872
24943
  return real.filter((root) => !real.some((other) => other !== root && isWithin(root, other)));
@@ -24904,31 +24975,96 @@ async function readFlowMember(anchorDir, target, roots, budget, opts) {
24904
24975
  if (!/\.ya?ml$/i.test(path5.basename(canonical))) {
24905
24976
  return refuse(`${target} links to a file that is not a YAML file`);
24906
24977
  }
24907
- const read = await readFileInputWire(canonical, { includeContent: true });
24978
+ const sent = await sendBytes(member, canonical, budget, opts);
24979
+ return { ...sent, text: sent.bytes?.toString("utf8") };
24980
+ }
24981
+ function callPlatform(args) {
24982
+ if (typeof args.device === "string" && args.device.startsWith("chromium-cdp-")) return "chromium";
24983
+ if (typeof args.platform !== "string") return void 0;
24984
+ return args.platform === "ios-remote" ? "ios" : args.platform;
24985
+ }
24986
+ async function baselineEntry(file2, roots, forWrite) {
24987
+ const { canonical, error: error52 } = await landing(file2);
24988
+ if (!roots.some((root) => isWithin(canonical, root))) {
24989
+ return {
24990
+ state: "refused",
24991
+ error: `${file2} is outside every root this client serves (${roots.join(", ")})`
24992
+ };
24993
+ }
24994
+ if (error52 !== void 0) return { state: "refused", error: error52 };
24995
+ const st = await stat3(canonical).catch((err) => err);
24996
+ if (st instanceof Error) {
24997
+ if (st.code !== "ENOENT") return { state: "refused", error: st.message };
24998
+ return forWrite ? { state: "refused", error: `${file2} is a symbolic link to a missing file` } : { state: "missing" };
24999
+ }
25000
+ if (!canonical.endsWith(".png")) {
25001
+ return { state: "refused", error: `${file2} links to a file that is not a PNG file` };
25002
+ }
25003
+ if (forWrite && !st.isFile()) return { state: "refused", error: `${file2} is not a regular file` };
25004
+ if (st.isDirectory()) {
25005
+ return { state: "refused", error: "EISDIR: illegal operation on a directory, read" };
25006
+ }
25007
+ return st.isFile() ? { state: "ok", real: canonical } : { state: "missing" };
25008
+ }
25009
+ async function sendBytes(member, real, budget, opts) {
25010
+ const read = await readFileInputWire(real, { includeContent: true });
24908
25011
  if (read?.contentOmitted) {
24909
- return refuse(`${canonical} is larger than the 32 MiB cap on a file sent to the tool-server`);
25012
+ const error52 = `${real} is larger than the 32 MiB cap on a file sent to the tool-server`;
25013
+ return { member: { ...member, state: "refused", error: error52 }, sent: `refused (${error52})` };
24910
25014
  }
24911
25015
  if (read?.content === void 0) {
24912
- return refuse(
24913
- await readFile4(canonical).then(
24914
- () => `${canonical} could not be read on this client`,
24915
- (err) => err instanceof Error ? err.message : String(err)
24916
- )
25016
+ const error52 = await readFile4(real).then(
25017
+ () => `${real} could not be read on this client`,
25018
+ (err) => err instanceof Error ? err.message : String(err)
24917
25019
  );
25020
+ return { member: { ...member, state: "refused", error: error52 }, sent: `refused (${error52})` };
24918
25021
  }
24919
- const text2 = Buffer.from(read.content, "base64").toString("utf8");
25022
+ const bytes = Buffer.from(read.content, "base64");
24920
25023
  const size = read.size ?? 0;
24921
25024
  if (budget.inline + size <= INLINE_MEMBERS_BYTES || !opts.uploadEndpoint) {
24922
25025
  budget.inline += size;
24923
- return { member: { ...member, ...read }, text: text2, sent: `inline ${size}` };
25026
+ return { member: { ...member, ...read }, sent: `inline ${size}`, bytes };
24924
25027
  }
24925
- const uploaded = await uploadFile(canonical, opts.uploadEndpoint, opts.signal);
25028
+ const uploaded = await uploadFile(real, opts.uploadEndpoint, opts.signal);
24926
25029
  return {
24927
25030
  member: { ...member, size: read.size, mtimeMs: read.mtimeMs, ...uploaded },
24928
- text: text2,
24929
- sent: `upload ${size}`
25031
+ sent: `upload ${size}`,
25032
+ bytes
24930
25033
  };
24931
25034
  }
25035
+ async function collectBaselineMembers(canonical, flowName, snapshots, args, roots, budget, opts, emit) {
25036
+ const dir = path5.join(
25037
+ path5.dirname(canonical),
25038
+ "__baselines__",
25039
+ baselineKeyFor(canonical, flowName)
25040
+ );
25041
+ const real = (await landing(dir)).canonical;
25042
+ if (!roots.some((root) => isWithin(real, root))) return;
25043
+ const updates = args.updateBaselines === true;
25044
+ if (updates) opts.baselineDirs?.push(dir);
25045
+ const platform = callPlatform(args);
25046
+ const prefixes = snapshots.map(
25047
+ (name) => `${name}__${platform === void 0 ? "" : `${platform}-`}`
25048
+ );
25049
+ const names = await readdir4(dir).catch(() => []);
25050
+ for (const name of names.sort()) {
25051
+ if (!name.endsWith(".png")) continue;
25052
+ if (!updates && !prefixes.some((prefix) => name.startsWith(prefix))) continue;
25053
+ const file2 = path5.join(dir, name);
25054
+ const member = { role: "baseline", key: file2, path: file2 };
25055
+ const entry = await baselineEntry(file2, roots, updates);
25056
+ if (entry.state === "refused") {
25057
+ emit({ ...member, state: "refused", error: entry.error }, `refused (${entry.error})`);
25058
+ } else if (entry.state === "missing") {
25059
+ emit({ ...member, state: "missing" }, "missing");
25060
+ } else if (updates) {
25061
+ emit({ ...member, state: "listed" }, "listed");
25062
+ } else {
25063
+ const sent = await sendBytes(member, entry.real, budget, opts);
25064
+ emit(sent.member, sent.sent);
25065
+ }
25066
+ }
25067
+ }
24932
25068
  async function collectFlowMembers(rootPath, rootBytes, args, opts) {
24933
25069
  if (!namesValidFlow(args)) return {};
24934
25070
  const canonical = await canonicalFlowPath(rootPath);
@@ -24940,10 +25076,16 @@ async function collectFlowMembers(rootPath, rootBytes, args, opts) {
24940
25076
  const roots = await closureRoots(rootPath, canonical, args.project_root);
24941
25077
  const members = [];
24942
25078
  const seen = /* @__PURE__ */ new Set();
25079
+ const snapshots = /* @__PURE__ */ new Set();
24943
25080
  const budget = { inline: 0 };
24944
25081
  const logging = process.env[FLOW_FILES_LOG_ENV] === "1";
24945
25082
  const log2 = opts.log ?? ((line) => void process.stderr.write(`${line}
24946
25083
  `));
25084
+ const emit = (member, sent) => {
25085
+ members.push(member);
25086
+ const subject = member.role === "flow" ? member.canonical : member.key;
25087
+ if (logging) log2(printable(`[flow-files] ${member.role} ${subject}: ${sent}`));
25088
+ };
24947
25089
  const queue = [{ canonical, text: rootBytes.toString("utf8"), hop: 0 }];
24948
25090
  for (let file2 = queue.shift(); file2 !== void 0; file2 = queue.shift()) {
24949
25091
  let doc;
@@ -24952,19 +25094,33 @@ async function collectFlowMembers(rootPath, rootBytes, args, opts) {
24952
25094
  } catch {
24953
25095
  continue;
24954
25096
  }
25097
+ const requests = collectFlowRequests(doc);
25098
+ for (const name of requests.snapshots) snapshots.add(name);
24955
25099
  const anchorDir = path5.dirname(file2.canonical);
24956
- for (const target of collectFlowRequests(doc).runTargets) {
25100
+ for (const target of requests.runTargets) {
24957
25101
  const key = flowMemberKey(anchorDir, target);
24958
25102
  if (seen.has(key)) continue;
24959
25103
  seen.add(key);
24960
25104
  const { member, text: text2, sent } = await readFlowMember(anchorDir, target, roots, budget, opts);
24961
- members.push(member);
24962
- if (logging) log2(printable(`[flow-files] flow ${member.canonical}: ${sent}`));
25105
+ emit(member, sent);
24963
25106
  if (text2 !== void 0 && file2.hop + 1 < MAX_RUN_DEPTH) {
24964
25107
  queue.push({ canonical: member.canonical, text: text2, hop: file2.hop + 1 });
24965
25108
  }
24966
25109
  }
24967
25110
  }
25111
+ if (snapshots.size > 0) {
25112
+ const flowName = path5.basename(rootPath, ".yaml");
25113
+ await collectBaselineMembers(
25114
+ canonical,
25115
+ flowName,
25116
+ [...snapshots],
25117
+ args,
25118
+ roots,
25119
+ budget,
25120
+ opts,
25121
+ emit
25122
+ );
25123
+ }
24968
25124
  return { canonical, spelling, members };
24969
25125
  }
24970
25126
 
@@ -24999,7 +25155,20 @@ async function consumeToolStream(name, body, onProgress, signal) {
24999
25155
  }
25000
25156
  };
25001
25157
  const decoder = new TextDecoder();
25002
- let buffered = "";
25158
+ let pieces = [];
25159
+ const take = (text2) => {
25160
+ let start = 0;
25161
+ let newline;
25162
+ while ((newline = text2.indexOf("\n", start)) !== -1) {
25163
+ signal?.throwIfAborted();
25164
+ pieces.push(text2.slice(start, newline));
25165
+ const line = pieces.join("");
25166
+ pieces = [];
25167
+ start = newline + 1;
25168
+ handleLine(line);
25169
+ }
25170
+ if (start < text2.length) pieces.push(text2.slice(start));
25171
+ };
25003
25172
  try {
25004
25173
  for (; ; ) {
25005
25174
  let chunk;
@@ -25011,17 +25180,11 @@ async function consumeToolStream(name, body, onProgress, signal) {
25011
25180
  }
25012
25181
  const { done, value } = chunk;
25013
25182
  if (done) break;
25014
- buffered += decoder.decode(value, { stream: true });
25015
- let newline;
25016
- while ((newline = buffered.indexOf("\n")) !== -1) {
25017
- signal?.throwIfAborted();
25018
- const line = buffered.slice(0, newline);
25019
- buffered = buffered.slice(newline + 1);
25020
- handleLine(line);
25021
- }
25183
+ take(decoder.decode(value, { stream: true }));
25022
25184
  }
25023
- buffered += decoder.decode();
25024
- if (buffered.trim()) handleLine(buffered);
25185
+ take(decoder.decode());
25186
+ const last = pieces.join("");
25187
+ if (last.trim()) handleLine(last);
25025
25188
  } catch (err) {
25026
25189
  void reader.cancel().catch(() => {
25027
25190
  });
@@ -25031,8 +25194,7 @@ async function consumeToolStream(name, body, onProgress, signal) {
25031
25194
  if (!final) {
25032
25195
  throw brokenStream(name, "the stream ended without a result", progress);
25033
25196
  }
25034
- const { result: data } = await applyClientFileDirectives(final.data);
25035
- return { data, note: final.note };
25197
+ return { data: final.data, note: final.note };
25036
25198
  }
25037
25199
  function errorBodyMessage(body) {
25038
25200
  if (Array.isArray(body.issues) && typeof body.message === "string") return body.message;
@@ -25121,16 +25283,28 @@ function createToolsClient(options = {}) {
25121
25283
  const { url: url2, token, remote } = await route();
25122
25284
  let finalArgs = args;
25123
25285
  const meta3 = await fetchTool(name, { signal: opts?.signal });
25286
+ const baselineDirs = [];
25124
25287
  if (meta3?.fileInputs?.length) {
25125
25288
  if (remote) assertRequiredPresent(meta3, args);
25126
25289
  finalArgs = await prepareFileInputs(meta3.fileInputs, args ?? {}, {
25127
25290
  includeContent: remote,
25128
25291
  uploadEndpoint: remote ? { url: url2, token } : void 0,
25129
25292
  log: diagnose,
25293
+ baselineDirs,
25130
25294
  collectMembers: collectFlowMembers,
25131
25295
  signal: opts?.signal
25132
25296
  });
25133
25297
  }
25298
+ const settle = async (data) => {
25299
+ const applied = await applyClientFileDirectives(data, { allowedDirs: baselineDirs });
25300
+ for (const { path: file2, error: error52 } of applied.failed) {
25301
+ diagnose(`The baseline ${file2} was not written on this client: ${error52}`);
25302
+ }
25303
+ if (process.env.ARGENT_FLOW_FILES_LOG === "1") {
25304
+ for (const file2 of applied.written) diagnose(`[flow-files] baseline ${file2}: written`);
25305
+ }
25306
+ return applied.result;
25307
+ };
25134
25308
  const stream = opts?.onProgress !== void 0 || carriesMembers(finalArgs);
25135
25309
  const sentOnce = carriesUpload(finalArgs);
25136
25310
  const res = await doFetch(
@@ -25162,7 +25336,11 @@ function createToolsClient(options = {}) {
25162
25336
  }),
25163
25337
  opts?.signal
25164
25338
  );
25165
- return { ...streamed, outputHint: meta3?.outputHint };
25339
+ return {
25340
+ data: await settle(streamed.data),
25341
+ note: streamed.note,
25342
+ outputHint: meta3?.outputHint
25343
+ };
25166
25344
  }
25167
25345
  let json2;
25168
25346
  try {
@@ -25186,17 +25364,16 @@ function createToolsClient(options = {}) {
25186
25364
  });
25187
25365
  }
25188
25366
  opts?.signal?.throwIfAborted();
25189
- const { result: data } = await applyClientFileDirectives(json2.data);
25190
- return { data, note: json2.note, outputHint: meta3?.outputHint };
25367
+ return { data: await settle(json2.data), note: json2.note, outputHint: meta3?.outputHint };
25191
25368
  }
25192
25369
  return { fetchTools, fetchTool, callTool, baseUrl };
25193
25370
  }
25194
25371
 
25195
25372
  // ../argent-tools-client/src/artifacts.ts
25196
- import { copyFile, mkdir as mkdir4, readFile as readFile5, realpath as realpath3, rm as rm3, stat as stat4, writeFile as writeFile4 } from "node:fs/promises";
25373
+ import { copyFile, mkdir as mkdir4, readFile as readFile5, realpath as realpath4, rm as rm3, stat as stat4, writeFile as writeFile4 } from "node:fs/promises";
25197
25374
  import { constants as fsConstants } from "node:fs";
25198
25375
  import { tmpdir as tmpdir2 } from "node:os";
25199
- import { basename as basename6, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute5, join as join11, normalize, resolve as resolve6, sep as sep4 } from "node:path";
25376
+ import { basename as basename6, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute5, join as join11, normalize as normalize2, resolve as resolve6, sep as sep4 } from "node:path";
25200
25377
  import { createHash as createHash3 } from "node:crypto";
25201
25378
 
25202
25379
  // ../configuration-core/src/flags.ts
@@ -25935,8 +26112,8 @@ function durableBaseDir() {
25935
26112
  const projectRoot = findProjectRoot(process.cwd());
25936
26113
  return projectRoot ?? dirname7(argentHomeDir());
25937
26114
  }
25938
- var ALLOWED_SAVE_DIRS = /* @__PURE__ */ new Set([normalize(".argent/recordings")]);
25939
- var RECORDINGS_SAVE_DIR = normalize(".argent/recordings");
26115
+ var ALLOWED_SAVE_DIRS = /* @__PURE__ */ new Set([normalize2(".argent/recordings")]);
26116
+ var RECORDINGS_SAVE_DIR = normalize2(".argent/recordings");
25940
26117
  function configuredRecordingsDir() {
25941
26118
  let value;
25942
26119
  try {
@@ -25995,7 +26172,7 @@ async function writeDurableUnique(dir, filename, write) {
25995
26172
  }
25996
26173
  function durableSaveTarget(handle) {
25997
26174
  if (typeof handle.saveDir !== "string" || !handle.saveDir || handle.archive) return null;
25998
- const rel = normalize(handle.saveDir);
26175
+ const rel = normalize2(handle.saveDir);
25999
26176
  if (isAbsolute5(rel) || rel === ".." || rel.startsWith(`..${sep4}`) || rel.split(sep4).includes("..")) {
26000
26177
  return null;
26001
26178
  }
@@ -26017,8 +26194,8 @@ function durableSaveTarget(handle) {
26017
26194
  }
26018
26195
  async function confineToRealBase(dir, base, rel) {
26019
26196
  try {
26020
- const realDir = await realpath3(dir);
26021
- const realBase = await realpath3(base);
26197
+ const realDir = await realpath4(dir);
26198
+ const realBase = await realpath4(base);
26022
26199
  return realDir === join11(realBase, rel);
26023
26200
  } catch {
26024
26201
  return false;
@@ -29700,7 +29877,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
29700
29877
  var SESSION_ID2 = randomUUID5();
29701
29878
  function readCliVersion() {
29702
29879
  if (true) {
29703
- return "0.27.1-next.20";
29880
+ return "0.27.1-next.21";
29704
29881
  }
29705
29882
  return "0.0.0";
29706
29883
  }
@@ -31053,10 +31230,11 @@ counts the remaining flows skipped.
31053
31230
 
31054
31231
  The CLI sends a run to its tool-server: the local one that starts
31055
31232
  automatically, or the one that \`argent link\` or ARGENT_TOOLS_URL names. Over
31056
- a link, the CLI uploads the flow file and the run: fragments that it reaches.
31057
- The tool-server rejects the flow before the first step when the flow or a
31058
- fragment has one of these steps:
31059
- - script: or snapshot:
31233
+ a link, the CLI uploads the flow file, the run: fragments that it reaches and
31234
+ the snapshot baselines that the run compares. The CLI writes the new baselines
31235
+ that the run returns. The tool-server rejects the flow before the first step
31236
+ when the flow or a fragment has one of these steps:
31237
+ - script:
31060
31238
  - a tool: step that takes a file or records a flow
31061
31239
  Without a link, all step kinds run.
31062
31240
 
@@ -31596,6 +31774,14 @@ function isFlowReport(data) {
31596
31774
  const steps = data?.steps;
31597
31775
  return Array.isArray(steps) && steps.every((step) => !!step && typeof step === "object");
31598
31776
  }
31777
+ function unwrittenBaselines(report) {
31778
+ const writes = report.baselineWrites;
31779
+ if (!Array.isArray(writes)) return [];
31780
+ return writes.flatMap((write) => {
31781
+ const { path: file2, error: error52 } = write ?? {};
31782
+ return typeof file2 === "string" && typeof error52 === "string" ? [`${file2}: ${error52}`] : [];
31783
+ });
31784
+ }
31599
31785
  async function runFlowDirectory(dir, args, projectRoot, options) {
31600
31786
  const printError = (message, err = message) => {
31601
31787
  console.error(args.json ? JSON.stringify(errorRecord(err, message)) : message);
@@ -31666,8 +31852,17 @@ async function runFlowDirectory(dir, args, projectRoot, options) {
31666
31852
  flowPath,
31667
31853
  baseUrl
31668
31854
  );
31669
- results.push({ path: rel, status: report.ok ? "pass" : "fail", report });
31855
+ const unwritten = unwrittenBaselines(report);
31856
+ results.push({ path: rel, status: report.ok && !unwritten.length ? "pass" : "fail", report });
31670
31857
  if (!report.ok) failures.push({ path: rel, ...summarizeFailure(report), rerun });
31858
+ else if (unwritten.length) {
31859
+ failures.push({
31860
+ path: rel,
31861
+ headline: "baselines not written",
31862
+ detail: unwritten.join("\n"),
31863
+ rerun
31864
+ });
31865
+ }
31671
31866
  if (!args.json) {
31672
31867
  for (const line of renderFailedSteps(report)) console.log(line);
31673
31868
  console.log(` ${renderSummary(report, { withDevice: true })}`);
@@ -31928,7 +32123,12 @@ ${renderSummary(report, { withDevice: true })}`);
31928
32123
  } else {
31929
32124
  console.log(renderReport(report));
31930
32125
  }
31931
- return exitAfterFlush(report.ok ? 0 : 1);
32126
+ const unwritten = unwrittenBaselines(report);
32127
+ if (!args.json && !args.jsonStream) {
32128
+ for (const line of unwritten)
32129
+ console.log(` ${STATUS_GLYPH.error} baseline not written: ${line}`);
32130
+ }
32131
+ return exitAfterFlush(report.ok && unwritten.length === 0 ? 0 : 1);
31932
32132
  }
31933
32133
 
31934
32134
  // ../argent-cli/src/tools.ts