@avasapp/agent-bridge 0.2.0 → 0.3.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 +73 -11
- package/dist/adapters/ably.d.cts +1 -1
- package/dist/adapters/expo-router.d.cts +1 -1
- package/dist/adapters/react-native-mmkv.d.cts +1 -1
- package/dist/adapters/socket-io.d.cts +1 -1
- package/dist/adapters/tanstack-query.d.cts +1 -1
- package/dist/adapters/zustand.d.cts +1 -1
- package/dist/{chunk-LBYZ5JHF.js → chunk-S2FYN2R5.js} +117 -0
- package/dist/cli.js +34 -45
- package/dist/client/index.cjs +104 -2
- package/dist/client/index.d.ts +44 -1
- package/dist/client/index.js +5 -3
- package/dist/expo/index.d.cts +1 -1
- package/dist/network/index.cjs +186 -21
- package/dist/network/index.d.cts +45 -2
- package/dist/noop/index.cjs +9 -0
- package/dist/noop/network.cjs +7 -1
- package/dist/realtime/index.d.cts +1 -1
- package/dist/runtime/index.cjs +209 -13
- package/dist/runtime/index.d.cts +23 -3
- package/dist/{types-C6DUUHnB.d.cts → types-6kKQsLC1.d.cts} +40 -1
- package/package.json +2 -1
- package/skills/agent-bridge/SKILL.md +30 -3
package/dist/runtime/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var runtime_exports = {};
|
|
|
22
22
|
__export(runtime_exports, {
|
|
23
23
|
RUNTIME_MARKER: () => RUNTIME_MARKER,
|
|
24
24
|
cdpTransport: () => cdpTransport,
|
|
25
|
+
createGate: () => createGate,
|
|
25
26
|
settle: () => settle,
|
|
26
27
|
startAgentBridge: () => startAgentBridge,
|
|
27
28
|
useAgentBridge: () => useAgentBridge
|
|
@@ -73,14 +74,15 @@ function logTools(logs) {
|
|
|
73
74
|
// src/runtime/tools/restore.ts
|
|
74
75
|
var SUFFIX = ".restore";
|
|
75
76
|
var SELF = "bridge.restore";
|
|
77
|
+
var LAST = "scenario.restore";
|
|
76
78
|
function restoreTools(getTools) {
|
|
77
79
|
return {
|
|
78
80
|
[SELF]: {
|
|
79
|
-
description: "Undo what the agent changed: runs every *.restore tool in name order and returns each result or error.",
|
|
81
|
+
description: "Undo what the agent changed: runs every *.restore tool in name order, scenario.restore last, and returns each result or error.",
|
|
80
82
|
run: async () => {
|
|
81
83
|
const tools = getTools();
|
|
82
84
|
const results = {};
|
|
83
|
-
const names = Object.keys(tools).filter((name) => name.endsWith(SUFFIX) && name !== SELF).sort();
|
|
85
|
+
const names = Object.keys(tools).filter((name) => name.endsWith(SUFFIX) && name !== SELF).sort((a, b) => Number(a === LAST) - Number(b === LAST) || a.localeCompare(b));
|
|
84
86
|
for (const name of names) {
|
|
85
87
|
const definition = tools[name];
|
|
86
88
|
const run = typeof definition === "function" ? definition : definition.run;
|
|
@@ -98,6 +100,159 @@ function restoreTools(getTools) {
|
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
// src/runtime/options-schema.ts
|
|
104
|
+
var import_json_schema = require("@cfworker/json-schema");
|
|
105
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
106
|
+
var CASCADE = /* @__PURE__ */ new Set([
|
|
107
|
+
"properties",
|
|
108
|
+
"patternProperties",
|
|
109
|
+
"additionalProperties",
|
|
110
|
+
"unevaluatedProperties",
|
|
111
|
+
"items",
|
|
112
|
+
"prefixItems",
|
|
113
|
+
"unevaluatedItems",
|
|
114
|
+
"allOf",
|
|
115
|
+
"if",
|
|
116
|
+
"then",
|
|
117
|
+
"else",
|
|
118
|
+
"dependentSchemas",
|
|
119
|
+
"$ref",
|
|
120
|
+
"false"
|
|
121
|
+
]);
|
|
122
|
+
var path = (instanceLocation) => instanceLocation === "#" ? "options" : `options${instanceLocation.slice(1)}`;
|
|
123
|
+
function unknownKeys(schema, value, at, out) {
|
|
124
|
+
if (!isObject(schema)) return;
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
if (isObject(schema.items))
|
|
127
|
+
value.forEach((item, i) => unknownKeys(schema.items, item, `${at}/${i}`, out));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!isObject(value) || !isObject(schema.properties)) return;
|
|
131
|
+
const props = schema.properties;
|
|
132
|
+
const open = schema.additionalProperties !== void 0 && schema.additionalProperties !== false || schema.patternProperties !== void 0 || // Composed schemas split their properties across branches; leave them to the validator.
|
|
133
|
+
["allOf", "anyOf", "oneOf", "$ref"].some((k) => schema[k] !== void 0);
|
|
134
|
+
for (const [key, child] of Object.entries(value)) {
|
|
135
|
+
if (key in props) unknownKeys(props[key], child, `${at}/${key}`, out);
|
|
136
|
+
else if (!open)
|
|
137
|
+
out.push(`${path(at)}: unknown option "${key}". Known: ${Object.keys(props).join(", ") || "none"}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var validators = /* @__PURE__ */ new WeakMap();
|
|
141
|
+
function checkOptions(schema, options) {
|
|
142
|
+
let validator = validators.get(schema);
|
|
143
|
+
if (!validator) {
|
|
144
|
+
validator = new import_json_schema.Validator(schema, "2020-12", false);
|
|
145
|
+
validators.set(schema, validator);
|
|
146
|
+
}
|
|
147
|
+
const value = options === void 0 ? {} : options;
|
|
148
|
+
const problems = [];
|
|
149
|
+
unknownKeys(schema, value, "#", problems);
|
|
150
|
+
const result = validator.validate(value);
|
|
151
|
+
if (!result.valid) {
|
|
152
|
+
const leaves = result.errors.filter(
|
|
153
|
+
(e) => !CASCADE.has(e.keyword) && !/\/(anyOf|oneOf)\/\d/.test(e.keywordLocation)
|
|
154
|
+
);
|
|
155
|
+
const shown = leaves.length || problems.length ? leaves : result.errors;
|
|
156
|
+
for (const e of shown) problems.push(`${path(e.instanceLocation)}: ${e.error}`);
|
|
157
|
+
}
|
|
158
|
+
return [...new Set(problems)];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/runtime/tools/scenario.ts
|
|
162
|
+
var active = /* @__PURE__ */ new Map();
|
|
163
|
+
var message = (error) => error instanceof Error ? error.message : String(error);
|
|
164
|
+
async function runUndos(entry) {
|
|
165
|
+
const errors = [];
|
|
166
|
+
for (const undo2 of entry.undos.splice(0).reverse()) {
|
|
167
|
+
try {
|
|
168
|
+
await undo2();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
errors.push(message(error));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return errors;
|
|
174
|
+
}
|
|
175
|
+
async function undo(name) {
|
|
176
|
+
const entry = active.get(name);
|
|
177
|
+
if (!entry) return [];
|
|
178
|
+
active.delete(name);
|
|
179
|
+
return (await runUndos(entry)).map((e) => `${name}: ${e}`);
|
|
180
|
+
}
|
|
181
|
+
function scenarioTools(getScenarios, getTools) {
|
|
182
|
+
const find = (name) => {
|
|
183
|
+
const scenarios = getScenarios();
|
|
184
|
+
const scenario = scenarios[name];
|
|
185
|
+
if (!scenario)
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Unknown scenario "${name}". Known: ${Object.keys(scenarios).join(", ") || "none"}`
|
|
188
|
+
);
|
|
189
|
+
return scenario;
|
|
190
|
+
};
|
|
191
|
+
const call = async (tool, ...args) => {
|
|
192
|
+
const definition = getTools()[tool];
|
|
193
|
+
if (!definition) throw new Error(`Unknown tool "${tool}"`);
|
|
194
|
+
const run = typeof definition === "function" ? definition : definition.run;
|
|
195
|
+
return run(...args);
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
"scenario.list": {
|
|
199
|
+
description: "Setups the app defines, e.g. a signed-in user: name, description, options (a JSON Schema), active.",
|
|
200
|
+
run: () => Object.entries(getScenarios()).map(([name, s]) => ({
|
|
201
|
+
name,
|
|
202
|
+
description: s.description,
|
|
203
|
+
options: s.options,
|
|
204
|
+
active: active.has(name)
|
|
205
|
+
})).sort((a, b) => a.name.localeCompare(b.name))
|
|
206
|
+
},
|
|
207
|
+
"scenario.apply": {
|
|
208
|
+
description: "Apply a scenario: [name, options?]. Options must fit its schema (scenario.list). Returns what it returns. Applying an active one undoes it first. bridge.restore undoes it.",
|
|
209
|
+
run: async (name, options) => {
|
|
210
|
+
const scenario = find(name);
|
|
211
|
+
if (scenario.options) {
|
|
212
|
+
const problems = checkOptions(scenario.options, options);
|
|
213
|
+
if (problems.length)
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Scenario "${name}" got bad options, nothing was applied:
|
|
216
|
+
- ${problems.join("\n- ")}
|
|
217
|
+
scenario.list shows its options schema.`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const previous = await undo(name);
|
|
221
|
+
if (previous.length)
|
|
222
|
+
throw new Error(`Undoing the active "${name}" failed: ${previous.join("; ")}`);
|
|
223
|
+
const entry = { options, undos: [] };
|
|
224
|
+
active.set(name, entry);
|
|
225
|
+
const context = {
|
|
226
|
+
options,
|
|
227
|
+
call,
|
|
228
|
+
onUndo: (fn) => {
|
|
229
|
+
if (typeof fn !== "function")
|
|
230
|
+
throw new Error("onUndo takes a function");
|
|
231
|
+
entry.undos.push(fn);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
try {
|
|
235
|
+
return await scenario.apply(context);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
const errors = await undo(name);
|
|
238
|
+
const extra = errors.length ? ` (undo: ${errors.join("; ")})` : "";
|
|
239
|
+
throw new Error(`Scenario "${name}" failed: ${message(error)}${extra}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"scenario.restore": {
|
|
244
|
+
description: "Undo every active scenario, newest first. bridge.restore runs this after every other restorer.",
|
|
245
|
+
run: async () => {
|
|
246
|
+
const names = [...active.keys()].reverse();
|
|
247
|
+
const errors = [];
|
|
248
|
+
for (const name of names) errors.push(...await undo(name));
|
|
249
|
+
if (errors.length) throw new Error(errors.join("; "));
|
|
250
|
+
return names;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
101
256
|
// src/runtime/tools/screen.ts
|
|
102
257
|
var import_react_native = require("react-native");
|
|
103
258
|
|
|
@@ -639,12 +794,14 @@ function screenTools() {
|
|
|
639
794
|
}
|
|
640
795
|
|
|
641
796
|
// src/runtime/builtin-tools.ts
|
|
642
|
-
function builtinTools(listTools, getTools, logs) {
|
|
797
|
+
function builtinTools(listTools, getTools, logs, getScenarios) {
|
|
643
798
|
return {
|
|
644
799
|
...bridgeTools(listTools),
|
|
645
800
|
...logTools(logs),
|
|
646
801
|
...restoreTools(getTools),
|
|
647
|
-
...screenTools()
|
|
802
|
+
...screenTools(),
|
|
803
|
+
// Only when the app defines scenarios, so `tools` doesn't list empty ones.
|
|
804
|
+
...getScenarios && scenarioTools(getScenarios, getTools)
|
|
648
805
|
};
|
|
649
806
|
}
|
|
650
807
|
|
|
@@ -673,6 +830,38 @@ function cdpTransport() {
|
|
|
673
830
|
};
|
|
674
831
|
}
|
|
675
832
|
|
|
833
|
+
// src/runtime/gate.ts
|
|
834
|
+
function createGate(name) {
|
|
835
|
+
let holds = 0;
|
|
836
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
837
|
+
const notify = () => {
|
|
838
|
+
for (const fn of [...listeners]) fn(holds > 0);
|
|
839
|
+
};
|
|
840
|
+
return {
|
|
841
|
+
name,
|
|
842
|
+
get closed() {
|
|
843
|
+
return holds > 0;
|
|
844
|
+
},
|
|
845
|
+
close: () => {
|
|
846
|
+
let released = false;
|
|
847
|
+
holds += 1;
|
|
848
|
+
if (holds === 1) notify();
|
|
849
|
+
return () => {
|
|
850
|
+
if (released) return;
|
|
851
|
+
released = true;
|
|
852
|
+
holds -= 1;
|
|
853
|
+
if (holds === 0) notify();
|
|
854
|
+
};
|
|
855
|
+
},
|
|
856
|
+
subscribe: (fn) => {
|
|
857
|
+
listeners.add(fn);
|
|
858
|
+
return () => {
|
|
859
|
+
listeners.delete(fn);
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
676
865
|
// src/runtime/logs.ts
|
|
677
866
|
var CAPACITY = 200;
|
|
678
867
|
var STACK_LINES = 10;
|
|
@@ -735,9 +924,9 @@ function formatArgs(args) {
|
|
|
735
924
|
}
|
|
736
925
|
parts.push(...rest.map(describe2));
|
|
737
926
|
const error = args.find((a) => a instanceof Error);
|
|
738
|
-
const
|
|
927
|
+
const message2 = parts.join(" ");
|
|
739
928
|
return {
|
|
740
|
-
message:
|
|
929
|
+
message: message2.length > MESSAGE_CHARS ? `${message2.slice(0, MESSAGE_CHARS)}...` : message2,
|
|
741
930
|
...error?.stack ? { stack: trimStack(error.stack) } : {}
|
|
742
931
|
};
|
|
743
932
|
}
|
|
@@ -756,9 +945,9 @@ function describe2(value) {
|
|
|
756
945
|
var trimStack = (stack) => stack.split("\n").slice(0, STACK_LINES).join("\n");
|
|
757
946
|
function installLogHooks(capture, env) {
|
|
758
947
|
let quiet = 0;
|
|
759
|
-
let
|
|
948
|
+
let active2 = true;
|
|
760
949
|
const record = (level, args) => {
|
|
761
|
-
if (!
|
|
950
|
+
if (!active2 || quiet) return;
|
|
762
951
|
quiet++;
|
|
763
952
|
try {
|
|
764
953
|
capture.record(level, args);
|
|
@@ -801,7 +990,7 @@ function installLogHooks(capture, env) {
|
|
|
801
990
|
env.addEventListener?.("unhandledrejection", onRejection);
|
|
802
991
|
}
|
|
803
992
|
return () => {
|
|
804
|
-
|
|
993
|
+
active2 = false;
|
|
805
994
|
if (console.error === wrappers.error) console.error = originals.error;
|
|
806
995
|
if (console.warn === wrappers.warn) console.warn = originals.warn;
|
|
807
996
|
if (errorUtils && originalHandler && errorUtils.getGlobalHandler() === handler2)
|
|
@@ -877,8 +1066,8 @@ function createRegistry(getTools, logs) {
|
|
|
877
1066
|
const value = await unwrap(definition).run(...call.args ?? []);
|
|
878
1067
|
return { id: call.id, from, ok: true, value: toJson(value), ms: ms() };
|
|
879
1068
|
} catch (error) {
|
|
880
|
-
const
|
|
881
|
-
return { id: call.id, from, ok: false, error:
|
|
1069
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
1070
|
+
return { id: call.id, from, ok: false, error: message2, ms: ms() };
|
|
882
1071
|
}
|
|
883
1072
|
}
|
|
884
1073
|
return { list, dispatch };
|
|
@@ -889,9 +1078,11 @@ var randomId = () => Math.random().toString(36).slice(2, 10);
|
|
|
889
1078
|
function startAgentBridge(options = {}) {
|
|
890
1079
|
const deviceId = randomId();
|
|
891
1080
|
const userTools = () => typeof options.tools === "function" ? options.tools() : options.tools ?? {};
|
|
1081
|
+
const { scenarios } = options;
|
|
1082
|
+
const getScenarios = scenarios ? () => typeof scenarios === "function" ? scenarios() : scenarios : void 0;
|
|
892
1083
|
const logs = startLogCapture();
|
|
893
1084
|
const allTools = () => ({
|
|
894
|
-
...builtinTools(() => registry.list(), allTools, logs.capture),
|
|
1085
|
+
...builtinTools(() => registry.list(), allTools, logs.capture, getScenarios),
|
|
895
1086
|
...userTools()
|
|
896
1087
|
});
|
|
897
1088
|
const registry = createRegistry(allTools, logs.capture);
|
|
@@ -927,7 +1118,12 @@ function useAgentBridge(options = {}) {
|
|
|
927
1118
|
tools: () => {
|
|
928
1119
|
const { tools } = latest.current;
|
|
929
1120
|
return typeof tools === "function" ? tools() : tools ?? {};
|
|
930
|
-
}
|
|
1121
|
+
},
|
|
1122
|
+
// Decided at mount: scenario.* tools are there or not for the bridge's life.
|
|
1123
|
+
scenarios: latest.current.scenarios ? () => {
|
|
1124
|
+
const { scenarios } = latest.current;
|
|
1125
|
+
return typeof scenarios === "function" ? scenarios() : scenarios ?? {};
|
|
1126
|
+
} : void 0
|
|
931
1127
|
});
|
|
932
1128
|
}, []);
|
|
933
1129
|
}
|
package/dist/runtime/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as Transport, A as AgentBridgeOptions } from '../types-
|
|
2
|
-
export { C as CallMessage, D as DeviceInfo, L as LogEntry, R as RUNTIME_MARKER, b as ResultMessage, c as
|
|
1
|
+
import { a as Transport, A as AgentBridgeOptions } from '../types-6kKQsLC1.cjs';
|
|
2
|
+
export { C as CallMessage, D as DeviceInfo, L as LogEntry, O as OptionsSchema, R as RUNTIME_MARKER, b as ResultMessage, S as Scenario, c as ScenarioContext, d as Scenarios, e as ToolDefinition, f as ToolFn, g as ToolInfo, T as Tools } from '../types-6kKQsLC1.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Works in any React Native app served by Metro. The client evaluates one fixed
|
|
@@ -8,6 +8,26 @@ export { C as CallMessage, D as DeviceInfo, L as LogEntry, R as RUNTIME_MARKER,
|
|
|
8
8
|
*/
|
|
9
9
|
declare function cdpTransport(): Transport;
|
|
10
10
|
|
|
11
|
+
type Gate = {
|
|
12
|
+
name: string;
|
|
13
|
+
/** True while anything holds the gate closed. */
|
|
14
|
+
readonly closed: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Closes the gate until the returned function is called. Closes are
|
|
17
|
+
* counted, so the gate opens when the last one is released. Hand the
|
|
18
|
+
* result to a scenario's `onUndo`.
|
|
19
|
+
*/
|
|
20
|
+
close: () => () => void;
|
|
21
|
+
/** Calls `fn(closed)` when the gate closes or opens. Returns an unsubscribe. */
|
|
22
|
+
subscribe: (fn: (closed: boolean) => void) => () => void;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* A switch app code checks before a side effect a scenario must hold off,
|
|
26
|
+
* e.g. connecting a realtime client with a fake token. Always open in a
|
|
27
|
+
* release build.
|
|
28
|
+
*/
|
|
29
|
+
declare function createGate(name: string): Gate;
|
|
30
|
+
|
|
11
31
|
type SettleResult = {
|
|
12
32
|
commits: number;
|
|
13
33
|
ms: number;
|
|
@@ -66,4 +86,4 @@ declare function startAgentBridge(options?: AgentBridgeOptions): () => void;
|
|
|
66
86
|
*/
|
|
67
87
|
declare function useAgentBridge(options?: AgentBridgeOptions): void;
|
|
68
88
|
|
|
69
|
-
export { AgentBridgeOptions, type ScreenElement, type SettleResult, type Target, Transport, cdpTransport, settle, startAgentBridge, useAgentBridge };
|
|
89
|
+
export { AgentBridgeOptions, type Gate, type ScreenElement, type SettleResult, type Target, Transport, cdpTransport, createGate, settle, startAgentBridge, useAgentBridge };
|
|
@@ -48,6 +48,14 @@ type DeviceInfo = {
|
|
|
48
48
|
tools: ToolInfo[];
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* A JSON Schema (draft 2020-12) for a scenario's options. Typed loosely, so
|
|
53
|
+
* `as const` schemas and ones generated by other libraries fit.
|
|
54
|
+
*/
|
|
55
|
+
type OptionsSchema = {
|
|
56
|
+
readonly [keyword: string]: unknown;
|
|
57
|
+
};
|
|
58
|
+
|
|
51
59
|
type ToolFn = (...args: any[]) => unknown;
|
|
52
60
|
type ToolDefinition = ToolFn | {
|
|
53
61
|
description?: string;
|
|
@@ -64,13 +72,44 @@ type Transport = {
|
|
|
64
72
|
/** Starts listening; returns a function that stops. */
|
|
65
73
|
start: (context: TransportContext) => () => void;
|
|
66
74
|
};
|
|
75
|
+
type ScenarioContext<O = unknown> = {
|
|
76
|
+
/** What the agent or flow passed to `scenario.apply`, or undefined. */
|
|
77
|
+
options: O | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Calls any bridge tool by name, e.g. `call('store.set', 'auth', {...})`.
|
|
80
|
+
* What a tool changes is undone by its own restorer in `bridge.restore`.
|
|
81
|
+
*/
|
|
82
|
+
call: (tool: string, ...args: unknown[]) => Promise<unknown>;
|
|
83
|
+
/**
|
|
84
|
+
* Runs `fn` when the scenario is undone, after every other restorer. Use it
|
|
85
|
+
* for anything the tools' restorers don't cover: app mocks, gates, native
|
|
86
|
+
* state. Undo callbacks run newest first.
|
|
87
|
+
*/
|
|
88
|
+
onUndo: (fn: () => unknown) => void;
|
|
89
|
+
};
|
|
90
|
+
/** A named setup the app defines and the agent applies, e.g. a signed-in user. */
|
|
91
|
+
type Scenario<O = unknown> = {
|
|
92
|
+
description?: string;
|
|
93
|
+
/**
|
|
94
|
+
* JSON Schema (2020-12) for `options`. `scenario.apply` rejects options
|
|
95
|
+
* that don't fit before `apply` runs, and `scenario.list` shows it. Keys an
|
|
96
|
+
* object schema doesn't list are rejected unless it sets
|
|
97
|
+
* `additionalProperties`. No options are checked as `{}`.
|
|
98
|
+
*/
|
|
99
|
+
options?: OptionsSchema;
|
|
100
|
+
/** Sets the app up. Its return value goes back to the agent. */
|
|
101
|
+
apply: (context: ScenarioContext<O>) => unknown;
|
|
102
|
+
};
|
|
103
|
+
type Scenarios = Record<string, Scenario<any>>;
|
|
67
104
|
type AgentBridgeOptions = {
|
|
68
105
|
/** Your tools, or a function returning them (read on every call). */
|
|
69
106
|
tools?: Tools | (() => Tools);
|
|
107
|
+
/** Named setups for `scenario.*`, or a function returning them (read on every call). */
|
|
108
|
+
scenarios?: Scenarios | (() => Scenarios);
|
|
70
109
|
/** How this app shows up in `agent-bridge devices`. */
|
|
71
110
|
name?: string;
|
|
72
111
|
/** Defaults to CDP only. Add `expoTransport()` on Expo. */
|
|
73
112
|
transports?: Transport[];
|
|
74
113
|
};
|
|
75
114
|
|
|
76
|
-
export { type AgentBridgeOptions as A, type CallMessage as C, type DeviceInfo as D, type LogEntry as L, RUNTIME_MARKER as R, type Tools as T, type Transport as a, type ResultMessage as b, type
|
|
115
|
+
export { type AgentBridgeOptions as A, type CallMessage as C, type DeviceInfo as D, type LogEntry as L, type OptionsSchema as O, RUNTIME_MARKER as R, type Scenario as S, type Tools as T, type Transport as a, type ResultMessage as b, type ScenarioContext as c, type Scenarios as d, type ToolDefinition as e, type ToolFn as f, type ToolInfo as g };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avasapp/agent-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Let coding agents drive a running React Native app directly: seed state, flip flags, navigate and check the screen in about a millisecond per call, with no taps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
120
|
"dependencies": {
|
|
121
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
121
122
|
"ws": "^8.21.3"
|
|
122
123
|
},
|
|
123
124
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-bridge
|
|
3
|
-
description: "Drive a running React Native or Expo dev build directly through @avasapp/agent-bridge: see what's on screen, fill in forms, press buttons, seed query data, flip feature flags, set store state, navigate, mock the network, fake realtime messages and undo it all, in milliseconds per call instead of tapping through the app. Pairs with agent-device, which opens the app and covers native UI, real taps and screenshots. Use whenever you need the app in a particular state to test a change, or need to verify what a screen shows."
|
|
3
|
+
description: "Drive a running React Native or Expo dev build directly through @avasapp/agent-bridge: start from an app-defined scenario such as a locally signed-in user, see what's on screen, fill in forms, press buttons, seed query data, flip feature flags, set store state, navigate, mock the network, fake realtime messages and undo it all, in milliseconds per call instead of tapping through the app. Pairs with agent-device, which opens the app and covers native UI, real taps and screenshots. Use whenever you need the app in a particular state to test a change, or need to verify what a screen shows."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Driving an app with agent-bridge
|
|
@@ -37,6 +37,30 @@ minutes without calls and runs `bridge.restore` when it does.
|
|
|
37
37
|
If `tools` fails with "agent-bridge isn't running", the hook isn't mounted in
|
|
38
38
|
this build. Say so; don't fall back to tapping silently.
|
|
39
39
|
|
|
40
|
+
## Start from a scenario
|
|
41
|
+
|
|
42
|
+
Apps can define named setups, such as a locally signed-in user. Use one
|
|
43
|
+
instead of signing in by hand or seeding auth state yourself:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npx agent-bridge scenarios # what this app defines
|
|
47
|
+
npx agent-bridge call scenario.apply '["signedIn", {"user": {"name": "Ada"}}]'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`scenarios` shows each one's options as a JSON Schema. Bad options fail
|
|
51
|
+
before anything changes, and the error lists every problem
|
|
52
|
+
(`options: unknown option "usr". Known: user`): fix them and apply again.
|
|
53
|
+
`bridge.restore` (and `session stop`) undoes it after everything else. A local
|
|
54
|
+
scenario usually turns on strict network mode: any request no mock answers
|
|
55
|
+
fails with a 501, and the reply carries an error that names it
|
|
56
|
+
(`! error during …: agent-bridge strict network: no mock for GET https://…`).
|
|
57
|
+
Add a mock for it (`net.mock`), don't turn strict mode off.
|
|
58
|
+
`npx agent-bridge call net.strict` lists what it blocked. Strict mode sees JS
|
|
59
|
+
`fetch`/XHR only, not images, WebSockets or native SDKs.
|
|
60
|
+
|
|
61
|
+
If the app has no scenario for the state you need, say so. Don't sign in to a
|
|
62
|
+
real account instead.
|
|
63
|
+
|
|
40
64
|
## See and use the screen
|
|
41
65
|
|
|
42
66
|
```sh
|
|
@@ -95,6 +119,8 @@ last 200 errors and warnings.
|
|
|
95
119
|
Write the steps into a flow file and run it:
|
|
96
120
|
|
|
97
121
|
```js
|
|
122
|
+
export const scenario = 'signedIn' // applied first, undone after, even on failure
|
|
123
|
+
|
|
98
124
|
export default async ({ step }) => {
|
|
99
125
|
await step('open form', 'screen.press', 'add-plant')
|
|
100
126
|
await step('save empty', 'screen.press', 'save-plant')
|
|
@@ -105,6 +131,7 @@ export default async ({ step }) => {
|
|
|
105
131
|
|
|
106
132
|
```sh
|
|
107
133
|
npx agent-bridge run flow.mjs --strict # --strict fails if the app logged an error
|
|
134
|
+
npx agent-bridge run flow.mjs --scenario signedIn # add a scenario the flow doesn't declare
|
|
108
135
|
```
|
|
109
136
|
|
|
110
137
|
## Clean up
|
|
@@ -114,8 +141,8 @@ npx agent-bridge session stop # runs bridge.restore; --keep leaves t
|
|
|
114
141
|
```
|
|
115
142
|
|
|
116
143
|
`bridge.restore` unpins queries, puts back store and MMKV values, removes
|
|
117
|
-
network mocks, unmutes realtime channels and ends a faked connection state,
|
|
118
|
-
the next run starts from the real state.
|
|
144
|
+
network mocks, unmutes realtime channels and ends a faked connection state,
|
|
145
|
+
then undoes active scenarios, so the next run starts from the real state.
|
|
119
146
|
|
|
120
147
|
## Traps
|
|
121
148
|
|