@swmansion/argent 0.19.1-next.2 → 0.19.1-next.4

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.
@@ -45,7 +45,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
45
45
  // ../../node_modules/dotenv/lib/main.js
46
46
  var require_main = __commonJS({
47
47
  "../../node_modules/dotenv/lib/main.js"(exports2, module2) {
48
- var fs54 = require("fs");
48
+ var fs55 = require("fs");
49
49
  var path40 = require("path");
50
50
  var os15 = require("os");
51
51
  var crypto8 = require("crypto");
@@ -177,7 +177,7 @@ var require_main = __commonJS({
177
177
  if (options && options.path && options.path.length > 0) {
178
178
  if (Array.isArray(options.path)) {
179
179
  for (const filepath of options.path) {
180
- if (fs54.existsSync(filepath)) {
180
+ if (fs55.existsSync(filepath)) {
181
181
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
182
182
  }
183
183
  }
@@ -187,7 +187,7 @@ var require_main = __commonJS({
187
187
  } else {
188
188
  possibleVaultPath = path40.resolve(process.cwd(), ".env.vault");
189
189
  }
190
- if (fs54.existsSync(possibleVaultPath)) {
190
+ if (fs55.existsSync(possibleVaultPath)) {
191
191
  return possibleVaultPath;
192
192
  }
193
193
  return null;
@@ -240,7 +240,7 @@ var require_main = __commonJS({
240
240
  const parsedAll = {};
241
241
  for (const path41 of optionPaths) {
242
242
  try {
243
- const parsed = DotenvModule.parse(fs54.readFileSync(path41, { encoding }));
243
+ const parsed = DotenvModule.parse(fs55.readFileSync(path41, { encoding }));
244
244
  DotenvModule.populate(parsedAll, parsed, options);
245
245
  } catch (e) {
246
246
  if (debug) {
@@ -523,6 +523,7 @@ var init_failure_codes = __esm({
523
523
  REGISTRY_TOOL_NOT_FOUND: "REGISTRY_TOOL_NOT_FOUND",
524
524
  REGISTRY_TOOL_EXECUTION_FAILED: "REGISTRY_TOOL_EXECUTION_FAILED",
525
525
  REGISTRY_TOOL_FAILURE_UNCLASSIFIED: "REGISTRY_TOOL_FAILURE_UNCLASSIFIED",
526
+ REGISTRY_SERVICE_TERMINATING: "REGISTRY_SERVICE_TERMINATING",
526
527
  HTTP_TOOL_NOT_FOUND: "HTTP_TOOL_NOT_FOUND",
527
528
  HTTP_ZOD_VALIDATION_FAILED: "HTTP_ZOD_VALIDATION_FAILED",
528
529
  HTTP_CAPABILITY_UNSUPPORTED_OPERATION: "HTTP_CAPABILITY_UNSUPPORTED_OPERATION",
@@ -646,6 +647,13 @@ var init_failure_codes = __esm({
646
647
  DEBUGGER_CDP_BINDING_TIMEOUT: "DEBUGGER_CDP_BINDING_TIMEOUT",
647
648
  DEBUGGER_CDP_BINDING_UNAVAILABLE: "DEBUGGER_CDP_BINDING_UNAVAILABLE",
648
649
  DEBUGGER_CDP_PROTOCOL_ERROR: "DEBUGGER_CDP_PROTOCOL_ERROR",
650
+ DEBUGGER_CDP_CONNECT_FAILED: "DEBUGGER_CDP_CONNECT_FAILED",
651
+ DEBUGGER_CDP_SOCKET_CLOSED_BEFORE_OPEN: "DEBUGGER_CDP_SOCKET_CLOSED_BEFORE_OPEN",
652
+ DEBUGGER_CDP_NOT_CONNECTED: "DEBUGGER_CDP_NOT_CONNECTED",
653
+ DEBUGGER_CDP_REQUEST_TIMEOUT: "DEBUGGER_CDP_REQUEST_TIMEOUT",
654
+ DEBUGGER_CDP_CONNECTION_CLOSED: "DEBUGGER_CDP_CONNECTION_CLOSED",
655
+ DEBUGGER_TARGET_DEVICE_MISMATCH: "DEBUGGER_TARGET_DEVICE_MISMATCH",
656
+ DEBUGGER_EVALUATE_EXPRESSION_THREW: "DEBUGGER_EVALUATE_EXPRESSION_THREW",
649
657
  DEBUGGER_RELOAD_FAILED: "DEBUGGER_RELOAD_FAILED",
650
658
  JS_RUNTIME_CONSOLE_SERVER_BIND_FAILED: "JS_RUNTIME_CONSOLE_SERVER_BIND_FAILED",
651
659
  JS_RUNTIME_PAYLOAD_INVALID: "JS_RUNTIME_PAYLOAD_INVALID",
@@ -16042,6 +16050,14 @@ var init_zod_to_json_schema = __esm({
16042
16050
  });
16043
16051
 
16044
16052
  // ../registry/src/registry.ts
16053
+ function terminatingSignalCause(message) {
16054
+ return new FailureError(message, {
16055
+ error_code: FAILURE_CODES.REGISTRY_SERVICE_TERMINATING,
16056
+ failure_stage: "registry_service_terminating",
16057
+ failure_area: "registry",
16058
+ error_kind: "unknown"
16059
+ });
16060
+ }
16045
16061
  function formatInteractionMessage(format, fallback) {
16046
16062
  try {
16047
16063
  return format() ?? fallback;
@@ -16057,6 +16073,7 @@ var init_registry = __esm({
16057
16073
  init_types();
16058
16074
  init_artifacts();
16059
16075
  init_errors();
16076
+ init_failure_codes();
16060
16077
  init_urn();
16061
16078
  init_zod_to_json_schema();
16062
16079
  import_node_crypto2 = require("node:crypto");
@@ -16142,7 +16159,7 @@ var init_registry = __esm({
16142
16159
  urn: typeof ref === "string" ? ref : ref.urn,
16143
16160
  options: typeof ref === "string" ? void 0 : ref.options
16144
16161
  }));
16145
- const ctx = { ...options, artifacts: this.artifacts };
16162
+ const ctx = { ...options, toolInvocationId, artifacts: this.artifacts };
16146
16163
  const runOnce = async () => {
16147
16164
  const resolvedServices = {};
16148
16165
  for (const { alias, urn, options: resolveOptions } of refs) {
@@ -16288,7 +16305,9 @@ var init_registry = __esm({
16288
16305
  }
16289
16306
  if (node.state === "TERMINATING" /* TERMINATING */) {
16290
16307
  return Promise.reject(
16291
- new ServiceInitializationError(urn, "Service is currently terminating")
16308
+ new ServiceInitializationError(urn, "Service is currently terminating", {
16309
+ cause: terminatingSignalCause("Service is currently terminating")
16310
+ })
16292
16311
  );
16293
16312
  }
16294
16313
  if (node.state === "RUNNING" /* RUNNING */ && node.instance) {
@@ -16320,7 +16339,9 @@ var init_registry = __esm({
16320
16339
  } catch {
16321
16340
  }
16322
16341
  node.initPromise = null;
16323
- throw new ServiceInitializationError(urn, "Service was terminated during initialization");
16342
+ throw new ServiceInitializationError(urn, "Service was terminated during initialization", {
16343
+ cause: terminatingSignalCause("Service was terminated during initialization")
16344
+ });
16324
16345
  }
16325
16346
  this._transition(node, "RUNNING" /* RUNNING */);
16326
16347
  node.instance = instance;
@@ -35264,7 +35285,7 @@ var require_view = __commonJS({
35264
35285
  "use strict";
35265
35286
  var debug = require_src()("express:view");
35266
35287
  var path40 = require("node:path");
35267
- var fs54 = require("node:fs");
35288
+ var fs55 = require("node:fs");
35268
35289
  var dirname15 = path40.dirname;
35269
35290
  var basename13 = path40.basename;
35270
35291
  var extname6 = path40.extname;
@@ -35344,7 +35365,7 @@ var require_view = __commonJS({
35344
35365
  function tryStat(path41) {
35345
35366
  debug('stat "%s"', path41);
35346
35367
  try {
35347
- return fs54.statSync(path41);
35368
+ return fs55.statSync(path41);
35348
35369
  } catch (e) {
35349
35370
  return void 0;
35350
35371
  }
@@ -67649,7 +67670,7 @@ var require_send = __commonJS({
67649
67670
  var escapeHtml = require_escape_html();
67650
67671
  var etag = require_etag();
67651
67672
  var fresh = require_fresh();
67652
- var fs54 = require("fs");
67673
+ var fs55 = require("fs");
67653
67674
  var mime = require_mime_types4();
67654
67675
  var ms = require_ms();
67655
67676
  var onFinished = require_on_finished();
@@ -67931,7 +67952,7 @@ var require_send = __commonJS({
67931
67952
  var i = 0;
67932
67953
  var self = this;
67933
67954
  debug('stat "%s"', path41);
67934
- fs54.stat(path41, function onstat(err, stat5) {
67955
+ fs55.stat(path41, function onstat(err, stat5) {
67935
67956
  var pathEndsWithSep = path41[path41.length - 1] === sep7;
67936
67957
  if (err && err.code === "ENOENT" && !extname6(path41) && !pathEndsWithSep) {
67937
67958
  return next(err);
@@ -67948,7 +67969,7 @@ var require_send = __commonJS({
67948
67969
  }
67949
67970
  var p = path41 + "." + self._extensions[i++];
67950
67971
  debug('stat "%s"', p);
67951
- fs54.stat(p, function(err2, stat5) {
67972
+ fs55.stat(p, function(err2, stat5) {
67952
67973
  if (err2) return next(err2);
67953
67974
  if (stat5.isDirectory()) return next();
67954
67975
  self.emit("file", p, stat5);
@@ -67966,7 +67987,7 @@ var require_send = __commonJS({
67966
67987
  }
67967
67988
  var p = join47(path41, self._index[i]);
67968
67989
  debug('stat "%s"', p);
67969
- fs54.stat(p, function(err2, stat5) {
67990
+ fs55.stat(p, function(err2, stat5) {
67970
67991
  if (err2) return next(err2);
67971
67992
  if (stat5.isDirectory()) return next();
67972
67993
  self.emit("file", p, stat5);
@@ -67978,7 +67999,7 @@ var require_send = __commonJS({
67978
67999
  SendStream.prototype.stream = function stream(path41, options) {
67979
68000
  var self = this;
67980
68001
  var res = this.res;
67981
- var stream2 = fs54.createReadStream(path41, options);
68002
+ var stream2 = fs55.createReadStream(path41, options);
67982
68003
  this.emit("stream", stream2);
67983
68004
  stream2.pipe(res);
67984
68005
  function cleanup() {
@@ -75469,10 +75490,10 @@ function avdRootCandidates() {
75469
75490
  return candidates.filter((p) => Boolean(p && (0, import_node_path6.isAbsolute)(p)));
75470
75491
  }
75471
75492
  async function resolveAvdPath(avdName) {
75472
- const { readFile: readFile12 } = await import("node:fs/promises");
75493
+ const { readFile: readFile13 } = await import("node:fs/promises");
75473
75494
  for (const root of avdRootCandidates()) {
75474
75495
  try {
75475
- const content = await readFile12(`${root}/${avdName}.ini`, "utf-8");
75496
+ const content = await readFile13(`${root}/${avdName}.ini`, "utf-8");
75476
75497
  const raw = (0, import_ini.parse)(content).path;
75477
75498
  if (typeof raw !== "string") continue;
75478
75499
  const trimmed = raw.trim();
@@ -75602,7 +75623,7 @@ var require_safe_json_stringify = __commonJS({
75602
75623
  // ../../node_modules/ncp/lib/ncp.js
75603
75624
  var require_ncp = __commonJS({
75604
75625
  "../../node_modules/ncp/lib/ncp.js"(exports2, module2) {
75605
- var fs54 = require("fs");
75626
+ var fs55 = require("fs");
75606
75627
  var path40 = require("path");
75607
75628
  module2.exports = ncp;
75608
75629
  ncp.ncp = ncp;
@@ -75631,7 +75652,7 @@ var require_ncp = __commonJS({
75631
75652
  return getStats(source2);
75632
75653
  }
75633
75654
  function getStats(source2) {
75634
- var stat5 = dereference ? fs54.stat : fs54.lstat;
75655
+ var stat5 = dereference ? fs55.stat : fs55.lstat;
75635
75656
  if (running >= limit) {
75636
75657
  return setImmediate(function() {
75637
75658
  getStats(source2);
@@ -75671,7 +75692,7 @@ var require_ncp = __commonJS({
75671
75692
  });
75672
75693
  }
75673
75694
  if (modified) {
75674
- var stat5 = dereference ? fs54.stat : fs54.lstat;
75695
+ var stat5 = dereference ? fs55.stat : fs55.lstat;
75675
75696
  stat5(target, function(err, stats) {
75676
75697
  if (file2.mtime.getTime() > stats.mtime.getTime())
75677
75698
  copyFile2(file2, target);
@@ -75683,7 +75704,7 @@ var require_ncp = __commonJS({
75683
75704
  });
75684
75705
  }
75685
75706
  function copyFile2(file2, target) {
75686
- var readStream = fs54.createReadStream(file2.name), writeStream = fs54.createWriteStream(target, { mode: file2.mode });
75707
+ var readStream = fs55.createReadStream(file2.name), writeStream = fs55.createWriteStream(target, { mode: file2.mode });
75687
75708
  readStream.on("error", onError);
75688
75709
  writeStream.on("error", onError);
75689
75710
  if (transform2) {
@@ -75695,13 +75716,13 @@ var require_ncp = __commonJS({
75695
75716
  }
75696
75717
  writeStream.once("finish", function() {
75697
75718
  if (modified) {
75698
- fs54.utimesSync(target, file2.atime, file2.mtime);
75719
+ fs55.utimesSync(target, file2.atime, file2.mtime);
75699
75720
  cb();
75700
75721
  } else cb();
75701
75722
  });
75702
75723
  }
75703
75724
  function rmFile(file2, done) {
75704
- fs54.unlink(file2, function(err) {
75725
+ fs55.unlink(file2, function(err) {
75705
75726
  if (err) {
75706
75727
  return onError(err);
75707
75728
  }
@@ -75718,7 +75739,7 @@ var require_ncp = __commonJS({
75718
75739
  });
75719
75740
  }
75720
75741
  function mkDir(dir, target) {
75721
- fs54.mkdir(target, dir.mode, function(err) {
75742
+ fs55.mkdir(target, dir.mode, function(err) {
75722
75743
  if (err) {
75723
75744
  return onError(err);
75724
75745
  }
@@ -75726,7 +75747,7 @@ var require_ncp = __commonJS({
75726
75747
  });
75727
75748
  }
75728
75749
  function copyDir(dir) {
75729
- fs54.readdir(dir, function(err, items) {
75750
+ fs55.readdir(dir, function(err, items) {
75730
75751
  if (err) {
75731
75752
  return onError(err);
75732
75753
  }
@@ -75738,7 +75759,7 @@ var require_ncp = __commonJS({
75738
75759
  }
75739
75760
  function onLink(link) {
75740
75761
  var target = link.replace(currentPath, targetPath);
75741
- fs54.readlink(link, function(err, resolvedPath) {
75762
+ fs55.readlink(link, function(err, resolvedPath) {
75742
75763
  if (err) {
75743
75764
  return onError(err);
75744
75765
  }
@@ -75753,7 +75774,7 @@ var require_ncp = __commonJS({
75753
75774
  if (writable) {
75754
75775
  return makeLink(resolvedPath, target);
75755
75776
  }
75756
- fs54.readlink(target, function(err, targetDest) {
75777
+ fs55.readlink(target, function(err, targetDest) {
75757
75778
  if (err) {
75758
75779
  return onError(err);
75759
75780
  }
@@ -75770,7 +75791,7 @@ var require_ncp = __commonJS({
75770
75791
  });
75771
75792
  }
75772
75793
  function makeLink(linkPath, target) {
75773
- fs54.symlink(linkPath, target, function(err) {
75794
+ fs55.symlink(linkPath, target, function(err) {
75774
75795
  if (err) {
75775
75796
  return onError(err);
75776
75797
  }
@@ -75778,7 +75799,7 @@ var require_ncp = __commonJS({
75778
75799
  });
75779
75800
  }
75780
75801
  function isWritable2(path41, done) {
75781
- fs54.lstat(path41, function(err) {
75802
+ fs55.lstat(path41, function(err) {
75782
75803
  if (err) {
75783
75804
  if (err.code === "ENOENT") return done(true);
75784
75805
  return done(false);
@@ -75790,7 +75811,7 @@ var require_ncp = __commonJS({
75790
75811
  if (options.stopOnError) {
75791
75812
  return cback(err);
75792
75813
  } else if (!errs && options.errs) {
75793
- errs = fs54.createWriteStream(options.errs);
75814
+ errs = fs55.createWriteStream(options.errs);
75794
75815
  } else if (!errs) {
75795
75816
  errs = [];
75796
75817
  }
@@ -76919,7 +76940,7 @@ var require_sync = __commonJS({
76919
76940
  "../../node_modules/glob/sync.js"(exports2, module2) {
76920
76941
  module2.exports = globSync;
76921
76942
  globSync.GlobSync = GlobSync;
76922
- var fs54 = require("fs");
76943
+ var fs55 = require("fs");
76923
76944
  var minimatch = require_minimatch();
76924
76945
  var Minimatch = minimatch.Minimatch;
76925
76946
  var Glob = require_glob().Glob;
@@ -76964,7 +76985,7 @@ var require_sync = __commonJS({
76964
76985
  for (var p in matchset) {
76965
76986
  try {
76966
76987
  p = self._makeAbs(p);
76967
- var real = fs54.realpathSync(p, self.realpathCache);
76988
+ var real = fs55.realpathSync(p, self.realpathCache);
76968
76989
  set2[real] = true;
76969
76990
  } catch (er) {
76970
76991
  if (er.syscall === "stat")
@@ -77091,7 +77112,7 @@ var require_sync = __commonJS({
77091
77112
  var lstat;
77092
77113
  var stat5;
77093
77114
  try {
77094
- lstat = fs54.lstatSync(abs);
77115
+ lstat = fs55.lstatSync(abs);
77095
77116
  } catch (er) {
77096
77117
  return null;
77097
77118
  }
@@ -77115,7 +77136,7 @@ var require_sync = __commonJS({
77115
77136
  return c;
77116
77137
  }
77117
77138
  try {
77118
- return this._readdirEntries(abs, fs54.readdirSync(abs));
77139
+ return this._readdirEntries(abs, fs55.readdirSync(abs));
77119
77140
  } catch (er) {
77120
77141
  this._readdirError(abs, er);
77121
77142
  return null;
@@ -77219,13 +77240,13 @@ var require_sync = __commonJS({
77219
77240
  if (!stat5) {
77220
77241
  var lstat;
77221
77242
  try {
77222
- lstat = fs54.lstatSync(abs);
77243
+ lstat = fs55.lstatSync(abs);
77223
77244
  } catch (er) {
77224
77245
  return false;
77225
77246
  }
77226
77247
  if (lstat.isSymbolicLink()) {
77227
77248
  try {
77228
- stat5 = fs54.statSync(abs);
77249
+ stat5 = fs55.statSync(abs);
77229
77250
  } catch (er) {
77230
77251
  stat5 = lstat;
77231
77252
  }
@@ -77299,7 +77320,7 @@ var require_inflight = __commonJS({
77299
77320
  var require_glob = __commonJS({
77300
77321
  "../../node_modules/glob/glob.js"(exports2, module2) {
77301
77322
  module2.exports = glob;
77302
- var fs54 = require("fs");
77323
+ var fs55 = require("fs");
77303
77324
  var minimatch = require_minimatch();
77304
77325
  var Minimatch = minimatch.Minimatch;
77305
77326
  var inherits = require_inherits();
@@ -77436,7 +77457,7 @@ var require_glob = __commonJS({
77436
77457
  var set2 = this.matches[index] = /* @__PURE__ */ Object.create(null);
77437
77458
  found.forEach(function(p, i) {
77438
77459
  p = self._makeAbs(p);
77439
- fs54.realpath(p, self.realpathCache, function(er, real) {
77460
+ fs55.realpath(p, self.realpathCache, function(er, real) {
77440
77461
  if (!er)
77441
77462
  set2[real] = true;
77442
77463
  else if (er.syscall === "stat")
@@ -77631,7 +77652,7 @@ var require_glob = __commonJS({
77631
77652
  var self = this;
77632
77653
  var lstatcb = inflight(lstatkey, lstatcb_);
77633
77654
  if (lstatcb)
77634
- fs54.lstat(abs, lstatcb);
77655
+ fs55.lstat(abs, lstatcb);
77635
77656
  function lstatcb_(er, lstat) {
77636
77657
  if (er)
77637
77658
  return cb();
@@ -77660,7 +77681,7 @@ var require_glob = __commonJS({
77660
77681
  return cb(null, c);
77661
77682
  }
77662
77683
  var self = this;
77663
- fs54.readdir(abs, readdirCb(this, abs, cb));
77684
+ fs55.readdir(abs, readdirCb(this, abs, cb));
77664
77685
  };
77665
77686
  function readdirCb(self, abs, cb) {
77666
77687
  return function(er, entries) {
@@ -77798,10 +77819,10 @@ var require_glob = __commonJS({
77798
77819
  var self = this;
77799
77820
  var statcb = inflight("stat\0" + abs, lstatcb_);
77800
77821
  if (statcb)
77801
- fs54.lstat(abs, statcb);
77822
+ fs55.lstat(abs, statcb);
77802
77823
  function lstatcb_(er, lstat) {
77803
77824
  if (lstat && lstat.isSymbolicLink()) {
77804
- return fs54.stat(abs, function(er2, stat6) {
77825
+ return fs55.stat(abs, function(er2, stat6) {
77805
77826
  if (er2)
77806
77827
  self._stat2(f, abs, null, lstat, cb);
77807
77828
  else
@@ -77837,7 +77858,7 @@ var require_rimraf = __commonJS({
77837
77858
  rimraf.sync = rimrafSync;
77838
77859
  var assert2 = require("assert");
77839
77860
  var path40 = require("path");
77840
- var fs54 = require("fs");
77861
+ var fs55 = require("fs");
77841
77862
  var glob = require_glob();
77842
77863
  var globOpts = {
77843
77864
  nosort: true,
@@ -77857,9 +77878,9 @@ var require_rimraf = __commonJS({
77857
77878
  "readdir"
77858
77879
  ];
77859
77880
  methods.forEach(function(m) {
77860
- options[m] = options[m] || fs54[m];
77881
+ options[m] = options[m] || fs55[m];
77861
77882
  m = m + "Sync";
77862
- options[m] = options[m] || fs54[m];
77883
+ options[m] = options[m] || fs55[m];
77863
77884
  });
77864
77885
  options.maxBusyTries = options.maxBusyTries || 3;
77865
77886
  options.emfileWait = options.emfileWait || 1e3;
@@ -77881,7 +77902,7 @@ var require_rimraf = __commonJS({
77881
77902
  var n = 0;
77882
77903
  if (options.disableGlob || !glob.hasMagic(p))
77883
77904
  return afterGlob(null, [p]);
77884
- fs54.lstat(p, function(er, stat5) {
77905
+ fs55.lstat(p, function(er, stat5) {
77885
77906
  if (!er)
77886
77907
  return afterGlob(null, [p]);
77887
77908
  glob(p, globOpts, afterGlob);
@@ -78038,7 +78059,7 @@ var require_rimraf = __commonJS({
78038
78059
  results = [p];
78039
78060
  } else {
78040
78061
  try {
78041
- fs54.lstatSync(p);
78062
+ fs55.lstatSync(p);
78042
78063
  results = [p];
78043
78064
  } catch (er) {
78044
78065
  results = glob.sync(p, globOpts);
@@ -78101,7 +78122,7 @@ var require_rimraf = __commonJS({
78101
78122
  var require_mkdirp = __commonJS({
78102
78123
  "../../node_modules/mkdirp/index.js"(exports2, module2) {
78103
78124
  var path40 = require("path");
78104
- var fs54 = require("fs");
78125
+ var fs55 = require("fs");
78105
78126
  var _0777 = parseInt("0777", 8);
78106
78127
  module2.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
78107
78128
  function mkdirP(p, opts, f, made) {
@@ -78112,7 +78133,7 @@ var require_mkdirp = __commonJS({
78112
78133
  opts = { mode: opts };
78113
78134
  }
78114
78135
  var mode = opts.mode;
78115
- var xfs = opts.fs || fs54;
78136
+ var xfs = opts.fs || fs55;
78116
78137
  if (mode === void 0) {
78117
78138
  mode = _0777;
78118
78139
  }
@@ -78151,7 +78172,7 @@ var require_mkdirp = __commonJS({
78151
78172
  opts = { mode: opts };
78152
78173
  }
78153
78174
  var mode = opts.mode;
78154
- var xfs = opts.fs || fs54;
78175
+ var xfs = opts.fs || fs55;
78155
78176
  if (mode === void 0) {
78156
78177
  mode = _0777;
78157
78178
  }
@@ -78188,7 +78209,7 @@ var require_mkdirp = __commonJS({
78188
78209
  // ../../node_modules/mv/index.js
78189
78210
  var require_mv = __commonJS({
78190
78211
  "../../node_modules/mv/index.js"(exports2, module2) {
78191
- var fs54 = require("fs");
78212
+ var fs55 = require("fs");
78192
78213
  var ncp = require_ncp().ncp;
78193
78214
  var path40 = require("path");
78194
78215
  var rimraf = require_rimraf();
@@ -78215,13 +78236,13 @@ var require_mv = __commonJS({
78215
78236
  }
78216
78237
  function doRename() {
78217
78238
  if (clobber) {
78218
- fs54.rename(source, dest, function(err) {
78239
+ fs55.rename(source, dest, function(err) {
78219
78240
  if (!err) return cb();
78220
78241
  if (err.code !== "EXDEV") return cb(err);
78221
78242
  moveFileAcrossDevice(source, dest, clobber, limit, cb);
78222
78243
  });
78223
78244
  } else {
78224
- fs54.link(source, dest, function(err) {
78245
+ fs55.link(source, dest, function(err) {
78225
78246
  if (err) {
78226
78247
  if (err.code === "EXDEV") {
78227
78248
  moveFileAcrossDevice(source, dest, clobber, limit, cb);
@@ -78234,15 +78255,15 @@ var require_mv = __commonJS({
78234
78255
  cb(err);
78235
78256
  return;
78236
78257
  }
78237
- fs54.unlink(source, cb);
78258
+ fs55.unlink(source, cb);
78238
78259
  });
78239
78260
  }
78240
78261
  }
78241
78262
  }
78242
78263
  function moveFileAcrossDevice(source, dest, clobber, limit, cb) {
78243
78264
  var outFlags = clobber ? "w" : "wx";
78244
- var ins = fs54.createReadStream(source);
78245
- var outs = fs54.createWriteStream(dest, { flags: outFlags });
78265
+ var ins = fs55.createReadStream(source);
78266
+ var outs = fs55.createWriteStream(dest, { flags: outFlags });
78246
78267
  ins.on("error", function(err) {
78247
78268
  ins.destroy();
78248
78269
  outs.destroy();
@@ -78262,7 +78283,7 @@ var require_mv = __commonJS({
78262
78283
  outs.once("close", onClose);
78263
78284
  ins.pipe(outs);
78264
78285
  function onClose() {
78265
- fs54.unlink(source, cb);
78286
+ fs55.unlink(source, cb);
78266
78287
  }
78267
78288
  }
78268
78289
  function moveDirAcrossDevice(source, dest, clobber, limit, cb) {
@@ -78317,7 +78338,7 @@ var require_bunyan = __commonJS({
78317
78338
  throw new Error("unknown runtime environment");
78318
78339
  }
78319
78340
  var os15;
78320
- var fs54;
78341
+ var fs55;
78321
78342
  var dtrace;
78322
78343
  if (runtimeEnv === "browser") {
78323
78344
  os15 = {
@@ -78325,11 +78346,11 @@ var require_bunyan = __commonJS({
78325
78346
  return window.location.host;
78326
78347
  }
78327
78348
  };
78328
- fs54 = {};
78349
+ fs55 = {};
78329
78350
  dtrace = null;
78330
78351
  } else {
78331
78352
  os15 = require("os");
78332
- fs54 = require("fs");
78353
+ fs55 = require("fs");
78333
78354
  try {
78334
78355
  dtrace = require("dtrace-provider");
78335
78356
  } catch (e) {
@@ -78709,7 +78730,7 @@ var require_bunyan = __commonJS({
78709
78730
  s.reemitErrorEvents = true;
78710
78731
  }
78711
78732
  if (!s.stream) {
78712
- s.stream = fs54.createWriteStream(
78733
+ s.stream = fs55.createWriteStream(
78713
78734
  s.path,
78714
78735
  { flags: "a", encoding: "utf8" }
78715
78736
  );
@@ -78779,7 +78800,7 @@ var require_bunyan = __commonJS({
78779
78800
  s.stream.destroySoon();
78780
78801
  delete s.stream;
78781
78802
  }
78782
- s.stream = fs54.createWriteStream(
78803
+ s.stream = fs55.createWriteStream(
78783
78804
  s.path,
78784
78805
  { flags: "a", encoding: "utf8" }
78785
78806
  );
@@ -79121,7 +79142,7 @@ var require_bunyan = __commonJS({
79121
79142
  }
79122
79143
  var lastModified = null;
79123
79144
  try {
79124
- var fileInfo = fs54.statSync(this.path);
79145
+ var fileInfo = fs55.statSync(this.path);
79125
79146
  lastModified = fileInfo.mtime.getTime();
79126
79147
  } catch (err) {
79127
79148
  }
@@ -79132,7 +79153,7 @@ var require_bunyan = __commonJS({
79132
79153
  rotateAfterOpen = true;
79133
79154
  }
79134
79155
  }
79135
- this.stream = fs54.createWriteStream(
79156
+ this.stream = fs55.createWriteStream(
79136
79157
  this.path,
79137
79158
  { flags: "a", encoding: "utf8" }
79138
79159
  );
@@ -79299,7 +79320,7 @@ var require_bunyan = __commonJS({
79299
79320
  }
79300
79321
  n -= 1;
79301
79322
  self._debug(" rm %s", toDel);
79302
- fs54.unlink(toDel, function(delErr) {
79323
+ fs55.unlink(toDel, function(delErr) {
79303
79324
  moves();
79304
79325
  });
79305
79326
  }
@@ -79313,7 +79334,7 @@ var require_bunyan = __commonJS({
79313
79334
  before += "." + String(n - 1);
79314
79335
  }
79315
79336
  n -= 1;
79316
- fs54.exists(before, function(exists2) {
79337
+ fs55.exists(before, function(exists2) {
79317
79338
  if (!exists2) {
79318
79339
  moves();
79319
79340
  } else {
@@ -79331,7 +79352,7 @@ var require_bunyan = __commonJS({
79331
79352
  }
79332
79353
  function finish() {
79333
79354
  self._debug(" open %s", self.path);
79334
- self.stream = fs54.createWriteStream(
79355
+ self.stream = fs55.createWriteStream(
79335
79356
  self.path,
79336
79357
  { flags: "a", encoding: "utf8" }
79337
79358
  );
@@ -83276,7 +83297,7 @@ var require_png = __commonJS({
83276
83297
  var Parser = require_parser_async();
83277
83298
  var Packer = require_packer_async();
83278
83299
  var PNGSync = require_png_sync();
83279
- var PNG4 = exports2.PNG = function(options) {
83300
+ var PNG5 = exports2.PNG = function(options) {
83280
83301
  Stream2.call(this);
83281
83302
  options = options || {};
83282
83303
  this.width = options.width | 0;
@@ -83305,9 +83326,9 @@ var require_png = __commonJS({
83305
83326
  this._parser.on("close", this._handleClose.bind(this));
83306
83327
  this._packer.on("error", this.emit.bind(this, "error"));
83307
83328
  };
83308
- util.inherits(PNG4, Stream2);
83309
- PNG4.sync = PNGSync;
83310
- PNG4.prototype.pack = function() {
83329
+ util.inherits(PNG5, Stream2);
83330
+ PNG5.sync = PNGSync;
83331
+ PNG5.prototype.pack = function() {
83311
83332
  if (!this.data || !this.data.length) {
83312
83333
  this.emit("error", "No data provided");
83313
83334
  return this;
@@ -83319,7 +83340,7 @@ var require_png = __commonJS({
83319
83340
  );
83320
83341
  return this;
83321
83342
  };
83322
- PNG4.prototype.parse = function(data, callback) {
83343
+ PNG5.prototype.parse = function(data, callback) {
83323
83344
  if (callback) {
83324
83345
  let onParsed, onError;
83325
83346
  onParsed = function(parsedData) {
@@ -83337,27 +83358,27 @@ var require_png = __commonJS({
83337
83358
  this.end(data);
83338
83359
  return this;
83339
83360
  };
83340
- PNG4.prototype.write = function(data) {
83361
+ PNG5.prototype.write = function(data) {
83341
83362
  this._parser.write(data);
83342
83363
  return true;
83343
83364
  };
83344
- PNG4.prototype.end = function(data) {
83365
+ PNG5.prototype.end = function(data) {
83345
83366
  this._parser.end(data);
83346
83367
  };
83347
- PNG4.prototype._metadata = function(metadata) {
83368
+ PNG5.prototype._metadata = function(metadata) {
83348
83369
  this.width = metadata.width;
83349
83370
  this.height = metadata.height;
83350
83371
  this.emit("metadata", metadata);
83351
83372
  };
83352
- PNG4.prototype._gamma = function(gamma) {
83373
+ PNG5.prototype._gamma = function(gamma) {
83353
83374
  this.gamma = gamma;
83354
83375
  };
83355
- PNG4.prototype._handleClose = function() {
83376
+ PNG5.prototype._handleClose = function() {
83356
83377
  if (!this._parser.writable && !this._packer.readable) {
83357
83378
  this.emit("close");
83358
83379
  }
83359
83380
  };
83360
- PNG4.bitblt = function(src, dst, srcX, srcY, width, height, deltaX, deltaY) {
83381
+ PNG5.bitblt = function(src, dst, srcX, srcY, width, height, deltaX, deltaY) {
83361
83382
  srcX |= 0;
83362
83383
  srcY |= 0;
83363
83384
  width |= 0;
@@ -83379,11 +83400,11 @@ var require_png = __commonJS({
83379
83400
  );
83380
83401
  }
83381
83402
  };
83382
- PNG4.prototype.bitblt = function(dst, srcX, srcY, width, height, deltaX, deltaY) {
83383
- PNG4.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY);
83403
+ PNG5.prototype.bitblt = function(dst, srcX, srcY, width, height, deltaX, deltaY) {
83404
+ PNG5.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY);
83384
83405
  return this;
83385
83406
  };
83386
- PNG4.adjustGamma = function(src) {
83407
+ PNG5.adjustGamma = function(src) {
83387
83408
  if (src.gamma) {
83388
83409
  for (let y = 0; y < src.height; y++) {
83389
83410
  for (let x = 0; x < src.width; x++) {
@@ -83398,8 +83419,8 @@ var require_png = __commonJS({
83398
83419
  src.gamma = 0;
83399
83420
  }
83400
83421
  };
83401
- PNG4.prototype.adjustGamma = function() {
83402
- PNG4.adjustGamma(this);
83422
+ PNG5.prototype.adjustGamma = function() {
83423
+ PNG5.adjustGamma(this);
83403
83424
  };
83404
83425
  }
83405
83426
  });
@@ -90282,14 +90303,14 @@ var require_parser2 = __commonJS({
90282
90303
  case "scalar":
90283
90304
  case "single-quoted-scalar":
90284
90305
  case "double-quoted-scalar": {
90285
- const fs54 = this.flowScalar(this.type);
90306
+ const fs55 = this.flowScalar(this.type);
90286
90307
  if (atNextItem || it.value) {
90287
- map2.items.push({ start: start2, key: fs54, sep: [] });
90308
+ map2.items.push({ start: start2, key: fs55, sep: [] });
90288
90309
  this.onKeyLine = true;
90289
90310
  } else if (it.sep) {
90290
- this.stack.push(fs54);
90311
+ this.stack.push(fs55);
90291
90312
  } else {
90292
- Object.assign(it, { key: fs54, sep: [] });
90313
+ Object.assign(it, { key: fs55, sep: [] });
90293
90314
  this.onKeyLine = true;
90294
90315
  }
90295
90316
  return;
@@ -90417,13 +90438,13 @@ var require_parser2 = __commonJS({
90417
90438
  case "scalar":
90418
90439
  case "single-quoted-scalar":
90419
90440
  case "double-quoted-scalar": {
90420
- const fs54 = this.flowScalar(this.type);
90441
+ const fs55 = this.flowScalar(this.type);
90421
90442
  if (!it || it.value)
90422
- fc.items.push({ start: [], key: fs54, sep: [] });
90443
+ fc.items.push({ start: [], key: fs55, sep: [] });
90423
90444
  else if (it.sep)
90424
- this.stack.push(fs54);
90445
+ this.stack.push(fs55);
90425
90446
  else
90426
- Object.assign(it, { key: fs54, sep: [] });
90447
+ Object.assign(it, { key: fs55, sep: [] });
90427
90448
  return;
90428
90449
  }
90429
90450
  case "flow-map-end":
@@ -96039,6 +96060,16 @@ var PLATFORMS = [
96039
96060
  "tvos",
96040
96061
  "android-tv"
96041
96062
  ];
96063
+ var DEBUGGER_NOT_CONNECTED_REASONS = [
96064
+ "metro_not_running",
96065
+ "no_app_connected",
96066
+ "device_mismatch",
96067
+ "cdp_unreachable",
96068
+ "runtime_unresponsive",
96069
+ "stale_connection",
96070
+ "reconnecting"
96071
+ ];
96072
+ var DEBUGGER_TOOL_OUTCOMES = ["connected", ...DEBUGGER_NOT_CONNECTED_REASONS];
96042
96073
 
96043
96074
  // ../telemetry/src/ai-identity.ts
96044
96075
  var AI_CLIENTS = [
@@ -96233,9 +96264,19 @@ var ALLOWED = {
96233
96264
  tool_invocation_id: UUID2,
96234
96265
  platform: PLATFORM,
96235
96266
  duration_ms: DURATION_MS,
96267
+ // Schema-declared parameter names only (emit side filters against the tool's
96268
+ // own zod shape and caps at 16 before this gate); the array validator also
96269
+ // voids anything longer or with a non-identifier element.
96270
+ invalid_params: arrayOf(matches(/^[a-z][a-z0-9_]{0,63}$/i, 64), 16),
96236
96271
  ...FAILURE_SIGNAL2,
96237
96272
  ...AI_TELEMETRY
96238
96273
  },
96274
+ "debugger:tool_outcome": {
96275
+ tool: oneOf(["debugger-status", "debugger-log-registry"]),
96276
+ outcome: oneOf(DEBUGGER_TOOL_OUTCOMES),
96277
+ platform: PLATFORM,
96278
+ tool_invocation_id: UUID2
96279
+ },
96239
96280
  "cli:run_fail": {
96240
96281
  tool: TOOL_NAME,
96241
96282
  duration_ms: DURATION_MS,
@@ -109729,11 +109770,29 @@ var CDPClient = class {
109729
109770
  };
109730
109771
  const onError = (err) => {
109731
109772
  cleanup();
109732
- reject(err);
109773
+ reject(
109774
+ new FailureError(
109775
+ err.message,
109776
+ {
109777
+ error_code: FAILURE_CODES.DEBUGGER_CDP_CONNECT_FAILED,
109778
+ failure_stage: "debugger_cdp_connect",
109779
+ failure_area: "tool_server",
109780
+ error_kind: "network"
109781
+ },
109782
+ { cause: err }
109783
+ )
109784
+ );
109733
109785
  };
109734
109786
  const onClose = () => {
109735
109787
  cleanup();
109736
- reject(new Error("WebSocket closed before open"));
109788
+ reject(
109789
+ new FailureError("WebSocket closed before open", {
109790
+ error_code: FAILURE_CODES.DEBUGGER_CDP_SOCKET_CLOSED_BEFORE_OPEN,
109791
+ failure_stage: "debugger_cdp_connect",
109792
+ failure_area: "tool_server",
109793
+ error_kind: "network"
109794
+ })
109795
+ );
109737
109796
  };
109738
109797
  const cleanup = () => {
109739
109798
  ws.removeListener("open", onOpen);
@@ -109798,12 +109857,33 @@ var CDPClient = class {
109798
109857
  send(method, params, timeout = DEFAULT_TIMEOUT_MS2) {
109799
109858
  return new Promise((resolve11, reject) => {
109800
109859
  if (!this.ws || this.ws.readyState !== wrapper_default.OPEN) {
109801
- return reject(new Error("CDP not connected"));
109860
+ return reject(
109861
+ new FailureError("CDP not connected", {
109862
+ error_code: FAILURE_CODES.DEBUGGER_CDP_NOT_CONNECTED,
109863
+ failure_stage: "debugger_cdp_send",
109864
+ failure_area: "tool_server",
109865
+ error_kind: "network"
109866
+ })
109867
+ );
109802
109868
  }
109803
109869
  const id = this.nextId++;
109804
109870
  const timer = setTimeout(() => {
109805
109871
  this.pending.delete(id);
109806
- reject(new Error(`CDP request ${method} (id=${id}) timed out`));
109872
+ reject(
109873
+ // The message carries its own recovery guidance so skills don't have
109874
+ // to re-explain this state: the runtime is reachable but not
109875
+ // answering, which agents otherwise read as a transient worth
109876
+ // retry-looping (each loop iteration waits out this full timeout).
109877
+ new FailureError(
109878
+ `CDP request ${method} (id=${id}) timed out \u2014 the runtime accepted the connection but did not answer; it may be frozen, or paused at a breakpoint. debugger-status can still report "connected" in this state (the socket is open). Do not retry in a loop \u2014 restart the app, then reconnect and retry once.`,
109879
+ {
109880
+ error_code: FAILURE_CODES.DEBUGGER_CDP_REQUEST_TIMEOUT,
109881
+ failure_stage: "debugger_cdp_send",
109882
+ failure_area: "tool_server",
109883
+ error_kind: "timeout"
109884
+ }
109885
+ )
109886
+ );
109807
109887
  }, timeout);
109808
109888
  this.pending.set(id, {
109809
109889
  resolve: (result) => {
@@ -109971,14 +110051,20 @@ var CDPClient = class {
109971
110051
  else if (action === "disable") this.enabledDomains.delete(domain2);
109972
110052
  }
109973
110053
  cleanup() {
110054
+ const connectionClosed = () => new FailureError("CDP connection closed", {
110055
+ error_code: FAILURE_CODES.DEBUGGER_CDP_CONNECTION_CLOSED,
110056
+ failure_stage: "debugger_cdp_lifecycle",
110057
+ failure_area: "tool_server",
110058
+ error_kind: "network"
110059
+ });
109974
110060
  for (const [, req] of this.pending) {
109975
110061
  clearTimeout(req.timer);
109976
- req.reject(new Error("CDP connection closed"));
110062
+ req.reject(connectionClosed());
109977
110063
  }
109978
110064
  this.pending.clear();
109979
110065
  for (const [, binding] of this.pendingBindings) {
109980
110066
  clearTimeout(binding.timer);
109981
- binding.reject(new Error("CDP connection closed"));
110067
+ binding.reject(connectionClosed());
109982
110068
  }
109983
110069
  this.pendingBindings.clear();
109984
110070
  this.scripts.clear();
@@ -114838,6 +114924,45 @@ async function resolveFileInputs(def, body, lookupUpload) {
114838
114924
  return { args, fileInputs: resolved, cleanup };
114839
114925
  }
114840
114926
 
114927
+ // ../tool-server/src/utils/debugger/device-alias.ts
114928
+ var logicalIdToConnectId = /* @__PURE__ */ new Map();
114929
+ function rememberDeviceAlias(logicalDeviceId, connectDeviceId) {
114930
+ if (!logicalDeviceId || logicalDeviceId === connectDeviceId) return;
114931
+ logicalIdToConnectId.set(logicalDeviceId, connectDeviceId);
114932
+ }
114933
+ function canonicalDeviceId(deviceId) {
114934
+ if (!deviceId) return deviceId;
114935
+ return logicalIdToConnectId.get(deviceId) ?? deviceId;
114936
+ }
114937
+ function forgetDeviceAlias(logicalDeviceId) {
114938
+ if (logicalDeviceId) logicalIdToConnectId.delete(logicalDeviceId);
114939
+ }
114940
+ var logicalKeyedConnectIds = /* @__PURE__ */ new Set();
114941
+ function rememberLogicalKeyedDevice(logicalDeviceId, connectDeviceId) {
114942
+ if (logicalDeviceId && logicalDeviceId === connectDeviceId) {
114943
+ logicalKeyedConnectIds.add(connectDeviceId.toLowerCase());
114944
+ }
114945
+ }
114946
+ function isLogicalKeyedDevice(deviceId) {
114947
+ return deviceId !== void 0 && logicalKeyedConnectIds.has(deviceId.toLowerCase());
114948
+ }
114949
+ function forgetLogicalKeyedDevice(connectDeviceId) {
114950
+ logicalKeyedConnectIds.delete(connectDeviceId.toLowerCase());
114951
+ }
114952
+
114953
+ // ../tool-server/src/utils/invalid-params.ts
114954
+ function deriveInvalidParams(error52, declared) {
114955
+ const out = [];
114956
+ for (const issue2 of error52.issues) {
114957
+ const name = issue2.code === "unrecognized_keys" ? "unrecognized_keys" : String(issue2.path[0] ?? "");
114958
+ if (!name) continue;
114959
+ if (name !== "unrecognized_keys" && !declared.has(name)) continue;
114960
+ if (!out.includes(name)) out.push(name);
114961
+ if (out.length >= 16) break;
114962
+ }
114963
+ return out;
114964
+ }
114965
+
114841
114966
  // ../tool-server/src/chromium-server/http-api.ts
114842
114967
  var import_express2 = __toESM(require_express2());
114843
114968
  init_wrapper();
@@ -115122,7 +115247,8 @@ function extractDeviceArg(data) {
115122
115247
  function inferPlatform(deviceId) {
115123
115248
  if (!deviceId) return null;
115124
115249
  try {
115125
- return refineTvPlatform(resolveDevice(deviceId).platform, deviceId);
115250
+ const canonical = canonicalDeviceId(deviceId) ?? deviceId;
115251
+ return refineTvPlatform(resolveDevice(canonical).platform, canonical);
115126
115252
  } catch {
115127
115253
  return null;
115128
115254
  }
@@ -115393,7 +115519,7 @@ function createHttpApp(registry2, options) {
115393
115519
  const name = req.params.name;
115394
115520
  const requestStartedAt = performance.now();
115395
115521
  const aiMeta = extractAiTelemetryMeta(req);
115396
- const emitHttpFailure = (signal, parsedDataForMeta = req.body) => {
115522
+ const emitHttpFailure = (signal, parsedDataForMeta = req.body, extraMeta) => {
115397
115523
  if (!options?.recordFailure) return;
115398
115524
  const failedDeviceArg = extractDeviceArg(parsedDataForMeta);
115399
115525
  const platform = inferPlatform(failedDeviceArg);
@@ -115401,6 +115527,7 @@ function createHttpApp(registry2, options) {
115401
115527
  name,
115402
115528
  {
115403
115529
  ...platform ? { platform } : {},
115530
+ ...extraMeta?.invalid_params?.length ? { invalid_params: extraMeta.invalid_params } : {},
115404
115531
  ...aiMeta
115405
115532
  },
115406
115533
  signal,
@@ -115444,6 +115571,7 @@ function createHttpApp(registry2, options) {
115444
115571
  if (def.zodSchema) {
115445
115572
  const parseResult = def.zodSchema.safeParse(bodyArgs);
115446
115573
  if (!parseResult.success) {
115574
+ const declared = new Set(Object.keys(def.zodSchema.shape ?? {}));
115447
115575
  emitHttpFailure(
115448
115576
  {
115449
115577
  error_code: FAILURE_CODES.HTTP_ZOD_VALIDATION_FAILED,
@@ -115451,7 +115579,8 @@ function createHttpApp(registry2, options) {
115451
115579
  failure_area: "http",
115452
115580
  error_kind: "validation"
115453
115581
  },
115454
- req.body
115582
+ req.body,
115583
+ { invalid_params: deriveInvalidParams(parseResult.error, declared) }
115455
115584
  );
115456
115585
  res.status(400).json({ error: parseResult.error.message });
115457
115586
  return;
@@ -116258,6 +116387,22 @@ var chromiumJsRuntimeDebuggerBlueprint = {
116258
116387
  getDependencies(_payload) {
116259
116388
  return { chromium: `${CHROMIUM_CDP_NAMESPACE}:${_payload}` };
116260
116389
  },
116390
+ // Deliberately NO recoverable() here. The registry's self-heal disposes the
116391
+ // recovering node before it retries, and this blueprint's dispose closes the
116392
+ // LogFileWriter — which UNLINKS the session's captured console log from disk
116393
+ // (log-file-writer.ts) — so a recovery pass would destroy the logs whether or
116394
+ // not the retry then succeeds. It would also buy nothing: the one window
116395
+ // where a call fails while this node and its ChromiumCdp dependency stay
116396
+ // RUNNING is a tab switch, where CDPClient.reconnect() rejects in-flight
116397
+ // requests with CONNECTION_CLOSED (late sends with NOT_CONNECTED) but
116398
+ // re-points the SAME client object at the new tab — the cached node heals
116399
+ // itself for the next call without any dispose. The failing call surfaces a
116400
+ // classified error that debugger-status maps to a structured "reconnecting"
116401
+ // result with retry-once guidance. A genuinely dead Chromium socket instead
116402
+ // fires ChromiumCdp's own terminated event, whose teardown cascades into this
116403
+ // dependent — the node leaves RUNNING and the next call re-resolves fresh.
116404
+ // (Same log-preservation reasoning as debugger-log-registry's missing socket
116405
+ // gate — see that tool's comment.)
116261
116406
  async factory(deps, payload, options) {
116262
116407
  const opts = options;
116263
116408
  const device = opts?.device;
@@ -117460,7 +117605,7 @@ async function discoverMetro(port) {
117460
117605
  statusRes = await fetch(`http://localhost:${port}/status`);
117461
117606
  } catch (err) {
117462
117607
  throw new FailureError(
117463
- `Metro at port ${port} is not running (got: ${err instanceof Error ? err.message : String(err)})`,
117608
+ `Metro at port ${port} is not running (got: ${err instanceof Error ? err.message : String(err)}). Do not retry in a loop \u2014 the result will not change until Metro is started. Start Metro (e.g. \`npx react-native start\` or \`npx expo start\`) or ask the user, wait for it to report ready, then retry once.`,
117464
117609
  {
117465
117610
  error_code: FAILURE_CODES.DEBUGGER_METRO_NOT_RUNNING,
117466
117611
  failure_stage: "debugger_discover_metro_status",
@@ -117469,10 +117614,24 @@ async function discoverMetro(port) {
117469
117614
  }
117470
117615
  );
117471
117616
  }
117472
- const statusText = await statusRes.text();
117617
+ const notRunning = (stage, err) => new FailureError(
117618
+ `Metro at port ${port} is not running (got: ${err instanceof Error ? err.message : String(err)}). Do not retry in a loop \u2014 the result will not change until Metro is started. Start Metro (e.g. \`npx react-native start\` or \`npx expo start\`) or ask the user, wait for it to report ready, then retry once.`,
117619
+ {
117620
+ error_code: FAILURE_CODES.DEBUGGER_METRO_NOT_RUNNING,
117621
+ failure_stage: stage,
117622
+ failure_area: "tool_server",
117623
+ error_kind: "network"
117624
+ }
117625
+ );
117626
+ let statusText;
117627
+ try {
117628
+ statusText = await statusRes.text();
117629
+ } catch (err) {
117630
+ throw notRunning("debugger_discover_metro_status_body", err);
117631
+ }
117473
117632
  if (!statusText.includes("packager-status:running")) {
117474
117633
  throw new FailureError(
117475
- `Metro at port ${port} is not running (got: ${statusText.slice(0, 100)})`,
117634
+ `Metro at port ${port} is not running (got: ${statusText.slice(0, 100)}). Something else is listening on this port \u2014 it did not answer like Metro. Do not retry in a loop; find the port Metro actually runs on (or start it), then retry once.`,
117476
117635
  {
117477
117636
  error_code: FAILURE_CODES.DEBUGGER_METRO_NOT_RUNNING,
117478
117637
  failure_stage: "debugger_discover_metro_status",
@@ -117482,14 +117641,19 @@ async function discoverMetro(port) {
117482
117641
  );
117483
117642
  }
117484
117643
  const projectRoot = statusRes.headers.get("X-React-Native-Project-Root") ?? "";
117485
- const listRes = await fetch(`http://localhost:${port}/json/list`);
117644
+ let listRes;
117645
+ try {
117646
+ listRes = await fetch(`http://localhost:${port}/json/list`);
117647
+ } catch (err) {
117648
+ throw notRunning("debugger_discover_metro_list", err);
117649
+ }
117486
117650
  const parsed = await listRes.json().catch(() => null);
117487
117651
  const targets = (Array.isArray(parsed) ? parsed : []).filter(
117488
117652
  (t) => t?.vm !== DECOY_VM
117489
117653
  );
117490
117654
  if (!targets.length) {
117491
117655
  throw new FailureError(
117492
- `Metro at port ${port} has no CDP targets \u2014 is a React Native app connected?`,
117656
+ `Metro at port ${port} has no CDP targets \u2014 is a React Native app connected? Do not retry immediately \u2014 this will not change until an app attaches. Launch or restart the RN app on the target device (launch-app / restart-app), wait a few seconds for the bundle to load, then retry once. On Android, a missing port reverse-proxy is the most common cause (see the metro-debugger skill's Android prerequisites).`,
117493
117657
  {
117494
117658
  error_code: FAILURE_CODES.DEBUGGER_METRO_NO_TARGETS,
117495
117659
  failure_stage: "debugger_discover_metro_targets",
@@ -117502,6 +117666,7 @@ async function discoverMetro(port) {
117502
117666
  }
117503
117667
 
117504
117668
  // ../tool-server/src/utils/debugger/target-selection.ts
117669
+ init_src();
117505
117670
  function deviceKey(target) {
117506
117671
  const logicalId = target.reactNative?.logicalDeviceId;
117507
117672
  if (logicalId) return logicalId;
@@ -117535,8 +117700,14 @@ function selectTarget(targets, port, options) {
117535
117700
  const listed = [...distinctDevices.values()].map(
117536
117701
  (d) => d.logicalId ? `${d.name ?? "unknown"} (${d.logicalId})` : `${d.name ?? "unknown"} (legacy inspector \u2014 no logicalDeviceId)`
117537
117702
  ).join(", ");
117538
- throw new Error(
117539
- `No debugger target matches device_id "${deviceId}". ${distinctDevices.size} devices are connected to Metro on port ${port}: ${listed}. Re-target with the logicalDeviceId in parentheses \u2014 that is what debugger-connect returns and what subsequent debugger-* calls must pass. A legacy-inspector device (RN 0.72 / Vega) reports none and cannot be singled out of a shared Metro: give it its own Metro port.`
117703
+ throw new FailureError(
117704
+ `No debugger target matches device_id "${deviceId}". ${distinctDevices.size} devices are connected to Metro on port ${port}: ${listed}. Re-target with the logicalDeviceId in parentheses \u2014 that is what debugger-connect returns and what subsequent debugger-* calls must pass. A legacy-inspector device (RN 0.72 / Vega) reports none and cannot be singled out of a shared Metro: give it its own Metro port.`,
117705
+ {
117706
+ error_code: FAILURE_CODES.DEBUGGER_TARGET_DEVICE_MISMATCH,
117707
+ failure_stage: "debugger_select_target",
117708
+ failure_area: "tool_server",
117709
+ error_kind: "not_found"
117710
+ }
117540
117711
  );
117541
117712
  }
117542
117713
  }
@@ -117570,32 +117741,6 @@ function normalizeWsUrl(wsUrl, port) {
117570
117741
  return url2.toString();
117571
117742
  }
117572
117743
 
117573
- // ../tool-server/src/utils/debugger/device-alias.ts
117574
- var logicalIdToConnectId = /* @__PURE__ */ new Map();
117575
- function rememberDeviceAlias(logicalDeviceId, connectDeviceId) {
117576
- if (!logicalDeviceId || logicalDeviceId === connectDeviceId) return;
117577
- logicalIdToConnectId.set(logicalDeviceId, connectDeviceId);
117578
- }
117579
- function canonicalDeviceId(deviceId) {
117580
- if (!deviceId) return deviceId;
117581
- return logicalIdToConnectId.get(deviceId) ?? deviceId;
117582
- }
117583
- function forgetDeviceAlias(logicalDeviceId) {
117584
- if (logicalDeviceId) logicalIdToConnectId.delete(logicalDeviceId);
117585
- }
117586
- var logicalKeyedConnectIds = /* @__PURE__ */ new Set();
117587
- function rememberLogicalKeyedDevice(logicalDeviceId, connectDeviceId) {
117588
- if (logicalDeviceId && logicalDeviceId === connectDeviceId) {
117589
- logicalKeyedConnectIds.add(connectDeviceId.toLowerCase());
117590
- }
117591
- }
117592
- function isLogicalKeyedDevice(deviceId) {
117593
- return deviceId !== void 0 && logicalKeyedConnectIds.has(deviceId.toLowerCase());
117594
- }
117595
- function forgetLogicalKeyedDevice(connectDeviceId) {
117596
- logicalKeyedConnectIds.delete(connectDeviceId.toLowerCase());
117597
- }
117598
-
117599
117744
  // ../tool-server/src/utils/debugger/source-resolver.ts
117600
117745
  var fs22 = __toESM(require("node:fs/promises"));
117601
117746
  var path19 = __toESM(require("node:path"));
@@ -117911,6 +118056,25 @@ var jsRuntimeDebuggerBlueprint = {
117911
118056
  getURN(payload) {
117912
118057
  return `${JS_RUNTIME_DEBUGGER_NAMESPACE}:${payload}`;
117913
118058
  },
118059
+ // Consulted by the registry's dispose-and-retry-once self-heal, and only for
118060
+ // a node still in RUNNING state. On this blueprint a detected socket death
118061
+ // tears the node down via the terminated cascade before the failing call's
118062
+ // catch runs, so the only recoverable window is the send() guard rejecting
118063
+ // while the WebSocket is CLOSING but the close event has not dispatched yet —
118064
+ // there the request provably never left the host, making a retry safe.
118065
+ // Deliberately NOT recoverable:
118066
+ // - DEBUGGER_CDP_CONNECTION_CLOSED: the request was delivered and may have
118067
+ // taken effect (double-execution risk); on this path the node has also
118068
+ // already left RUNNING when it fires.
118069
+ // - DEBUGGER_CDP_REQUEST_TIMEOUT: the request may have taken effect, and a
118070
+ // hung-but-open runtime (e.g. paused at a breakpoint) is not fixed by
118071
+ // reconnecting.
118072
+ // - Metro discovery / target-selection codes: init-path failures — the node
118073
+ // never reaches RUNNING, so recovery is never consulted, and a retry would
118074
+ // be hopeless anyway.
118075
+ recoverable(error52) {
118076
+ return getFailureSignal(error52)?.error_code === FAILURE_CODES.DEBUGGER_CDP_NOT_CONNECTED;
118077
+ },
117914
118078
  async factory(_deps, payload, options) {
117915
118079
  const colonIdx = payload.indexOf(":");
117916
118080
  if (colonIdx < 0) {
@@ -129807,46 +129971,158 @@ Use when starting a debug session or before calling other debugger-* tools. Fail
129807
129971
 
129808
129972
  // ../tool-server/src/tools/debugger/debugger-status.ts
129809
129973
  init_zod();
129974
+
129975
+ // ../tool-server/src/tools/debugger/not-connected.ts
129976
+ init_src();
129977
+ var GUIDANCE = {
129978
+ metro_not_running: "Metro is not running on this port. Do not retry in a loop \u2014 the result will not change until Metro is started. Start Metro (e.g. `npx react-native start` or `npx expo start`) or ask the user, wait for it to report ready, then retry once.",
129979
+ no_app_connected: "Metro is running but no app is attached. Do not retry immediately \u2014 launch or restart the RN app on the target device (launch-app / restart-app), wait a few seconds for the bundle to load, then retry once.",
129980
+ device_mismatch: "The device_id does not match any debugger target on this Metro. Re-target with the logicalDeviceId listed in the detail message, or give the device its own Metro port.",
129981
+ cdp_unreachable: "The runtime's CDP endpoint could not be reached. Verify the app is running (launch-app), then call debugger-connect and retry once.",
129982
+ runtime_unresponsive: "The runtime accepted the debugger connection but did not answer within the timeout \u2014 it is likely frozen, or paused at a breakpoint. Do not retry in a loop (each attempt waits out the full timeout). Check the app; if it is hung, restart it (restart-app), then retry once.",
129983
+ stale_connection: "The cached debugger connection went stale; it has been discarded. Restart the app (restart-app) if it is not running, then call debugger-connect \u2014 the next call reconnects fresh.",
129984
+ reconnecting: "The debugger connection is being re-established (the previous one was torn down or a tab switch is in progress). Wait a moment and retry once."
129985
+ };
129986
+ var NOT_CONNECTED_CODE_MAP = {
129987
+ [FAILURE_CODES.DEBUGGER_METRO_NOT_RUNNING]: "metro_not_running",
129988
+ [FAILURE_CODES.DEBUGGER_METRO_NO_TARGETS]: "no_app_connected",
129989
+ [FAILURE_CODES.DEBUGGER_TARGET_DEVICE_MISMATCH]: "device_mismatch",
129990
+ [FAILURE_CODES.DEBUGGER_CDP_CONNECT_FAILED]: "cdp_unreachable",
129991
+ [FAILURE_CODES.DEBUGGER_CDP_SOCKET_CLOSED_BEFORE_OPEN]: "cdp_unreachable",
129992
+ [FAILURE_CODES.DEBUGGER_CDP_NOT_CONNECTED]: "cdp_unreachable",
129993
+ [FAILURE_CODES.DEBUGGER_CDP_CONNECTION_CLOSED]: "cdp_unreachable",
129994
+ // Reachable from the connect pipeline's enable/binding sends when the target
129995
+ // accepts the socket but its JS runtime never answers (frozen, or paused at a
129996
+ // breakpoint). Post-connect hangs are different: an OPEN socket still reports
129997
+ // status "connected" (see the socket-state gate comment in debugger-status).
129998
+ [FAILURE_CODES.DEBUGGER_CDP_REQUEST_TIMEOUT]: "runtime_unresponsive",
129999
+ [FAILURE_CODES.CHROMIUM_CDP_UNREACHABLE]: "cdp_unreachable",
130000
+ // "Reached but not CDP / malformed answer" — a non-CDP server squatting the
130001
+ // debug port, an HTTP error status, or a non-JSON body. Same precondition
130002
+ // class as the Metro arm's non-Metro-port-occupant (detail names what
130003
+ // actually answered), so it must not escape as a thrown tool failure.
130004
+ [FAILURE_CODES.CHROMIUM_CDP_INVALID_RESPONSE]: "cdp_unreachable",
130005
+ [FAILURE_CODES.CHROMIUM_CDP_NO_PAGE_TARGET]: "cdp_unreachable",
130006
+ [FAILURE_CODES.REGISTRY_SERVICE_TERMINATING]: "reconnecting"
130007
+ };
130008
+ function classifyNotConnected(err) {
130009
+ const code = getFailureSignal(err)?.error_code;
130010
+ return code ? NOT_CONNECTED_CODE_MAP[code] : void 0;
130011
+ }
130012
+ var CHROMIUM_GUIDANCE = {
130013
+ cdp_unreachable: "The app's CDP endpoint could not be reached (or did not answer like CDP \u2014 see detail). launch-app cannot start a Chromium app; make sure the app is running with --remote-debugging-port (for an Electron app, boot-device with electronAppPath relaunches it), then retry once.",
130014
+ runtime_unresponsive: "The app accepted the debugger connection but did not answer within the timeout \u2014 it is likely frozen. Do not retry in a loop (each attempt waits out the full timeout). Restart the app (for an Electron app, boot-device with electronAppPath and force: true), then retry once."
130015
+ };
130016
+ function buildNotConnected(reason, err, params) {
130017
+ const isChromium = params.device_id?.startsWith(CHROMIUM_ID_PREFIX) ?? false;
130018
+ return {
130019
+ status: "not_connected",
130020
+ connected: false,
130021
+ ...isChromium ? {} : { port: params.port },
130022
+ reason,
130023
+ detail: err instanceof Error ? err.message : String(err),
130024
+ guidance: (isChromium ? CHROMIUM_GUIDANCE[reason] : void 0) ?? GUIDANCE[reason]
130025
+ };
130026
+ }
130027
+ function trackDebuggerOutcome(tool, outcome, params, ctx) {
130028
+ let platform;
130029
+ try {
130030
+ const deviceId = canonicalDeviceId(params.device_id);
130031
+ platform = deviceId ? classifyDeviceForTelemetry(deviceId) : void 0;
130032
+ } catch {
130033
+ platform = void 0;
130034
+ }
130035
+ track("debugger:tool_outcome", {
130036
+ tool,
130037
+ outcome,
130038
+ ...platform ? { platform } : {},
130039
+ ...ctx?.toolInvocationId ? { tool_invocation_id: ctx.toolInvocationId } : {}
130040
+ });
130041
+ }
130042
+ async function resolveDebuggerService(registry2, params) {
130043
+ const ref = debuggerServiceRef(params);
130044
+ return typeof ref === "string" ? registry2.resolveService(ref) : registry2.resolveService(ref.urn, ref.options);
130045
+ }
130046
+ function isDebuggerNotConnectedResult(toolId, result) {
130047
+ return (toolId === "debugger-status" || toolId === "debugger-log-registry") && typeof result === "object" && result !== null && result.status === "not_connected";
130048
+ }
130049
+
130050
+ // ../tool-server/src/tools/debugger/debugger-status.ts
129810
130051
  var zodSchema30 = external_exports.object({
129811
130052
  port: external_exports.coerce.number().default(8081).describe("Metro server port (ignored for Chromium)"),
129812
130053
  device_id: external_exports.string().describe(
129813
130054
  "Device id from list-devices \u2014 the SAME id you passed to debugger-connect (iOS simulator UDID, Android serial, Vega serial, or Chromium device id). The logicalDeviceId debugger-connect returns also resolves here, but prefer the stable list-devices id."
129814
130055
  )
129815
130056
  });
129816
- var debuggerStatusTool = {
129817
- id: "debugger-status",
129818
- interaction: {
129819
- startedMsg: () => "Checking JavaScript debugger",
129820
- completedMsg: () => "Checked JavaScript debugger",
129821
- failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to check JavaScript debugger: ${failureSignal2.error_code}`
129822
- },
129823
- description: `Get JS runtime debugger connection status and diagnostic info.
129824
- Use when you need to verify connectivity before using other debugger tools. Returns port, projectRoot (empty on Chromium and on legacy Metro, e.g. Vega), deviceName, appName, logicalDeviceId (absent on Vega), isNewDebugger (false on the legacy inspector), connected flag, loadedScripts count, and sourceMapReady (always true \u2014 waits for pending source maps before returning; no-op on Chromium). Fails if the runtime is unreachable.`,
129825
- zodSchema: zodSchema30,
129826
- capability: DEBUGGER_TOOL_CAPABILITY,
129827
- services: (params) => ({
129828
- debugger: debuggerServiceRef(params)
129829
- }),
129830
- async execute(services) {
129831
- const api = services.debugger;
129832
- await api.sourceMaps.waitForPending();
129833
- return {
129834
- port: api.port,
129835
- projectRoot: api.projectRoot,
129836
- deviceName: api.deviceName,
129837
- appName: api.appName,
129838
- logicalDeviceId: api.logicalDeviceId,
129839
- isNewDebugger: api.isNewDebugger,
129840
- connected: api.cdp.isConnected(),
129841
- loadedScripts: api.cdp.getLoadedScripts().size,
129842
- enabledDomains: [...api.cdp.getEnabledDomains()],
129843
- sourceMapReady: true
129844
- };
129845
- }
129846
- };
130057
+ function createDebuggerStatusTool(registry2) {
130058
+ return {
130059
+ id: "debugger-status",
130060
+ interaction: {
130061
+ startedMsg: () => "Checking JavaScript debugger",
130062
+ completedMsg: ({ result }) => result.status === "connected" ? "Checked JavaScript debugger" : "JavaScript debugger is not connected",
130063
+ failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to check JavaScript debugger: ${failureSignal2.error_code}`
130064
+ },
130065
+ description: `Get JS runtime debugger connection status and diagnostic info.
130066
+ Use when you need to verify connectivity before using other debugger tools. Never fails when the runtime is simply unreachable \u2014 it returns a discriminated result instead:
130067
+ - { status: "connected", ... } with port, projectRoot (empty on Chromium and on legacy Metro, e.g. Vega), deviceName, appName, logicalDeviceId (absent on Vega), isNewDebugger (false on the legacy inspector), connected flag, loadedScripts count, and sourceMapReady (always true \u2014 waits for pending source maps before returning; no-op on Chromium).
130068
+ - { status: "not_connected", connected: false, reason, detail, guidance } (port omitted on Chromium) when Metro is not running (reason "metro_not_running"), no app is attached ("no_app_connected"), the device_id matches no target ("device_mismatch"), the CDP endpoint is unreachable or answered malformed ("cdp_unreachable"), the runtime accepted the connection but never answered ("runtime_unresponsive"), the cached connection went stale ("stale_connection"), or a reconnect is in flight ("reconnecting"). Follow the guidance field \u2014 do not retry in a loop.`,
130069
+ zodSchema: zodSchema30,
130070
+ capability: DEBUGGER_TOOL_CAPABILITY,
130071
+ // Resolved manually in execute so a not-connected precondition becomes a
130072
+ // structured result instead of a service-resolution tool failure.
130073
+ services: () => ({}),
130074
+ async execute(_services, params, ctx) {
130075
+ try {
130076
+ const api = await resolveDebuggerService(registry2, params);
130077
+ await api.sourceMaps.waitForPending();
130078
+ if (!api.cdp.isConnected()) {
130079
+ const isChromium = params.device_id?.startsWith(CHROMIUM_ID_PREFIX) ?? false;
130080
+ if (isChromium) {
130081
+ const result = buildNotConnected(
130082
+ "reconnecting",
130083
+ new Error("CDP socket is reconnecting (tab switch in progress)"),
130084
+ params
130085
+ );
130086
+ trackDebuggerOutcome("debugger-status", "reconnecting", params, ctx);
130087
+ return result;
130088
+ }
130089
+ trackDebuggerOutcome("debugger-status", "stale_connection", params, ctx);
130090
+ const ref = debuggerServiceRef(params);
130091
+ await registry2.disposeService(typeof ref === "string" ? ref : ref.urn).catch(() => {
130092
+ });
130093
+ return buildNotConnected(
130094
+ "stale_connection",
130095
+ new Error("Cached debugger connection is no longer open"),
130096
+ params
130097
+ );
130098
+ }
130099
+ trackDebuggerOutcome("debugger-status", "connected", params, ctx);
130100
+ return {
130101
+ status: "connected",
130102
+ port: api.port,
130103
+ projectRoot: api.projectRoot,
130104
+ deviceName: api.deviceName,
130105
+ appName: api.appName,
130106
+ logicalDeviceId: api.logicalDeviceId,
130107
+ isNewDebugger: api.isNewDebugger,
130108
+ connected: true,
130109
+ loadedScripts: api.cdp.getLoadedScripts().size,
130110
+ enabledDomains: [...api.cdp.getEnabledDomains()],
130111
+ sourceMapReady: true
130112
+ };
130113
+ } catch (err) {
130114
+ const reason = classifyNotConnected(err);
130115
+ if (!reason) throw err;
130116
+ trackDebuggerOutcome("debugger-status", reason, params, ctx);
130117
+ return buildNotConnected(reason, err, params);
130118
+ }
130119
+ }
130120
+ };
130121
+ }
129847
130122
 
129848
130123
  // ../tool-server/src/tools/debugger/debugger-evaluate.ts
129849
130124
  init_zod();
130125
+ init_src();
129850
130126
  var zodSchema31 = external_exports.object({
129851
130127
  port: external_exports.coerce.number().default(8081).describe("Metro server port (ignored for Chromium)"),
129852
130128
  device_id: external_exports.string().describe(
@@ -129870,7 +130146,24 @@ Returns the evaluation result as a JSON-serializable value, along with deviceNam
129870
130146
  }),
129871
130147
  async execute(services, params) {
129872
130148
  const api = services.debugger;
129873
- const result = await api.cdp.evaluate(params.expression);
130149
+ let result;
130150
+ try {
130151
+ result = await api.cdp.evaluate(params.expression);
130152
+ } catch (err) {
130153
+ if (err instanceof Error && getFailureSignal(err)?.error_code === FAILURE_CODES.DEBUGGER_CDP_RUNTIME_EXCEPTION) {
130154
+ throw new FailureError(
130155
+ err.message,
130156
+ {
130157
+ error_code: FAILURE_CODES.DEBUGGER_EVALUATE_EXPRESSION_THREW,
130158
+ failure_stage: "debugger_evaluate_expression",
130159
+ failure_area: "tool_server",
130160
+ error_kind: "unknown"
130161
+ },
130162
+ { cause: err }
130163
+ );
130164
+ }
130165
+ throw err;
130166
+ }
129874
130167
  return {
129875
130168
  result,
129876
130169
  deviceName: api.deviceName,
@@ -131363,48 +131656,60 @@ var zodSchema35 = external_exports.object({
131363
131656
  "Device id from list-devices \u2014 the SAME id you passed to debugger-connect (iOS simulator UDID, Android serial, Vega serial, or Chromium device id). The logicalDeviceId debugger-connect returns also resolves here, but prefer the stable list-devices id."
131364
131657
  )
131365
131658
  });
131366
- var debuggerLogRegistryTool = {
131367
- id: "debugger-log-registry",
131368
- interaction: {
131369
- startedMsg: () => "Reading app logs",
131370
- completedMsg: () => "Read app logs",
131371
- failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to read app logs: ${failureSignal2.error_code}`
131372
- },
131373
- description: `Get a summary of all console logs captured from the app's JS runtime.
131659
+ function createDebuggerLogRegistryTool(registry2) {
131660
+ return {
131661
+ id: "debugger-log-registry",
131662
+ interaction: {
131663
+ startedMsg: () => "Reading app logs",
131664
+ completedMsg: ({ result }) => result.status === "connected" ? "Read app logs" : "JavaScript debugger is not connected \u2014 no logs captured",
131665
+ failedMsg: ({ failureSignal: failureSignal2 }) => `Failed to read app logs: ${failureSignal2.error_code}`
131666
+ },
131667
+ description: `Get a summary of all console logs captured from the app's JS runtime.
131374
131668
  Returns the log file path, entry counts by level, and message clusters (grouped by similarity). Works against Hermes (iOS / Android / Vega) and V8 (Chromium).
131375
- Use when investigating warnings, errors, or unexpected output \u2014 call this first for an overview, then read the returned file for details. Returns empty stats if no log data has been captured yet \u2014 but check { note }, which is present only when the stats are empty BECAUSE a stop-all-simulator-servers tore the previous debugger session down and deleted its log file. Absent that note, empty really does mean the app has logged nothing.`,
131376
- zodSchema: zodSchema35,
131377
- capability: DEBUGGER_TOOL_CAPABILITY,
131378
- services: (params) => ({
131379
- debugger: debuggerServiceRef(params)
131380
- }),
131381
- async execute(services, params) {
131382
- const api = services.debugger;
131383
- const stats = api.logWriter.getStats();
131384
- const clusters = api.logWriter.getClusters(20);
131385
- const response = {
131386
- ...stats,
131387
- clusters,
131388
- deviceName: api.deviceName,
131389
- appName: api.appName,
131390
- logicalDeviceId: api.logicalDeviceId
131391
- };
131392
- if (stats.totalEntries === 0) {
131393
- const aliases = [
131394
- canonicalDeviceId(params.device_id),
131395
- params.device_id,
131396
- api.logicalDeviceId
131397
- ].filter((id) => id !== void 0);
131398
- let reaped2;
131399
- for (const id of new Set(aliases)) {
131400
- const entry = takeReapedSession("js-runtime-debugger", id);
131401
- reaped2 ??= entry;
131669
+ Use when investigating warnings, errors, or unexpected output \u2014 call this first for an overview, then read the returned file for details. Returns empty stats if no log data has been captured yet \u2014 but check { note }, which is present only when the stats are empty BECAUSE a stop-all-simulator-servers tore the previous debugger session down and deleted its log file. Absent that note, empty really does mean the app has logged nothing.
131670
+ When the debugger cannot be reached, this tool does not fail: it returns { status: "not_connected", reason, detail, guidance } with NO log file \u2014 follow the guidance (do not retry in a loop, and do not try to read a log file from this state). A "connected" result's stats may come from a session whose socket has since died \u2014 use debugger-status, not this tool, to judge debugger health.`,
131671
+ zodSchema: zodSchema35,
131672
+ capability: DEBUGGER_TOOL_CAPABILITY,
131673
+ // Resolved manually in execute so a not-connected precondition becomes a
131674
+ // structured result instead of a service-resolution tool failure.
131675
+ services: () => ({}),
131676
+ async execute(_services, params, ctx) {
131677
+ try {
131678
+ const api = await resolveDebuggerService(registry2, params);
131679
+ const stats = api.logWriter.getStats();
131680
+ const clusters = api.logWriter.getClusters(20);
131681
+ trackDebuggerOutcome("debugger-log-registry", "connected", params, ctx);
131682
+ const response = {
131683
+ status: "connected",
131684
+ ...stats,
131685
+ clusters,
131686
+ deviceName: api.deviceName,
131687
+ appName: api.appName,
131688
+ logicalDeviceId: api.logicalDeviceId
131689
+ };
131690
+ if (stats.totalEntries === 0) {
131691
+ const aliases = [
131692
+ canonicalDeviceId(params.device_id),
131693
+ params.device_id,
131694
+ api.logicalDeviceId
131695
+ ].filter((id) => id !== void 0);
131696
+ let reaped2;
131697
+ for (const id of new Set(aliases)) {
131698
+ const entry = takeReapedSession("js-runtime-debugger", id);
131699
+ reaped2 ??= entry;
131700
+ }
131701
+ if (reaped2) response.note = describeReapedSession(reaped2, "JS-runtime debugger session");
131702
+ }
131703
+ return response;
131704
+ } catch (err) {
131705
+ const reason = classifyNotConnected(err);
131706
+ if (!reason) throw err;
131707
+ trackDebuggerOutcome("debugger-log-registry", reason, params, ctx);
131708
+ return buildNotConnected(reason, err, params);
131402
131709
  }
131403
- if (reaped2) response.note = describeReapedSession(reaped2, "JS-runtime debugger session");
131404
131710
  }
131405
- return response;
131406
- }
131407
- };
131711
+ };
131712
+ }
131408
131713
 
131409
131714
  // ../tool-server/src/tools/network/network-logs.ts
131410
131715
  init_zod();
@@ -135709,14 +136014,14 @@ function parseHangsXml(xml) {
135709
136014
  function parseLeaksXml(xml) {
135710
136015
  const leaks = [];
135711
136016
  const rowRe = /<row\s+([^>]*?)\/>/g;
135712
- let rm7;
135713
- while ((rm7 = rowRe.exec(xml)) !== null) {
135714
- const objectType = attr(rm7[0], "leaked-object") ?? "Unknown";
135715
- const sizeStr = attr(rm7[0], "size");
136017
+ let rm8;
136018
+ while ((rm8 = rowRe.exec(xml)) !== null) {
136019
+ const objectType = attr(rm8[0], "leaked-object") ?? "Unknown";
136020
+ const sizeStr = attr(rm8[0], "size");
135716
136021
  const sizeBytes = sizeStr ? parseInt(sizeStr, 10) : 0;
135717
- const responsibleFrame = attr(rm7[0], "responsible-frame") ?? "Unknown";
135718
- const responsibleLibrary = attr(rm7[0], "responsible-library") ?? "";
135719
- const countStr = attr(rm7[0], "count");
136022
+ const responsibleFrame = attr(rm8[0], "responsible-frame") ?? "Unknown";
136023
+ const responsibleLibrary = attr(rm8[0], "responsible-library") ?? "";
136024
+ const countStr = attr(rm8[0], "count");
135720
136025
  const count2 = countStr ? parseInt(countStr, 10) : 1;
135721
136026
  leaks.push({
135722
136027
  objectType,
@@ -142440,10 +142745,18 @@ function waitToYaml(condition, selector, expectedText, textMatch, timeoutMs) {
142440
142745
  if (timeoutMs !== void 0) body.timeout = timeoutMs;
142441
142746
  return body;
142442
142747
  }
142748
+ function idleToYaml(step) {
142749
+ const body = { idle: true };
142750
+ if (step.stableFor !== void 0) body.stableFor = step.stableFor;
142751
+ if (step.timeout !== void 0) body.timeout = step.timeout;
142752
+ return { await: body };
142753
+ }
142443
142754
  function toYamlStep(step) {
142444
142755
  switch (step.kind) {
142445
142756
  case "echo":
142446
142757
  return { echo: step.message };
142758
+ case "idle":
142759
+ return idleToYaml(step);
142447
142760
  case "launch":
142448
142761
  return { launch: step.app };
142449
142762
  case "run":
@@ -142735,16 +143048,14 @@ var TEXT_MATCH_MODES = Object.keys({
142735
143048
  });
142736
143049
  var SCROLL_DIRECTIONS = ["up", "down", "left", "right"];
142737
143050
  function parseWaitFields(raw, kind) {
143051
+ const legalKeys = kind === "await" ? [...WAIT_CONDITIONS, IDLE_CONDITION] : WAIT_CONDITIONS;
142738
143052
  if (raw === null || typeof raw !== "object") {
142739
- badEntry({ [kind]: raw }, `${kind} needs a condition (${WAIT_CONDITIONS.join(", ")})`);
143053
+ badEntry({ [kind]: raw }, `${kind} needs a condition (${legalKeys.join(", ")})`);
142740
143054
  }
142741
143055
  const b = raw;
142742
143056
  const present = WAIT_CONDITIONS.filter((c) => c in b);
142743
143057
  if (present.length !== 1) {
142744
- badEntry(
142745
- { [kind]: b },
142746
- `${kind} needs exactly one condition key (${WAIT_CONDITIONS.join(", ")})`
142747
- );
143058
+ badEntry({ [kind]: b }, `${kind} needs exactly one condition key (${legalKeys.join(", ")})`);
142748
143059
  }
142749
143060
  const condition = present[0];
142750
143061
  let timeout;
@@ -142755,13 +143066,7 @@ function parseWaitFields(raw, kind) {
142755
143066
  "assert has no timeout \u2014 it is an immediate check; use `await` for a timed wait"
142756
143067
  );
142757
143068
  }
142758
- if (typeof b.timeout !== "number" || !Number.isFinite(b.timeout) || b.timeout <= 0) {
142759
- badEntry(
142760
- { [kind]: b },
142761
- "await.timeout needs a positive number of milliseconds (e.g. `timeout: 10000`)"
142762
- );
142763
- }
142764
- timeout = b.timeout;
143069
+ timeout = parseAwaitTimeout({ [kind]: b }, b.timeout);
142765
143070
  }
142766
143071
  rejectUnknownKeys(
142767
143072
  { [kind]: b },
@@ -142806,6 +143111,76 @@ function parseWaitFields(raw, kind) {
142806
143111
  }
142807
143112
  return { condition, selector: parseSelector(b[condition], `${kind}.${condition}`), timeout };
142808
143113
  }
143114
+ var IDLE_CONDITION = "idle";
143115
+ var IDLE_DEFAULT_TIMEOUT_MS = 7500;
143116
+ var IDLE_DEFAULT_STABLE_FOR_MS = 250;
143117
+ var IDLE_POLL_MS = 200;
143118
+ var IDLE_MIN_STILL_INTERVALS = 2;
143119
+ var IDLE_SETTLE_SPAN_MS = IDLE_MIN_STILL_INTERVALS * IDLE_POLL_MS;
143120
+ function idleMinimumTimeoutMs(stableFor) {
143121
+ return Math.max(IDLE_SETTLE_SPAN_MS, stableFor) + IDLE_POLL_MS;
143122
+ }
143123
+ var IDLE_MAX_STABLE_FOR_MS = 6e5;
143124
+ function parseAwaitTimeout(entry, value) {
143125
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
143126
+ badEntry(
143127
+ entry,
143128
+ "await.timeout needs a positive number of milliseconds (e.g. `timeout: 10000`)"
143129
+ );
143130
+ }
143131
+ return value;
143132
+ }
143133
+ function parseBoundedMs(entry, value, where, max) {
143134
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > max) {
143135
+ badEntry(entry, `${where} needs an integer between 0 and ${max} (milliseconds)`);
143136
+ }
143137
+ return value;
143138
+ }
143139
+ function parseIdleFields(raw, kind) {
143140
+ const entry = { [kind]: raw };
143141
+ if (kind !== "await") {
143142
+ const mixed = WAIT_CONDITIONS.filter((c) => c in raw);
143143
+ badEntry(
143144
+ entry,
143145
+ "idle has no assert form \u2014 it waits for the screen to stop changing, which is an `await`" + (mixed.length > 0 ? `. Give it its own step as \`await: { idle: true }\` and leave \`${mixed.join(
143146
+ "`, `"
143147
+ )}\` in the assert \u2014 a step checks exactly one condition` : "")
143148
+ );
143149
+ }
143150
+ rejectUnknownKeys(entry, raw, ["idle", "stableFor", "timeout"], kind);
143151
+ if (raw.idle !== true) {
143152
+ badEntry(entry, "idle takes only `true` (`await: { idle: true }`)");
143153
+ }
143154
+ const step = { kind: "idle" };
143155
+ if ("timeout" in raw) step.timeout = parseAwaitTimeout(entry, raw.timeout);
143156
+ if (raw.stableFor !== void 0) {
143157
+ step.stableFor = parseBoundedMs(entry, raw.stableFor, "idle.stableFor", IDLE_MAX_STABLE_FOR_MS);
143158
+ }
143159
+ const timeoutMs = step.timeout ?? IDLE_DEFAULT_TIMEOUT_MS;
143160
+ const stableFor = step.stableFor ?? IDLE_DEFAULT_STABLE_FOR_MS;
143161
+ const needed = idleMinimumTimeoutMs(stableFor);
143162
+ if (timeoutMs < needed) {
143163
+ badEntry(
143164
+ entry,
143165
+ `idle needs a timeout of at least ${needed}ms to hold still for ${step.stableFor === void 0 ? `the default ` : ``}${stableFor}ms: a settle is ${IDLE_MIN_STILL_INTERVALS + 1} reads spanning ${IDLE_MIN_STILL_INTERVALS} ${IDLE_POLL_MS}ms polls, and the hold is counted across those polls rather than after them \u2014 so the wait has to contain whichever of the two is longer, plus the ${IDLE_POLL_MS}ms of budget the closing round has to have left to be allowed to start. Raise \`timeout\`${step.stableFor === void 0 ? "" : " or lower `stableFor`"}`
143166
+ );
143167
+ }
143168
+ return step;
143169
+ }
143170
+ function isIdleCondition(raw, kind) {
143171
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
143172
+ const body = raw;
143173
+ if (!(IDLE_CONDITION in body)) return false;
143174
+ if (kind === "assert") return true;
143175
+ const selectorConditions = WAIT_CONDITIONS.filter((c) => c in body);
143176
+ if (selectorConditions.length > 0) {
143177
+ badEntry(
143178
+ { [kind]: body },
143179
+ `${kind} mixes \`${IDLE_CONDITION}\` with \`${selectorConditions.join("`, `")}\` \u2014 a step checks exactly one condition`
143180
+ );
143181
+ }
143182
+ return true;
143183
+ }
142809
143184
  var LAUNCH_PLATFORMS = ["ios", "android", "chromium", "vega"];
142810
143185
  var LAUNCH_MAP_KEYS = ["native", ...LAUNCH_PLATFORMS];
142811
143186
  function parseChromiumLaunch(raw) {
@@ -143019,6 +143394,12 @@ function parseWhenCondition(raw) {
143019
143394
  badEntry({ when: raw }, `when needs exactly one condition key (${conditionKeys})`);
143020
143395
  }
143021
143396
  const b = raw;
143397
+ if (IDLE_CONDITION in b) {
143398
+ badEntry(
143399
+ { when: raw },
143400
+ "when has no idle form \u2014 stillness is a wait, and a guard asks what is on the screen now. Put `await: { idle: true }` before the block instead"
143401
+ );
143402
+ }
143022
143403
  const present = [...WAIT_CONDITIONS, "platform"].filter((c) => c in b);
143023
143404
  if (present.length !== 1) {
143024
143405
  badEntry({ when: raw }, `when needs exactly one condition key (${conditionKeys})`);
@@ -143128,6 +143509,9 @@ function fromYamlStep(raw, whenDepth = 0) {
143128
143509
  }
143129
143510
  const kinds = STEP_DIRECTIVE_KEYS.filter((k) => k in entry);
143130
143511
  if (kinds.length === 0) {
143512
+ if (IDLE_CONDITION in entry) {
143513
+ badEntry(raw, `idle is a condition, not a step kind \u2014 write it as \`await: { idle: true }\``);
143514
+ }
143131
143515
  const hint = Object.keys(entry).map((k) => closestKey(k, STEP_DIRECTIVE_KEYS)).find((h) => h !== null);
143132
143516
  badEntry(raw, `unrecognized step kind${hint ? ` (did you mean \`${hint}\`?)` : ""}`);
143133
143517
  }
@@ -143175,10 +143559,18 @@ function fromYamlStep(raw, whenDepth = 0) {
143175
143559
  return step;
143176
143560
  }
143177
143561
  if ("await" in raw) {
143178
- return { kind: "await", ...parseWaitFields(raw.await, "await") };
143562
+ const body = raw.await;
143563
+ if (isIdleCondition(body, "await")) {
143564
+ return parseIdleFields(body, "await");
143565
+ }
143566
+ return { kind: "await", ...parseWaitFields(body, "await") };
143179
143567
  }
143180
143568
  if ("assert" in raw) {
143181
- return { kind: "assert", ...parseWaitFields(raw.assert, "assert") };
143569
+ const body = raw.assert;
143570
+ if (isIdleCondition(body, "assert")) {
143571
+ return parseIdleFields(body, "assert");
143572
+ }
143573
+ return { kind: "assert", ...parseWaitFields(body, "assert") };
143182
143574
  }
143183
143575
  if ("wait" in raw) {
143184
143576
  const ms = Number(raw.wait);
@@ -143709,6 +144101,7 @@ function stepRequiresDevice(registry2, step) {
143709
144101
  case "type":
143710
144102
  case "await":
143711
144103
  case "assert":
144104
+ case "idle":
143712
144105
  case "scroll-to":
143713
144106
  case "pinch":
143714
144107
  case "rotate":
@@ -144563,6 +144956,8 @@ function summarizeSteps(flow) {
144563
144956
  return `${n}. rotate: by ${step.by}\xB0${step.selector ? ` on ${selectorLabel(step.selector)}` : ""}`;
144564
144957
  case "snapshot":
144565
144958
  return `${n}. snapshot: ${step.name}`;
144959
+ case "idle":
144960
+ return `${n}. await: screen idle`;
144566
144961
  case "tool":
144567
144962
  default:
144568
144963
  return `${n}. tool: ${step.name} ${renderToolArgs(step.args)}`;
@@ -144572,7 +144967,7 @@ function summarizeSteps(flow) {
144572
144967
 
144573
144968
  // ../tool-server/src/tools/flows/flow-run.ts
144574
144969
  init_zod();
144575
- var fs48 = __toESM(require("node:fs/promises"));
144970
+ var fs49 = __toESM(require("node:fs/promises"));
144576
144971
  var path35 = __toESM(require("node:path"));
144577
144972
  init_src();
144578
144973
 
@@ -144644,6 +145039,122 @@ function nestedOrchestratorOutcome(tool, result) {
144644
145039
  return void 0;
144645
145040
  }
144646
145041
 
145042
+ // ../tool-server/src/tools/flows/flow-pixels.ts
145043
+ var fs46 = __toESM(require("node:fs/promises"));
145044
+ var import_pngjs2 = __toESM(require_png());
145045
+ init_adb();
145046
+ var CAPTURE_SCALE = 0.25;
145047
+ var PIXEL_THRESHOLD = 0.03;
145048
+ var MAX_RGB_DISTANCE_SQUARED = 255 * 255 * 3;
145049
+ var PIXEL_THRESHOLD_SQUARED = PIXEL_THRESHOLD * PIXEL_THRESHOLD * MAX_RGB_DISTANCE_SQUARED;
145050
+ var MOTION_FRACTION = 2e-3;
145051
+ var MOTION_MIN_PIXELS = 100;
145052
+ var MOTION_MIN_PIXELS_FRAME_SHARE = 0.1;
145053
+ var LOCALIZED_MOTION_MIN_PIXELS = 10;
145054
+ var STATUS_BAR_MASK_FRACTION = 0.06;
145055
+ async function statusBarMaskFraction(device) {
145056
+ if (device.platform === "android") {
145057
+ return await isAndroidTv(device.id) ? 0 : STATUS_BAR_MASK_FRACTION;
145058
+ }
145059
+ if (device.platform === "ios-remote") return STATUS_BAR_MASK_FRACTION;
145060
+ if (device.platform !== "ios") return 0;
145061
+ return await isTvOsSimulator(device.id) ? 0 : STATUS_BAR_MASK_FRACTION;
145062
+ }
145063
+ var FIRST_CAPTURE_COMPLETION_MARGIN_MS = 500;
145064
+ var FIRST_PIXEL_CAPTURE_TIMEOUT_MS = FIRST_FRAME_WAIT_MS + FIRST_CAPTURE_COMPLETION_MARGIN_MS;
145065
+ var PIXEL_CAPTURE_TIMEOUT_MS = 2e3;
145066
+ function pixelCaptureTimeoutMs(device, firstCapture) {
145067
+ const warmFromTheStart = device.platform === "chromium" || device.platform === "vega";
145068
+ return firstCapture && !warmFromTheStart ? FIRST_PIXEL_CAPTURE_TIMEOUT_MS : PIXEL_CAPTURE_TIMEOUT_MS;
145069
+ }
145070
+ async function captureChromiumPng(env) {
145071
+ const ref = chromiumCdpRef(env.device);
145072
+ const api = await env.registry.resolveService(ref.urn, ref.options);
145073
+ const { width, height } = api.getViewport();
145074
+ const { x, y } = await chromiumScrollOffset(api);
145075
+ const shot = await api.cdp.send("Page.captureScreenshot", {
145076
+ format: "png",
145077
+ captureBeyondViewport: false,
145078
+ clip: { x, y, width, height, scale: CAPTURE_SCALE }
145079
+ });
145080
+ if (!shot.data) throw new Error("Page.captureScreenshot returned no data");
145081
+ return Buffer.from(shot.data, "base64");
145082
+ }
145083
+ async function chromiumScrollOffset(api) {
145084
+ try {
145085
+ const metrics = await api.cdp.send("Page.getLayoutMetrics");
145086
+ const vp = metrics.cssVisualViewport ?? metrics.cssLayoutViewport ?? metrics.layoutViewport;
145087
+ const x = vp?.pageX;
145088
+ const y = vp?.pageY;
145089
+ return {
145090
+ x: typeof x === "number" && Number.isFinite(x) ? x : 0,
145091
+ y: typeof y === "number" && Number.isFinite(y) ? y : 0
145092
+ };
145093
+ } catch {
145094
+ return { x: 0, y: 0 };
145095
+ }
145096
+ }
145097
+ async function captureFile(env, budgetMs) {
145098
+ if (env.device.platform === "vega") {
145099
+ return captureVegaScreenshotPng({ scale: CAPTURE_SCALE });
145100
+ }
145101
+ if (env.device.platform === "ios" && await isTvOsSimulator(env.device.id)) {
145102
+ return tvScreenshot(env.device.id, CAPTURE_SCALE, captureAbortSignal(env, budgetMs));
145103
+ }
145104
+ const ref = simulatorServerRef(env.device);
145105
+ const api = await env.registry.resolveService(ref.urn, ref.options);
145106
+ const { path: path40 } = await httpScreenshot(api, void 0, void 0, CAPTURE_SCALE);
145107
+ return path40;
145108
+ }
145109
+ async function capturePng(env, budgetMs) {
145110
+ if (env.device.platform === "chromium") return captureChromiumPng(env);
145111
+ const file2 = await captureFile(env, budgetMs);
145112
+ try {
145113
+ return await fs46.readFile(file2);
145114
+ } finally {
145115
+ await fs46.rm(file2, { force: true }).catch(() => {
145116
+ });
145117
+ }
145118
+ }
145119
+ async function capturePixels(env, budgetMs) {
145120
+ try {
145121
+ const png = import_pngjs2.PNG.sync.read(await capturePng(env, budgetMs));
145122
+ return { width: png.width, height: png.height, data: png.data };
145123
+ } catch {
145124
+ return void 0;
145125
+ }
145126
+ }
145127
+ async function capturePixelsWithin(env, deadline, firstCapture) {
145128
+ const budget = Math.min(deadline - Date.now(), pixelCaptureTimeoutMs(env.device, firstCapture));
145129
+ if (budget <= 0) return void 0;
145130
+ const result = await settleWithin(capturePixels(env, budget), budget, env.signal);
145131
+ return result.type === "value" ? result.value : void 0;
145132
+ }
145133
+ function captureAbortSignal(env, budgetMs) {
145134
+ const bound = AbortSignal.timeout(budgetMs);
145135
+ return env.signal ? AbortSignal.any([env.signal, bound]) : bound;
145136
+ }
145137
+ function comparePixels(a, b, maskTopFraction = 0) {
145138
+ if (a.width !== b.width || a.height !== b.height) return "moving";
145139
+ const maskedRows = Math.min(a.height, Math.floor(a.height * maskTopFraction));
145140
+ const total = a.width * (a.height - maskedRows);
145141
+ if (total <= 0) return "still";
145142
+ const limit = Math.min(a.data.length, b.data.length);
145143
+ let changed = 0;
145144
+ for (let o = maskedRows * a.width * 4; o + 2 < limit; o += 4) {
145145
+ const dr = a.data[o] - b.data[o];
145146
+ const dg = a.data[o + 1] - b.data[o + 1];
145147
+ const db = a.data[o + 2] - b.data[o + 2];
145148
+ if (dr * dr + dg * dg + db * db > PIXEL_THRESHOLD_SQUARED) changed++;
145149
+ }
145150
+ const motionThreshold = Math.max(
145151
+ total * MOTION_FRACTION,
145152
+ Math.min(MOTION_MIN_PIXELS, total * MOTION_MIN_PIXELS_FRAME_SHARE)
145153
+ );
145154
+ if (changed > motionThreshold) return "moving";
145155
+ return changed >= LOCALIZED_MOTION_MIN_PIXELS ? "localized" : "still";
145156
+ }
145157
+
144647
145158
  // ../tool-server/src/tools/flows/flow-actions.ts
144648
145159
  var ABORTED_OUTCOME = {
144649
145160
  ok: false,
@@ -144889,6 +145400,8 @@ async function runDirective(env, step) {
144889
145400
  return waitForCondition(env, step, step.timeout ?? DEFAULT_ACTION_TIMEOUT_MS);
144890
145401
  case "assert":
144891
145402
  return waitForCondition(env, step, DEFAULT_ASSERT_TIMEOUT_MS);
145403
+ case "idle":
145404
+ return waitForIdle(env, step);
144892
145405
  case "scroll-to": {
144893
145406
  const r = await scrollToVisible(env, step.target, step.direction, step.within);
144894
145407
  if (r.aborted) return ABORTED_OUTCOME;
@@ -145131,6 +145644,215 @@ async function waitForCondition(env, step, timeoutMs) {
145131
145644
  reason: assertReason(step.condition, step.selector, step.expectedText, step.textMatch, lastMatches) + blipNote
145132
145645
  };
145133
145646
  }
145647
+ var MIN_STILL_INTERVALS = IDLE_MIN_STILL_INTERVALS;
145648
+ var MIN_ROUND_BUDGET_MS = IDLE_POLL_MS;
145649
+ var LOCALIZED_MOTION_WARNING = `the screen settled, but a small part of it was still changing while it did \u2014 a spinner, a caret, a progress dot. If it is a loading spinner then the screen had not finished loading, and stillness cannot tell those apart: look at what is moving, and gate the next action on the element the loading produces rather than on this settle.`;
145650
+ var HUNG_TREE_READ_MS = 2e3;
145651
+ var IDLE_TOLERATED_DARK_READS = 1;
145652
+ function isBlindTreeRead(data) {
145653
+ return data.tree.children.length === 0 && Boolean(data.hint || data.should_restart);
145654
+ }
145655
+ async function waitForIdle(env, step) {
145656
+ const timeoutMs = step.timeout ?? IDLE_DEFAULT_TIMEOUT_MS;
145657
+ const stableFor = step.stableFor ?? IDLE_DEFAULT_STABLE_FOR_MS;
145658
+ const maskTopFraction = await statusBarMaskFraction(env.device);
145659
+ const deadline = Date.now() + timeoutMs;
145660
+ let treeSignature2;
145661
+ let treeSince = 0;
145662
+ let treeStillIntervals = 0;
145663
+ let treeSettledAtLastRead = false;
145664
+ let previousFrame;
145665
+ let bothSince = 0;
145666
+ let stillIntervals = 0;
145667
+ let heldForMs = 0;
145668
+ let localizedMotionDuringHold = false;
145669
+ let readsSucceeded = 0;
145670
+ let contentReads = 0;
145671
+ let lastRead;
145672
+ let treeErrorMessage;
145673
+ let treeReadBlind = false;
145674
+ let blindHint;
145675
+ let darkReads = 0;
145676
+ let treeReadHung = false;
145677
+ let slowestAnsweredReadMs = 0;
145678
+ let sawContent = false;
145679
+ let pixelsEverMoved = false;
145680
+ let comparedAPair = false;
145681
+ let firstCapture = true;
145682
+ for (; ; ) {
145683
+ if (env.signal?.aborted) return ABORTED_OUTCOME;
145684
+ const roundBudget = Math.max(1, deadline - Date.now());
145685
+ const roundStartedAt = Date.now();
145686
+ let answeredReadMs;
145687
+ const [read, frame] = await Promise.all([
145688
+ settleWithin(fetchFlowTree(env.registry, env.device), roundBudget, env.signal).then((r) => {
145689
+ answeredReadMs = Date.now() - roundStartedAt;
145690
+ return r;
145691
+ }),
145692
+ capturePixelsWithin(env, deadline, firstCapture)
145693
+ ]);
145694
+ firstCapture = false;
145695
+ if (env.signal?.aborted || read.type === "aborted") return ABORTED_OUTCOME;
145696
+ if (read.type !== "timeout" && answeredReadMs !== void 0) {
145697
+ slowestAnsweredReadMs = Math.max(slowestAnsweredReadMs, answeredReadMs);
145698
+ }
145699
+ if (read.type === "timeout") {
145700
+ lastRead = "timeout";
145701
+ darkReads += 1;
145702
+ if (roundBudget >= HUNG_TREE_READ_MS && roundBudget > slowestAnsweredReadMs + HUNG_TREE_READ_MS) {
145703
+ treeReadHung = true;
145704
+ }
145705
+ } else if (read.type === "value" && isBlindTreeRead(read.value)) {
145706
+ lastRead = "blind";
145707
+ darkReads += 1;
145708
+ treeReadBlind = true;
145709
+ blindHint = read.value.hint;
145710
+ } else if (read.type === "error") {
145711
+ lastRead = "error";
145712
+ darkReads += 1;
145713
+ treeErrorMessage = read.error;
145714
+ treeSignature2 = void 0;
145715
+ previousFrame = void 0;
145716
+ treeSince = 0;
145717
+ treeStillIntervals = 0;
145718
+ treeSettledAtLastRead = false;
145719
+ bothSince = 0;
145720
+ stillIntervals = 0;
145721
+ } else {
145722
+ lastRead = "value";
145723
+ readsSucceeded += 1;
145724
+ darkReads = 0;
145725
+ treeErrorMessage = void 0;
145726
+ treeReadBlind = false;
145727
+ blindHint = void 0;
145728
+ treeReadHung = false;
145729
+ const tree = read.value.tree;
145730
+ if (tree.children.length === 0) {
145731
+ treeSignature2 = void 0;
145732
+ previousFrame = void 0;
145733
+ treeSince = 0;
145734
+ treeStillIntervals = 0;
145735
+ treeSettledAtLastRead = false;
145736
+ bothSince = 0;
145737
+ stillIntervals = 0;
145738
+ } else {
145739
+ sawContent = true;
145740
+ contentReads += 1;
145741
+ const signature = treeFingerprint(tree);
145742
+ const now = Date.now();
145743
+ const treeHeld = signature === treeSignature2;
145744
+ treeSignature2 = signature;
145745
+ if (!treeHeld) {
145746
+ treeSince = now;
145747
+ treeStillIntervals = 0;
145748
+ } else {
145749
+ treeStillIntervals += 1;
145750
+ }
145751
+ treeSettledAtLastRead = treeStillIntervals >= MIN_STILL_INTERVALS && now - treeSince >= stableFor;
145752
+ let pixelsHeld;
145753
+ let localizedThisInterval = false;
145754
+ if (frame !== void 0) {
145755
+ if (previousFrame !== void 0) {
145756
+ comparedAPair = true;
145757
+ const change = comparePixels(previousFrame, frame, maskTopFraction);
145758
+ if (change === "moving") {
145759
+ pixelsEverMoved = true;
145760
+ pixelsHeld = false;
145761
+ } else {
145762
+ pixelsHeld = true;
145763
+ localizedThisInterval = change === "localized";
145764
+ }
145765
+ }
145766
+ previousFrame = frame;
145767
+ }
145768
+ if (!treeHeld || pixelsHeld === false) {
145769
+ bothSince = now;
145770
+ stillIntervals = 0;
145771
+ heldForMs = 0;
145772
+ localizedMotionDuringHold = false;
145773
+ } else if (pixelsHeld === true) {
145774
+ stillIntervals += 1;
145775
+ heldForMs = now - bothSince;
145776
+ if (localizedThisInterval) localizedMotionDuringHold = true;
145777
+ if (stillIntervals >= MIN_STILL_INTERVALS && heldForMs >= stableFor) {
145778
+ return localizedMotionDuringHold ? { ok: true, warning: LOCALIZED_MOTION_WARNING } : { ok: true };
145779
+ }
145780
+ }
145781
+ }
145782
+ }
145783
+ if (env.signal?.aborted) return ABORTED_OUTCOME;
145784
+ const left = deadline - Date.now();
145785
+ if (left < MIN_ROUND_BUDGET_MS) break;
145786
+ if (!await sleepOrAbort(Math.min(IDLE_POLL_MS, left), env.signal)) return ABORTED_OUTCOME;
145787
+ }
145788
+ const unreadable = (underlying) => ({
145789
+ ok: false,
145790
+ indeterminate: true,
145791
+ // The underlying reader reports an instrumentation failure, whose remedy
145792
+ // (relaunch the app) is the wrong repair for the commonest cause of it
145793
+ // here: the app is simply not in the foreground, which reads exactly the
145794
+ // same from the tree source. Name that first so the author checks it
145795
+ // before relaunching anything.
145796
+ reason: `could not read the UI tree while waiting for the screen to settle \u2014 check the app is still in the foreground (a backgrounded app reads the same as an uninstrumented one). Underlying error: ${underlying}`
145797
+ });
145798
+ const degraded = () => ({
145799
+ ok: false,
145800
+ indeterminate: true,
145801
+ reason: `the UI tree read back empty and degraded while waiting for the screen to settle, so the screen was never observed \u2014 this is the reader reporting it could not see the app, not the app rendering nothing` + (blindHint === void 0 ? "" : `. ${blindHint}`)
145802
+ });
145803
+ if (readsSucceeded === 0) {
145804
+ if (treeErrorMessage !== void 0) return unreadable(treeErrorMessage);
145805
+ if (treeReadBlind) return degraded();
145806
+ return {
145807
+ ok: false,
145808
+ indeterminate: true,
145809
+ reason: `the tree source never answered within the step's ${timeoutMs}ms \u2014 raise this step's \`timeout:\` if it is merely slow (a tree read on a busy screen can take seconds), or repair it if it has stopped answering altogether`
145810
+ };
145811
+ }
145812
+ if (treeErrorMessage !== void 0 && darkReads > IDLE_TOLERATED_DARK_READS) {
145813
+ return unreadable(treeErrorMessage);
145814
+ }
145815
+ if (treeReadBlind && darkReads > IDLE_TOLERATED_DARK_READS) {
145816
+ return degraded();
145817
+ }
145818
+ if (lastRead === "timeout" && treeReadHung) {
145819
+ return {
145820
+ ok: false,
145821
+ indeterminate: true,
145822
+ reason: `the UI tree source answered and then stopped: a read given at least ${HUNG_TREE_READ_MS}ms never came back, so the screen could not be observed for the rest of the wait \u2014 check the app is still in the foreground and responding (a wedged app reads the same as a backgrounded one)`
145823
+ };
145824
+ }
145825
+ const blipNote = treeErrorMessage !== void 0 ? ` (the last read that came back failed: ${treeErrorMessage})` : "";
145826
+ if (!sawContent) {
145827
+ return {
145828
+ ok: true,
145829
+ warning: `the UI tree stayed empty for ${timeoutMs}ms \u2014 the screen never rendered content, so there was nothing to settle. If the screen is meant to render accessible content, this is where it did not; if it is a canvas or a video surface, it has none to read. Gate the next action on an element check either way.` + blipNote
145830
+ };
145831
+ }
145832
+ if (contentReads <= MIN_STILL_INTERVALS) {
145833
+ return {
145834
+ ok: true,
145835
+ warning: `the screen came back with content on ${contentReads} read${contentReads === 1 ? "" : "s"} in ${timeoutMs}ms, and a settle takes ${MIN_STILL_INTERVALS + 1} of them spanning ${MIN_STILL_INTERVALS} ${IDLE_POLL_MS}ms polls \u2014 so this step ended without ever being able to tell whether the screen was moving. Raise its \`timeout:\`, and gate the next action on a stable element rather than on stillness.` + blipNote
145836
+ };
145837
+ }
145838
+ if (treeSettledAtLastRead && !pixelsEverMoved && !comparedAPair) {
145839
+ return {
145840
+ ok: true,
145841
+ warning: `settled on the UI tree alone \u2014 this screen could not be screenshotted on enough polls to compare a pair of them, so animation that moves pixels without moving nodes (a push, a fade, a dismissing modal) was not waited out. Follow this with the element check the next step actually needs.`
145842
+ };
145843
+ }
145844
+ if (stillIntervals > 0) {
145845
+ const shortOf = stillIntervals < MIN_STILL_INTERVALS ? `a settle takes ${MIN_STILL_INTERVALS} consecutive ones \u2014 a single agreeing interval can be two samples either side of an animation's turning point` : `the hold asks for ${stableFor}ms of it`;
145846
+ return {
145847
+ ok: true,
145848
+ warning: `the screen was still for the last ${heldForMs}ms of the ${timeoutMs}ms wait, over ${stillIntervals} ${IDLE_POLL_MS}ms interval${stillIntervals === 1 ? "" : "s"} \u2014 but ${shortOf}, so the wait ran out before the settle could be confirmed rather than because the screen never stopped. Raise this step's \`timeout:\`, and gate the next action on a stable element rather than on stillness.` + blipNote
145849
+ };
145850
+ }
145851
+ return {
145852
+ ok: true,
145853
+ warning: `the screen never held still for ${MIN_STILL_INTERVALS} consecutive ${IDLE_POLL_MS}ms intervals${stableFor > 0 ? ` spanning ${stableFor}ms` : ""} within ${timeoutMs}ms, and was moving again on the last one, so this step went ahead without waiting it out. Either something on it never stops (a video, a looping animation, a carousel, live-updating text) or the screen never finished loading. Look at what is moving, and make sure the next action is gated on a stable element rather than on stillness.` + blipNote
145854
+ };
145855
+ }
145134
145856
  function assertReason(condition, selector, expectedText, textMatch, matches2) {
145135
145857
  const sel = describeSelector(selector);
145136
145858
  switch (condition) {
@@ -145156,15 +145878,15 @@ function assertReason(condition, selector, expectedText, textMatch, matches2) {
145156
145878
 
145157
145879
  // ../tool-server/src/tools/flows/flow-visual.ts
145158
145880
  var import_node_crypto8 = require("node:crypto");
145159
- var fs47 = __toESM(require("node:fs/promises"));
145881
+ var fs48 = __toESM(require("node:fs/promises"));
145160
145882
  var os12 = __toESM(require("node:os"));
145161
145883
  var path34 = __toESM(require("node:path"));
145162
- var import_pngjs3 = __toESM(require_png());
145884
+ var import_pngjs4 = __toESM(require_png());
145163
145885
 
145164
145886
  // ../tool-server/src/tools/screenshot-diff/screenshot-diff.ts
145165
145887
  var import_promises11 = __toESM(require("fs/promises"));
145166
145888
  var import_path9 = __toESM(require("path"));
145167
- var import_pngjs2 = __toESM(require_png());
145889
+ var import_pngjs3 = __toESM(require_png());
145168
145890
 
145169
145891
  // ../tool-server/src/tools/screenshot-diff/screenshot-diff-ocr.ts
145170
145892
  var import_child_process8 = require("child_process");
@@ -146709,7 +147431,7 @@ function formatPercentage(value) {
146709
147431
  }
146710
147432
 
146711
147433
  // ../tool-server/src/tools/screenshot-diff/screenshot-diff.ts
146712
- var MAX_RGB_DISTANCE_SQUARED = 255 * 255 * 3;
147434
+ var MAX_RGB_DISTANCE_SQUARED2 = 255 * 255 * 3;
146713
147435
  var DEFAULT_THRESHOLD = 0.1;
146714
147436
  var DEFAULT_IGNORE_TOP_NORMALIZED_Y = 0.06;
146715
147437
  var DEFAULT_REGION_MERGE_DISTANCE = 8;
@@ -146795,7 +147517,7 @@ async function diffPngFiles(options) {
146795
147517
  }
146796
147518
  function resolveDiffSettings(topMask) {
146797
147519
  return {
146798
- thresholdSquared: DEFAULT_THRESHOLD * DEFAULT_THRESHOLD * MAX_RGB_DISTANCE_SQUARED,
147520
+ thresholdSquared: DEFAULT_THRESHOLD * DEFAULT_THRESHOLD * MAX_RGB_DISTANCE_SQUARED2,
146799
147521
  ignoreTopNormalizedY: topMask === "none" ? 0 : DEFAULT_IGNORE_TOP_NORMALIZED_Y,
146800
147522
  joinGapPixels: DEFAULT_REGION_MERGE_DISTANCE,
146801
147523
  contextDiffScale: DEFAULT_CONTEXT_DIFF_SCALE
@@ -146900,7 +147622,7 @@ async function writeDiffArtifacts(params) {
146900
147622
  }
146901
147623
  async function decodePngFile(filePath) {
146902
147624
  const buffer = await import_promises11.default.readFile(filePath);
146903
- const png = import_pngjs2.PNG.sync.read(buffer);
147625
+ const png = import_pngjs3.PNG.sync.read(buffer);
146904
147626
  return {
146905
147627
  width: png.width,
146906
147628
  height: png.height,
@@ -146919,7 +147641,7 @@ async function analyzeScreenshotTextChangesSafely(options) {
146919
147641
  }
146920
147642
  }
146921
147643
  function buildContextDiff(params) {
146922
- const output = new import_pngjs2.PNG({ width: params.current.width, height: params.current.height });
147644
+ const output = new import_pngjs3.PNG({ width: params.current.width, height: params.current.height });
146923
147645
  for (let pixelIndex = 0; pixelIndex < params.mask.length; pixelIndex++) {
146924
147646
  const offset = pixelIndex * 4;
146925
147647
  if (params.mask[pixelIndex]) {
@@ -146944,7 +147666,7 @@ function luminanceFromOffset(data, offset) {
146944
147666
  }
146945
147667
  async function writePngFile(filePath, png) {
146946
147668
  await import_promises11.default.mkdir(import_path9.default.dirname(filePath), { recursive: true });
146947
- await import_promises11.default.writeFile(filePath, import_pngjs2.PNG.sync.write(png));
147669
+ await import_promises11.default.writeFile(filePath, import_pngjs3.PNG.sync.write(png));
146948
147670
  }
146949
147671
  function downscalePng(source, scale) {
146950
147672
  const resized = resizeDecodedPng(
@@ -146952,7 +147674,7 @@ function downscalePng(source, scale) {
146952
147674
  source.width * scale,
146953
147675
  source.height * scale
146954
147676
  );
146955
- const target = new import_pngjs2.PNG({ width: resized.width, height: resized.height });
147677
+ const target = new import_pngjs3.PNG({ width: resized.width, height: resized.height });
146956
147678
  resized.data.copy(target.data);
146957
147679
  return target;
146958
147680
  }
@@ -147217,7 +147939,7 @@ function roundToOne(value) {
147217
147939
  // ../tool-server/src/tools/flows/flow-visual.ts
147218
147940
  var DEFAULT_MAX_MISMATCH = 0.5;
147219
147941
  async function pngDimensions(file2) {
147220
- const fh = await fs47.open(file2, "r");
147942
+ const fh = await fs48.open(file2, "r");
147221
147943
  try {
147222
147944
  const buf = Buffer.alloc(24);
147223
147945
  await fh.read(buf, 0, 24, 0);
@@ -147241,18 +147963,18 @@ function baselineDir(flowsDir, flowName) {
147241
147963
  async function cleanupDiffDir(dir, keep) {
147242
147964
  try {
147243
147965
  if (!keep) {
147244
- await fs47.rm(dir, { recursive: true, force: true });
147966
+ await fs48.rm(dir, { recursive: true, force: true });
147245
147967
  return;
147246
147968
  }
147247
- for (const entry of await fs47.readdir(dir)) {
147969
+ for (const entry of await fs48.readdir(dir)) {
147248
147970
  const entryPath = path34.join(dir, entry);
147249
- if (entryPath !== keep) await fs47.rm(entryPath, { recursive: true, force: true });
147971
+ if (entryPath !== keep) await fs48.rm(entryPath, { recursive: true, force: true });
147250
147972
  }
147251
147973
  } catch {
147252
147974
  }
147253
147975
  }
147254
147976
  async function cropPngFile(src, dest, frame) {
147255
- const png = import_pngjs3.PNG.sync.read(await fs47.readFile(src));
147977
+ const png = import_pngjs4.PNG.sync.read(await fs48.readFile(src));
147256
147978
  const left = Math.max(0, Math.round(frame.x * png.width));
147257
147979
  const top = Math.max(0, Math.round(frame.y * png.height));
147258
147980
  const right = Math.min(png.width, Math.round((frame.x + frame.width) * png.width));
@@ -147260,9 +147982,9 @@ async function cropPngFile(src, dest, frame) {
147260
147982
  const w = right - left;
147261
147983
  const h = bottom - top;
147262
147984
  if (w < 1 || h < 1) return null;
147263
- const out = new import_pngjs3.PNG({ width: w, height: h });
147264
- import_pngjs3.PNG.bitblt(png, out, left, top, w, h, 0, 0);
147265
- await fs47.writeFile(dest, import_pngjs3.PNG.sync.write(out));
147985
+ const out = new import_pngjs4.PNG({ width: w, height: h });
147986
+ import_pngjs4.PNG.bitblt(png, out, left, top, w, h, 0, 0);
147987
+ await fs48.writeFile(dest, import_pngjs4.PNG.sync.write(out));
147266
147988
  return { w, h };
147267
147989
  }
147268
147990
  async function runSnapshot(env, opts) {
@@ -147319,7 +148041,7 @@ async function runSnapshot(env, opts) {
147319
148041
  };
147320
148042
  try {
147321
148043
  if (cropFrame !== void 0) {
147322
- cropDir = await fs47.mkdtemp(path34.join(os12.tmpdir(), "argent-flow-crop-"));
148044
+ cropDir = await fs48.mkdtemp(path34.join(os12.tmpdir(), "argent-flow-crop-"));
147323
148045
  const croppedPath = path34.join(cropDir, key2);
147324
148046
  const cropped = await cropPngFile(shot.image.hostPath, croppedPath, cropFrame);
147325
148047
  if (cropped === null) {
@@ -147333,10 +148055,10 @@ async function runSnapshot(env, opts) {
147333
148055
  }
147334
148056
  currentPath = croppedPath;
147335
148057
  }
147336
- const exists2 = await fs47.access(baselinePath).then(() => true).catch(() => false);
148058
+ const exists2 = await fs48.access(baselinePath).then(() => true).catch(() => false);
147337
148059
  if (opts.updateBaselines) {
147338
- await fs47.mkdir(dir, { recursive: true });
147339
- await fs47.copyFile(currentPath, baselinePath);
148060
+ await fs48.mkdir(dir, { recursive: true });
148061
+ await fs48.copyFile(currentPath, baselinePath);
147340
148062
  const baseline = await store.register(baselinePath, { mimeType: "image/png" });
147341
148063
  return {
147342
148064
  status: "pass",
@@ -147353,7 +148075,7 @@ async function runSnapshot(env, opts) {
147353
148075
  artifacts: { current: await currentArtifact() }
147354
148076
  };
147355
148077
  }
147356
- const outputDir = await fs47.mkdtemp(path34.join(os12.tmpdir(), "argent-flow-diff-"));
148078
+ const outputDir = await fs48.mkdtemp(path34.join(os12.tmpdir(), "argent-flow-diff-"));
147357
148079
  let keepInOutputDir;
147358
148080
  try {
147359
148081
  const result = await diffPngFiles({
@@ -147819,7 +148541,13 @@ reads "inside card inside list", each container's frame inside the next);
147819
148541
  (\`pinch: { on?, scale }\` \u2014 scale > 1 in, < 1 out; screen center when \`on\` is omitted); \`rotate\` is the
147820
148542
  two-finger rotation gesture (\`rotate: { on?, by }\` \u2014 degrees, + clockwise, within \xB13000\xB0; screen center
147821
148543
  when \`on\` is omitted; distinct from the \`rotate\` tool, which changes device orientation); \`await\` waits
147822
- for a UI condition; \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
148544
+ for a UI condition, and additionally takes the one condition that has no selector: \`idle: true\` waits
148545
+ until the screen has content and stops moving in BOTH the UI tree and the rendered pixels (it never
148546
+ fails a run \u2014 a screen that never settles passes carrying a \`warning\`, which is what makes it safe to
148547
+ persist; the one outcome that does stop the run is an \`error\` for a tree source that could not be read
148548
+ at all \u2014 a broken window rather than a verdict about the app, which leaves the run not-ok and skips
148549
+ every later step; it says nothing about WHICH screen settled \u2014 a dropped tap leaves the source screen
148550
+ perfectly idle \u2014 so pair it with the element check that names the destination); \`wait\` pauses for a fixed number of milliseconds; \`assert\` checks one now; \`snapshot\`
147823
148551
  diffs a screenshot \u2014 or, with \`cropOn: <selector>\`, one element's cropped region \u2014 against a stored
147824
148552
  baseline (a missing baseline fails the step \u2014 set updateBaselines to adopt the current screen; a
147825
148553
  cropped element whose size drifted fails on dimensions); \`echo\` annotates; \`run\` executes another flow
@@ -147863,7 +148591,7 @@ returns a notice with the prerequisite instead of running.`,
147863
148591
  );
147864
148592
  const canonicalPath = await canonicalFlowPath2(filePath);
147865
148593
  const flowsDir = path35.dirname(canonicalPath);
147866
- const flow = parseFlow(await fs48.readFile(canonicalPath, "utf8"));
148594
+ const flow = parseFlow(await fs49.readFile(canonicalPath, "utf8"));
147867
148595
  if (viaUpload) assertUploadSelfContained(flow);
147868
148596
  const rootEntry = { canonical: canonicalPath, display: flowName };
147869
148597
  if (flow.executionPrerequisite && !pinnedToChromium(params.device)) {
@@ -148007,7 +148735,7 @@ async function scanLeadingLaunch(flow, stack) {
148007
148735
  const supplied = path35.posix.basename(step.flow);
148008
148736
  const spelling = await classifyOnDiskSpelling(path35.dirname(spelled), supplied);
148009
148737
  if (spelling.state === "case_folded") return null;
148010
- nested = parseFlow(await fs48.readFile(canonical, "utf8"));
148738
+ nested = parseFlow(await fs49.readFile(canonical, "utf8"));
148011
148739
  } catch {
148012
148740
  return null;
148013
148741
  }
@@ -148034,7 +148762,7 @@ function launchTargetPlatform(launch, platform) {
148034
148762
  async function resolveAppPath(specPath, flowDir) {
148035
148763
  const lexical = path35.resolve(flowDir, specPath);
148036
148764
  try {
148037
- return await fs48.realpath(lexical);
148765
+ return await fs49.realpath(lexical);
148038
148766
  } catch {
148039
148767
  return lexical;
148040
148768
  }
@@ -148142,6 +148870,8 @@ function stepTarget(step) {
148142
148870
  case "await":
148143
148871
  case "assert":
148144
148872
  return conditionLabel(step, selectorLabel2);
148873
+ case "idle":
148874
+ return void 0;
148145
148875
  case "when":
148146
148876
  return step.condition.kind === "platform" ? `platform ${step.condition.platform}` : conditionLabel(step.condition, selectorLabel2);
148147
148877
  case "scroll-to": {
@@ -148320,10 +149050,10 @@ async function execWhenStep(state3, step, scope) {
148320
149050
  }
148321
149051
  async function canonicalFlowPath2(p) {
148322
149052
  try {
148323
- return await fs48.realpath(p);
149053
+ return await fs49.realpath(p);
148324
149054
  } catch {
148325
149055
  try {
148326
- return path35.join(await fs48.realpath(path35.dirname(p)), path35.basename(p));
149056
+ return path35.join(await fs49.realpath(path35.dirname(p)), path35.basename(p));
148327
149057
  } catch {
148328
149058
  return p;
148329
149059
  }
@@ -148371,7 +149101,7 @@ async function execRunStep(state3, step, scope) {
148371
149101
  }
148372
149102
  let fragment;
148373
149103
  try {
148374
- fragment = parseFlow(await fs48.readFile(canonical, "utf8"));
149104
+ fragment = parseFlow(await fs49.readFile(canonical, "utf8"));
148375
149105
  } catch (err) {
148376
149106
  return fail(`could not load fragment "${target}": ${errMsg2(err)}`);
148377
149107
  }
@@ -148411,13 +149141,22 @@ async function execLeafStep(state3, step, index, scope) {
148411
149141
  case "type":
148412
149142
  case "await":
148413
149143
  case "assert":
149144
+ case "idle":
148414
149145
  case "scroll-to":
148415
149146
  case "pinch":
148416
149147
  case "rotate": {
148417
149148
  try {
148418
149149
  const r = await runDirective(deviceEnv(state3), step);
148419
149150
  if (r.aborted) return { ...base, status: "skip", reason: r.reason };
148420
- return { ...base, status: r.ok ? "pass" : "fail", reason: r.reason };
149151
+ if (!r.ok && r.indeterminate && step.kind === "idle") {
149152
+ return { ...base, status: "error", reason: r.reason };
149153
+ }
149154
+ return {
149155
+ ...base,
149156
+ status: r.ok ? "pass" : "fail",
149157
+ reason: r.reason,
149158
+ ...r.warning !== void 0 ? { warning: r.warning } : {}
149159
+ };
148421
149160
  } catch (err) {
148422
149161
  return { ...base, status: "error", reason: errMsg2(err) };
148423
149162
  }
@@ -148486,6 +149225,17 @@ async function execLeafStep(state3, step, index, scope) {
148486
149225
  args
148487
149226
  };
148488
149227
  }
149228
+ if (isDebuggerNotConnectedResult(step.name, result)) {
149229
+ return {
149230
+ ...base,
149231
+ status: "fail",
149232
+ tool: step.name,
149233
+ reason: `debugger not connected (${result.reason}): ${result.detail} \u2014 ${result.guidance}`,
149234
+ result,
149235
+ outputHint,
149236
+ args
149237
+ };
149238
+ }
148489
149239
  return { ...base, status: "pass", tool: step.name, result, outputHint, args };
148490
149240
  } catch (err) {
148491
149241
  return { ...base, status: "error", tool: step.name, reason: errMsg2(err) };
@@ -148618,7 +149368,7 @@ async function resolveFlowSource(params, fileInput, flowPathInput) {
148618
149368
 
148619
149369
  // ../tool-server/src/tools/flows/flow-read-prerequisite.ts
148620
149370
  init_zod();
148621
- var fs49 = __toESM(require("node:fs/promises"));
149371
+ var fs50 = __toESM(require("node:fs/promises"));
148622
149372
  init_src();
148623
149373
  var zodSchema66 = external_exports.object({
148624
149374
  name: external_exports.string().optional().describe(
@@ -148688,7 +149438,7 @@ Fails if the flow file does not exist.`,
148688
149438
  ctx?.fileInputs?.flow_file,
148689
149439
  ctx?.fileInputs?.flow_path
148690
149440
  );
148691
- const flow = parseFlow(await fs49.readFile(filePath, "utf8"));
149441
+ const flow = parseFlow(await fs50.readFile(filePath, "utf8"));
148692
149442
  return {
148693
149443
  flow: flowName,
148694
149444
  executionPrerequisite: flow.executionPrerequisite
@@ -149036,13 +149786,13 @@ Fails if the workspacePath is not an absolute path or the directory cannot be ac
149036
149786
 
149037
149787
  // ../tool-server/src/tools/system/update-argent.ts
149038
149788
  var import_node_child_process29 = require("node:child_process");
149039
- var fs50 = __toESM(require("node:fs"));
149789
+ var fs51 = __toESM(require("node:fs"));
149040
149790
  var path36 = __toESM(require("node:path"));
149041
149791
  init_zod();
149042
149792
  function resolveCliEntry() {
149043
149793
  try {
149044
149794
  const entry = path36.join(__dirname, "cli.js");
149045
- return fs50.existsSync(entry) ? entry : null;
149795
+ return fs51.existsSync(entry) ? entry : null;
149046
149796
  } catch {
149047
149797
  return null;
149048
149798
  }
@@ -149056,14 +149806,14 @@ function classifyRunningInstall() {
149056
149806
  }
149057
149807
  let runningRoot;
149058
149808
  try {
149059
- runningRoot = fs50.realpathSync(path36.dirname(__dirname));
149809
+ runningRoot = fs51.realpathSync(path36.dirname(__dirname));
149060
149810
  } catch {
149061
149811
  return { kind: "global", projectRoot: null };
149062
149812
  }
149063
149813
  let dir = process.cwd();
149064
149814
  while (true) {
149065
149815
  try {
149066
- const nmReal = fs50.realpathSync(path36.join(dir, "node_modules"));
149816
+ const nmReal = fs51.realpathSync(path36.join(dir, "node_modules"));
149067
149817
  if (runningRoot === nmReal || runningRoot.startsWith(nmReal + path36.sep)) {
149068
149818
  return { kind: "local", projectRoot: dir };
149069
149819
  }
@@ -149078,14 +149828,14 @@ function classifyRunningInstall() {
149078
149828
  function findDeclaringProjectRoot(startDir) {
149079
149829
  let dir;
149080
149830
  try {
149081
- dir = fs50.realpathSync(startDir);
149831
+ dir = fs51.realpathSync(startDir);
149082
149832
  } catch {
149083
149833
  return null;
149084
149834
  }
149085
149835
  while (true) {
149086
- if (fs50.existsSync(path36.join(dir, ".argent", "install.json"))) return dir;
149836
+ if (fs51.existsSync(path36.join(dir, ".argent", "install.json"))) return dir;
149087
149837
  try {
149088
- const manifest = JSON.parse(fs50.readFileSync(path36.join(dir, "package.json"), "utf8"));
149838
+ const manifest = JSON.parse(fs51.readFileSync(path36.join(dir, "package.json"), "utf8"));
149089
149839
  if (manifest.devDependencies?.[PACKAGE_NAME2] || manifest.dependencies?.[PACKAGE_NAME2] || manifest.optionalDependencies?.[PACKAGE_NAME2]) {
149090
149840
  return dir;
149091
149841
  }
@@ -149124,7 +149874,7 @@ var updateArgentTool = {
149124
149874
  let projectRoot = null;
149125
149875
  let effectiveTarget = resolved;
149126
149876
  if (resolved === "local" || resolved === "both") {
149127
- const recordedRoot = running.projectRoot && fs50.existsSync(running.projectRoot) ? running.projectRoot : null;
149877
+ const recordedRoot = running.projectRoot && fs51.existsSync(running.projectRoot) ? running.projectRoot : null;
149128
149878
  projectRoot = recordedRoot ?? (running.kind === "local" ? findDeclaringProjectRoot(process.cwd()) : null);
149129
149879
  if (!projectRoot) {
149130
149880
  if (resolved === "local") {
@@ -149166,7 +149916,7 @@ var updateArgentTool = {
149166
149916
  if (targetsOnlyRunningInstall && installableVersion) {
149167
149917
  updateArgs.push("--version", installableVersion);
149168
149918
  }
149169
- const spawnRoot = projectRoot ?? (running.projectRoot && fs50.existsSync(running.projectRoot) ? running.projectRoot : null);
149919
+ const spawnRoot = projectRoot ?? (running.projectRoot && fs51.existsSync(running.projectRoot) ? running.projectRoot : null);
149170
149920
  if (spawnRoot) updateArgs.push("--project-root", spawnRoot);
149171
149921
  const cmd = cliEntry ? process.execPath : "argent";
149172
149922
  const args = cliEntry ? [cliEntry, ...updateArgs] : updateArgs;
@@ -150011,12 +150761,12 @@ function createRegistry() {
150011
150761
  registry2.registerTool(createTvRemoteTool(registry2));
150012
150762
  registry2.registerTool(createRunSequenceTool(registry2));
150013
150763
  registry2.registerTool(debuggerConnectTool);
150014
- registry2.registerTool(debuggerStatusTool);
150764
+ registry2.registerTool(createDebuggerStatusTool(registry2));
150015
150765
  registry2.registerTool(debuggerEvaluateTool);
150016
150766
  registry2.registerTool(debuggerReloadMetroTool);
150017
150767
  registry2.registerTool(debuggerComponentTreeTool);
150018
150768
  registry2.registerTool(debuggerInspectElementTool);
150019
- registry2.registerTool(debuggerLogRegistryTool);
150769
+ registry2.registerTool(createDebuggerLogRegistryTool(registry2));
150020
150770
  registry2.registerTool(networkLogsTool);
150021
150771
  registry2.registerTool(networkRequestTool);
150022
150772
  registry2.registerTool(createDescribeTool(registry2));
@@ -150086,7 +150836,7 @@ function probeArgentToolServer(host, port, timeoutMs = 500) {
150086
150836
  }
150087
150837
 
150088
150838
  // ../tool-server/src/utils/simulator-watcher.ts
150089
- var fs52 = __toESM(require("node:fs"));
150839
+ var fs53 = __toESM(require("node:fs"));
150090
150840
  var import_node_child_process30 = require("node:child_process");
150091
150841
  var import_node_util21 = require("node:util");
150092
150842
  var execFileAsync23 = (0, import_node_util21.promisify)(import_node_child_process30.execFile);
@@ -150113,7 +150863,7 @@ async function getBootedUdidsInSet(deviceSet) {
150113
150863
  async function getBootedUdids() {
150114
150864
  const sets = [
150115
150865
  null,
150116
- ...configuredAdditionalDeviceSets().filter((p) => fs52.existsSync(p))
150866
+ ...configuredAdditionalDeviceSets().filter((p) => fs53.existsSync(p))
150117
150867
  ];
150118
150868
  const perSet = await Promise.all(sets.map(getBootedUdidsInSet));
150119
150869
  return new Set(perSet.flatMap((s) => [...s]));
@@ -150166,7 +150916,7 @@ function startSimulatorWatcher(registry2) {
150166
150916
 
150167
150917
  // ../tool-server/src/utils/preview-window.ts
150168
150918
  var import_node_child_process31 = require("node:child_process");
150169
- var fs53 = __toESM(require("node:fs"));
150919
+ var fs54 = __toESM(require("node:fs"));
150170
150920
  var os14 = __toESM(require("node:os"));
150171
150921
  var path38 = __toESM(require("node:path"));
150172
150922
  function ensureLensAppBundle(electronBin) {
@@ -150183,18 +150933,18 @@ function ensureLensAppBundle(electronBin) {
150183
150933
  );
150184
150934
  const wrapperExec = path38.join(wrapperContents, "MacOS", "Electron");
150185
150935
  try {
150186
- if (fs53.realpathSync(wrapperExec) === fs53.realpathSync(electronBin)) return wrapperExec;
150936
+ if (fs54.realpathSync(wrapperExec) === fs54.realpathSync(electronBin)) return wrapperExec;
150187
150937
  } catch {
150188
150938
  }
150189
- fs53.rmSync(path38.dirname(wrapperContents), { recursive: true, force: true });
150190
- fs53.mkdirSync(path38.join(wrapperContents, "MacOS"), { recursive: true });
150191
- fs53.symlinkSync(path38.join(realContents, "Frameworks"), path38.join(wrapperContents, "Frameworks"));
150192
- fs53.symlinkSync(path38.join(realContents, "Resources"), path38.join(wrapperContents, "Resources"));
150193
- fs53.symlinkSync(electronBin, wrapperExec);
150939
+ fs54.rmSync(path38.dirname(wrapperContents), { recursive: true, force: true });
150940
+ fs54.mkdirSync(path38.join(wrapperContents, "MacOS"), { recursive: true });
150941
+ fs54.symlinkSync(path38.join(realContents, "Frameworks"), path38.join(wrapperContents, "Frameworks"));
150942
+ fs54.symlinkSync(path38.join(realContents, "Resources"), path38.join(wrapperContents, "Resources"));
150943
+ fs54.symlinkSync(electronBin, wrapperExec);
150194
150944
  const pkgInfo = path38.join(realContents, "PkgInfo");
150195
- if (fs53.existsSync(pkgInfo)) fs53.copyFileSync(pkgInfo, path38.join(wrapperContents, "PkgInfo"));
150945
+ if (fs54.existsSync(pkgInfo)) fs54.copyFileSync(pkgInfo, path38.join(wrapperContents, "PkgInfo"));
150196
150946
  const plist = path38.join(wrapperContents, "Info.plist");
150197
- fs53.copyFileSync(path38.join(realContents, "Info.plist"), plist);
150947
+ fs54.copyFileSync(path38.join(realContents, "Info.plist"), plist);
150198
150948
  const setPlist = (entry, value) => {
150199
150949
  try {
150200
150950
  (0, import_node_child_process31.execFileSync)("/usr/libexec/PlistBuddy", ["-c", `Set :${entry} ${value}`, plist]);
@@ -150236,7 +150986,7 @@ function createPreviewWindowManager(opts = {}) {
150236
150986
  const resolveMainScript = () => {
150237
150987
  if (opts.mainScript) return opts.mainScript;
150238
150988
  const bundled = path38.join(__dirname, "preview-window", "main.cjs");
150239
- if (fs53.existsSync(bundled)) return bundled;
150989
+ if (fs54.existsSync(bundled)) return bundled;
150240
150990
  return require.resolve("@argent/preview-window/dist/main.js");
150241
150991
  };
150242
150992
  const isAlive = (c) => c !== null && c.exitCode === null && !c.killed;
@@ -150547,6 +151297,7 @@ function start() {
150547
151297
  track("tool:fail", {
150548
151298
  tool: toolId,
150549
151299
  ...meta3.platform ? { platform: meta3.platform } : {},
151300
+ ...meta3.invalid_params?.length ? { invalid_params: meta3.invalid_params } : {},
150550
151301
  duration_ms: durationMs,
150551
151302
  ...signal,
150552
151303
  ...aiTelemetryFromMeta(meta3)