@avasapp/agent-bridge 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/adapters/expo-router.cjs +144 -0
  4. package/dist/adapters/expo-router.d.cts +38 -0
  5. package/dist/adapters/react-native-mmkv.cjs +102 -0
  6. package/dist/adapters/react-native-mmkv.d.cts +15 -0
  7. package/dist/adapters/tanstack-query.cjs +161 -0
  8. package/dist/adapters/tanstack-query.d.cts +10 -0
  9. package/dist/adapters/zustand.cjs +97 -0
  10. package/dist/adapters/zustand.d.cts +15 -0
  11. package/dist/chunk-UEWFQWCY.js +575 -0
  12. package/dist/cli.js +657 -0
  13. package/dist/client/index.cjs +568 -0
  14. package/dist/client/index.d.ts +112 -0
  15. package/dist/client/index.js +14 -0
  16. package/dist/expo/index.cjs +62 -0
  17. package/dist/expo/index.d.cts +9 -0
  18. package/dist/network/index.cjs +568 -0
  19. package/dist/network/index.d.cts +85 -0
  20. package/dist/noop/expo-router.cjs +26 -0
  21. package/dist/noop/expo.cjs +27 -0
  22. package/dist/noop/index.cjs +36 -0
  23. package/dist/noop/network.cjs +34 -0
  24. package/dist/noop/react-native-mmkv.cjs +26 -0
  25. package/dist/noop/tanstack-query.cjs +26 -0
  26. package/dist/noop/zustand.cjs +26 -0
  27. package/dist/runtime/index.cjs +933 -0
  28. package/dist/runtime/index.d.cts +69 -0
  29. package/dist/types-C6DUUHnB.d.cts +76 -0
  30. package/entries/expo-router.cjs +9 -0
  31. package/entries/expo.cjs +9 -0
  32. package/entries/index.cjs +9 -0
  33. package/entries/network.cjs +9 -0
  34. package/entries/react-native-mmkv.cjs +9 -0
  35. package/entries/tanstack-query.cjs +9 -0
  36. package/entries/zustand.cjs +9 -0
  37. package/package.json +120 -0
  38. package/skills/agent-bridge/SKILL.md +89 -0
