@avasapp/agent-bridge 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,22 @@
1
1
  // src/client/discover.ts
2
+ var within = (ms, signal) => signal ? AbortSignal.any([AbortSignal.timeout(ms), signal]) : AbortSignal.timeout(ms);
2
3
  var DEFAULT_METRO = "localhost:8081";
4
+ var stripScheme = (metro) => metro.replace(/^https?:\/\//, "").replace(/\/+$/, "");
5
+ function explicitMetro(metro) {
6
+ const given = metro ?? process.env.AGENT_BRIDGE_METRO;
7
+ return given ? stripScheme(given) : void 0;
8
+ }
3
9
  function metroHost(metro) {
4
- return (metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO).replace(/^https?:\/\//, "").replace(/\/+$/, "");
10
+ return stripScheme(metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO);
5
11
  }
6
- async function expoHostUri(metro) {
12
+ async function expoHostUri(metro, signal) {
7
13
  try {
8
14
  const res = await fetch(`http://${metro}/`, {
9
15
  headers: {
10
16
  "expo-platform": "ios",
11
17
  accept: "application/expo+json,application/json"
12
18
  },
13
- signal: AbortSignal.timeout(3e3)
19
+ signal: within(3e3, signal)
14
20
  });
15
21
  if (!res.ok) return null;
16
22
  const manifest = await res.json();
@@ -19,9 +25,9 @@ async function expoHostUri(metro) {
19
25
  return null;
20
26
  }
21
27
  }
22
- async function listCdpTargets(metro) {
28
+ async function listCdpTargets(metro, signal) {
23
29
  const res = await fetch(`http://${metro}/json/list`, {
24
- signal: AbortSignal.timeout(3e3)
30
+ signal: within(3e3, signal)
25
31
  });
26
32
  if (!res.ok)
27
33
  throw new Error(
@@ -61,21 +67,43 @@ function toAsciiJson(value) {
61
67
 
62
68
  // src/client/connection.ts
63
69
  import WebSocket from "ws";
64
- function openSocket(url, headers) {
65
- return new Promise((resolve, reject) => {
70
+ var OPEN_SOCKET_MS = 5e3;
71
+ var OpenGaveUp = class extends Error {
72
+ };
73
+ function openSocket(url, headers, signal, timeoutMs = OPEN_SOCKET_MS) {
74
+ return new Promise((resolve2, reject) => {
66
75
  const ws = new WebSocket(url, headers ? { headers } : void 0);
67
76
  let opened = false;
77
+ const giveUp = (why) => {
78
+ if (opened) return;
79
+ reject(new OpenGaveUp(`Could not open ${url}: ${why}`));
80
+ ws.terminate();
81
+ };
82
+ const timer = setTimeout(
83
+ () => giveUp(`no answer within ${timeoutMs} ms`),
84
+ timeoutMs
85
+ );
86
+ const onAbort = () => giveUp("aborted");
87
+ if (signal?.aborted) onAbort();
88
+ else signal?.addEventListener("abort", onAbort, { once: true });
89
+ const settled = () => {
90
+ clearTimeout(timer);
91
+ signal?.removeEventListener("abort", onAbort);
92
+ };
68
93
  ws.on("error", (event) => {
69
94
  if (opened) return;
95
+ settled();
70
96
  const message = event?.message ?? String(event);
71
97
  reject(new Error(`Could not open ${url}: ${message}`));
72
98
  });
73
99
  ws.once("close", () => {
100
+ settled();
74
101
  if (!opened) reject(new Error(`${url} closed before opening`));
75
102
  });
76
103
  ws.once("open", () => {
104
+ settled();
77
105
  opened = true;
78
- resolve(ws);
106
+ resolve2(ws);
79
107
  });
80
108
  });
81
109
  }
@@ -84,7 +112,7 @@ function createPending() {
84
112
  const waiting = /* @__PURE__ */ new Map();
85
113
  return {
86
114
  wait(id, tool, timeoutMs) {
87
- return new Promise((resolve, reject) => {
115
+ return new Promise((resolve2, reject) => {
88
116
  const timer = setTimeout(() => {
89
117
  waiting.delete(id);
90
118
  reject(new Error(`No reply to "${tool}" within ${timeoutMs} ms`));
@@ -92,7 +120,7 @@ function createPending() {
92
120
  waiting.set(id, (result) => {
93
121
  clearTimeout(timer);
94
122
  waiting.delete(id);
95
- resolve(result);
123
+ resolve2(result);
96
124
  });
97
125
  });
98
126
  },
@@ -109,10 +137,15 @@ function createPending() {
109
137
 
110
138
  // src/client/expo.ts
111
139
  var deviceLabel = (d) => `${d.name} (${d.platform}, ${d.deviceId})`;
112
- async function openBroadcast(metro, discoveryMs) {
113
- const ws = await openSocket(`ws://${metro}/expo-dev-plugins/broadcast`);
140
+ async function openBroadcast(metro, discoveryMs, signal) {
141
+ const ws = await openSocket(
142
+ `ws://${metro}/expo-dev-plugins/broadcast`,
143
+ void 0,
144
+ signal
145
+ );
114
146
  const devices = /* @__PURE__ */ new Map();
115
147
  const pending = createPending();
148
+ const announced = [];
116
149
  ws.on("message", (data, isBinary) => {
117
150
  if (isBinary) return;
118
151
  let frame;
@@ -124,7 +157,9 @@ async function openBroadcast(metro, discoveryMs) {
124
157
  if (frame.messageKey?.pluginName !== PLUGIN_NAME) return;
125
158
  if (frame.messageKey.method === "hello:reply") {
126
159
  const info = frame.payload;
160
+ const fresh = !devices.has(info.deviceId);
127
161
  devices.set(info.deviceId, info);
162
+ if (fresh) for (const listener of announced) listener(info);
128
163
  } else if (frame.messageKey.method === "result") {
129
164
  pending.settle(frame.payload);
130
165
  }
@@ -137,16 +172,32 @@ async function openBroadcast(metro, discoveryMs) {
137
172
  })
138
173
  );
139
174
  send("hello", {});
140
- await new Promise((r) => setTimeout(r, discoveryMs));
141
- return { ws, devices, pending, send };
175
+ await new Promise((resolve2, reject) => {
176
+ const onAbort = () => {
177
+ clearTimeout(timer);
178
+ ws.terminate();
179
+ reject(new Error("aborted while waiting for hello replies"));
180
+ };
181
+ const timer = setTimeout(() => {
182
+ signal?.removeEventListener("abort", onAbort);
183
+ resolve2();
184
+ }, discoveryMs);
185
+ if (signal?.aborted) onAbort();
186
+ else signal?.addEventListener("abort", onAbort, { once: true });
187
+ });
188
+ return { ws, devices, pending, send, announced };
142
189
  }
143
190
  async function listExpoDevices(metro, discoveryMs = 600) {
144
191
  const { ws, devices } = await openBroadcast(metro, discoveryMs);
145
192
  ws.close();
146
193
  return [...devices.values()];
147
194
  }
148
- async function connectExpo(metro, device, discoveryMs = 600) {
149
- const { ws, devices, pending, send } = await openBroadcast(metro, discoveryMs);
195
+ async function connectExpo(metro, device, discoveryMs = 600, signal) {
196
+ const { ws, devices, pending, send, announced } = await openBroadcast(
197
+ metro,
198
+ discoveryMs,
199
+ signal
200
+ );
150
201
  let info;
151
202
  try {
152
203
  info = pickOne([...devices.values()], device, deviceLabel);
@@ -154,10 +205,25 @@ async function connectExpo(metro, device, discoveryMs = 600) {
154
205
  ws.close();
155
206
  throw error;
156
207
  }
208
+ let superseded = null;
209
+ announced.push((next) => {
210
+ if (superseded || next.name !== info.name || next.platform !== info.platform)
211
+ return;
212
+ superseded = `${info.name} announced a new bridge (${next.deviceId}); the app reloaded`;
213
+ pending.failAll(superseded);
214
+ });
157
215
  return {
158
216
  transport: "expo",
159
217
  device: info,
160
218
  call(tool, args, timeoutMs) {
219
+ if (superseded)
220
+ return Promise.resolve({
221
+ id: "",
222
+ from: "",
223
+ ok: false,
224
+ error: superseded,
225
+ ms: 0
226
+ });
161
227
  const call = {
162
228
  id: newCallId(),
163
229
  tool,
@@ -177,17 +243,26 @@ import WebSocket2 from "ws";
177
243
  var CannotEvaluate = class extends Error {
178
244
  };
179
245
  var targetLabel = (t) => `${t.title}${t.deviceName ? ` [${t.deviceName}]` : ""}`;
180
- async function connectCdp(metro, device) {
181
- const target = pickOne(await listCdpTargets(metro), device, targetLabel);
182
- const hostUri = await expoHostUri(metro);
246
+ async function connectCdp(metro, device, signal) {
247
+ const target = pickOne(
248
+ await listCdpTargets(metro, signal),
249
+ device,
250
+ targetLabel
251
+ );
252
+ const hostUri = await expoHostUri(metro, signal);
183
253
  const port = metro.split(":")[1] ?? "8081";
184
254
  const origin = hostUri ? `http://${hostUri}` : `http://localhost:${port}`;
185
255
  const closedEarly = `Metro closed the debugger socket. Origin sent: ${origin}. Is that the host Metro advertises?`;
186
- const ws = await openSocket(target.webSocketDebuggerUrl, {
187
- Origin: origin
188
- }).catch(() => {
189
- throw new Error(closedEarly);
256
+ const ws = await openSocket(
257
+ target.webSocketDebuggerUrl,
258
+ { Origin: origin },
259
+ signal
260
+ ).catch((error) => {
261
+ throw error instanceof OpenGaveUp ? error : new Error(closedEarly);
190
262
  });
263
+ const dropOnAbort = () => ws.terminate();
264
+ if (signal?.aborted) dropOnAbort();
265
+ else signal?.addEventListener("abort", dropOnAbort, { once: true });
191
266
  let seq = 0;
192
267
  let answered = false;
193
268
  const commands = /* @__PURE__ */ new Map();
@@ -208,13 +283,13 @@ async function connectCdp(metro, device) {
208
283
  commands.clear();
209
284
  pending.failAll(reason);
210
285
  });
211
- const send = (method, params = {}) => new Promise((resolve, reject) => {
286
+ const send = (method, params = {}) => new Promise((resolve2, reject) => {
212
287
  if (ws.readyState !== WebSocket2.OPEN) {
213
288
  reject(new Error(answered ? "The debugger socket closed" : closedEarly));
214
289
  return;
215
290
  }
216
291
  const id = ++seq;
217
- commands.set(id, { resolve, reject });
292
+ commands.set(id, { resolve: resolve2, reject });
218
293
  ws.send(JSON.stringify({ id, method, params }));
219
294
  });
220
295
  const evaluate = async (expression) => {
@@ -230,25 +305,35 @@ async function connectCdp(metro, device) {
230
305
  }
231
306
  return message.result?.result?.value;
232
307
  };
233
- await send("Runtime.enable");
234
- await send("Runtime.addBinding", { name: CDP_REPLY_BINDING });
308
+ try {
309
+ await send("Runtime.enable");
310
+ await send("Runtime.addBinding", { name: CDP_REPLY_BINDING });
311
+ } catch (error) {
312
+ ws.terminate();
313
+ signal?.removeEventListener("abort", dropOnAbort);
314
+ throw error;
315
+ }
235
316
  let info;
236
317
  try {
237
318
  info = JSON.parse(
238
319
  String(await evaluate(`${CDP_GLOBAL}.info()`))
239
320
  );
240
321
  } catch (error) {
241
- ws.close();
322
+ ws.terminate();
323
+ signal?.removeEventListener("abort", dropOnAbort);
242
324
  throw new Error(
243
325
  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?`
244
326
  );
245
327
  }
328
+ signal?.removeEventListener("abort", dropOnAbort);
246
329
  return {
247
330
  transport: "cdp",
248
331
  device: { ...info, name: `${info.name} (${targetLabel(target)})` },
249
332
  async call(tool, args, timeoutMs) {
250
333
  const call = { id: newCallId(), tool, args };
251
334
  const reply = pending.wait(call.id, tool, timeoutMs);
335
+ reply.catch(() => {
336
+ });
252
337
  try {
253
338
  await evaluate(
254
339
  `${CDP_GLOBAL}.dispatch(${JSON.stringify(toAsciiJson(call))})`
@@ -269,25 +354,57 @@ async function connectCdp(metro, device) {
269
354
  }
270
355
 
271
356
  // src/client/open.ts
357
+ var OPEN_MS = 15e3;
358
+ var abortable = (work, signal) => new Promise((resolve2, reject) => {
359
+ const onAbort = () => reject(new Error("aborted"));
360
+ signal.addEventListener("abort", onAbort, { once: true });
361
+ work.then(resolve2, reject).finally(() => {
362
+ signal.removeEventListener("abort", onAbort);
363
+ });
364
+ });
365
+ function phase(options, run) {
366
+ const ms = options.timeoutMs ?? OPEN_MS;
367
+ const timeout = AbortSignal.timeout(ms);
368
+ const signal = options.signal ? AbortSignal.any([timeout, options.signal]) : timeout;
369
+ const attempt = run(signal);
370
+ attempt.then(
371
+ (late) => {
372
+ if (signal.aborted) late.close();
373
+ },
374
+ () => {
375
+ }
376
+ );
377
+ return abortable(attempt, signal).catch((error) => {
378
+ if (!signal.aborted) throw error;
379
+ throw new OpenGaveUp(
380
+ timeout.aborted ? `Timed out after ${ms} ms opening a connection to Metro` : "Connecting was aborted"
381
+ );
382
+ });
383
+ }
272
384
  async function openConnection(options = {}) {
273
385
  const metro = metroHost(options.metro);
274
386
  const want = options.transport ?? "auto";
275
387
  let expoError;
276
388
  if (want !== "cdp") {
277
389
  try {
278
- return await connectExpo(metro, options.device);
390
+ return await phase(
391
+ options,
392
+ (signal) => connectExpo(metro, options.device, void 0, signal)
393
+ );
279
394
  } catch (error) {
280
- if (want === "expo") throw error;
395
+ if (want === "expo" || options.signal?.aborted) throw error;
281
396
  expoError = error;
282
397
  }
283
398
  }
284
399
  try {
285
- return await connectCdp(metro, options.device);
400
+ return await phase(
401
+ options,
402
+ (signal) => connectCdp(metro, options.device, signal)
403
+ );
286
404
  } catch (error) {
287
405
  const expoNote = expoError ? ` (Expo socket: ${String(expoError)})` : "";
288
- throw new Error(
289
- `${error instanceof Error ? error.message : String(error)}${expoNote}`
290
- );
406
+ const text = `${error instanceof Error ? error.message : String(error)}${expoNote}`;
407
+ throw error instanceof OpenGaveUp ? new OpenGaveUp(text) : new Error(text);
291
408
  }
292
409
  }
293
410
 
@@ -331,6 +448,7 @@ function mergeScenarios(declared, extra) {
331
448
  return [...declared.filter((s) => !names.has(s.name)), ...extra];
332
449
  }
333
450
  var failedLogs = (error) => error instanceof AgentBridgeCallError ? error.logs : [];
451
+ var failedNotice = (error) => error instanceof AgentBridgeCallError ? error.notice : void 0;
334
452
  async function runFlow(bridge, flow, options = {}) {
335
453
  const print = options.print ?? console.log;
336
454
  const needs = mergeScenarios(flowScenarios(flow), options.scenarios ?? []);
@@ -338,31 +456,37 @@ async function runFlow(bridge, flow, options = {}) {
338
456
  let total = 0;
339
457
  let errors = 0;
340
458
  const t0 = performance.now();
341
- const report = (logs) => {
459
+ const notices = [];
460
+ const report = (logs, notice) => {
342
461
  errors += logs.length;
343
462
  for (const e of logs) print(` ${logLine(e)}`);
463
+ if (notice) {
464
+ notices.push(notice);
465
+ print(` ! ${notice}`);
466
+ }
344
467
  };
345
468
  const timed = async (tool, ...args) => {
346
469
  try {
347
470
  const result = await bridge.timed(tool, ...args);
348
- report(result.logs);
471
+ report(result.logs, result.notice);
349
472
  return result;
350
473
  } catch (error) {
351
- report(failedLogs(error));
474
+ report(failedLogs(error), failedNotice(error));
352
475
  throw error;
353
476
  }
354
477
  };
355
478
  const call = async (tool, ...args) => (await timed(tool, ...args)).value;
356
- const step = async (label, tool, ...args) => {
357
- const { value, ms, logs } = await bridge.timed(tool, ...args).catch((error) => {
358
- report(failedLogs(error));
479
+ const step = async (label, tool, ...given) => {
480
+ const args = given.length === 1 && Array.isArray(given[0]) ? given[0] : given;
481
+ const { value, ms, logs, notice } = await bridge.timed(tool, ...args).catch((error) => {
482
+ report(failedLogs(error), failedNotice(error));
359
483
  throw error;
360
484
  });
361
485
  total += ms;
362
486
  print(
363
487
  `${String(++n).padStart(2, "0")} ${label.padEnd(30)} ${ms.toFixed(1).padStart(7)} ms`
364
488
  );
365
- report(logs);
489
+ report(logs, notice);
366
490
  return value;
367
491
  };
368
492
  const restoreErrors = [];
@@ -399,19 +523,24 @@ async function runFlow(bridge, flow, options = {}) {
399
523
  }
400
524
  }
401
525
  print(
402
- `${n} steps, ${total.toFixed(1)} ms in calls, ${(performance.now() - t0).toFixed(0)} ms wall (${bridge.transport})${errors ? `, ${errors} error${errors === 1 ? "" : "s"}` : ""}`
526
+ `${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" : ""}`
403
527
  );
404
- return { steps: n, errors, restoreErrors };
528
+ return { steps: n, errors, restoreErrors, notices };
405
529
  }
406
530
 
407
531
  // src/client/session/client.ts
408
532
  import { createConnection } from "net";
409
533
  import { createInterface } from "readline";
410
534
 
535
+ // src/client/session/protocol.ts
536
+ var encodeLine = (message) => `${JSON.stringify(message).replace(/[\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16)}`)}
537
+ `;
538
+
411
539
  // src/client/session/state.ts
412
540
  import { createHash } from "crypto";
413
541
  import {
414
542
  chmodSync,
543
+ existsSync,
415
544
  lstatSync,
416
545
  mkdirSync,
417
546
  readFileSync,
@@ -420,7 +549,13 @@ import {
420
549
  writeFileSync
421
550
  } from "fs";
422
551
  import { tmpdir, userInfo } from "os";
423
- import { join } from "path";
552
+ import { dirname, join, resolve } from "path";
553
+ function projectRoot(from = process.cwd()) {
554
+ for (let dir = resolve(from); ; dir = dirname(dir)) {
555
+ if (existsSync(join(dir, "package.json"))) return dir;
556
+ if (dirname(dir) === dir) return resolve(from);
557
+ }
558
+ }
424
559
  var uid = () => process.getuid?.() ?? userInfo().username;
425
560
  function privateDir(dir) {
426
561
  mkdirSync(dir, { recursive: true, mode: 448 });
@@ -517,15 +652,21 @@ function pickSession(filter) {
517
652
  return named;
518
653
  }
519
654
  const device = filter.device?.toLowerCase();
655
+ const explicit = !!(filter.metro || device || filter.transport && filter.transport !== "auto");
656
+ const project = projectRoot();
520
657
  const matching = listSessions().filter(
521
- (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)
658
+ (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)
522
659
  );
523
- return matching.length === 1 ? matching[0] : null;
660
+ if (matching.length > 1)
661
+ throw new Error(
662
+ `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`
663
+ );
664
+ return matching[0] ?? null;
524
665
  }
525
666
 
526
667
  // src/client/session/client.ts
527
668
  function openSessionLink(state) {
528
- return new Promise((resolve, reject) => {
669
+ return new Promise((resolve2, reject) => {
529
670
  const socket = createConnection(state.socket);
530
671
  const waiting = /* @__PURE__ */ new Map();
531
672
  let seq = 0;
@@ -533,7 +674,7 @@ function openSessionLink(state) {
533
674
  const gone = `Session "${state.name}" closed the connection`;
534
675
  socket.once("connect", () => {
535
676
  open = true;
536
- resolve({
677
+ resolve2({
537
678
  request: (req) => new Promise((done, fail) => {
538
679
  if (socket.destroyed || socket.writableEnded) {
539
680
  fail(new Error(gone));
@@ -541,8 +682,7 @@ function openSessionLink(state) {
541
682
  }
542
683
  const id = ++seq;
543
684
  waiting.set(id, done);
544
- socket.write(`${JSON.stringify({ ...req, id })}
545
- `);
685
+ socket.write(encodeLine({ ...req, id }));
546
686
  }),
547
687
  close: () => socket.end()
548
688
  });
@@ -577,7 +717,7 @@ async function sessionRequest(state, req) {
577
717
  async function connectSession(options = {}) {
578
718
  const state = pickSession({
579
719
  name: options.name ?? process.env.AGENT_BRIDGE_SESSION,
580
- metro: metroHost(options.metro),
720
+ metro: explicitMetro(options.metro),
581
721
  device: options.device,
582
722
  transport: options.transport
583
723
  });
@@ -599,12 +739,32 @@ async function connectSession(options = {}) {
599
739
  args,
600
740
  timeoutMs: options.timeoutMs
601
741
  });
602
- if (res.error || !res.result) throw new Error(res.error ?? "No result");
742
+ if (res.error || !res.result) {
743
+ const why = res.error ?? "No result";
744
+ throw res.notice ? new AgentBridgeCallError(tool, why, [], res.notice) : new Error(why);
745
+ }
603
746
  const result = res.result;
604
747
  const logs = result.logs ?? [];
605
- if (!result.ok) throw new AgentBridgeCallError(tool, result.error, logs);
606
- const { id: _id, from: _from, ok: _ok, ms: appMs, value, ...extra } = result;
607
- return { ...extra, value, ms: res.ms ?? 0, appMs, logs };
748
+ if (!result.ok)
749
+ throw new AgentBridgeCallError(tool, result.error, logs, res.notice);
750
+ const {
751
+ id: _id,
752
+ from: _from,
753
+ ok: _ok,
754
+ ms: appMs,
755
+ value,
756
+ loadId: _loadId,
757
+ pending: _pending,
758
+ ...extra
759
+ } = result;
760
+ return {
761
+ ...extra,
762
+ value,
763
+ ms: res.ms ?? 0,
764
+ appMs,
765
+ logs,
766
+ ...res.notice && { notice: res.notice }
767
+ };
608
768
  };
609
769
  return {
610
770
  session: info.state ?? state,
@@ -619,14 +779,17 @@ async function connectSession(options = {}) {
619
779
 
620
780
  // src/client/index.ts
621
781
  var AgentBridgeCallError = class extends Error {
622
- constructor(tool, message, logs = []) {
623
- super(`${tool}: ${message}`);
782
+ constructor(tool, message, logs = [], notice) {
783
+ super(`${tool}: ${message}${notice ? `
784
+ Warning: ${notice}` : ""}`);
624
785
  this.tool = tool;
625
786
  this.logs = logs;
787
+ this.notice = notice;
626
788
  this.name = "AgentBridgeCallError";
627
789
  }
628
790
  tool;
629
791
  logs;
792
+ notice;
630
793
  };
631
794
  async function connect(options = {}) {
632
795
  const timeoutMs = options.timeoutMs ?? 1e4;
@@ -669,6 +832,7 @@ async function listDevices(options = {}) {
669
832
  }
670
833
 
671
834
  export {
835
+ explicitMetro,
672
836
  metroHost,
673
837
  RUNTIME_MARKER,
674
838
  openConnection,
@@ -676,6 +840,8 @@ export {
676
840
  flowScenarios,
677
841
  parseScenarioFlag,
678
842
  runFlow,
843
+ encodeLine,
844
+ projectRoot,
679
845
  stateDir,
680
846
  sessionFiles,
681
847
  checkName,