execbro 1.10.16 → 1.10.18
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 +60 -360
- package/build/__tests__/unit/flowpoints.test.d.ts +2 -0
- package/build/__tests__/unit/flowpoints.test.d.ts.map +1 -0
- package/build/__tests__/unit/flowpoints.test.js +126 -0
- package/build/__tests__/unit/flowpoints.test.js.map +1 -0
- package/build/__tests__/unit/flowpointsQuery.test.d.ts +2 -0
- package/build/__tests__/unit/flowpointsQuery.test.d.ts.map +1 -0
- package/build/__tests__/unit/flowpointsQuery.test.js +91 -0
- package/build/__tests__/unit/flowpointsQuery.test.js.map +1 -0
- package/build/__tests__/unit/licenseGrace.test.d.ts +2 -0
- package/build/__tests__/unit/licenseGrace.test.d.ts.map +1 -0
- package/build/__tests__/unit/licenseGrace.test.js +62 -0
- package/build/__tests__/unit/licenseGrace.test.js.map +1 -0
- package/build/__tests__/unit/telemetryModes.test.d.ts +2 -0
- package/build/__tests__/unit/telemetryModes.test.d.ts.map +1 -0
- package/build/__tests__/unit/telemetryModes.test.js +29 -0
- package/build/__tests__/unit/telemetryModes.test.js.map +1 -0
- package/build/__tests__/unit/usageGate.test.d.ts +2 -0
- package/build/__tests__/unit/usageGate.test.d.ts.map +1 -0
- package/build/__tests__/unit/usageGate.test.js +78 -0
- package/build/__tests__/unit/usageGate.test.js.map +1 -0
- package/build/__tests__/unit/usageNotifications.test.d.ts +2 -0
- package/build/__tests__/unit/usageNotifications.test.d.ts.map +1 -0
- package/build/__tests__/unit/usageNotifications.test.js +27 -0
- package/build/__tests__/unit/usageNotifications.test.js.map +1 -0
- package/build/__tests__/unit/verifyFlow.test.d.ts +2 -0
- package/build/__tests__/unit/verifyFlow.test.d.ts.map +1 -0
- package/build/__tests__/unit/verifyFlow.test.js +45 -0
- package/build/__tests__/unit/verifyFlow.test.js.map +1 -0
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +54 -10
- package/build/core/guides.js.map +1 -1
- package/build/core/license.d.ts +8 -0
- package/build/core/license.d.ts.map +1 -1
- package/build/core/license.js +60 -4
- package/build/core/license.js.map +1 -1
- package/build/core/register.d.ts.map +1 -1
- package/build/core/register.js +19 -3
- package/build/core/register.js.map +1 -1
- package/build/core/telemetry.d.ts +22 -0
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +122 -73
- package/build/core/telemetry.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/pro/flowpointTools.d.ts +4 -0
- package/build/pro/flowpointTools.d.ts.map +1 -0
- package/build/pro/flowpointTools.js +266 -0
- package/build/pro/flowpointTools.js.map +1 -0
- package/build/pro/flowpoints.d.ts +86 -0
- package/build/pro/flowpoints.d.ts.map +1 -0
- package/build/pro/flowpoints.js +283 -0
- package/build/pro/flowpoints.js.map +1 -0
- package/build/pro/usageGate.d.ts +10 -0
- package/build/pro/usageGate.d.ts.map +1 -0
- package/build/pro/usageGate.js +72 -0
- package/build/pro/usageGate.js.map +1 -0
- package/build/pro/usageNotifications.d.ts +5 -0
- package/build/pro/usageNotifications.d.ts.map +1 -0
- package/build/pro/usageNotifications.js +88 -0
- package/build/pro/usageNotifications.js.map +1 -0
- package/build/tools/accountTools.d.ts.map +1 -1
- package/build/tools/accountTools.js +12 -1
- package/build/tools/accountTools.js.map +1 -1
- package/build/tools/metaTools.d.ts.map +1 -1
- package/build/tools/metaTools.js +23 -8
- package/build/tools/metaTools.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerToolWithTelemetry } from "../core/register.js";
|
|
3
|
+
import { getConnectedAppByDevice, getFirstConnectedApp, executeInApp } from "../core/index.js";
|
|
4
|
+
import { UserInputError } from "../core/errors.js";
|
|
5
|
+
import { allStoredFlowpoints, clearFlowpointStores, drainFlowpoints, filterFlowpoints, formatFlowpoints, formatMeta, getFlowpointStore, matchesPoint, buildClearExpression, verifyFlow, resolveLastRuns, } from "./flowpoints.js";
|
|
6
|
+
const NO_FLOWPOINTS_HINT = "No flowpoints captured. Instrument the flow under test (execbro-sdk required):\n\n" +
|
|
7
|
+
" import { flowpoint } from 'execbro-sdk'\n" +
|
|
8
|
+
" flowpoint({ name: 'my-flow', step: 'start', begin: true })\n" +
|
|
9
|
+
" flowpoint({ name: 'my-flow', step: 'done', meta: { anything: true } })\n\n" +
|
|
10
|
+
"Then reload the app, drive the flow, and query again. " +
|
|
11
|
+
'See get_usage_guide(topic="flowpoints").';
|
|
12
|
+
export function resolveTargetDeviceName(device) {
|
|
13
|
+
if (device) {
|
|
14
|
+
const app = getConnectedAppByDevice(device);
|
|
15
|
+
if (!app)
|
|
16
|
+
throw new UserInputError(`No connected device matches "${device}"`);
|
|
17
|
+
return app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
18
|
+
}
|
|
19
|
+
const first = getFirstConnectedApp();
|
|
20
|
+
if (!first)
|
|
21
|
+
throw new UserInputError("No connected app. Run scan_metro first.");
|
|
22
|
+
return first.deviceInfo.deviceName || first.deviceInfo.title || "unknown";
|
|
23
|
+
}
|
|
24
|
+
const deviceParam = z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.");
|
|
28
|
+
export function registerFlowpointTools(server) {
|
|
29
|
+
registerToolWithTelemetry(server, "get_flowpoints", {
|
|
30
|
+
description: "Read flowpoint breadcrumbs — structured, timestamped points emitted by flowpoint() calls " +
|
|
31
|
+
"(execbro-sdk) — grouped by flow and run, with inter-point timing deltas.\n" +
|
|
32
|
+
"PURPOSE: verify what actually happened inside a flow (steps, order, timing, failures) " +
|
|
33
|
+
"instead of inferring it from console logs.\n" +
|
|
34
|
+
"WHEN TO USE: after driving an instrumented flow; or with level: 'error' to ask " +
|
|
35
|
+
'"did any flow fail?" across all flows at once.\n' +
|
|
36
|
+
"WORKFLOW: instrument with flowpoint() -> reload -> drive the flow -> " +
|
|
37
|
+
"wait_for_flowpoint (sync) -> verify_flow (assert) -> get_flowpoints (inspect details).\n" +
|
|
38
|
+
"LIMITATIONS: requires execbro-sdk init() and flowpoint() instrumentation in app code.\n" +
|
|
39
|
+
"GOOD: get_flowpoints({ name: 'add-to-cart', run: 'last' }) after a retry.\n" +
|
|
40
|
+
"BAD: polling get_flowpoints in a loop — use wait_for_flowpoint instead.\n" +
|
|
41
|
+
'SEE ALSO: call get_usage_guide(topic="flowpoints") for the full playbook.',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
name: z.string().optional().describe("Filter to one flow name. Omit for all flows."),
|
|
44
|
+
step: z.string().optional().describe("Filter to one step label (exact match)."),
|
|
45
|
+
run: z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Filter to one run id, or 'last' for the most recent run per flow."),
|
|
49
|
+
level: z.enum(["info", "warn", "error"]).optional().describe("Filter by severity."),
|
|
50
|
+
metaIncludes: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Case-insensitive substring match against the stringified meta payload."),
|
|
54
|
+
since: z.number().optional().describe("Epoch-ms lower bound on the point timestamp (exclusive)."),
|
|
55
|
+
limit: z.number().optional().default(200).describe("Max points returned, newest kept (default 200)."),
|
|
56
|
+
device: deviceParam,
|
|
57
|
+
},
|
|
58
|
+
}, async ({ name, step, run, level, metaIncludes, since, limit, device }) => {
|
|
59
|
+
const deviceName = resolveTargetDeviceName(device);
|
|
60
|
+
const drained = await drainFlowpoints(deviceName, device);
|
|
61
|
+
if (!drained.ok) {
|
|
62
|
+
return { isError: true, content: [{ type: "text", text: drained.error }] };
|
|
63
|
+
}
|
|
64
|
+
const sourceEntries = device ? getFlowpointStore(deviceName).entries : allStoredFlowpoints();
|
|
65
|
+
const entries = filterFlowpoints(sourceEntries, {
|
|
66
|
+
name,
|
|
67
|
+
step,
|
|
68
|
+
run,
|
|
69
|
+
level,
|
|
70
|
+
metaIncludes,
|
|
71
|
+
since,
|
|
72
|
+
limit,
|
|
73
|
+
});
|
|
74
|
+
if (entries.length === 0) {
|
|
75
|
+
if (sourceEntries.length === 0) {
|
|
76
|
+
return { content: [{ type: "text", text: NO_FLOWPOINTS_HINT }] };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: `${sourceEntries.length} flowpoints stored, but 0 matched your filters. ` +
|
|
83
|
+
"Loosen or drop filters (name/step/run/level/metaIncludes/since) to see them.",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const flows = new Set(entries.map((e) => e.name)).size;
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `Flowpoints (${entries.length} points, ${flows} flow${flows === 1 ? "" : "s"}):\n\n${formatFlowpoints(entries)}`,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}, (result) => !!result?.content?.[0]?.text?.startsWith("No flowpoints captured"));
|
|
98
|
+
registerToolWithTelemetry(server, "clear_flowpoints", {
|
|
99
|
+
description: "Clear stored flowpoints.\n" +
|
|
100
|
+
"PURPOSE: reset flowpoint history between test sessions.\n" +
|
|
101
|
+
"WHEN TO USE: rarely — prefer begin: true + run: 'last' filtering between attempts. " +
|
|
102
|
+
"With name: clears one flow from the server store only (cleared entries never re-drain). " +
|
|
103
|
+
"Without name: clears everything server-side AND the in-app buffer.",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
name: z.string().optional().describe("Clear only this flow (server-side). Omit to clear all."),
|
|
106
|
+
device: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Target device (substring match). Scopes both the server-store clear and the in-app " +
|
|
110
|
+
"buffer clear. Omit for all devices."),
|
|
111
|
+
},
|
|
112
|
+
}, async ({ name, device }) => {
|
|
113
|
+
const deviceName = device ? resolveTargetDeviceName(device) : undefined;
|
|
114
|
+
let cleared = clearFlowpointStores(name, deviceName);
|
|
115
|
+
let caveat = "";
|
|
116
|
+
if (!name) {
|
|
117
|
+
const result = await executeInApp(buildClearExpression(), false, { timeoutMs: 3000, originatingToolName: "clear_flowpoints" }, device);
|
|
118
|
+
if (result.success) {
|
|
119
|
+
const inApp = parseInt(result.result || "0", 10);
|
|
120
|
+
if (!isNaN(inApp))
|
|
121
|
+
cleared = Math.max(cleared, inApp);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
caveat = " (in-app buffer clear failed — still-buffered points may re-drain after the next app event)";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const scope = name ? ` for flow "${name}"` : "";
|
|
128
|
+
return { content: [{ type: "text", text: `Cleared ${cleared} flowpoints${scope}.${caveat}` }] };
|
|
129
|
+
});
|
|
130
|
+
registerToolWithTelemetry(server, "wait_for_flowpoint", {
|
|
131
|
+
description: "Block until a flowpoint matching the criteria arrives, or timeout.\n" +
|
|
132
|
+
"PURPOSE: deterministic synchronization for drive-then-verify — act first, then block on the flow's " +
|
|
133
|
+
"terminal step instead of sleeping and re-polling.\n" +
|
|
134
|
+
"WHEN TO USE: after tapping/driving an instrumented flow, with the flow's terminal step.\n" +
|
|
135
|
+
"Points emitted after your action but before this call still match (only points already " +
|
|
136
|
+
"seen by a previous flowpoint tool call are excluded).\n" +
|
|
137
|
+
"On timeout, returns whatever points DID arrive for the flow — that partial trail is the " +
|
|
138
|
+
"diagnostic for what stalled.\n" +
|
|
139
|
+
'SEE ALSO: get_usage_guide(topic="flowpoints").',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
name: z.string().describe("Flow name to watch."),
|
|
142
|
+
step: z.string().optional().describe("Step label to wait for (exact match). Omit to match any step."),
|
|
143
|
+
level: z.enum(["info", "warn", "error"]).optional().describe("Only match this severity."),
|
|
144
|
+
metaIncludes: z
|
|
145
|
+
.string()
|
|
146
|
+
.optional()
|
|
147
|
+
.describe("Case-insensitive substring the stringified meta must contain."),
|
|
148
|
+
timeoutMs: z.number().optional().default(5000).describe("Max wait in ms (default 5000, cap 30000)."),
|
|
149
|
+
device: deviceParam,
|
|
150
|
+
},
|
|
151
|
+
}, async ({ name, step, level, metaIncludes, timeoutMs, device }) => {
|
|
152
|
+
const effectiveTimeout = Math.min(timeoutMs, 30000);
|
|
153
|
+
const deviceName = resolveTargetDeviceName(device);
|
|
154
|
+
const store = getFlowpointStore(deviceName);
|
|
155
|
+
const baselineEntry = store.entries.length > 0 ? store.entries[store.entries.length - 1] : null;
|
|
156
|
+
const start = Date.now();
|
|
157
|
+
const deadline = start + effectiveTimeout;
|
|
158
|
+
const freshEntries = () => {
|
|
159
|
+
const startIdx = baselineEntry ? store.entries.indexOf(baselineEntry) + 1 : 0;
|
|
160
|
+
return store.entries.slice(startIdx).filter((e) => e.name === name);
|
|
161
|
+
};
|
|
162
|
+
for (;;) {
|
|
163
|
+
const remaining = deadline - Date.now();
|
|
164
|
+
if (remaining > 0) {
|
|
165
|
+
const drained = await Promise.race([
|
|
166
|
+
drainFlowpoints(deviceName, device),
|
|
167
|
+
new Promise((resolve) => setTimeout(() => resolve(null), remaining)),
|
|
168
|
+
]);
|
|
169
|
+
if (drained && !drained.ok) {
|
|
170
|
+
return { isError: true, content: [{ type: "text", text: drained.error }] };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const fresh = freshEntries();
|
|
174
|
+
const match = fresh.find((e) => matchesPoint(e, { step, level, metaIncludes }));
|
|
175
|
+
if (match) {
|
|
176
|
+
const meta = match.meta !== undefined ? `\nmeta: ${formatMeta(match.meta)}` : "";
|
|
177
|
+
return {
|
|
178
|
+
content: [
|
|
179
|
+
{
|
|
180
|
+
type: "text",
|
|
181
|
+
text: `Matched after ${Date.now() - start}ms: flow "${match.name}" ` +
|
|
182
|
+
`run ${match.run} step "${match.step}" [${match.level}]${meta}`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (Date.now() >= deadline) {
|
|
188
|
+
const trail = fresh.length
|
|
189
|
+
? `Points that DID arrive for "${name}" during the wait:\n\n${formatFlowpoints(fresh)}`
|
|
190
|
+
: `No new points arrived for flow "${name}" during the wait.`;
|
|
191
|
+
return {
|
|
192
|
+
content: [
|
|
193
|
+
{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `Timeout after ${effectiveTimeout}ms waiting for flow "${name}"${step ? ` step "${step}"` : ""}.\n${trail}`,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(250, Math.max(1, deadline - Date.now()))));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
registerToolWithTelemetry(server, "verify_flow", {
|
|
204
|
+
description: "Assert a flow behaved as expected: compare the actual flowpoint trail of one run " +
|
|
205
|
+
"against an expected step sequence and get a factual PASS/FAIL diff.\n" +
|
|
206
|
+
"PURPOSE: factual PASS/FAIL assertion of a flow run against an expected step sequence.\n" +
|
|
207
|
+
"WHEN TO USE: after wait_for_flowpoint confirms the flow finished; or write the expectation first " +
|
|
208
|
+
"(runtime TDD) and iterate until PASS.\n" +
|
|
209
|
+
"Matching is subsequence: expected steps must appear in order; extra points in between " +
|
|
210
|
+
"are fine. Any unexpected error-level point fails the run unless allowErrors is true or " +
|
|
211
|
+
"that step is explicitly expected.\n" +
|
|
212
|
+
"WORKFLOW: write the expected sequence (even before implementing — runtime TDD), drive " +
|
|
213
|
+
"the flow, wait_for_flowpoint on the terminal step, then verify_flow.\n" +
|
|
214
|
+
'SEE ALSO: get_usage_guide(topic="flowpoints").',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
name: z.string().describe("Flow name to verify."),
|
|
217
|
+
expect: z
|
|
218
|
+
.array(z.union([
|
|
219
|
+
z.string(),
|
|
220
|
+
z.object({
|
|
221
|
+
step: z.string(),
|
|
222
|
+
level: z.enum(["info", "warn", "error"]).optional(),
|
|
223
|
+
metaIncludes: z.string().optional(),
|
|
224
|
+
}),
|
|
225
|
+
]))
|
|
226
|
+
.describe("Expected step sequence, in order. Strings are shorthand for { step }."),
|
|
227
|
+
run: z.string().optional().default("last").describe("Run id to check, or 'last' (default)."),
|
|
228
|
+
allowErrors: z
|
|
229
|
+
.boolean()
|
|
230
|
+
.optional()
|
|
231
|
+
.default(false)
|
|
232
|
+
.describe("Tolerate unexpected error-level points (default false)."),
|
|
233
|
+
device: deviceParam,
|
|
234
|
+
},
|
|
235
|
+
}, async ({ name, expect: expected, run, allowErrors, device }) => {
|
|
236
|
+
const deviceName = resolveTargetDeviceName(device);
|
|
237
|
+
const drained = await drainFlowpoints(deviceName, device);
|
|
238
|
+
if (!drained.ok) {
|
|
239
|
+
return { isError: true, content: [{ type: "text", text: drained.error }] };
|
|
240
|
+
}
|
|
241
|
+
const sourceEntries = device ? getFlowpointStore(deviceName).entries : allStoredFlowpoints();
|
|
242
|
+
const flowEntries = sourceEntries.filter((e) => e.name === name);
|
|
243
|
+
if (flowEntries.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
content: [
|
|
246
|
+
{
|
|
247
|
+
type: "text",
|
|
248
|
+
text: `No flowpoints recorded for flow "${name}".\n\n${NO_FLOWPOINTS_HINT}`,
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const runId = run === "last" || run === undefined ? resolveLastRuns(flowEntries).get(name) : run;
|
|
254
|
+
const runEntries = flowEntries.filter((e) => e.run === runId);
|
|
255
|
+
if (runEntries.length === 0) {
|
|
256
|
+
return {
|
|
257
|
+
isError: true,
|
|
258
|
+
content: [{ type: "text", text: `No entries for flow "${name}" run "${runId}".` }],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const result = verifyFlow(name, runId, runEntries, expected, allowErrors ?? false);
|
|
262
|
+
// A FAIL verdict is a successful verification, not a tool error — never set isError for it.
|
|
263
|
+
return { content: [{ type: "text", text: result.text }] };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=flowpointTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flowpointTools.js","sourceRoot":"","sources":["../../src/pro/flowpointTools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type FlowpointLevel = "info" | "warn" | "error";
|
|
2
|
+
export interface FlowpointEntry {
|
|
3
|
+
seq: number;
|
|
4
|
+
t: number;
|
|
5
|
+
name: string;
|
|
6
|
+
step: string;
|
|
7
|
+
run: string;
|
|
8
|
+
level: FlowpointLevel;
|
|
9
|
+
meta?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface FlowpointSnapshot {
|
|
12
|
+
contextId: string;
|
|
13
|
+
entries: FlowpointEntry[];
|
|
14
|
+
}
|
|
15
|
+
export declare const FLOWPOINT_STORE_CAP = 2000;
|
|
16
|
+
export interface FlowpointStoreState {
|
|
17
|
+
entries: FlowpointEntry[];
|
|
18
|
+
cursor: number;
|
|
19
|
+
contextId: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function createFlowpointStore(): FlowpointStoreState;
|
|
22
|
+
/** Pure drain reducer: diff a full in-app snapshot against the store. Returns entries appended. */
|
|
23
|
+
export declare function applyDrain(store: FlowpointStoreState, snapshot: FlowpointSnapshot): number;
|
|
24
|
+
export declare function buildDrainExpression(): string;
|
|
25
|
+
export declare function buildClearExpression(): string;
|
|
26
|
+
export type FlowpointMissingReason = "uninitialized" | "outdated";
|
|
27
|
+
export declare function parseDrainResult(raw: string): FlowpointSnapshot | {
|
|
28
|
+
missing: true;
|
|
29
|
+
reason: FlowpointMissingReason;
|
|
30
|
+
} | null;
|
|
31
|
+
export interface FlowpointFilters {
|
|
32
|
+
name?: string;
|
|
33
|
+
step?: string;
|
|
34
|
+
run?: string;
|
|
35
|
+
level?: FlowpointLevel;
|
|
36
|
+
metaIncludes?: string;
|
|
37
|
+
since?: number;
|
|
38
|
+
limit?: number;
|
|
39
|
+
}
|
|
40
|
+
export declare function formatMeta(meta: unknown): string;
|
|
41
|
+
export interface PointMatch {
|
|
42
|
+
step?: string;
|
|
43
|
+
level?: FlowpointLevel;
|
|
44
|
+
metaIncludes?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function matchesPoint(entry: FlowpointEntry, match: PointMatch): boolean;
|
|
47
|
+
/** Per flow name, the run id of the chronologically last entry. Assumes entries sorted by t. */
|
|
48
|
+
export declare function resolveLastRuns(entries: FlowpointEntry[]): Map<string, string>;
|
|
49
|
+
export declare function filterFlowpoints(entries: FlowpointEntry[], filters: FlowpointFilters): FlowpointEntry[];
|
|
50
|
+
/** Compact grouped text: flow → run → points with inter-point deltas. Assumes entries sorted by t. */
|
|
51
|
+
export declare function formatFlowpoints(entries: FlowpointEntry[]): string;
|
|
52
|
+
export declare function getFlowpointStore(deviceName: string): FlowpointStoreState;
|
|
53
|
+
/** All stored entries across devices, sorted chronologically. */
|
|
54
|
+
export declare function allStoredFlowpoints(): FlowpointEntry[];
|
|
55
|
+
/**
|
|
56
|
+
* Remove entries from the server stores. Cursors/contextIds are kept so cleared
|
|
57
|
+
* entries never re-drain. When deviceName is given, only that device's store is
|
|
58
|
+
* touched; otherwise all stores are.
|
|
59
|
+
*/
|
|
60
|
+
export declare function clearFlowpointStores(name?: string, deviceName?: string): number;
|
|
61
|
+
export declare const SDK_FLOWPOINTS_MISSING: string;
|
|
62
|
+
export declare const SDK_FLOWPOINTS_OUTDATED: string;
|
|
63
|
+
export type DrainOutcome = {
|
|
64
|
+
ok: true;
|
|
65
|
+
} | {
|
|
66
|
+
ok: false;
|
|
67
|
+
error: string;
|
|
68
|
+
sdkMissing?: boolean;
|
|
69
|
+
};
|
|
70
|
+
export declare function drainFlowpoints(deviceName: string, device?: string): Promise<DrainOutcome>;
|
|
71
|
+
export type ExpectStep = string | {
|
|
72
|
+
step: string;
|
|
73
|
+
level?: FlowpointLevel;
|
|
74
|
+
metaIncludes?: string;
|
|
75
|
+
};
|
|
76
|
+
export interface VerifyOutcome {
|
|
77
|
+
pass: boolean;
|
|
78
|
+
text: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Subsequence assertion: expected steps must appear in order within the run; extra
|
|
82
|
+
* points in between are allowed. Unexpected error-level points fail the verification
|
|
83
|
+
* unless allowErrors is true or the error point matched an expected step.
|
|
84
|
+
*/
|
|
85
|
+
export declare function verifyFlow(name: string, runId: string, runEntries: FlowpointEntry[], expect: ExpectStep[], allowErrors: boolean): VerifyOutcome;
|
|
86
|
+
//# sourceMappingURL=flowpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flowpoints.d.ts","sourceRoot":"","sources":["../../src/pro/flowpoints.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { executeInApp } from "../core/executor.js";
|
|
2
|
+
import { pushLogBox } from "../core/logbox.js";
|
|
3
|
+
export const FLOWPOINT_STORE_CAP = 2000;
|
|
4
|
+
export function createFlowpointStore() {
|
|
5
|
+
return { entries: [], cursor: 0, contextId: null };
|
|
6
|
+
}
|
|
7
|
+
/** Pure drain reducer: diff a full in-app snapshot against the store. Returns entries appended. */
|
|
8
|
+
export function applyDrain(store, snapshot) {
|
|
9
|
+
const reset = store.contextId !== snapshot.contextId;
|
|
10
|
+
const fresh = reset ? snapshot.entries : snapshot.entries.filter((e) => e.seq > store.cursor);
|
|
11
|
+
store.entries.push(...fresh);
|
|
12
|
+
if (store.entries.length > FLOWPOINT_STORE_CAP) {
|
|
13
|
+
store.entries.splice(0, store.entries.length - FLOWPOINT_STORE_CAP);
|
|
14
|
+
}
|
|
15
|
+
const maxSeq = snapshot.entries.reduce((m, e) => Math.max(m, e.seq), 0);
|
|
16
|
+
store.cursor = reset ? maxSeq : Math.max(store.cursor, maxSeq);
|
|
17
|
+
store.contextId = snapshot.contextId;
|
|
18
|
+
return fresh.length;
|
|
19
|
+
}
|
|
20
|
+
export function buildDrainExpression() {
|
|
21
|
+
return `(() => {
|
|
22
|
+
const g = globalThis.__EXECBRO__ ?? globalThis.__RN_AI_DEVTOOLS__;
|
|
23
|
+
if (!g || typeof g.getFlowpointSnapshot !== "function") {
|
|
24
|
+
const reason = g ? "outdated" : "uninitialized";
|
|
25
|
+
if (!globalThis.__EXECBRO_FLOWPOINT_WARNED__) {
|
|
26
|
+
globalThis.__EXECBRO_FLOWPOINT_WARNED__ = true;
|
|
27
|
+
console.warn(reason === "outdated"
|
|
28
|
+
? "[execbro] An AI agent tried to use flowpoints, but this app's execbro-sdk version does not support them. Upgrade it: npm install execbro-sdk@latest, then reload."
|
|
29
|
+
: "[execbro] An AI agent tried to use flowpoints, but execbro-sdk is not initialized. Install it (npm install execbro-sdk) and call init() at app start.");
|
|
30
|
+
}
|
|
31
|
+
return JSON.stringify({ __missing: true, __reason: reason });
|
|
32
|
+
}
|
|
33
|
+
const s = g.getFlowpointSnapshot();
|
|
34
|
+
const entries = s.entries.map((e) => {
|
|
35
|
+
try { JSON.stringify(e.meta); return e; }
|
|
36
|
+
catch { return Object.assign({}, e, { meta: { __error: "meta not JSON-serializable" } }); }
|
|
37
|
+
});
|
|
38
|
+
return JSON.stringify({ contextId: s.contextId, entries });
|
|
39
|
+
})()`;
|
|
40
|
+
}
|
|
41
|
+
export function buildClearExpression() {
|
|
42
|
+
return `(() => {
|
|
43
|
+
const g = globalThis.__EXECBRO__ ?? globalThis.__RN_AI_DEVTOOLS__;
|
|
44
|
+
return g && typeof g.clearFlowpoints === "function" ? String(g.clearFlowpoints()) : "0";
|
|
45
|
+
})()`;
|
|
46
|
+
}
|
|
47
|
+
export function parseDrainResult(raw) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (parsed && parsed.__missing === true) {
|
|
51
|
+
return { missing: true, reason: parsed.__reason === "outdated" ? "outdated" : "uninitialized" };
|
|
52
|
+
}
|
|
53
|
+
if (parsed && typeof parsed.contextId === "string" && Array.isArray(parsed.entries)) {
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function formatMeta(meta) {
|
|
63
|
+
if (meta === undefined)
|
|
64
|
+
return "";
|
|
65
|
+
if (typeof meta === "string")
|
|
66
|
+
return meta;
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(meta);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return String(meta);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function matchesPoint(entry, match) {
|
|
75
|
+
if (match.step !== undefined && entry.step !== match.step)
|
|
76
|
+
return false;
|
|
77
|
+
if (match.level !== undefined && entry.level !== match.level)
|
|
78
|
+
return false;
|
|
79
|
+
if (match.metaIncludes !== undefined &&
|
|
80
|
+
!formatMeta(entry.meta).toLowerCase().includes(match.metaIncludes.toLowerCase())) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/** Per flow name, the run id of the chronologically last entry. Assumes entries sorted by t. */
|
|
86
|
+
export function resolveLastRuns(entries) {
|
|
87
|
+
const last = new Map();
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
last.set(entry.name, entry.run);
|
|
90
|
+
}
|
|
91
|
+
return last;
|
|
92
|
+
}
|
|
93
|
+
export function filterFlowpoints(entries, filters) {
|
|
94
|
+
let result = entries;
|
|
95
|
+
if (filters.name !== undefined)
|
|
96
|
+
result = result.filter((e) => e.name === filters.name);
|
|
97
|
+
if (filters.run === "last") {
|
|
98
|
+
const lastRuns = resolveLastRuns(result);
|
|
99
|
+
result = result.filter((e) => lastRuns.get(e.name) === e.run);
|
|
100
|
+
}
|
|
101
|
+
else if (filters.run !== undefined) {
|
|
102
|
+
result = result.filter((e) => e.run === filters.run);
|
|
103
|
+
}
|
|
104
|
+
if (filters.step !== undefined)
|
|
105
|
+
result = result.filter((e) => e.step === filters.step);
|
|
106
|
+
if (filters.level !== undefined)
|
|
107
|
+
result = result.filter((e) => e.level === filters.level);
|
|
108
|
+
if (filters.metaIncludes !== undefined) {
|
|
109
|
+
const needle = filters.metaIncludes.toLowerCase();
|
|
110
|
+
result = result.filter((e) => formatMeta(e.meta).toLowerCase().includes(needle));
|
|
111
|
+
}
|
|
112
|
+
if (filters.since !== undefined)
|
|
113
|
+
result = result.filter((e) => e.t > filters.since);
|
|
114
|
+
const limit = filters.limit ?? 200;
|
|
115
|
+
if (result.length > limit)
|
|
116
|
+
result = result.slice(result.length - limit);
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function formatPointLine(entry, t0, indent) {
|
|
120
|
+
const delta = `+${entry.t - t0}ms`.padEnd(9);
|
|
121
|
+
const levelPrefix = entry.level !== "info" ? `[${entry.level}] ` : "";
|
|
122
|
+
const meta = entry.meta !== undefined ? ` ${formatMeta(entry.meta)}` : "";
|
|
123
|
+
return `${indent}${delta}${levelPrefix}${entry.step}${meta}`;
|
|
124
|
+
}
|
|
125
|
+
function formatRunBlock(runId, runEntries, latest, indent) {
|
|
126
|
+
const span = runEntries[runEntries.length - 1].t - runEntries[0].t;
|
|
127
|
+
const label = latest ? ` (latest)` : "";
|
|
128
|
+
const header = `${indent}run ${runId}${label} — ${runEntries.length} points (${span}ms span):`;
|
|
129
|
+
const lines = runEntries.map((e) => formatPointLine(e, runEntries[0].t, indent + " "));
|
|
130
|
+
return [header, ...lines].join("\n");
|
|
131
|
+
}
|
|
132
|
+
/** Compact grouped text: flow → run → points with inter-point deltas. Assumes entries sorted by t. */
|
|
133
|
+
export function formatFlowpoints(entries) {
|
|
134
|
+
const lastRuns = resolveLastRuns(entries);
|
|
135
|
+
const byName = new Map();
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
let runs = byName.get(entry.name);
|
|
138
|
+
if (!runs) {
|
|
139
|
+
runs = new Map();
|
|
140
|
+
byName.set(entry.name, runs);
|
|
141
|
+
}
|
|
142
|
+
let run = runs.get(entry.run);
|
|
143
|
+
if (!run) {
|
|
144
|
+
run = [];
|
|
145
|
+
runs.set(entry.run, run);
|
|
146
|
+
}
|
|
147
|
+
run.push(entry);
|
|
148
|
+
}
|
|
149
|
+
const blocks = [];
|
|
150
|
+
for (const [name, runs] of byName) {
|
|
151
|
+
const runIds = [...runs.keys()];
|
|
152
|
+
if (runIds.length === 1) {
|
|
153
|
+
const runEntries = runs.get(runIds[0]);
|
|
154
|
+
const span = runEntries[runEntries.length - 1].t - runEntries[0].t;
|
|
155
|
+
const header = `Flow "${name}" run ${runIds[0]} — ${runEntries.length} points (${span}ms span):`;
|
|
156
|
+
blocks.push([header, ...runEntries.map((e) => formatPointLine(e, runEntries[0].t, " "))].join("\n"));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const header = `Flow "${name}" — ${runIds.length} runs`;
|
|
160
|
+
const runBlocks = runIds.map((id) => formatRunBlock(id, runs.get(id), id === lastRuns.get(name), " "));
|
|
161
|
+
blocks.push([header, ...runBlocks].join("\n"));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return blocks.join("\n\n");
|
|
165
|
+
}
|
|
166
|
+
const flowpointStores = new Map();
|
|
167
|
+
export function getFlowpointStore(deviceName) {
|
|
168
|
+
let store = flowpointStores.get(deviceName);
|
|
169
|
+
if (!store) {
|
|
170
|
+
store = createFlowpointStore();
|
|
171
|
+
flowpointStores.set(deviceName, store);
|
|
172
|
+
}
|
|
173
|
+
return store;
|
|
174
|
+
}
|
|
175
|
+
/** All stored entries across devices, sorted chronologically. */
|
|
176
|
+
export function allStoredFlowpoints() {
|
|
177
|
+
const all = [];
|
|
178
|
+
for (const store of flowpointStores.values()) {
|
|
179
|
+
all.push(...store.entries);
|
|
180
|
+
}
|
|
181
|
+
return all.sort((a, b) => a.t - b.t);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Remove entries from the server stores. Cursors/contextIds are kept so cleared
|
|
185
|
+
* entries never re-drain. When deviceName is given, only that device's store is
|
|
186
|
+
* touched; otherwise all stores are.
|
|
187
|
+
*/
|
|
188
|
+
export function clearFlowpointStores(name, deviceName) {
|
|
189
|
+
let removed = 0;
|
|
190
|
+
const stores = deviceName !== undefined ? [getFlowpointStore(deviceName)] : [...flowpointStores.values()];
|
|
191
|
+
for (const store of stores) {
|
|
192
|
+
if (name === undefined) {
|
|
193
|
+
removed += store.entries.length;
|
|
194
|
+
store.entries = [];
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const before = store.entries.length;
|
|
198
|
+
store.entries = store.entries.filter((e) => e.name !== name);
|
|
199
|
+
removed += before - store.entries.length;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return removed;
|
|
203
|
+
}
|
|
204
|
+
const LOGBOX_FLOWPOINTS_OUTDATED = "An AI agent tried to use flowpoints, but this app's execbro-sdk version does not support them.\n" +
|
|
205
|
+
"Upgrade it: npm install execbro-sdk@latest, then rebuild/reload.";
|
|
206
|
+
const LOGBOX_FLOWPOINTS_UNINITIALIZED = "An AI agent tried to use flowpoints, but execbro-sdk is not initialized.\n" +
|
|
207
|
+
"Install it (npm install execbro-sdk) and call init() at app start.";
|
|
208
|
+
// Once per device+reason per server session — LogBox banners must not repeat on every poll.
|
|
209
|
+
const flowpointLogBoxNotified = new Set();
|
|
210
|
+
export const SDK_FLOWPOINTS_MISSING = "Flowpoints require the execbro-sdk: npm install execbro-sdk, call init() at app start, " +
|
|
211
|
+
"then instrument the flow with flowpoint({ name, step }). Older SDK versions without " +
|
|
212
|
+
"flowpoint support must be upgraded. A notice with these instructions was also shown on " +
|
|
213
|
+
'the device (LogBox error banner). See get_usage_guide(topic="flowpoints").';
|
|
214
|
+
export const SDK_FLOWPOINTS_OUTDATED = "This app's execbro-sdk version predates flowpoints. Upgrade it (npm install execbro-sdk@latest), " +
|
|
215
|
+
"rebuild/reload the app, then retry. A notice with these instructions was also shown on the " +
|
|
216
|
+
'device (LogBox error banner). See get_usage_guide(topic="flowpoints").';
|
|
217
|
+
export async function drainFlowpoints(deviceName, device) {
|
|
218
|
+
const result = await executeInApp(buildDrainExpression(), false, { timeoutMs: 5000, originatingToolName: "flowpoints" }, device);
|
|
219
|
+
if (!result.success) {
|
|
220
|
+
return { ok: false, error: result.error || "executeInApp failed" };
|
|
221
|
+
}
|
|
222
|
+
const parsed = parseDrainResult(result.result ?? "");
|
|
223
|
+
if (parsed === null) {
|
|
224
|
+
return { ok: false, error: "Failed to parse flowpoint snapshot from the app" };
|
|
225
|
+
}
|
|
226
|
+
if ("missing" in parsed) {
|
|
227
|
+
const notifyKey = `${deviceName}:${parsed.reason}`;
|
|
228
|
+
if (!flowpointLogBoxNotified.has(notifyKey)) {
|
|
229
|
+
flowpointLogBoxNotified.add(notifyKey);
|
|
230
|
+
await pushLogBox(parsed.reason === "outdated" ? LOGBOX_FLOWPOINTS_OUTDATED : LOGBOX_FLOWPOINTS_UNINITIALIZED, "error", false, "logbox", "Flowpoints", device);
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
error: parsed.reason === "outdated" ? SDK_FLOWPOINTS_OUTDATED : SDK_FLOWPOINTS_MISSING,
|
|
235
|
+
sdkMissing: true,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
applyDrain(getFlowpointStore(deviceName), parsed);
|
|
239
|
+
return { ok: true };
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Subsequence assertion: expected steps must appear in order within the run; extra
|
|
243
|
+
* points in between are allowed. Unexpected error-level points fail the verification
|
|
244
|
+
* unless allowErrors is true or the error point matched an expected step.
|
|
245
|
+
*/
|
|
246
|
+
export function verifyFlow(name, runId, runEntries, expect, allowErrors) {
|
|
247
|
+
const matchers = expect.map((x) => (typeof x === "string" ? { step: x } : x));
|
|
248
|
+
const t0 = runEntries.length > 0 ? runEntries[0].t : 0;
|
|
249
|
+
const matchedIndexes = new Set();
|
|
250
|
+
const lines = [];
|
|
251
|
+
let pass = true;
|
|
252
|
+
let cursor = 0;
|
|
253
|
+
for (const matcher of matchers) {
|
|
254
|
+
let found = -1;
|
|
255
|
+
for (let i = cursor; i < runEntries.length; i++) {
|
|
256
|
+
if (matchesPoint(runEntries[i], matcher)) {
|
|
257
|
+
found = i;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (found === -1) {
|
|
262
|
+
pass = false;
|
|
263
|
+
lines.push(` ✗ ${matcher.step} — not seen`);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
matchedIndexes.add(found);
|
|
267
|
+
cursor = found + 1;
|
|
268
|
+
lines.push(` ✓ ${matcher.step} (+${runEntries[found].t - t0}ms)`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (!allowErrors) {
|
|
272
|
+
runEntries.forEach((entry, i) => {
|
|
273
|
+
if (entry.level === "error" && !matchedIndexes.has(i)) {
|
|
274
|
+
pass = false;
|
|
275
|
+
const meta = entry.meta !== undefined ? ` ${formatMeta(entry.meta)}` : "";
|
|
276
|
+
lines.push(` ! ${entry.step} [error] (+${entry.t - t0}ms)${meta} — unexpected error point`);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const header = `${pass ? "PASS" : "FAIL"} — flow "${name}" run ${runId}:`;
|
|
281
|
+
return { pass, text: [header, ...lines].join("\n") };
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=flowpoints.js.map
|