@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
|
@@ -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)
|
|
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:
|
|
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:
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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((
|
|
141
|
-
|
|
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(
|
|
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(
|
|
182
|
-
|
|
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(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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((
|
|
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
|
-
|
|
234
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
289
|
-
|
|
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
|
|
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, ...
|
|
357
|
-
const
|
|
358
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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)
|
|
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)
|
|
606
|
-
|
|
607
|
-
|
|
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,
|