@@ -0,0 +1,933 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/runtime/index.ts
21
+ var runtime_exports = {};
22
+ __export(runtime_exports, {
23
+ RUNTIME_MARKER: () => RUNTIME_MARKER,
24
+ cdpTransport: () => cdpTransport,
25
+ settle: () => settle,
26
+ startAgentBridge: () => startAgentBridge,
27
+ useAgentBridge: () => useAgentBridge
28
+ });
29
+ module.exports = __toCommonJS(runtime_exports);
30
+ var import_react = require("react");
31
+ var import_react_native2 = require("react-native");
32
+
33
+ // src/shared/protocol.ts
34
+ var PROTOCOL_VERSION = 1;
35
+ var RUNTIME_MARKER = "@avasapp/agent-bridge/runtime";
36
+ var CDP_GLOBAL = "__AGENT_BRIDGE__";
37
+ var CDP_REPLY_BINDING = "__agentBridgeReply";
38
+ function toAsciiJson(value) {
39
+ return JSON.stringify(value).replace(
40
+ /[\u007f-￿]/g,
41
+ (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`
42
+ );
43
+ }
44
+
45
+ // src/runtime/tools/bridge.ts
46
+ function bridgeTools(listTools) {
47
+ return {
48
+ "bridge.ping": {
49
+ description: "Round-trip check. Returns the app clock.",
50
+ run: () => ({ pong: true, at: Date.now() })
51
+ },
52
+ "bridge.tools": {
53
+ description: "Every tool this app exposes.",
54
+ run: listTools
55
+ }
56
+ };
57
+ }
58
+
59
+ // src/runtime/tools/logs.ts
60
+ function logTools(logs) {
61
+ return {
62
+ "bridge.logs": {
63
+ description: 'Errors and warnings the app logged, newest last. Options: {level: "error"|"warn"|"all", limit, clear}.',
64
+ run: (options = {}) => {
65
+ const entries = logs.read(options);
66
+ if (options.clear) logs.clear();
67
+ return entries;
68
+ }
69
+ }
70
+ };
71
+ }
72
+
73
+ // src/runtime/tools/restore.ts
74
+ var SUFFIX = ".restore";
75
+ var SELF = "bridge.restore";
76
+ function restoreTools(getTools) {
77
+ return {
78
+ [SELF]: {
79
+ description: "Undo what the agent changed: runs every *.restore tool in name order and returns each result or error.",
80
+ run: async () => {
81
+ const tools = getTools();
82
+ const results = {};
83
+ const names = Object.keys(tools).filter((name) => name.endsWith(SUFFIX) && name !== SELF).sort();
84
+ for (const name of names) {
85
+ const definition = tools[name];
86
+ const run = typeof definition === "function" ? definition : definition.run;
87
+ try {
88
+ results[name] = await run();
89
+ } catch (error) {
90
+ results[name] = {
91
+ error: error instanceof Error ? error.message : String(error)
92
+ };
93
+ }
94
+ }
95
+ return results;
96
+ }
97
+ }
98
+ };
99
+ }
100
+
101
+ // src/runtime/tools/screen.ts
102
+ var import_react_native = require("react-native");
103
+
104
+ // src/runtime/find-text-core.ts
105
+ var HOST_COMPONENT = 5;
106
+ var HOST_TEXT = 6;
107
+ function fiberRoots(hook2) {
108
+ if (!hook2?.getFiberRoots) return [];
109
+ const roots = [];
110
+ for (const id of hook2.renderers.keys()) {
111
+ for (const root of hook2.getFiberRoots(id)) roots.push(root.current);
112
+ }
113
+ return roots;
114
+ }
115
+ function measureHost(host) {
116
+ const node = host.stateNode;
117
+ if (node && "getBoundingClientRect" in node) {
118
+ const displayed = node.checkVisibility ? node.checkVisibility() : (node.getClientRects?.().length ?? 1) > 0;
119
+ if (!displayed) return null;
120
+ const r = node.getBoundingClientRect();
121
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
122
+ }
123
+ const ui = globalThis.nativeFabricUIManager;
124
+ const shadowNode = node?.node;
125
+ if (!ui || !shadowNode) return null;
126
+ let rect = null;
127
+ ui.measureInWindow(shadowNode, (x, y, width, height) => {
128
+ rect = { x, y, width, height };
129
+ });
130
+ return rect;
131
+ }
132
+ function textOf(fiber) {
133
+ if (fiber.tag === HOST_TEXT && typeof fiber.memoizedProps === "string") {
134
+ let host = fiber.return;
135
+ while (host && host.tag !== HOST_COMPONENT) host = host.return;
136
+ return { text: fiber.memoizedProps, host };
137
+ }
138
+ const children = fiber.memoizedProps?.children;
139
+ if (fiber.tag === HOST_COMPONENT && !fiber.child && (typeof children === "string" || typeof children === "number")) {
140
+ return { text: String(children), host: fiber };
141
+ }
142
+ return null;
143
+ }
144
+ function inActiveScreen(fiber) {
145
+ for (let f = fiber; f; f = f.return) {
146
+ if (f.tag !== HOST_COMPONENT) continue;
147
+ const props = f.memoizedProps;
148
+ if (props?.activityState === 0) return false;
149
+ }
150
+ return true;
151
+ }
152
+ function findTextInTree(roots, text, window, options = {}) {
153
+ const stack = [...roots];
154
+ const matches = [];
155
+ while (stack.length) {
156
+ const fiber = stack.pop();
157
+ const found = textOf(fiber);
158
+ if (found && (options.exact ? found.text === text : found.text.includes(text))) {
159
+ const rect = found.host ? measureHost(found.host) : null;
160
+ const onScreen = !!rect && inActiveScreen(fiber) && rect.width > 0 && rect.height > 0 && rect.x < window.width && rect.y < window.height && rect.x + rect.width > 0 && rect.y + rect.height > 0;
161
+ matches.push({ text: found.text, rect, onScreen });
162
+ }
163
+ if (fiber.child) stack.push(fiber.child);
164
+ if (fiber.sibling) stack.push(fiber.sibling);
165
+ }
166
+ return {
167
+ found: matches.length,
168
+ onScreen: matches.filter((m) => m.onScreen).length,
169
+ matches
170
+ };
171
+ }
172
+
173
+ // src/runtime/screen/elements.ts
174
+ var propsOf = (fiber) => fiber.memoizedProps && typeof fiber.memoizedProps === "object" ? fiber.memoizedProps : null;
175
+ var isFn = (v) => typeof v === "function";
176
+ var isInputProps = (p) => isFn(p.onChangeText) || isFn(p.onChange) && ("value" in p || "defaultValue" in p || "placeholder" in p);
177
+ var interesting = (p) => isFn(p.onPress) || isInputProps(p) || typeof p.testID === "string";
178
+ var ICON_GLYPHS = /[\uE000-\uF8FF]/g;
179
+ function firstHost(fiber) {
180
+ if (fiber.tag === HOST_COMPONENT) return fiber;
181
+ const stack = fiber.child ? [fiber.child] : [];
182
+ while (stack.length) {
183
+ const f = stack.pop();
184
+ if (f.tag === HOST_COMPONENT) return f;
185
+ if (f.sibling) stack.push(f.sibling);
186
+ if (f.child) stack.push(f.child);
187
+ }
188
+ return null;
189
+ }
190
+ function absorb(rec, fiber, p) {
191
+ const e = rec.element;
192
+ if (!rec.input && isInputProps(p)) rec.input = fiber;
193
+ if (!rec.press && isFn(p.onPress)) rec.press = fiber;
194
+ const fill = (key, v) => {
195
+ if (e[key] === void 0 && typeof v === "string") e[key] = v;
196
+ };
197
+ fill("testID", p.testID);
198
+ fill("label", p.accessibilityLabel ?? p["aria-label"]);
199
+ fill("role", p.accessibilityRole ?? p.role);
200
+ fill("placeholder", p.placeholder);
201
+ if (e.value === void 0 && ("value" in p || "defaultValue" in p)) {
202
+ const v = p.value ?? p.defaultValue;
203
+ if (v != null) e.value = String(v);
204
+ }
205
+ if (rec.maxLength === void 0 && typeof p.maxLength === "number")
206
+ rec.maxLength = p.maxLength;
207
+ if (p.editable === false || p.readOnly === true) e.editable = false;
208
+ const state = p.accessibilityState;
209
+ if (p.disabled === true || state?.disabled === true || p["aria-disabled"] === true)
210
+ e.disabled = true;
211
+ }
212
+ function isOnScreen(rect, window) {
213
+ return !!rect && rect.width > 0 && rect.height > 0 && rect.x < window.width && rect.y < window.height && rect.x + rect.width > 0 && rect.y + rect.height > 0;
214
+ }
215
+ var round = (r) => r && {
216
+ x: Math.round(r.x),
217
+ y: Math.round(r.y),
218
+ width: Math.round(r.width),
219
+ height: Math.round(r.height)
220
+ };
221
+ var handles = (control, p, name) => !!control && isFn(p[name]) && propsOf(control)?.[name] === p[name];
222
+ function sameControl(rec, p) {
223
+ for (let r = rec; r; r = r.parent) {
224
+ if (!r.press && !r.input) continue;
225
+ const same = handles(r.press, p, "onPress") || handles(r.input, p, "onChangeText") || handles(r.input, p, "onChange");
226
+ return same ? r : null;
227
+ }
228
+ return null;
229
+ }
230
+ function absorbChain(rec, fiber, host) {
231
+ for (let f = fiber; f; f = f.child) {
232
+ const p = propsOf(f);
233
+ if (p) absorb(rec, f, p);
234
+ if (f === host) break;
235
+ }
236
+ }
237
+ function collectElements(roots, window) {
238
+ const byHost = /* @__PURE__ */ new Map();
239
+ const order = [];
240
+ const recFor = (fiber, host, parent) => {
241
+ let rec = byHost.get(host);
242
+ if (rec) return rec;
243
+ rec = {
244
+ element: { kind: "view", rect: null },
245
+ onScreen: false,
246
+ fiber,
247
+ host,
248
+ press: null,
249
+ input: null,
250
+ parent,
251
+ parts: []
252
+ };
253
+ absorbChain(rec, fiber, host);
254
+ byHost.set(host, rec);
255
+ order.push(rec);
256
+ return rec;
257
+ };
258
+ const buttonOf = (rec) => {
259
+ for (let r = rec; r; r = r.parent) if (r.press) return r;
260
+ return null;
261
+ };
262
+ const stack = roots.map((fiber) => ({ fiber, rec: null }));
263
+ while (stack.length) {
264
+ const { fiber, rec: parentRec } = stack.pop();
265
+ if (fiber.sibling) stack.push({ fiber: fiber.sibling, rec: parentRec });
266
+ const p = propsOf(fiber);
267
+ if (fiber.tag === HOST_COMPONENT && p?.activityState === 0) continue;
268
+ let rec = parentRec;
269
+ if (p && interesting(p)) {
270
+ const host = firstHost(fiber);
271
+ const same = host && !byHost.has(host) ? sameControl(parentRec, p) : null;
272
+ if (host && same) {
273
+ byHost.set(host, same);
274
+ same.host = host;
275
+ absorbChain(same, fiber, host);
276
+ rec = same;
277
+ } else if (host) {
278
+ rec = recFor(fiber, host, parentRec);
279
+ absorb(rec, fiber, p);
280
+ }
281
+ }
282
+ const found = textOf(fiber);
283
+ if (found?.host) {
284
+ const owner = buttonOf(rec) ?? recFor(found.host, found.host, rec);
285
+ const last = owner.parts[owner.parts.length - 1];
286
+ if (last?.host === found.host) last.text += found.text;
287
+ else owner.parts.push({ host: found.host, text: found.text });
288
+ }
289
+ if (fiber.child) stack.push({ fiber: fiber.child, rec });
290
+ }
291
+ for (const rec of order) {
292
+ const e = rec.element;
293
+ const text = rec.parts.map((part) => part.text.replace(ICON_GLYPHS, "").replace(/\s+/g, " ").trim()).filter(Boolean).join(" ");
294
+ if (text) e.text = text;
295
+ e.kind = rec.input ? "input" : rec.press ? "button" : rec.parts.length ? "text" : "view";
296
+ if (e.kind === "input" && e.editable === void 0) e.editable = true;
297
+ const rect = measureHost(rec.host);
298
+ e.rect = round(rect);
299
+ rec.onScreen = isOnScreen(rect, window);
300
+ }
301
+ return order.filter(
302
+ (rec) => rec.element.kind !== "text" || rec.element.text !== void 0
303
+ );
304
+ }
305
+ function pressFiberOf(found) {
306
+ if (found.press) return found.press;
307
+ for (let f = found.fiber.return; f; f = f.return) {
308
+ const p = propsOf(f);
309
+ if (p && isFn(p.onPress)) return f;
310
+ }
311
+ return null;
312
+ }
313
+
314
+ // src/runtime/screen/targets.ts
315
+ function tiers(target) {
316
+ if (typeof target === "string") {
317
+ return [
318
+ (e) => e.testID === target,
319
+ (e) => e.label === target,
320
+ (e) => e.placeholder === target,
321
+ (e) => e.text === target,
322
+ (e) => !!e.text?.includes(target)
323
+ ];
324
+ }
325
+ const { testID, label, placeholder, text } = target;
326
+ const fields = (e) => (testID === void 0 || e.testID === testID) && (label === void 0 || e.label === label) && (placeholder === void 0 || e.placeholder === placeholder);
327
+ if (text === void 0) return [fields];
328
+ return [
329
+ (e) => fields(e) && e.text === text,
330
+ (e) => fields(e) && !!e.text?.includes(text)
331
+ ];
332
+ }
333
+ function matchTarget(found, target) {
334
+ for (const test of tiers(target)) {
335
+ const matches = found.filter((f) => test(f.element));
336
+ if (matches.length) return matches;
337
+ }
338
+ return [];
339
+ }
340
+ var indexOf = (target) => typeof target === "object" ? target.index : void 0;
341
+ var clip = (s) => s.length > 40 ? `${s.slice(0, 39)}\u2026` : s;
342
+ function describe(e) {
343
+ const bits = [e.kind];
344
+ if (e.testID) bits.push(`#${e.testID}`);
345
+ if (e.text) bits.push(JSON.stringify(clip(e.text)));
346
+ if (e.label && e.label !== e.text) bits.push(`label=${JSON.stringify(clip(e.label))}`);
347
+ if (e.placeholder) bits.push(`placeholder=${JSON.stringify(clip(e.placeholder))}`);
348
+ if (e.value !== void 0) bits.push(`value=${JSON.stringify(clip(e.value))}`);
349
+ if (e.disabled) bits.push("disabled");
350
+ return bits.join(" ");
351
+ }
352
+ var showTarget = (target) => JSON.stringify(target);
353
+ function onScreenSummary(found, max = 15) {
354
+ const on = found.filter((f) => f.onScreen);
355
+ if (!on.length) return "nothing";
356
+ const shown = on.slice(0, max).map((f) => describe(f.element));
357
+ if (on.length > max) shown.push(`+${on.length - max} more`);
358
+ return shown.join("; ");
359
+ }
360
+ function resolveTarget(found, target, prefer) {
361
+ let matches = matchTarget(
362
+ found.filter((f) => f.onScreen),
363
+ target
364
+ );
365
+ if (!matches.length) {
366
+ const hidden = matchTarget(found, target)[0];
367
+ if (hidden)
368
+ throw new Error(
369
+ `${showTarget(target)} matches ${describe(hidden.element)}, which is not on screen`
370
+ );
371
+ throw new Error(
372
+ `Nothing on screen matches ${showTarget(target)}. On screen: ${onScreenSummary(found)}`
373
+ );
374
+ }
375
+ const preferred = prefer ? matches.filter(prefer) : [];
376
+ if (preferred.length) matches = preferred;
377
+ const index = indexOf(target);
378
+ if (index !== void 0) {
379
+ const pick = matches[index];
380
+ if (!pick)
381
+ throw new Error(
382
+ `${showTarget(target)} has ${matches.length} match(es); index ${index} is out of range`
383
+ );
384
+ return pick;
385
+ }
386
+ if (matches.length > 1) {
387
+ const list = matches.slice(0, 10).map((f, i) => `${i}: ${describe(f.element)}`).join("; ");
388
+ throw new Error(
389
+ `${showTarget(target)} matches ${matches.length} elements; pass { index } or a narrower target. ${list}`
390
+ );
391
+ }
392
+ return matches[0];
393
+ }
394
+
395
+ // src/runtime/screen/actions.ts
396
+ var handler = (fiber, name) => {
397
+ const fn = propsOf(fiber)?.[name];
398
+ return typeof fn === "function" ? fn : null;
399
+ };
400
+ var noop = () => {
401
+ };
402
+ var event = (nativeEvent, target) => ({
403
+ nativeEvent,
404
+ target,
405
+ currentTarget: target,
406
+ timeStamp: Date.now(),
407
+ preventDefault: noop,
408
+ stopPropagation: noop,
409
+ isDefaultPrevented: () => false,
410
+ isPropagationStopped: () => false,
411
+ persist: noop
412
+ });
413
+ var eventCount = 0;
414
+ function fillInput(found, text, options = {}) {
415
+ const input = found.input;
416
+ if (!input) throw new Error(`${describe(found.element)} is not a text input`);
417
+ if (found.element.editable === false)
418
+ throw new Error(`${describe(found.element)} is not editable`);
419
+ const { maxLength } = found;
420
+ const value = maxLength === void 0 ? text : text.slice(0, Math.max(0, maxLength));
421
+ const target = { value };
422
+ handler(input, "onFocus")?.(event({ text: value }, target));
423
+ handler(input, "onChangeText")?.(value);
424
+ eventCount += 1;
425
+ handler(input, "onChange")?.(event({ text: value, eventCount }, target));
426
+ if (options.submit)
427
+ handler(input, "onSubmitEditing")?.(event({ text: value }, target));
428
+ handler(input, "onBlur")?.(event({ text: value }, target));
429
+ return value;
430
+ }
431
+ var center = (rect) => rect ? { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 } : { x: 0, y: 0 };
432
+ function pressElement(found) {
433
+ const fiber = pressFiberOf(found);
434
+ if (!fiber) throw new Error(`${describe(found.element)} has no onPress`);
435
+ if (found.element.disabled || propsOf(fiber)?.disabled === true)
436
+ throw new Error(`${describe(found.element)} is disabled`);
437
+ const { x, y } = center(found.element.rect);
438
+ const e = () => event({
439
+ locationX: found.element.rect ? found.element.rect.width / 2 : 0,
440
+ locationY: found.element.rect ? found.element.rect.height / 2 : 0,
441
+ pageX: x,
442
+ pageY: y,
443
+ timestamp: Date.now()
444
+ });
445
+ handler(fiber, "onPressIn")?.(e());
446
+ handler(fiber, "onPress")?.(e());
447
+ handler(fiber, "onPressOut")?.(e());
448
+ }
449
+
450
+ // src/runtime/screen/settle.ts
451
+ var STATE = /* @__PURE__ */ Symbol.for("@avasapp/agent-bridge/commits");
452
+ var UPDATE_LANES = (1 << 22) - 1;
453
+ var hook = () => globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
454
+ var now = () => performance.now();
455
+ function install() {
456
+ const h = hook();
457
+ if (!h) return null;
458
+ const current = h.onCommitFiberRoot;
459
+ const existing = current?.[STATE];
460
+ if (existing) return existing;
461
+ const state = { commits: 0, listeners: /* @__PURE__ */ new Set() };
462
+ const wrapped = function(...args) {
463
+ state.commits += 1;
464
+ for (const listener of state.listeners) {
465
+ try {
466
+ listener();
467
+ } catch {
468
+ }
469
+ }
470
+ return current?.apply(this, args);
471
+ };
472
+ Object.defineProperty(wrapped, STATE, { value: state });
473
+ h.onCommitFiberRoot = wrapped;
474
+ return state;
475
+ }
476
+ function onCommit(listener) {
477
+ const state = install();
478
+ state?.listeners.add(listener);
479
+ return () => state?.listeners.delete(listener);
480
+ }
481
+ function busy() {
482
+ const h = hook();
483
+ if (!h?.renderers || !h.getFiberRoots) return false;
484
+ for (const id of h.renderers.keys()) {
485
+ for (const root of h.getFiberRoots(id)) {
486
+ const pending = root.pendingLanes;
487
+ if (typeof pending !== "number") continue;
488
+ if (pending & ~(root.suspendedLanes ?? 0) & UPDATE_LANES) return true;
489
+ }
490
+ }
491
+ return false;
492
+ }
493
+ var frame = () => new Promise((resolve) => {
494
+ const raf = globalThis.requestAnimationFrame;
495
+ if (raf) raf(() => resolve());
496
+ else setTimeout(resolve, 16);
497
+ });
498
+ async function settle(options = {}) {
499
+ const maxMs = options.maxMs ?? 500;
500
+ const t0 = now();
501
+ const state = install();
502
+ const start = state?.commits ?? 0;
503
+ const result = () => ({
504
+ commits: (state?.commits ?? 0) - start,
505
+ ms: Math.round((now() - t0) * 100) / 100
506
+ });
507
+ await new Promise((resolve) => setTimeout(resolve, 0));
508
+ if (!state) return result();
509
+ let seen = state.commits;
510
+ if (seen === start && !busy()) return result();
511
+ while (now() - t0 < maxMs) {
512
+ await frame();
513
+ if (state.commits === seen && !busy()) break;
514
+ seen = state.commits;
515
+ }
516
+ return result();
517
+ }
518
+
519
+ // src/runtime/screen/wait-for.ts
520
+ var POLL_MS = 50;
521
+ function waitForTarget(collect, target, options = {}) {
522
+ const timeoutMs = options.timeoutMs ?? 5e3;
523
+ const t0 = performance.now();
524
+ const ms = () => Math.round((performance.now() - t0) * 100) / 100;
525
+ return new Promise((resolve, reject) => {
526
+ let queued = false;
527
+ let finished = false;
528
+ const finish = () => {
529
+ finished = true;
530
+ unsubscribe();
531
+ clearInterval(timer);
532
+ };
533
+ const check = () => {
534
+ queued = false;
535
+ if (finished) return;
536
+ try {
537
+ const found = collect();
538
+ const matches = matchTarget(
539
+ found.filter((f) => f.onScreen),
540
+ target
541
+ );
542
+ const hit = matches[indexOf(target) ?? 0];
543
+ if (options.gone ? !hit : hit) {
544
+ finish();
545
+ resolve(hit ? { ms: ms(), element: hit.element } : { ms: ms() });
546
+ } else if (performance.now() - t0 >= timeoutMs) {
547
+ finish();
548
+ const what = options.gone ? "to disappear" : "to appear";
549
+ reject(
550
+ new Error(
551
+ `Timed out after ${timeoutMs} ms waiting for ${showTarget(target)} ${what}. On screen: ${onScreenSummary(found)}`
552
+ )
553
+ );
554
+ }
555
+ } catch (error) {
556
+ finish();
557
+ reject(error);
558
+ }
559
+ };
560
+ const unsubscribe = onCommit(() => {
561
+ if (queued) return;
562
+ queued = true;
563
+ void Promise.resolve().then(check);
564
+ });
565
+ const timer = setInterval(check, POLL_MS);
566
+ check();
567
+ });
568
+ }
569
+
570
+ // src/runtime/screen/index.ts
571
+ function createScreen(env) {
572
+ const collect = () => collectElements(env.roots(), env.window());
573
+ const refresh = (found) => {
574
+ const node = found.host.stateNode;
575
+ const again = collect().find(
576
+ (f) => f.host === found.host || node != null && f.host.stateNode === node
577
+ );
578
+ return (again ?? found).element;
579
+ };
580
+ return {
581
+ snapshot(options = {}) {
582
+ const found = collect();
583
+ if (!options.all)
584
+ return { elements: found.filter((f) => f.onScreen).map((f) => f.element) };
585
+ return {
586
+ elements: found.map((f) => ({ ...f.element, onScreen: f.onScreen }))
587
+ };
588
+ },
589
+ async fill(target, text, options = {}) {
590
+ const found = resolveTarget(collect(), target, (f) => !!f.input);
591
+ const filled = fillInput(found, String(text), options);
592
+ await settle();
593
+ return { filled, element: refresh(found) };
594
+ },
595
+ async press(target) {
596
+ const found = resolveTarget(collect(), target, (f) => !!f.press);
597
+ pressElement(found);
598
+ await settle();
599
+ return found.element;
600
+ },
601
+ waitFor: (target, options) => waitForTarget(collect, target, options)
602
+ };
603
+ }
604
+
605
+ // src/runtime/tools/screen.ts
606
+ var devToolsHook = () => globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
607
+ function screenTools() {
608
+ const screen = createScreen({
609
+ roots: () => fiberRoots(devToolsHook()),
610
+ window: () => import_react_native.Dimensions.get("window")
611
+ });
612
+ return {
613
+ "screen.findText": {
614
+ description: "Find rendered text (substring, or exact with {exact:true}) and whether it is on screen. Ignores inactive tabs and screens.",
615
+ run: (text, options) => findTextInTree(
616
+ fiberRoots(devToolsHook()),
617
+ text,
618
+ import_react_native.Dimensions.get("window"),
619
+ options
620
+ )
621
+ },
622
+ "screen.snapshot": {
623
+ description: "Buttons, inputs, text and testID views on screen, with rects. {all:true} adds off-screen ones.",
624
+ run: (options) => screen.snapshot(options)
625
+ },
626
+ "screen.fill": {
627
+ description: "Type into an input by testID, label, placeholder or text, then wait for the render. {submit:true} also submits.",
628
+ run: (target, text, options) => screen.fill(target, text, options)
629
+ },
630
+ "screen.press": {
631
+ description: "Press a button by testID, label or text, then wait for the render.",
632
+ run: (target) => screen.press(target)
633
+ },
634
+ "screen.waitFor": {
635
+ description: "Wait until a target is on screen, or gone with {gone:true}. Default timeout 5000 ms.",
636
+ run: (target, options) => screen.waitFor(target, options)
637
+ }
638
+ };
639
+ }
640
+
641
+ // src/runtime/builtin-tools.ts
642
+ function builtinTools(listTools, getTools, logs) {
643
+ return {
644
+ ...bridgeTools(listTools),
645
+ ...logTools(logs),
646
+ ...restoreTools(getTools),
647
+ ...screenTools()
648
+ };
649
+ }
650
+
651
+ // src/runtime/cdp-transport.ts
652
+ function cdpTransport() {
653
+ return {
654
+ name: "cdp",
655
+ start(context) {
656
+ const g = globalThis;
657
+ const entry = {
658
+ info: () => toAsciiJson(context.info()),
659
+ dispatch: (payload) => {
660
+ const call = JSON.parse(payload);
661
+ void context.dispatch(call).then((result) => {
662
+ const reply = g[CDP_REPLY_BINDING];
663
+ if (typeof reply === "function") reply(toAsciiJson(result));
664
+ });
665
+ return call.id;
666
+ }
667
+ };
668
+ g[CDP_GLOBAL] = entry;
669
+ return () => {
670
+ if (g[CDP_GLOBAL] === entry) delete g[CDP_GLOBAL];
671
+ };
672
+ }
673
+ };
674
+ }
675
+
676
+ // src/runtime/logs.ts
677
+ var CAPACITY = 200;
678
+ var STACK_LINES = 10;
679
+ var MESSAGE_CHARS = 2e3;
680
+ function createLogCapture(now2 = Date.now) {
681
+ const buffer = [];
682
+ const running = [];
683
+ let seq = 0;
684
+ let sent = 0;
685
+ let lastTool;
686
+ return {
687
+ record(level, args) {
688
+ const entry = { level, ...formatArgs(args), at: now2() };
689
+ const during = running[running.length - 1];
690
+ if (during) entry.during = during;
691
+ else if (lastTool) entry.after = lastTool;
692
+ buffer.push({ seq: ++seq, entry });
693
+ if (buffer.length > CAPACITY) buffer.shift();
694
+ },
695
+ begin(tool) {
696
+ running.push(tool);
697
+ let done = false;
698
+ return () => {
699
+ if (done) return;
700
+ done = true;
701
+ running.splice(running.lastIndexOf(tool), 1);
702
+ lastTool = tool;
703
+ };
704
+ },
705
+ takeErrors() {
706
+ const since = sent;
707
+ sent = seq;
708
+ return buffer.filter((b) => b.seq > since && b.entry.level === "error").map((b) => b.entry);
709
+ },
710
+ read({ level = "all", limit } = {}) {
711
+ const entries = buffer.map((b) => b.entry).filter((e) => level === "all" || e.level === level);
712
+ return limit !== void 0 ? entries.slice(-limit) : entries;
713
+ },
714
+ clear() {
715
+ buffer.length = 0;
716
+ }
717
+ };
718
+ }
719
+ function formatArgs(args) {
720
+ const rest = [...args];
721
+ const parts = [];
722
+ if (typeof rest[0] === "string" && rest[0].includes("%")) {
723
+ const format = rest.shift();
724
+ parts.push(
725
+ format.replace(/%([sdifoOc%])/g, (match, type) => {
726
+ if (type === "%") return "%";
727
+ if (!rest.length) return match;
728
+ const arg = rest.shift();
729
+ if (type === "c") return "";
730
+ if (type === "d" || type === "i") return String(Math.trunc(Number(arg)));
731
+ if (type === "f") return String(Number(arg));
732
+ return describe2(arg);
733
+ })
734
+ );
735
+ }
736
+ parts.push(...rest.map(describe2));
737
+ const error = args.find((a) => a instanceof Error);
738
+ const message = parts.join(" ");
739
+ return {
740
+ message: message.length > MESSAGE_CHARS ? `${message.slice(0, MESSAGE_CHARS)}...` : message,
741
+ ...error?.stack ? { stack: trimStack(error.stack) } : {}
742
+ };
743
+ }
744
+ function describe2(value) {
745
+ if (typeof value === "string") return value;
746
+ if (value instanceof Error) return `${value.name}: ${value.message}`;
747
+ if (value === void 0 || typeof value === "function") return String(value);
748
+ try {
749
+ const json = JSON.stringify(value);
750
+ if (json === void 0) return String(value);
751
+ return json.length > 200 ? `${json.slice(0, 200)}...` : json;
752
+ } catch {
753
+ return String(value);
754
+ }
755
+ }
756
+ var trimStack = (stack) => stack.split("\n").slice(0, STACK_LINES).join("\n");
757
+ function installLogHooks(capture, env) {
758
+ let quiet = 0;
759
+ let active = true;
760
+ const record = (level, args) => {
761
+ if (!active || quiet) return;
762
+ quiet++;
763
+ try {
764
+ capture.record(level, args);
765
+ } catch {
766
+ } finally {
767
+ quiet--;
768
+ }
769
+ };
770
+ const { console } = env;
771
+ const originals = { error: console.error, warn: console.warn };
772
+ const wrappers = {
773
+ error: (...args) => {
774
+ record("error", args);
775
+ originals.error.apply(console, args);
776
+ },
777
+ warn: (...args) => {
778
+ record("warn", args);
779
+ originals.warn.apply(console, args);
780
+ }
781
+ };
782
+ console.error = wrappers.error;
783
+ console.warn = wrappers.warn;
784
+ const errorUtils = env.ErrorUtils;
785
+ const originalHandler = errorUtils?.getGlobalHandler();
786
+ const handler2 = (error, isFatal) => {
787
+ record("error", [error]);
788
+ quiet++;
789
+ try {
790
+ originalHandler?.(error, isFatal);
791
+ } finally {
792
+ quiet--;
793
+ }
794
+ };
795
+ if (errorUtils) errorUtils.setGlobalHandler(handler2);
796
+ const onError = (event2) => record("error", [event2.error ?? event2.message]);
797
+ const onRejection = (event2) => record("error", ["Unhandled promise rejection:", event2.reason]);
798
+ const listens = typeof env.addEventListener === "function" && typeof env.removeEventListener === "function";
799
+ if (listens) {
800
+ env.addEventListener?.("error", onError);
801
+ env.addEventListener?.("unhandledrejection", onRejection);
802
+ }
803
+ return () => {
804
+ active = false;
805
+ if (console.error === wrappers.error) console.error = originals.error;
806
+ if (console.warn === wrappers.warn) console.warn = originals.warn;
807
+ if (errorUtils && originalHandler && errorUtils.getGlobalHandler() === handler2)
808
+ errorUtils.setGlobalHandler(originalHandler);
809
+ if (listens) {
810
+ env.removeEventListener?.("error", onError);
811
+ env.removeEventListener?.("unhandledrejection", onRejection);
812
+ }
813
+ };
814
+ }
815
+ var STATE2 = /* @__PURE__ */ Symbol.for("@avasapp/agent-bridge/logs");
816
+ function startLogCapture(env = globalThis) {
817
+ const holder = env;
818
+ const state = holder[STATE2] ?? (holder[STATE2] = { capture: createLogCapture(), users: 0 });
819
+ if (state.users++ === 0) state.uninstall = installLogHooks(state.capture, env);
820
+ let stopped = false;
821
+ return {
822
+ capture: state.capture,
823
+ stop() {
824
+ if (stopped) return;
825
+ stopped = true;
826
+ if (--state.users === 0) {
827
+ state.uninstall?.();
828
+ state.uninstall = void 0;
829
+ }
830
+ }
831
+ };
832
+ }
833
+
834
+ // src/runtime/to-json.ts
835
+ function toJson(value) {
836
+ if (value === void 0) return null;
837
+ const seen = /* @__PURE__ */ new WeakSet();
838
+ const text = JSON.stringify(value, (_key, v) => {
839
+ if (typeof v === "bigint") return v.toString();
840
+ if (typeof v === "function" || typeof v === "symbol") return void 0;
841
+ if (v instanceof Error) return { name: v.name, message: v.message };
842
+ if (v instanceof Map) return Object.fromEntries(v);
843
+ if (v instanceof Set) return [...v];
844
+ if (v && typeof v === "object") {
845
+ if (seen.has(v)) return "[Circular]";
846
+ seen.add(v);
847
+ }
848
+ return v;
849
+ });
850
+ return text === void 0 ? null : JSON.parse(text);
851
+ }
852
+
853
+ // src/runtime/registry.ts
854
+ function unwrap(definition) {
855
+ return typeof definition === "function" ? { run: definition } : definition;
856
+ }
857
+ function createRegistry(getTools, logs) {
858
+ const list = () => Object.entries(getTools()).map(([name, definition]) => ({
859
+ name,
860
+ description: unwrap(definition).description
861
+ })).sort((a, b) => a.name.localeCompare(b.name));
862
+ async function dispatch(call, from) {
863
+ const end = logs?.begin(call.tool);
864
+ const result = await run(call, from).finally(end);
865
+ const errors = logs?.takeErrors();
866
+ return errors?.length ? { ...result, logs: errors } : result;
867
+ }
868
+ async function run(call, from) {
869
+ const t0 = performance.now();
870
+ const ms = () => Math.round((performance.now() - t0) * 100) / 100;
871
+ try {
872
+ const definition = getTools()[call.tool];
873
+ if (!definition) {
874
+ const known = list().map((t) => t.name).join(", ");
875
+ throw new Error(`Unknown tool "${call.tool}". Known: ${known}`);
876
+ }
877
+ const value = await unwrap(definition).run(...call.args ?? []);
878
+ return { id: call.id, from, ok: true, value: toJson(value), ms: ms() };
879
+ } catch (error) {
880
+ const message = error instanceof Error ? error.message : String(error);
881
+ return { id: call.id, from, ok: false, error: message, ms: ms() };
882
+ }
883
+ }
884
+ return { list, dispatch };
885
+ }
886
+
887
+ // src/runtime/index.ts
888
+ var randomId = () => Math.random().toString(36).slice(2, 10);
889
+ function startAgentBridge(options = {}) {
890
+ const deviceId = randomId();
891
+ const userTools = () => typeof options.tools === "function" ? options.tools() : options.tools ?? {};
892
+ const logs = startLogCapture();
893
+ const allTools = () => ({
894
+ ...builtinTools(() => registry.list(), allTools, logs.capture),
895
+ ...userTools()
896
+ });
897
+ const registry = createRegistry(allTools, logs.capture);
898
+ const info = () => ({
899
+ deviceId,
900
+ name: options.name ?? import_react_native2.Platform.OS,
901
+ platform: import_react_native2.Platform.OS,
902
+ protocol: PROTOCOL_VERSION,
903
+ tools: registry.list()
904
+ });
905
+ const context = {
906
+ info,
907
+ dispatch: (call) => registry.dispatch(call, deviceId)
908
+ };
909
+ const stops = (options.transports ?? [cdpTransport()]).map(
910
+ (t) => t.start(context)
911
+ );
912
+ return () => {
913
+ for (const stop of stops) stop();
914
+ logs.stop();
915
+ };
916
+ }
917
+ function useAgentBridge(options = {}) {
918
+ const latest = (0, import_react.useRef)(options);
919
+ (0, import_react.useEffect)(() => {
920
+ latest.current = options;
921
+ });
922
+ (0, import_react.useEffect)(() => {
923
+ const { transports, name } = latest.current;
924
+ return startAgentBridge({
925
+ name,
926
+ transports,
927
+ tools: () => {
928
+ const { tools } = latest.current;
929
+ return typeof tools === "function" ? tools() : tools ?? {};
930
+ }
931
+ });
932
+ }, []);
933
+ }