@avasapp/agent-bridge 0.3.0 → 0.4.0

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.
@@ -40,18 +40,24 @@ __export(client_exports, {
40
40
  module.exports = __toCommonJS(client_exports);
41
41
 
42
42
  // src/client/discover.ts
43
+ var within = (ms, signal) => signal ? AbortSignal.any([AbortSignal.timeout(ms), signal]) : AbortSignal.timeout(ms);
43
44
  var DEFAULT_METRO = "localhost:8081";
45
+ var stripScheme = (metro) => metro.replace(/^https?:\/\//, "").replace(/\/+$/, "");
46
+ function explicitMetro(metro) {
47
+ const given = metro ?? process.env.AGENT_BRIDGE_METRO;
48
+ return given ? stripScheme(given) : void 0;
49
+ }
44
50
  function metroHost(metro) {
45
- return (metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO).replace(/^https?:\/\//, "").replace(/\/+$/, "");
51
+ return stripScheme(metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO);
46
52
  }
47
- async function expoHostUri(metro) {
53
+ async function expoHostUri(metro, signal) {
48
54
  try {
49
55
  const res = await fetch(`http://${metro}/`, {
50
56
  headers: {
51
57
  "expo-platform": "ios",
52
58
  accept: "application/expo+json,application/json"
53
59
  },
54
- signal: AbortSignal.timeout(3e3)
60
+ signal: within(3e3, signal)
55
61
  });
56
62
  if (!res.ok) return null;
57
63
  const manifest = await res.json();
@@ -60,9 +66,9 @@ async function expoHostUri(metro) {
60
66
  return null;
61
67
  }
62
68
  }
63
- async function listCdpTargets(metro) {
69
+ async function listCdpTargets(metro, signal) {
64
70
  const res = await fetch(`http://${metro}/json/list`, {
65
- signal: AbortSignal.timeout(3e3)
71
+ signal: within(3e3, signal)
66
72
  });
67
73
  if (!res.ok)
68
74
  throw new Error(
@@ -101,21 +107,43 @@ function toAsciiJson(value) {
101
107
 
102
108
  // src/client/connection.ts
103
109
  var import_ws = __toESM(require("ws"), 1);
104
- function openSocket(url, headers) {
105
- return new Promise((resolve, reject) => {
110
+ var OPEN_SOCKET_MS = 5e3;
111
+ var OpenGaveUp = class extends Error {
112
+ };
113
+ function openSocket(url, headers, signal, timeoutMs = OPEN_SOCKET_MS) {
114
+ return new Promise((resolve2, reject) => {
106
115
  const ws = new import_ws.default(url, headers ? { headers } : void 0);
107
116
  let opened = false;
117
+ const giveUp = (why) => {
118
+ if (opened) return;
119
+ reject(new OpenGaveUp(`Could not open ${url}: ${why}`));
120
+ ws.terminate();
121
+ };
122
+ const timer = setTimeout(
123
+ () => giveUp(`no answer within ${timeoutMs} ms`),
124
+ timeoutMs
125
+ );
126
+ const onAbort = () => giveUp("aborted");
127
+ if (signal?.aborted) onAbort();
128
+ else signal?.addEventListener("abort", onAbort, { once: true });
129
+ const settled = () => {
130
+ clearTimeout(timer);
131
+ signal?.removeEventListener("abort", onAbort);
132
+ };
108
133
  ws.on("error", (event) => {
109
134
  if (opened) return;
135
+ settled();
110
136
  const message = event?.message ?? String(event);
111
137
  reject(new Error(`Could not open ${url}: ${message}`));
112
138
  });
113
139
  ws.once("close", () => {
140
+ settled();
114
141
  if (!opened) reject(new Error(`${url} closed before opening`));
115
142
  });
116
143
  ws.once("open", () => {
144
+ settled();
117
145
  opened = true;
118
- resolve(ws);
146
+ resolve2(ws);
119
147
  });
120
148
  });
121
149
  }
@@ -124,7 +152,7 @@ function createPending() {
124
152
  const waiting = /* @__PURE__ */ new Map();
125
153
  return {
126
154
  wait(id, tool, timeoutMs) {
127
- return new Promise((resolve, reject) => {
155
+ return new Promise((resolve2, reject) => {
128
156
  const timer = setTimeout(() => {
129
157
  waiting.delete(id);
130
158
  reject(new Error(`No reply to "${tool}" within ${timeoutMs} ms`));
@@ -132,7 +160,7 @@ function createPending() {
132
160
  waiting.set(id, (result) => {
133
161
  clearTimeout(timer);
134
162
  waiting.delete(id);
135
- resolve(result);
163
+ resolve2(result);
136
164
  });
137
165
  });
138
166
  },
@@ -149,10 +177,15 @@ function createPending() {
149
177
 
150
178
  // src/client/expo.ts
151
179
  var deviceLabel = (d) => `${d.name} (${d.platform}, ${d.deviceId})`;
152
- async function openBroadcast(metro, discoveryMs) {
153
- const ws = await openSocket(`ws://${metro}/expo-dev-plugins/broadcast`);
180
+ async function openBroadcast(metro, discoveryMs, signal) {
181
+ const ws = await openSocket(
182
+ `ws://${metro}/expo-dev-plugins/broadcast`,
183
+ void 0,
184
+ signal
185
+ );
154
186
  const devices = /* @__PURE__ */ new Map();
155
187
  const pending = createPending();
188
+ const announced = [];
156
189
  ws.on("message", (data, isBinary) => {
157
190
  if (isBinary) return;
158
191
  let frame;
@@ -164,7 +197,9 @@ async function openBroadcast(metro, discoveryMs) {
164
197
  if (frame.messageKey?.pluginName !== PLUGIN_NAME) return;
165
198
  if (frame.messageKey.method === "hello:reply") {
166
199
  const info = frame.payload;
200
+ const fresh = !devices.has(info.deviceId);
167
201
  devices.set(info.deviceId, info);
202
+ if (fresh) for (const listener of announced) listener(info);
168
203
  } else if (frame.messageKey.method === "result") {
169
204
  pending.settle(frame.payload);
170
205
  }
@@ -177,16 +212,32 @@ async function openBroadcast(metro, discoveryMs) {
177
212
  })
178
213
  );
179
214
  send("hello", {});
180
- await new Promise((r) => setTimeout(r, discoveryMs));
181
- return { ws, devices, pending, send };
215
+ await new Promise((resolve2, reject) => {
216
+ const onAbort = () => {
217
+ clearTimeout(timer);
218
+ ws.terminate();
219
+ reject(new Error("aborted while waiting for hello replies"));
220
+ };
221
+ const timer = setTimeout(() => {
222
+ signal?.removeEventListener("abort", onAbort);
223
+ resolve2();
224
+ }, discoveryMs);
225
+ if (signal?.aborted) onAbort();
226
+ else signal?.addEventListener("abort", onAbort, { once: true });
227
+ });
228
+ return { ws, devices, pending, send, announced };
182
229
  }
183
230
  async function listExpoDevices(metro, discoveryMs = 600) {
184
231
  const { ws, devices } = await openBroadcast(metro, discoveryMs);
185
232
  ws.close();
186
233
  return [...devices.values()];
187
234
  }
188
- async function connectExpo(metro, device, discoveryMs = 600) {
189
- const { ws, devices, pending, send } = await openBroadcast(metro, discoveryMs);
235
+ async function connectExpo(metro, device, discoveryMs = 600, signal) {
236
+ const { ws, devices, pending, send, announced } = await openBroadcast(
237
+ metro,
238
+ discoveryMs,
239
+ signal
240
+ );
190
241
  let info;
191
242
  try {
192
243
  info = pickOne([...devices.values()], device, deviceLabel);
@@ -194,10 +245,25 @@ async function connectExpo(metro, device, discoveryMs = 600) {
194
245
  ws.close();
195
246
  throw error;
196
247
  }
248
+ let superseded = null;
249
+ announced.push((next) => {
250
+ if (superseded || next.name !== info.name || next.platform !== info.platform)
251
+ return;
252
+ superseded = `${info.name} announced a new bridge (${next.deviceId}); the app reloaded`;
253
+ pending.failAll(superseded);
254
+ });
197
255
  return {
198
256
  transport: "expo",
199
257
  device: info,
200
258
  call(tool, args, timeoutMs) {
259
+ if (superseded)
260
+ return Promise.resolve({
261
+ id: "",
262
+ from: "",
263
+ ok: false,
264
+ error: superseded,
265
+ ms: 0
266
+ });
201
267
  const call = {
202
268
  id: newCallId(),
203
269
  tool,
@@ -217,17 +283,26 @@ var import_ws2 = __toESM(require("ws"), 1);
217
283
  var CannotEvaluate = class extends Error {
218
284
  };
219
285
  var targetLabel = (t) => `${t.title}${t.deviceName ? ` [${t.deviceName}]` : ""}`;
220
- async function connectCdp(metro, device) {
221
- const target = pickOne(await listCdpTargets(metro), device, targetLabel);
222
- const hostUri = await expoHostUri(metro);
286
+ async function connectCdp(metro, device, signal) {
287
+ const target = pickOne(
288
+ await listCdpTargets(metro, signal),
289
+ device,
290
+ targetLabel
291
+ );
292
+ const hostUri = await expoHostUri(metro, signal);
223
293
  const port = metro.split(":")[1] ?? "8081";
224
294
  const origin = hostUri ? `http://${hostUri}` : `http://localhost:${port}`;
225
295
  const closedEarly = `Metro closed the debugger socket. Origin sent: ${origin}. Is that the host Metro advertises?`;
226
- const ws = await openSocket(target.webSocketDebuggerUrl, {
227
- Origin: origin
228
- }).catch(() => {
229
- throw new Error(closedEarly);
296
+ const ws = await openSocket(
297
+ target.webSocketDebuggerUrl,
298
+ { Origin: origin },
299
+ signal
300
+ ).catch((error) => {
301
+ throw error instanceof OpenGaveUp ? error : new Error(closedEarly);
230
302
  });
303
+ const dropOnAbort = () => ws.terminate();
304
+ if (signal?.aborted) dropOnAbort();
305
+ else signal?.addEventListener("abort", dropOnAbort, { once: true });
231
306
  let seq = 0;
232
307
  let answered = false;
233
308
  const commands = /* @__PURE__ */ new Map();
@@ -248,13 +323,13 @@ async function connectCdp(metro, device) {
248
323
  commands.clear();
249
324
  pending.failAll(reason);
250
325
  });
251
- const send = (method, params = {}) => new Promise((resolve, reject) => {
326
+ const send = (method, params = {}) => new Promise((resolve2, reject) => {
252
327
  if (ws.readyState !== import_ws2.default.OPEN) {
253
328
  reject(new Error(answered ? "The debugger socket closed" : closedEarly));
254
329
  return;
255
330
  }
256
331
  const id = ++seq;
257
- commands.set(id, { resolve, reject });
332
+ commands.set(id, { resolve: resolve2, reject });
258
333
  ws.send(JSON.stringify({ id, method, params }));
259
334
  });
260
335
  const evaluate = async (expression) => {
@@ -270,25 +345,35 @@ async function connectCdp(metro, device) {
270
345
  }
271
346
  return message.result?.result?.value;
272
347
  };
273
- await send("Runtime.enable");
274
- await send("Runtime.addBinding", { name: CDP_REPLY_BINDING });
348
+ try {
349
+ await send("Runtime.enable");
350
+ await send("Runtime.addBinding", { name: CDP_REPLY_BINDING });
351
+ } catch (error) {
352
+ ws.terminate();
353
+ signal?.removeEventListener("abort", dropOnAbort);
354
+ throw error;
355
+ }
275
356
  let info;
276
357
  try {
277
358
  info = JSON.parse(
278
359
  String(await evaluate(`${CDP_GLOBAL}.info()`))
279
360
  );
280
361
  } catch (error) {
281
- ws.close();
362
+ ws.terminate();
363
+ signal?.removeEventListener("abort", dropOnAbort);
282
364
  throw new Error(
283
365
  error instanceof CannotEvaluate ? `The debugger for ${targetLabel(target)} can't run code (no Runtime.evaluate, as in Expo Go on Android). Use --transport expo, or a dev build.` : `agent-bridge isn't running in ${targetLabel(target)}. Is useAgentBridge mounted in a dev build?`
284
366
  );
285
367
  }
368
+ signal?.removeEventListener("abort", dropOnAbort);
286
369
  return {
287
370
  transport: "cdp",
288
371
  device: { ...info, name: `${info.name} (${targetLabel(target)})` },
289
372
  async call(tool, args, timeoutMs) {
290
373
  const call = { id: newCallId(), tool, args };
291
374
  const reply = pending.wait(call.id, tool, timeoutMs);
375
+ reply.catch(() => {
376
+ });
292
377
  try {
293
378
  await evaluate(
294
379
  `${CDP_GLOBAL}.dispatch(${JSON.stringify(toAsciiJson(call))})`
@@ -309,25 +394,57 @@ async function connectCdp(metro, device) {
309
394
  }
310
395
 
311
396
  // src/client/open.ts
397
+ var OPEN_MS = 15e3;
398
+ var abortable = (work, signal) => new Promise((resolve2, reject) => {
399
+ const onAbort = () => reject(new Error("aborted"));
400
+ signal.addEventListener("abort", onAbort, { once: true });
401
+ work.then(resolve2, reject).finally(() => {
402
+ signal.removeEventListener("abort", onAbort);
403
+ });
404
+ });
405
+ function phase(options, run) {
406
+ const ms = options.timeoutMs ?? OPEN_MS;
407
+ const timeout = AbortSignal.timeout(ms);
408
+ const signal = options.signal ? AbortSignal.any([timeout, options.signal]) : timeout;
409
+ const attempt = run(signal);
410
+ attempt.then(
411
+ (late) => {
412
+ if (signal.aborted) late.close();
413
+ },
414
+ () => {
415
+ }
416
+ );
417
+ return abortable(attempt, signal).catch((error) => {
418
+ if (!signal.aborted) throw error;
419
+ throw new OpenGaveUp(
420
+ timeout.aborted ? `Timed out after ${ms} ms opening a connection to Metro` : "Connecting was aborted"
421
+ );
422
+ });
423
+ }
312
424
  async function openConnection(options = {}) {
313
425
  const metro = metroHost(options.metro);
314
426
  const want = options.transport ?? "auto";
315
427
  let expoError;
316
428
  if (want !== "cdp") {
317
429
  try {
318
- return await connectExpo(metro, options.device);
430
+ return await phase(
431
+ options,
432
+ (signal) => connectExpo(metro, options.device, void 0, signal)
433
+ );
319
434
  } catch (error) {
320
- if (want === "expo") throw error;
435
+ if (want === "expo" || options.signal?.aborted) throw error;
321
436
  expoError = error;
322
437
  }
323
438
  }
324
439
  try {
325
- return await connectCdp(metro, options.device);
440
+ return await phase(
441
+ options,
442
+ (signal) => connectCdp(metro, options.device, signal)
443
+ );
326
444
  } catch (error) {
327
445
  const expoNote = expoError ? ` (Expo socket: ${String(expoError)})` : "";
328
- throw new Error(
329
- `${error instanceof Error ? error.message : String(error)}${expoNote}`
330
- );
446
+ const text = `${error instanceof Error ? error.message : String(error)}${expoNote}`;
447
+ throw error instanceof OpenGaveUp ? new OpenGaveUp(text) : new Error(text);
331
448
  }
332
449
  }
333
450
 
@@ -358,6 +475,7 @@ function mergeScenarios(declared, extra) {
358
475
  return [...declared.filter((s) => !names.has(s.name)), ...extra];
359
476
  }
360
477
  var failedLogs = (error) => error instanceof AgentBridgeCallError ? error.logs : [];
478
+ var failedNotice = (error) => error instanceof AgentBridgeCallError ? error.notice : void 0;
361
479
  async function runFlow(bridge, flow, options = {}) {
362
480
  const print = options.print ?? console.log;
363
481
  const needs = mergeScenarios(flowScenarios(flow), options.scenarios ?? []);
@@ -365,31 +483,37 @@ async function runFlow(bridge, flow, options = {}) {
365
483
  let total = 0;
366
484
  let errors = 0;
367
485
  const t0 = performance.now();
368
- const report = (logs) => {
486
+ const notices = [];
487
+ const report = (logs, notice) => {
369
488
  errors += logs.length;
370
489
  for (const e of logs) print(` ${logLine(e)}`);
490
+ if (notice) {
491
+ notices.push(notice);
492
+ print(` ! ${notice}`);
493
+ }
371
494
  };
372
495
  const timed = async (tool, ...args) => {
373
496
  try {
374
497
  const result = await bridge.timed(tool, ...args);
375
- report(result.logs);
498
+ report(result.logs, result.notice);
376
499
  return result;
377
500
  } catch (error) {
378
- report(failedLogs(error));
501
+ report(failedLogs(error), failedNotice(error));
379
502
  throw error;
380
503
  }
381
504
  };
382
505
  const call = async (tool, ...args) => (await timed(tool, ...args)).value;
383
- const step = async (label, tool, ...args) => {
384
- const { value, ms, logs } = await bridge.timed(tool, ...args).catch((error) => {
385
- report(failedLogs(error));
506
+ const step = async (label, tool, ...given) => {
507
+ const args = given.length === 1 && Array.isArray(given[0]) ? given[0] : given;
508
+ const { value, ms, logs, notice } = await bridge.timed(tool, ...args).catch((error) => {
509
+ report(failedLogs(error), failedNotice(error));
386
510
  throw error;
387
511
  });
388
512
  total += ms;
389
513
  print(
390
514
  `${String(++n).padStart(2, "0")} ${label.padEnd(30)} ${ms.toFixed(1).padStart(7)} ms`
391
515
  );
392
- report(logs);
516
+ report(logs, notice);
393
517
  return value;
394
518
  };
395
519
  const restoreErrors = [];
@@ -426,20 +550,30 @@ async function runFlow(bridge, flow, options = {}) {
426
550
  }
427
551
  }
428
552
  print(
429
- `${n} steps, ${total.toFixed(1)} ms in calls, ${(performance.now() - t0).toFixed(0)} ms wall (${bridge.transport})${errors ? `, ${errors} error${errors === 1 ? "" : "s"}` : ""}`
553
+ `${n} steps, ${total.toFixed(1)} ms in calls, ${(performance.now() - t0).toFixed(0)} ms wall (${bridge.transport})${errors ? `, ${errors} error${errors === 1 ? "" : "s"}` : ""}${notices.length ? ", the app reloaded" : ""}`
430
554
  );
431
- return { steps: n, errors, restoreErrors };
555
+ return { steps: n, errors, restoreErrors, notices };
432
556
  }
433
557
 
434
558
  // src/client/session/client.ts
435
559
  var import_node_net = require("net");
436
560
  var import_node_readline = require("readline");
437
561
 
562
+ // src/client/session/protocol.ts
563
+ var encodeLine = (message) => `${JSON.stringify(message).replace(/[\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16)}`)}
564
+ `;
565
+
438
566
  // src/client/session/state.ts
439
567
  var import_node_crypto = require("crypto");
440
568
  var import_node_fs = require("fs");
441
569
  var import_node_os = require("os");
442
570
  var import_node_path = require("path");
571
+ function projectRoot(from = process.cwd()) {
572
+ for (let dir = (0, import_node_path.resolve)(from); ; dir = (0, import_node_path.dirname)(dir)) {
573
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) return dir;
574
+ if ((0, import_node_path.dirname)(dir) === dir) return (0, import_node_path.resolve)(from);
575
+ }
576
+ }
443
577
  var uid = () => process.getuid?.() ?? (0, import_node_os.userInfo)().username;
444
578
  function privateDir(dir) {
445
579
  (0, import_node_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
@@ -520,15 +654,21 @@ function pickSession(filter) {
520
654
  return named;
521
655
  }
522
656
  const device = filter.device?.toLowerCase();
657
+ const explicit = !!(filter.metro || device || filter.transport && filter.transport !== "auto");
658
+ const project = projectRoot();
523
659
  const matching = listSessions().filter(
524
- (s) => s.metro === filter.metro && (!device || s.device.name.toLowerCase().includes(device) || s.device.deviceId.toLowerCase().includes(device)) && (!filter.transport || filter.transport === "auto" || filter.transport === s.transport)
660
+ (s) => (explicit || s.project === project) && (!filter.metro || s.metro === filter.metro) && (!device || s.device.name.toLowerCase().includes(device) || s.device.deviceId.toLowerCase().includes(device)) && (!filter.transport || filter.transport === "auto" || filter.transport === s.transport)
525
661
  );
526
- return matching.length === 1 ? matching[0] : null;
662
+ if (matching.length > 1)
663
+ throw new Error(
664
+ `Several sessions match (${matching.map((s) => `${s.name}: ${s.device.name} on ${s.metro}`).join("; ")}); pick one by name (--session, or --name for session stop/status), or narrow with --metro/--device`
665
+ );
666
+ return matching[0] ?? null;
527
667
  }
528
668
 
529
669
  // src/client/session/client.ts
530
670
  function openSessionLink(state) {
531
- return new Promise((resolve, reject) => {
671
+ return new Promise((resolve2, reject) => {
532
672
  const socket = (0, import_node_net.createConnection)(state.socket);
533
673
  const waiting = /* @__PURE__ */ new Map();
534
674
  let seq = 0;
@@ -536,7 +676,7 @@ function openSessionLink(state) {
536
676
  const gone = `Session "${state.name}" closed the connection`;
537
677
  socket.once("connect", () => {
538
678
  open = true;
539
- resolve({
679
+ resolve2({
540
680
  request: (req) => new Promise((done, fail) => {
541
681
  if (socket.destroyed || socket.writableEnded) {
542
682
  fail(new Error(gone));
@@ -544,8 +684,7 @@ function openSessionLink(state) {
544
684
  }
545
685
  const id = ++seq;
546
686
  waiting.set(id, done);
547
- socket.write(`${JSON.stringify({ ...req, id })}
548
- `);
687
+ socket.write(encodeLine({ ...req, id }));
549
688
  }),
550
689
  close: () => socket.end()
551
690
  });
@@ -572,7 +711,7 @@ function openSessionLink(state) {
572
711
  async function connectSession(options = {}) {
573
712
  const state = pickSession({
574
713
  name: options.name ?? process.env.AGENT_BRIDGE_SESSION,
575
- metro: metroHost(options.metro),
714
+ metro: explicitMetro(options.metro),
576
715
  device: options.device,
577
716
  transport: options.transport
578
717
  });
@@ -594,12 +733,32 @@ async function connectSession(options = {}) {
594
733
  args,
595
734
  timeoutMs: options.timeoutMs
596
735
  });
597
- if (res.error || !res.result) throw new Error(res.error ?? "No result");
736
+ if (res.error || !res.result) {
737
+ const why = res.error ?? "No result";
738
+ throw res.notice ? new AgentBridgeCallError(tool, why, [], res.notice) : new Error(why);
739
+ }
598
740
  const result = res.result;
599
741
  const logs = result.logs ?? [];
600
- if (!result.ok) throw new AgentBridgeCallError(tool, result.error, logs);
601
- const { id: _id, from: _from, ok: _ok, ms: appMs, value, ...extra } = result;
602
- return { ...extra, value, ms: res.ms ?? 0, appMs, logs };
742
+ if (!result.ok)
743
+ throw new AgentBridgeCallError(tool, result.error, logs, res.notice);
744
+ const {
745
+ id: _id,
746
+ from: _from,
747
+ ok: _ok,
748
+ ms: appMs,
749
+ value,
750
+ loadId: _loadId,
751
+ pending: _pending,
752
+ ...extra
753
+ } = result;
754
+ return {
755
+ ...extra,
756
+ value,
757
+ ms: res.ms ?? 0,
758
+ appMs,
759
+ logs,
760
+ ...res.notice && { notice: res.notice }
761
+ };
603
762
  };
604
763
  return {
605
764
  session: info.state ?? state,
@@ -614,14 +773,17 @@ async function connectSession(options = {}) {
614
773
 
615
774
  // src/client/index.ts
616
775
  var AgentBridgeCallError = class extends Error {
617
- constructor(tool, message, logs = []) {
618
- super(`${tool}: ${message}`);
776
+ constructor(tool, message, logs = [], notice) {
777
+ super(`${tool}: ${message}${notice ? `
778
+ Warning: ${notice}` : ""}`);
619
779
  this.tool = tool;
620
780
  this.logs = logs;
781
+ this.notice = notice;
621
782
  this.name = "AgentBridgeCallError";
622
783
  }
623
784
  tool;
624
785
  logs;
786
+ notice;
625
787
  };
626
788
  async function connect(options = {}) {
627
789
  const timeoutMs = options.timeoutMs ?? 1e4;
@@ -20,6 +20,8 @@ type DeviceInfo = {
20
20
  name: string;
21
21
  platform: string;
22
22
  protocol: number;
23
+ /** Same as ResultMessage.loadId. Absent from older runtimes. */
24
+ loadId?: string;
23
25
  tools: ToolInfo[];
24
26
  };
25
27
 
@@ -33,6 +35,12 @@ type FlowScenario = string | {
33
35
  type FlowApi = {
34
36
  bridge: AgentBridge;
35
37
  call: AgentBridge['call'];
38
+ /**
39
+ * Times one call. Arguments are spread, `step('press', 'screen.press', 'Confirm')`.
40
+ * A single array is the whole argument list, as with `agent-bridge call`, so
41
+ * to pass one array as the only argument wrap it: `step('s', 'cart.setItems', [[a, b]])`.
42
+ * `call` does not do this: it passes its arguments as they are.
43
+ */
36
44
  step: (label: string, tool: string, ...args: unknown[]) => Promise<unknown>;
37
45
  /** What each declared scenario's apply returned, by name. */
38
46
  scenarios: Record<string, unknown>;
@@ -50,6 +58,8 @@ type FlowResult = {
50
58
  errors: number;
51
59
  /** Restorers that failed in the closing bridge.restore, with why. */
52
60
  restoreErrors: string[];
61
+ /** "app reloaded; N pending restores lost" warnings seen during the flow. */
62
+ notices: string[];
53
63
  };
54
64
  type RunFlowOptions = {
55
65
  /** Where step lines go. Default: console.log. */
@@ -79,6 +89,8 @@ type SessionState = {
79
89
  deviceId: string;
80
90
  platform: string;
81
91
  };
92
+ /** The project the session was started in (see `projectRoot`). */
93
+ project?: string;
82
94
  /** The --device filter the session was started with, reused to reconnect. */
83
95
  deviceFilter?: string;
84
96
  transport: TransportName;
@@ -91,7 +103,7 @@ type SessionState = {
91
103
  declare function listSessions(dir?: string): SessionState[];
92
104
 
93
105
  type SessionConnectOptions = {
94
- /** Session name. Defaults to $AGENT_BRIDGE_SESSION, else the only session on this Metro. */
106
+ /** Session name. Defaults to $AGENT_BRIDGE_SESSION, else the only session (matching --metro/--device when given). */
95
107
  name?: string;
96
108
  metro?: string;
97
109
  device?: string;
@@ -121,9 +133,13 @@ declare class AgentBridgeCallError extends Error {
121
133
  readonly tool: string;
122
134
  /** Errors the app attached to the failed reply. */
123
135
  readonly logs: LogEntry[];
136
+ /** Through a session: the app reloaded, so its pending restores are lost. */
137
+ readonly notice?: string | undefined;
124
138
  constructor(tool: string, message: string,
125
139
  /** Errors the app attached to the failed reply. */
126
- logs?: LogEntry[]);
140
+ logs?: LogEntry[],
141
+ /** Through a session: the app reloaded, so its pending restores are lost. */
142
+ notice?: string | undefined);
127
143
  }
128
144
  /** `logs`: errors the app recorded since its previous reply. */
129
145
  type Timed<T> = {
@@ -131,6 +147,8 @@ type Timed<T> = {
131
147
  ms: number;
132
148
  appMs: number;
133
149
  logs: LogEntry[];
150
+ /** Through a session: the app reloaded since the previous call, so its restores are lost. */
151
+ notice?: string;
134
152
  };
135
153
  type AgentBridge = {
136
154
  transport: TransportName;
@@ -5,7 +5,7 @@ import {
5
5
  listDevices,
6
6
  listSessions,
7
7
  runFlow
8
- } from "../chunk-S2FYN2R5.js";
8
+ } from "../chunk-AJD3XTNT.js";
9
9
  export {
10
10
  AgentBridgeCallError,
11
11
  connect,
@@ -1,4 +1,4 @@
1
- import { a as Transport } from '../types-6kKQsLC1.cjs';
1
+ import { a as Transport } from '../types-BmpykP1P.cjs';
2
2
 
3
3
  /**
4
4
  * Plain JSON over Expo's dev-tools plugin socket: no string evaluation, emoji