@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.
- package/README.md +149 -11
- package/dist/adapters/ably.cjs +22 -4
- package/dist/adapters/ably.d.cts +8 -1
- package/dist/adapters/expo-router.cjs +22 -1
- package/dist/adapters/expo-router.d.cts +5 -1
- package/dist/adapters/react-native-mmkv.cjs +1 -0
- package/dist/adapters/react-native-mmkv.d.cts +1 -1
- package/dist/adapters/react-native-webview.cjs +672 -0
- package/dist/adapters/react-native-webview.d.cts +79 -0
- package/dist/adapters/socket-io.cjs +3 -1
- package/dist/adapters/socket-io.d.cts +1 -1
- package/dist/adapters/tanstack-query.cjs +256 -17
- package/dist/adapters/tanstack-query.d.cts +1 -1
- package/dist/adapters/zustand.cjs +152 -12
- package/dist/adapters/zustand.d.cts +19 -3
- package/dist/{chunk-S2FYN2R5.js → chunk-AJD3XTNT.js} +223 -57
- package/dist/cli.js +817 -56
- package/dist/client/index.cjs +218 -56
- package/dist/client/index.d.ts +20 -2
- package/dist/client/index.js +1 -1
- package/dist/expo/index.d.cts +1 -1
- package/dist/network/index.cjs +254 -51
- package/dist/network/index.d.cts +9 -2
- package/dist/noop/index.cjs +3 -0
- package/dist/noop/react-native-webview.cjs +31 -0
- package/dist/realtime/index.cjs +2 -1
- package/dist/realtime/index.d.cts +3 -1
- package/dist/runtime/index.cjs +860 -100
- package/dist/runtime/index.d.cts +31 -5
- package/dist/{types-6kKQsLC1.d.cts → types-BmpykP1P.d.cts} +23 -1
- package/entries/react-native-webview.cjs +9 -0
- package/package.json +17 -6
- package/skills/agent-bridge/SKILL.md +196 -5
package/dist/client/index.cjs
CHANGED
|
@@ -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)
|
|
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:
|
|
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:
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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((
|
|
181
|
-
|
|
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(
|
|
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(
|
|
222
|
-
|
|
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(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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((
|
|
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
|
-
|
|
274
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
329
|
-
|
|
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
|
|
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, ...
|
|
384
|
-
const
|
|
385
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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)
|
|
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)
|
|
601
|
-
|
|
602
|
-
|
|
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;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/client/index.js
CHANGED
package/dist/expo/index.d.cts
CHANGED