@avasapp/agent-bridge 0.2.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 +220 -20
- 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-LBYZ5JHF.js → chunk-AJD3XTNT.js} +329 -46
- package/dist/cli.js +848 -98
- package/dist/client/index.cjs +311 -47
- package/dist/client/index.d.ts +64 -3
- package/dist/client/index.js +5 -3
- package/dist/expo/index.d.cts +1 -1
- package/dist/network/index.cjs +434 -66
- package/dist/network/index.d.cts +53 -3
- package/dist/noop/index.cjs +12 -0
- package/dist/noop/network.cjs +7 -1
- 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 +1067 -111
- package/dist/runtime/index.d.cts +51 -5
- package/dist/types-BmpykP1P.d.cts +137 -0
- package/entries/react-native-webview.cjs +9 -0
- package/package.json +18 -6
- package/skills/agent-bridge/SKILL.md +226 -8
- package/dist/types-C6DUUHnB.d.cts +0 -76
package/dist/client/index.cjs
CHANGED
|
@@ -34,23 +34,30 @@ __export(client_exports, {
|
|
|
34
34
|
connect: () => connect,
|
|
35
35
|
connectSession: () => connectSession,
|
|
36
36
|
listDevices: () => listDevices,
|
|
37
|
-
listSessions: () => listSessions
|
|
37
|
+
listSessions: () => listSessions,
|
|
38
|
+
runFlow: () => runFlow
|
|
38
39
|
});
|
|
39
40
|
module.exports = __toCommonJS(client_exports);
|
|
40
41
|
|
|
41
42
|
// src/client/discover.ts
|
|
43
|
+
var within = (ms, signal) => signal ? AbortSignal.any([AbortSignal.timeout(ms), signal]) : AbortSignal.timeout(ms);
|
|
42
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
|
+
}
|
|
43
50
|
function metroHost(metro) {
|
|
44
|
-
return (metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO)
|
|
51
|
+
return stripScheme(metro ?? process.env.AGENT_BRIDGE_METRO ?? DEFAULT_METRO);
|
|
45
52
|
}
|
|
46
|
-
async function expoHostUri(metro) {
|
|
53
|
+
async function expoHostUri(metro, signal) {
|
|
47
54
|
try {
|
|
48
55
|
const res = await fetch(`http://${metro}/`, {
|
|
49
56
|
headers: {
|
|
50
57
|
"expo-platform": "ios",
|
|
51
58
|
accept: "application/expo+json,application/json"
|
|
52
59
|
},
|
|
53
|
-
signal:
|
|
60
|
+
signal: within(3e3, signal)
|
|
54
61
|
});
|
|
55
62
|
if (!res.ok) return null;
|
|
56
63
|
const manifest = await res.json();
|
|
@@ -59,9 +66,9 @@ async function expoHostUri(metro) {
|
|
|
59
66
|
return null;
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
|
-
async function listCdpTargets(metro) {
|
|
69
|
+
async function listCdpTargets(metro, signal) {
|
|
63
70
|
const res = await fetch(`http://${metro}/json/list`, {
|
|
64
|
-
signal:
|
|
71
|
+
signal: within(3e3, signal)
|
|
65
72
|
});
|
|
66
73
|
if (!res.ok)
|
|
67
74
|
throw new Error(
|
|
@@ -100,21 +107,43 @@ function toAsciiJson(value) {
|
|
|
100
107
|
|
|
101
108
|
// src/client/connection.ts
|
|
102
109
|
var import_ws = __toESM(require("ws"), 1);
|
|
103
|
-
|
|
104
|
-
|
|
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) => {
|
|
105
115
|
const ws = new import_ws.default(url, headers ? { headers } : void 0);
|
|
106
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
|
+
};
|
|
107
133
|
ws.on("error", (event) => {
|
|
108
134
|
if (opened) return;
|
|
135
|
+
settled();
|
|
109
136
|
const message = event?.message ?? String(event);
|
|
110
137
|
reject(new Error(`Could not open ${url}: ${message}`));
|
|
111
138
|
});
|
|
112
139
|
ws.once("close", () => {
|
|
140
|
+
settled();
|
|
113
141
|
if (!opened) reject(new Error(`${url} closed before opening`));
|
|
114
142
|
});
|
|
115
143
|
ws.once("open", () => {
|
|
144
|
+
settled();
|
|
116
145
|
opened = true;
|
|
117
|
-
|
|
146
|
+
resolve2(ws);
|
|
118
147
|
});
|
|
119
148
|
});
|
|
120
149
|
}
|
|
@@ -123,7 +152,7 @@ function createPending() {
|
|
|
123
152
|
const waiting = /* @__PURE__ */ new Map();
|
|
124
153
|
return {
|
|
125
154
|
wait(id, tool, timeoutMs) {
|
|
126
|
-
return new Promise((
|
|
155
|
+
return new Promise((resolve2, reject) => {
|
|
127
156
|
const timer = setTimeout(() => {
|
|
128
157
|
waiting.delete(id);
|
|
129
158
|
reject(new Error(`No reply to "${tool}" within ${timeoutMs} ms`));
|
|
@@ -131,7 +160,7 @@ function createPending() {
|
|
|
131
160
|
waiting.set(id, (result) => {
|
|
132
161
|
clearTimeout(timer);
|
|
133
162
|
waiting.delete(id);
|
|
134
|
-
|
|
163
|
+
resolve2(result);
|
|
135
164
|
});
|
|
136
165
|
});
|
|
137
166
|
},
|
|
@@ -148,10 +177,15 @@ function createPending() {
|
|
|
148
177
|
|
|
149
178
|
// src/client/expo.ts
|
|
150
179
|
var deviceLabel = (d) => `${d.name} (${d.platform}, ${d.deviceId})`;
|
|
151
|
-
async function openBroadcast(metro, discoveryMs) {
|
|
152
|
-
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
|
+
);
|
|
153
186
|
const devices = /* @__PURE__ */ new Map();
|
|
154
187
|
const pending = createPending();
|
|
188
|
+
const announced = [];
|
|
155
189
|
ws.on("message", (data, isBinary) => {
|
|
156
190
|
if (isBinary) return;
|
|
157
191
|
let frame;
|
|
@@ -163,7 +197,9 @@ async function openBroadcast(metro, discoveryMs) {
|
|
|
163
197
|
if (frame.messageKey?.pluginName !== PLUGIN_NAME) return;
|
|
164
198
|
if (frame.messageKey.method === "hello:reply") {
|
|
165
199
|
const info = frame.payload;
|
|
200
|
+
const fresh = !devices.has(info.deviceId);
|
|
166
201
|
devices.set(info.deviceId, info);
|
|
202
|
+
if (fresh) for (const listener of announced) listener(info);
|
|
167
203
|
} else if (frame.messageKey.method === "result") {
|
|
168
204
|
pending.settle(frame.payload);
|
|
169
205
|
}
|
|
@@ -176,16 +212,32 @@ async function openBroadcast(metro, discoveryMs) {
|
|
|
176
212
|
})
|
|
177
213
|
);
|
|
178
214
|
send("hello", {});
|
|
179
|
-
await new Promise((
|
|
180
|
-
|
|
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 };
|
|
181
229
|
}
|
|
182
230
|
async function listExpoDevices(metro, discoveryMs = 600) {
|
|
183
231
|
const { ws, devices } = await openBroadcast(metro, discoveryMs);
|
|
184
232
|
ws.close();
|
|
185
233
|
return [...devices.values()];
|
|
186
234
|
}
|
|
187
|
-
async function connectExpo(metro, device, discoveryMs = 600) {
|
|
188
|
-
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
|
+
);
|
|
189
241
|
let info;
|
|
190
242
|
try {
|
|
191
243
|
info = pickOne([...devices.values()], device, deviceLabel);
|
|
@@ -193,10 +245,25 @@ async function connectExpo(metro, device, discoveryMs = 600) {
|
|
|
193
245
|
ws.close();
|
|
194
246
|
throw error;
|
|
195
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
|
+
});
|
|
196
255
|
return {
|
|
197
256
|
transport: "expo",
|
|
198
257
|
device: info,
|
|
199
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
|
+
});
|
|
200
267
|
const call = {
|
|
201
268
|
id: newCallId(),
|
|
202
269
|
tool,
|
|
@@ -216,17 +283,26 @@ var import_ws2 = __toESM(require("ws"), 1);
|
|
|
216
283
|
var CannotEvaluate = class extends Error {
|
|
217
284
|
};
|
|
218
285
|
var targetLabel = (t) => `${t.title}${t.deviceName ? ` [${t.deviceName}]` : ""}`;
|
|
219
|
-
async function connectCdp(metro, device) {
|
|
220
|
-
const target = pickOne(
|
|
221
|
-
|
|
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);
|
|
222
293
|
const port = metro.split(":")[1] ?? "8081";
|
|
223
294
|
const origin = hostUri ? `http://${hostUri}` : `http://localhost:${port}`;
|
|
224
295
|
const closedEarly = `Metro closed the debugger socket. Origin sent: ${origin}. Is that the host Metro advertises?`;
|
|
225
|
-
const ws = await openSocket(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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);
|
|
229
302
|
});
|
|
303
|
+
const dropOnAbort = () => ws.terminate();
|
|
304
|
+
if (signal?.aborted) dropOnAbort();
|
|
305
|
+
else signal?.addEventListener("abort", dropOnAbort, { once: true });
|
|
230
306
|
let seq = 0;
|
|
231
307
|
let answered = false;
|
|
232
308
|
const commands = /* @__PURE__ */ new Map();
|
|
@@ -247,13 +323,13 @@ async function connectCdp(metro, device) {
|
|
|
247
323
|
commands.clear();
|
|
248
324
|
pending.failAll(reason);
|
|
249
325
|
});
|
|
250
|
-
const send = (method, params = {}) => new Promise((
|
|
326
|
+
const send = (method, params = {}) => new Promise((resolve2, reject) => {
|
|
251
327
|
if (ws.readyState !== import_ws2.default.OPEN) {
|
|
252
328
|
reject(new Error(answered ? "The debugger socket closed" : closedEarly));
|
|
253
329
|
return;
|
|
254
330
|
}
|
|
255
331
|
const id = ++seq;
|
|
256
|
-
commands.set(id, { resolve, reject });
|
|
332
|
+
commands.set(id, { resolve: resolve2, reject });
|
|
257
333
|
ws.send(JSON.stringify({ id, method, params }));
|
|
258
334
|
});
|
|
259
335
|
const evaluate = async (expression) => {
|
|
@@ -269,25 +345,35 @@ async function connectCdp(metro, device) {
|
|
|
269
345
|
}
|
|
270
346
|
return message.result?.result?.value;
|
|
271
347
|
};
|
|
272
|
-
|
|
273
|
-
|
|
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
|
+
}
|
|
274
356
|
let info;
|
|
275
357
|
try {
|
|
276
358
|
info = JSON.parse(
|
|
277
359
|
String(await evaluate(`${CDP_GLOBAL}.info()`))
|
|
278
360
|
);
|
|
279
361
|
} catch (error) {
|
|
280
|
-
ws.
|
|
362
|
+
ws.terminate();
|
|
363
|
+
signal?.removeEventListener("abort", dropOnAbort);
|
|
281
364
|
throw new Error(
|
|
282
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?`
|
|
283
366
|
);
|
|
284
367
|
}
|
|
368
|
+
signal?.removeEventListener("abort", dropOnAbort);
|
|
285
369
|
return {
|
|
286
370
|
transport: "cdp",
|
|
287
371
|
device: { ...info, name: `${info.name} (${targetLabel(target)})` },
|
|
288
372
|
async call(tool, args, timeoutMs) {
|
|
289
373
|
const call = { id: newCallId(), tool, args };
|
|
290
374
|
const reply = pending.wait(call.id, tool, timeoutMs);
|
|
375
|
+
reply.catch(() => {
|
|
376
|
+
});
|
|
291
377
|
try {
|
|
292
378
|
await evaluate(
|
|
293
379
|
`${CDP_GLOBAL}.dispatch(${JSON.stringify(toAsciiJson(call))})`
|
|
@@ -308,37 +394,186 @@ async function connectCdp(metro, device) {
|
|
|
308
394
|
}
|
|
309
395
|
|
|
310
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
|
+
}
|
|
311
424
|
async function openConnection(options = {}) {
|
|
312
425
|
const metro = metroHost(options.metro);
|
|
313
426
|
const want = options.transport ?? "auto";
|
|
314
427
|
let expoError;
|
|
315
428
|
if (want !== "cdp") {
|
|
316
429
|
try {
|
|
317
|
-
return await
|
|
430
|
+
return await phase(
|
|
431
|
+
options,
|
|
432
|
+
(signal) => connectExpo(metro, options.device, void 0, signal)
|
|
433
|
+
);
|
|
318
434
|
} catch (error) {
|
|
319
|
-
if (want === "expo") throw error;
|
|
435
|
+
if (want === "expo" || options.signal?.aborted) throw error;
|
|
320
436
|
expoError = error;
|
|
321
437
|
}
|
|
322
438
|
}
|
|
323
439
|
try {
|
|
324
|
-
return await
|
|
440
|
+
return await phase(
|
|
441
|
+
options,
|
|
442
|
+
(signal) => connectCdp(metro, options.device, signal)
|
|
443
|
+
);
|
|
325
444
|
} catch (error) {
|
|
326
445
|
const expoNote = expoError ? ` (Expo socket: ${String(expoError)})` : "";
|
|
446
|
+
const text = `${error instanceof Error ? error.message : String(error)}${expoNote}`;
|
|
447
|
+
throw error instanceof OpenGaveUp ? new OpenGaveUp(text) : new Error(text);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/client/log-lines.ts
|
|
452
|
+
function logLine(entry) {
|
|
453
|
+
const where = entry.during ? ` during ${entry.during}` : entry.after ? ` after ${entry.after}` : "";
|
|
454
|
+
const first = entry.message.split("\n")[0] ?? "";
|
|
455
|
+
const text = first.length > 300 ? `${first.slice(0, 300)}...` : first;
|
|
456
|
+
return `! ${entry.level}${where}: ${text}`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/client/flow.ts
|
|
460
|
+
function flowScenarios(flow) {
|
|
461
|
+
const declared = [
|
|
462
|
+
...flow.scenario === void 0 ? [] : [flow.scenario],
|
|
463
|
+
...flow.scenarios ?? []
|
|
464
|
+
];
|
|
465
|
+
return declared.map((s) => {
|
|
466
|
+
if (typeof s === "string") return { name: s };
|
|
467
|
+
if (s && typeof s === "object" && typeof s.name === "string") return s;
|
|
468
|
+
throw new Error(
|
|
469
|
+
`A flow's scenario is a name or { name, options }, not ${JSON.stringify(s)}`
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function mergeScenarios(declared, extra) {
|
|
474
|
+
const names = new Set(extra.map((s) => s.name));
|
|
475
|
+
return [...declared.filter((s) => !names.has(s.name)), ...extra];
|
|
476
|
+
}
|
|
477
|
+
var failedLogs = (error) => error instanceof AgentBridgeCallError ? error.logs : [];
|
|
478
|
+
var failedNotice = (error) => error instanceof AgentBridgeCallError ? error.notice : void 0;
|
|
479
|
+
async function runFlow(bridge, flow, options = {}) {
|
|
480
|
+
const print = options.print ?? console.log;
|
|
481
|
+
const needs = mergeScenarios(flowScenarios(flow), options.scenarios ?? []);
|
|
482
|
+
let n = 0;
|
|
483
|
+
let total = 0;
|
|
484
|
+
let errors = 0;
|
|
485
|
+
const t0 = performance.now();
|
|
486
|
+
const notices = [];
|
|
487
|
+
const report = (logs, notice) => {
|
|
488
|
+
errors += logs.length;
|
|
489
|
+
for (const e of logs) print(` ${logLine(e)}`);
|
|
490
|
+
if (notice) {
|
|
491
|
+
notices.push(notice);
|
|
492
|
+
print(` ! ${notice}`);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
const timed = async (tool, ...args) => {
|
|
496
|
+
try {
|
|
497
|
+
const result = await bridge.timed(tool, ...args);
|
|
498
|
+
report(result.logs, result.notice);
|
|
499
|
+
return result;
|
|
500
|
+
} catch (error) {
|
|
501
|
+
report(failedLogs(error), failedNotice(error));
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
const call = async (tool, ...args) => (await timed(tool, ...args)).value;
|
|
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));
|
|
510
|
+
throw error;
|
|
511
|
+
});
|
|
512
|
+
total += ms;
|
|
513
|
+
print(
|
|
514
|
+
`${String(++n).padStart(2, "0")} ${label.padEnd(30)} ${ms.toFixed(1).padStart(7)} ms`
|
|
515
|
+
);
|
|
516
|
+
report(logs, notice);
|
|
517
|
+
return value;
|
|
518
|
+
};
|
|
519
|
+
const restoreErrors = [];
|
|
520
|
+
const applied = {};
|
|
521
|
+
if (needs.length && !bridge.tools().some((t) => t.name === "scenario.apply"))
|
|
327
522
|
throw new Error(
|
|
328
|
-
|
|
523
|
+
`The flow needs scenario "${needs[0].name}", but the app defines none: pass \`scenarios\` to useAgentBridge.`
|
|
329
524
|
);
|
|
525
|
+
try {
|
|
526
|
+
for (const { name, options: options2 } of needs) {
|
|
527
|
+
const args = options2 === void 0 ? [name] : [name, options2];
|
|
528
|
+
applied[name] = await step(`scenario ${name}`, "scenario.apply", ...args);
|
|
529
|
+
}
|
|
530
|
+
await flow.default({
|
|
531
|
+
bridge: { ...bridge, timed, call },
|
|
532
|
+
call,
|
|
533
|
+
step,
|
|
534
|
+
scenarios: applied
|
|
535
|
+
});
|
|
536
|
+
} finally {
|
|
537
|
+
if (needs.length) {
|
|
538
|
+
try {
|
|
539
|
+
const result = await step("restore", "bridge.restore");
|
|
540
|
+
for (const [name, value] of Object.entries(result ?? {})) {
|
|
541
|
+
const error = value?.error;
|
|
542
|
+
if (error !== void 0) restoreErrors.push(`${name}: ${String(error)}`);
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
restoreErrors.push(
|
|
546
|
+
`bridge.restore: ${error instanceof Error ? error.message : String(error)}`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
for (const e of restoreErrors) print(` ! restore failed: ${e}`);
|
|
550
|
+
}
|
|
330
551
|
}
|
|
552
|
+
print(
|
|
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" : ""}`
|
|
554
|
+
);
|
|
555
|
+
return { steps: n, errors, restoreErrors, notices };
|
|
331
556
|
}
|
|
332
557
|
|
|
333
558
|
// src/client/session/client.ts
|
|
334
559
|
var import_node_net = require("net");
|
|
335
560
|
var import_node_readline = require("readline");
|
|
336
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
|
+
|
|
337
566
|
// src/client/session/state.ts
|
|
338
567
|
var import_node_crypto = require("crypto");
|
|
339
568
|
var import_node_fs = require("fs");
|
|
340
569
|
var import_node_os = require("os");
|
|
341
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
|
+
}
|
|
342
577
|
var uid = () => process.getuid?.() ?? (0, import_node_os.userInfo)().username;
|
|
343
578
|
function privateDir(dir) {
|
|
344
579
|
(0, import_node_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
@@ -419,15 +654,21 @@ function pickSession(filter) {
|
|
|
419
654
|
return named;
|
|
420
655
|
}
|
|
421
656
|
const device = filter.device?.toLowerCase();
|
|
657
|
+
const explicit = !!(filter.metro || device || filter.transport && filter.transport !== "auto");
|
|
658
|
+
const project = projectRoot();
|
|
422
659
|
const matching = listSessions().filter(
|
|
423
|
-
(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)
|
|
424
661
|
);
|
|
425
|
-
|
|
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;
|
|
426
667
|
}
|
|
427
668
|
|
|
428
669
|
// src/client/session/client.ts
|
|
429
670
|
function openSessionLink(state) {
|
|
430
|
-
return new Promise((
|
|
671
|
+
return new Promise((resolve2, reject) => {
|
|
431
672
|
const socket = (0, import_node_net.createConnection)(state.socket);
|
|
432
673
|
const waiting = /* @__PURE__ */ new Map();
|
|
433
674
|
let seq = 0;
|
|
@@ -435,7 +676,7 @@ function openSessionLink(state) {
|
|
|
435
676
|
const gone = `Session "${state.name}" closed the connection`;
|
|
436
677
|
socket.once("connect", () => {
|
|
437
678
|
open = true;
|
|
438
|
-
|
|
679
|
+
resolve2({
|
|
439
680
|
request: (req) => new Promise((done, fail) => {
|
|
440
681
|
if (socket.destroyed || socket.writableEnded) {
|
|
441
682
|
fail(new Error(gone));
|
|
@@ -443,8 +684,7 @@ function openSessionLink(state) {
|
|
|
443
684
|
}
|
|
444
685
|
const id = ++seq;
|
|
445
686
|
waiting.set(id, done);
|
|
446
|
-
socket.write(
|
|
447
|
-
`);
|
|
687
|
+
socket.write(encodeLine({ ...req, id }));
|
|
448
688
|
}),
|
|
449
689
|
close: () => socket.end()
|
|
450
690
|
});
|
|
@@ -471,7 +711,7 @@ function openSessionLink(state) {
|
|
|
471
711
|
async function connectSession(options = {}) {
|
|
472
712
|
const state = pickSession({
|
|
473
713
|
name: options.name ?? process.env.AGENT_BRIDGE_SESSION,
|
|
474
|
-
metro:
|
|
714
|
+
metro: explicitMetro(options.metro),
|
|
475
715
|
device: options.device,
|
|
476
716
|
transport: options.transport
|
|
477
717
|
});
|
|
@@ -493,12 +733,32 @@ async function connectSession(options = {}) {
|
|
|
493
733
|
args,
|
|
494
734
|
timeoutMs: options.timeoutMs
|
|
495
735
|
});
|
|
496
|
-
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
|
+
}
|
|
497
740
|
const result = res.result;
|
|
498
741
|
const logs = result.logs ?? [];
|
|
499
|
-
if (!result.ok)
|
|
500
|
-
|
|
501
|
-
|
|
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
|
+
};
|
|
502
762
|
};
|
|
503
763
|
return {
|
|
504
764
|
session: info.state ?? state,
|
|
@@ -513,14 +773,17 @@ async function connectSession(options = {}) {
|
|
|
513
773
|
|
|
514
774
|
// src/client/index.ts
|
|
515
775
|
var AgentBridgeCallError = class extends Error {
|
|
516
|
-
constructor(tool, message, logs = []) {
|
|
517
|
-
super(`${tool}: ${message}`
|
|
776
|
+
constructor(tool, message, logs = [], notice) {
|
|
777
|
+
super(`${tool}: ${message}${notice ? `
|
|
778
|
+
Warning: ${notice}` : ""}`);
|
|
518
779
|
this.tool = tool;
|
|
519
780
|
this.logs = logs;
|
|
781
|
+
this.notice = notice;
|
|
520
782
|
this.name = "AgentBridgeCallError";
|
|
521
783
|
}
|
|
522
784
|
tool;
|
|
523
785
|
logs;
|
|
786
|
+
notice;
|
|
524
787
|
};
|
|
525
788
|
async function connect(options = {}) {
|
|
526
789
|
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
@@ -567,5 +830,6 @@ async function listDevices(options = {}) {
|
|
|
567
830
|
connect,
|
|
568
831
|
connectSession,
|
|
569
832
|
listDevices,
|
|
570
|
-
listSessions
|
|
833
|
+
listSessions,
|
|
834
|
+
runFlow
|
|
571
835
|
});
|