@browserbasehq/orca 3.2.0-preview.2 → 3.2.0-preview.4

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 (167) hide show
  1. package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js +5 -5
  2. package/dist/cjs/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
  3. package/dist/cjs/lib/v3/agent/GoogleCUAClient.js +5 -5
  4. package/dist/cjs/lib/v3/agent/GoogleCUAClient.js.map +1 -1
  5. package/dist/cjs/lib/v3/agent/OpenAICUAClient.js +5 -5
  6. package/dist/cjs/lib/v3/agent/OpenAICUAClient.js.map +1 -1
  7. package/dist/cjs/lib/v3/agent/tools/act.js +1 -10
  8. package/dist/cjs/lib/v3/agent/tools/act.js.map +1 -1
  9. package/dist/cjs/lib/v3/agent/tools/ariaTree.js +1 -12
  10. package/dist/cjs/lib/v3/agent/tools/ariaTree.js.map +1 -1
  11. package/dist/cjs/lib/v3/agent/tools/braveSearch.js.map +1 -1
  12. package/dist/cjs/lib/v3/agent/tools/browserbaseSearch.js.map +1 -1
  13. package/dist/cjs/lib/v3/agent/tools/click.js.map +1 -1
  14. package/dist/cjs/lib/v3/agent/tools/clickAndHold.js.map +1 -1
  15. package/dist/cjs/lib/v3/agent/tools/dragAndDrop.js.map +1 -1
  16. package/dist/cjs/lib/v3/agent/tools/extract.js +1 -10
  17. package/dist/cjs/lib/v3/agent/tools/extract.js.map +1 -1
  18. package/dist/cjs/lib/v3/agent/tools/fillFormVision.js.map +1 -1
  19. package/dist/cjs/lib/v3/agent/tools/fillform.js +1 -10
  20. package/dist/cjs/lib/v3/agent/tools/fillform.js.map +1 -1
  21. package/dist/cjs/lib/v3/agent/tools/index.d.ts +2 -2
  22. package/dist/cjs/lib/v3/agent/tools/index.js +53 -5
  23. package/dist/cjs/lib/v3/agent/tools/index.js.map +1 -1
  24. package/dist/cjs/lib/v3/agent/tools/keys.d.ts +1 -1
  25. package/dist/cjs/lib/v3/agent/tools/keys.js.map +1 -1
  26. package/dist/cjs/lib/v3/agent/tools/type.js.map +1 -1
  27. package/dist/cjs/lib/v3/api.js +9 -2
  28. package/dist/cjs/lib/v3/api.js.map +1 -1
  29. package/dist/cjs/lib/v3/flowlogger/EventEmitter.d.ts +7 -0
  30. package/dist/cjs/lib/v3/flowlogger/EventEmitter.js +30 -0
  31. package/dist/cjs/lib/v3/flowlogger/EventEmitter.js.map +1 -0
  32. package/dist/cjs/lib/v3/flowlogger/EventSink.d.ts +44 -0
  33. package/dist/cjs/lib/v3/flowlogger/EventSink.js +217 -0
  34. package/dist/cjs/lib/v3/flowlogger/EventSink.js.map +1 -0
  35. package/dist/cjs/lib/v3/flowlogger/EventStore.d.ts +26 -0
  36. package/dist/cjs/lib/v3/flowlogger/EventStore.js +135 -0
  37. package/dist/cjs/lib/v3/flowlogger/EventStore.js.map +1 -0
  38. package/dist/{esm/lib/v3/flowLogger.d.ts → cjs/lib/v3/flowlogger/FlowLogger.d.ts} +32 -31
  39. package/dist/cjs/lib/v3/flowlogger/FlowLogger.js +591 -0
  40. package/dist/cjs/lib/v3/flowlogger/FlowLogger.js.map +1 -0
  41. package/dist/cjs/lib/v3/flowlogger/prettify.d.ts +6 -0
  42. package/dist/cjs/lib/v3/flowlogger/prettify.js +395 -0
  43. package/dist/cjs/lib/v3/flowlogger/prettify.js.map +1 -0
  44. package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js +26 -28
  45. package/dist/cjs/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
  46. package/dist/cjs/lib/v3/handlers/v3AgentHandler.js +2 -2
  47. package/dist/cjs/lib/v3/handlers/v3AgentHandler.js.map +1 -1
  48. package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js +3 -5
  49. package/dist/cjs/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
  50. package/dist/cjs/lib/v3/llm/aisdk.js +9 -9
  51. package/dist/cjs/lib/v3/llm/aisdk.js.map +1 -1
  52. package/dist/cjs/lib/v3/types/public/options.d.ts +2 -0
  53. package/dist/cjs/lib/v3/types/public/options.js.map +1 -1
  54. package/dist/cjs/lib/v3/understudy/cdp.d.ts +1 -1
  55. package/dist/cjs/lib/v3/understudy/cdp.js +83 -43
  56. package/dist/cjs/lib/v3/understudy/cdp.js.map +1 -1
  57. package/dist/cjs/lib/v3/understudy/page.js +18 -23
  58. package/dist/cjs/lib/v3/understudy/page.js.map +1 -1
  59. package/dist/cjs/lib/v3/v3.d.ts +5 -5
  60. package/dist/cjs/lib/v3/v3.js +48 -46
  61. package/dist/cjs/lib/v3/v3.js.map +1 -1
  62. package/dist/cjs/tests/integration/flowLogger.spec.d.ts +1 -0
  63. package/dist/cjs/tests/integration/flowLogger.spec.js +714 -0
  64. package/dist/cjs/tests/integration/flowLogger.spec.js.map +1 -0
  65. package/dist/cjs/tests/integration/testUtils.d.ts +33 -0
  66. package/dist/cjs/tests/integration/testUtils.js +144 -0
  67. package/dist/cjs/tests/integration/testUtils.js.map +1 -1
  68. package/dist/cjs/tests/integration/timeouts.spec.js +112 -2
  69. package/dist/cjs/tests/integration/timeouts.spec.js.map +1 -1
  70. package/dist/cjs/tests/unit/flowlogger-capturing-cdp.test.d.ts +1 -0
  71. package/dist/cjs/tests/unit/flowlogger-capturing-cdp.test.js +95 -0
  72. package/dist/cjs/tests/unit/flowlogger-capturing-cdp.test.js.map +1 -0
  73. package/dist/cjs/tests/unit/flowlogger-capturing-llm.test.d.ts +1 -0
  74. package/dist/cjs/tests/unit/flowlogger-capturing-llm.test.js +43 -0
  75. package/dist/cjs/tests/unit/flowlogger-capturing-llm.test.js.map +1 -0
  76. package/dist/cjs/tests/unit/flowlogger-eventstore.test.d.ts +1 -0
  77. package/dist/cjs/tests/unit/flowlogger-eventstore.test.js +250 -0
  78. package/dist/cjs/tests/unit/flowlogger-eventstore.test.js.map +1 -0
  79. package/dist/esm/lib/v3/agent/AnthropicCUAClient.js +1 -1
  80. package/dist/esm/lib/v3/agent/AnthropicCUAClient.js.map +1 -1
  81. package/dist/esm/lib/v3/agent/GoogleCUAClient.js +1 -1
  82. package/dist/esm/lib/v3/agent/GoogleCUAClient.js.map +1 -1
  83. package/dist/esm/lib/v3/agent/OpenAICUAClient.js +1 -1
  84. package/dist/esm/lib/v3/agent/OpenAICUAClient.js.map +1 -1
  85. package/dist/esm/lib/v3/agent/tools/act.js +1 -10
  86. package/dist/esm/lib/v3/agent/tools/act.js.map +1 -1
  87. package/dist/esm/lib/v3/agent/tools/ariaTree.js +1 -12
  88. package/dist/esm/lib/v3/agent/tools/ariaTree.js.map +1 -1
  89. package/dist/esm/lib/v3/agent/tools/braveSearch.js.map +1 -1
  90. package/dist/esm/lib/v3/agent/tools/browserbaseSearch.js.map +1 -1
  91. package/dist/esm/lib/v3/agent/tools/click.js.map +1 -1
  92. package/dist/esm/lib/v3/agent/tools/clickAndHold.js.map +1 -1
  93. package/dist/esm/lib/v3/agent/tools/dragAndDrop.js.map +1 -1
  94. package/dist/esm/lib/v3/agent/tools/extract.js +1 -10
  95. package/dist/esm/lib/v3/agent/tools/extract.js.map +1 -1
  96. package/dist/esm/lib/v3/agent/tools/fillFormVision.js.map +1 -1
  97. package/dist/esm/lib/v3/agent/tools/fillform.js +1 -10
  98. package/dist/esm/lib/v3/agent/tools/fillform.js.map +1 -1
  99. package/dist/esm/lib/v3/agent/tools/index.d.ts +2 -2
  100. package/dist/esm/lib/v3/agent/tools/index.js +53 -5
  101. package/dist/esm/lib/v3/agent/tools/index.js.map +1 -1
  102. package/dist/esm/lib/v3/agent/tools/keys.d.ts +1 -1
  103. package/dist/esm/lib/v3/agent/tools/keys.js.map +1 -1
  104. package/dist/esm/lib/v3/agent/tools/type.js.map +1 -1
  105. package/dist/esm/lib/v3/api.js +9 -2
  106. package/dist/esm/lib/v3/api.js.map +1 -1
  107. package/dist/esm/lib/v3/flowlogger/EventEmitter.d.ts +7 -0
  108. package/dist/esm/lib/v3/flowlogger/EventEmitter.js +26 -0
  109. package/dist/esm/lib/v3/flowlogger/EventEmitter.js.map +1 -0
  110. package/dist/esm/lib/v3/flowlogger/EventSink.d.ts +44 -0
  111. package/dist/esm/lib/v3/flowlogger/EventSink.js +206 -0
  112. package/dist/esm/lib/v3/flowlogger/EventSink.js.map +1 -0
  113. package/dist/esm/lib/v3/flowlogger/EventStore.d.ts +26 -0
  114. package/dist/esm/lib/v3/flowlogger/EventStore.js +127 -0
  115. package/dist/esm/lib/v3/flowlogger/EventStore.js.map +1 -0
  116. package/dist/{cjs/lib/v3/flowLogger.d.ts → esm/lib/v3/flowlogger/FlowLogger.d.ts} +32 -31
  117. package/dist/esm/lib/v3/flowlogger/FlowLogger.js +583 -0
  118. package/dist/esm/lib/v3/flowlogger/FlowLogger.js.map +1 -0
  119. package/dist/esm/lib/v3/flowlogger/prettify.d.ts +6 -0
  120. package/dist/esm/lib/v3/flowlogger/prettify.js +389 -0
  121. package/dist/esm/lib/v3/flowlogger/prettify.js.map +1 -0
  122. package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js +25 -27
  123. package/dist/esm/lib/v3/handlers/handlerUtils/actHandlerUtils.js.map +1 -1
  124. package/dist/esm/lib/v3/handlers/v3AgentHandler.js +1 -1
  125. package/dist/esm/lib/v3/handlers/v3AgentHandler.js.map +1 -1
  126. package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js +2 -4
  127. package/dist/esm/lib/v3/handlers/v3CuaAgentHandler.js.map +1 -1
  128. package/dist/esm/lib/v3/llm/aisdk.js +1 -1
  129. package/dist/esm/lib/v3/llm/aisdk.js.map +1 -1
  130. package/dist/esm/lib/v3/types/public/options.d.ts +2 -0
  131. package/dist/esm/lib/v3/types/public/options.js.map +1 -1
  132. package/dist/esm/lib/v3/understudy/cdp.d.ts +1 -1
  133. package/dist/esm/lib/v3/understudy/cdp.js +78 -38
  134. package/dist/esm/lib/v3/understudy/cdp.js.map +1 -1
  135. package/dist/esm/lib/v3/understudy/page.js +13 -18
  136. package/dist/esm/lib/v3/understudy/page.js.map +1 -1
  137. package/dist/esm/lib/v3/v3.d.ts +5 -5
  138. package/dist/esm/lib/v3/v3.js +43 -41
  139. package/dist/esm/lib/v3/v3.js.map +1 -1
  140. package/dist/esm/tests/integration/flowLogger.spec.d.ts +1 -0
  141. package/dist/esm/tests/integration/flowLogger.spec.js +712 -0
  142. package/dist/esm/tests/integration/flowLogger.spec.js.map +1 -0
  143. package/dist/esm/tests/integration/testUtils.d.ts +33 -0
  144. package/dist/esm/tests/integration/testUtils.js +138 -0
  145. package/dist/esm/tests/integration/testUtils.js.map +1 -1
  146. package/dist/esm/tests/integration/timeouts.spec.js +112 -2
  147. package/dist/esm/tests/integration/timeouts.spec.js.map +1 -1
  148. package/dist/esm/tests/unit/flowlogger-capturing-cdp.test.d.ts +1 -0
  149. package/dist/esm/tests/unit/flowlogger-capturing-cdp.test.js +93 -0
  150. package/dist/esm/tests/unit/flowlogger-capturing-cdp.test.js.map +1 -0
  151. package/dist/esm/tests/unit/flowlogger-capturing-llm.test.d.ts +1 -0
  152. package/dist/esm/tests/unit/flowlogger-capturing-llm.test.js +41 -0
  153. package/dist/esm/tests/unit/flowlogger-capturing-llm.test.js.map +1 -0
  154. package/dist/esm/tests/unit/flowlogger-eventstore.test.d.ts +1 -0
  155. package/dist/esm/tests/unit/flowlogger-eventstore.test.js +248 -0
  156. package/dist/esm/tests/unit/flowlogger-eventstore.test.js.map +1 -0
  157. package/package.json +3 -1
  158. package/dist/cjs/lib/v3/eventStore.d.ts +0 -41
  159. package/dist/cjs/lib/v3/eventStore.js +0 -375
  160. package/dist/cjs/lib/v3/eventStore.js.map +0 -1
  161. package/dist/cjs/lib/v3/flowLogger.js +0 -470
  162. package/dist/cjs/lib/v3/flowLogger.js.map +0 -1
  163. package/dist/esm/lib/v3/eventStore.d.ts +0 -41
  164. package/dist/esm/lib/v3/eventStore.js +0 -363
  165. package/dist/esm/lib/v3/eventStore.js.map +0 -1
  166. package/dist/esm/lib/v3/flowLogger.js +0 -462
  167. package/dist/esm/lib/v3/flowLogger.js.map +0 -1
@@ -0,0 +1,712 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { z } from "zod";
3
+ import { InMemoryEventSink } from "../../lib/v3/flowlogger/EventSink.js";
4
+ import { FlowEvent } from "../../lib/v3/flowlogger/FlowLogger.js";
5
+ import { performUnderstudyMethod } from "../../lib/v3/handlers/handlerUtils/actHandlerUtils.js";
6
+ import { V3 } from "../../lib/v3/v3.js";
7
+ import { createScriptedAisdkTestLlmClient, closeV3, doneToolResponse, findLastEncodedId, toolCallResponse, } from "./testUtils.js";
8
+ import { getV3TestConfig } from "./v3.config.js";
9
+ function encodeHtml(html) {
10
+ return `data:text/html,${encodeURIComponent(html)}`;
11
+ }
12
+ function createRecordedFlowLoggerV3(overrides = {}) {
13
+ const v3 = new V3(getV3TestConfig(overrides));
14
+ const sink = new InMemoryEventSink();
15
+ v3.bus.on("*", (event) => {
16
+ if (event instanceof FlowEvent) {
17
+ void sink.emit(event);
18
+ }
19
+ });
20
+ v3.eventStore.query = (query) => sink.query({ ...query, sessionId: v3.eventStore.sessionId });
21
+ return v3;
22
+ }
23
+ async function listRecordedFlowEvents(v3) {
24
+ return v3.eventStore.query({});
25
+ }
26
+ async function captureFlowEventBaseline(v3) {
27
+ const events = await listRecordedFlowEvents(v3);
28
+ return new Set(events.map((event) => event.eventId));
29
+ }
30
+ async function listRecordedFlowEventsSince(v3, baseline) {
31
+ const events = await listRecordedFlowEvents(v3);
32
+ return events.filter((event) => !baseline.has(event.eventId));
33
+ }
34
+ function eventsOfType(events, eventType) {
35
+ return events.filter((event) => event.eventType === eventType);
36
+ }
37
+ function requireSingleEvent(events, eventType) {
38
+ const matches = eventsOfType(events, eventType);
39
+ expect(matches, `expected a single ${eventType}`).toHaveLength(1);
40
+ return matches[0];
41
+ }
42
+ function expectRootEvent(event) {
43
+ expect(event.eventParentIds).toEqual([]);
44
+ }
45
+ function expectDirectParent(child, parent) {
46
+ expect(child.eventParentIds).toEqual([
47
+ ...parent.eventParentIds,
48
+ parent.eventId,
49
+ ]);
50
+ }
51
+ function assertAllParentIdsResolve(events) {
52
+ const eventIds = new Set(events.map((event) => event.eventId));
53
+ for (const event of events) {
54
+ for (const parentId of event.eventParentIds) {
55
+ expect(eventIds.has(parentId), `${event.eventType} references missing parent ${parentId}`).toBe(true);
56
+ }
57
+ }
58
+ }
59
+ function assertSessionIds(events, sessionId) {
60
+ for (const event of events) {
61
+ expect(event.sessionId).toBe(sessionId);
62
+ }
63
+ }
64
+ function directChildrenOfType(events, parent, eventType) {
65
+ const expectedParentIds = [...parent.eventParentIds, parent.eventId];
66
+ return events.filter((event) => event.eventType === eventType &&
67
+ JSON.stringify(event.eventParentIds) ===
68
+ JSON.stringify(expectedParentIds));
69
+ }
70
+ function assertCompletedEnvelope(events, eventType, completedEventType = `${eventType.replace(/Event$/, "")}CompletedEvent`) {
71
+ const root = requireSingleEvent(events, eventType);
72
+ const completed = requireSingleEvent(events, completedEventType);
73
+ expectDirectParent(completed, root);
74
+ return root;
75
+ }
76
+ function assertNoFloatingLlmEvents(events) {
77
+ const llmEvents = events.filter((event) => event.eventType === "LlmRequestEvent" ||
78
+ event.eventType === "LlmResponseEvent");
79
+ const byId = new Map(events.map((event) => [event.eventId, event]));
80
+ expect(llmEvents.length).toBeGreaterThan(0);
81
+ for (const event of llmEvents) {
82
+ expect(event.eventParentIds.length, `${event.eventType} is floating`).toBeGreaterThan(0);
83
+ const lastParentId = event.eventParentIds.at(-1);
84
+ const lastParent = lastParentId ? byId.get(lastParentId) : undefined;
85
+ expect(lastParent, `${event.eventType} has no resolved parent`).toBeDefined();
86
+ expect(lastParent?.eventType.startsWith("Llm")).toBe(false);
87
+ }
88
+ }
89
+ function assertNoFloatingCdpEvents(events) {
90
+ const cdpEvents = events.filter((event) => event.eventType.startsWith("Cdp"));
91
+ const byId = new Map(events.map((event) => [event.eventId, event]));
92
+ expect(cdpEvents.length).toBeGreaterThan(0);
93
+ for (const event of cdpEvents) {
94
+ expect(event.eventParentIds.length, `${event.eventType} is floating`).toBeGreaterThan(0);
95
+ const lastParentId = event.eventParentIds.at(-1);
96
+ const lastParent = lastParentId ? byId.get(lastParentId) : undefined;
97
+ expect(lastParent, `${event.eventType} has no resolved parent`).toBeDefined();
98
+ if (event.eventType === "CdpCallEvent") {
99
+ expect(lastParent?.eventType.startsWith("Cdp")).toBe(false);
100
+ }
101
+ else {
102
+ expect(lastParent?.eventType).toBe("CdpCallEvent");
103
+ }
104
+ }
105
+ }
106
+ function assertDirectRootCdpEvents(events, sessionId) {
107
+ const call = requireSingleEvent(events, "CdpCallEvent");
108
+ const responseTypes = ["CdpResponseEvent", "CdpResponseErrorEvent"];
109
+ const response = events.find((event) => responseTypes.includes(event.eventType));
110
+ expect(response, "expected a direct CDP response event").toBeDefined();
111
+ assertSessionIds(events, sessionId);
112
+ expectRootEvent(call);
113
+ expect(response?.eventParentIds).toEqual([call.eventId]);
114
+ }
115
+ function sortCountRecord(input) {
116
+ return Object.fromEntries(Object.entries(input).sort(([left], [right]) => left.localeCompare(right)));
117
+ }
118
+ function assertNonCdpEventCounts(events, expected) {
119
+ const actual = events.reduce((counts, event) => {
120
+ if (event.eventType.startsWith("Cdp")) {
121
+ return counts;
122
+ }
123
+ counts[event.eventType] = (counts[event.eventType] ?? 0) + 1;
124
+ return counts;
125
+ }, {});
126
+ expect(sortCountRecord(actual)).toEqual(sortCountRecord(expected));
127
+ }
128
+ test.describe("flow logger integration", () => {
129
+ test.describe.configure({ mode: "serial" });
130
+ test("act emits a rooted tree with nested understudy, llm, and cdp events", async () => {
131
+ const buttonText = "Flow Logger Act Button";
132
+ const llmClient = createScriptedAisdkTestLlmClient({
133
+ jsonResponses: {
134
+ act: (options) => ({
135
+ elementId: findLastEncodedId(options),
136
+ description: `click ${buttonText}`,
137
+ method: "click",
138
+ arguments: [],
139
+ twoStep: false,
140
+ }),
141
+ },
142
+ });
143
+ const v3 = createRecordedFlowLoggerV3({
144
+ llmClient,
145
+ });
146
+ await v3.init();
147
+ try {
148
+ const page = v3.context.pages()[0];
149
+ await page.goto(encodeHtml(`
150
+ <!doctype html>
151
+ <html>
152
+ <body>
153
+ <button
154
+ id="act-target"
155
+ onclick="document.body.dataset.clicked='true'"
156
+ >
157
+ ${buttonText}
158
+ </button>
159
+ </body>
160
+ </html>
161
+ `));
162
+ const baseline = await captureFlowEventBaseline(v3);
163
+ const result = await v3.act(`Click the ${buttonText}`);
164
+ const events = await listRecordedFlowEventsSince(v3, baseline);
165
+ expect(result.success).toBe(true);
166
+ expect(await page.evaluate(() => document.body.dataset.clicked ?? "")).toBe("true");
167
+ const root = requireSingleEvent(events, "StagehandActEvent");
168
+ const completed = requireSingleEvent(events, "StagehandActCompletedEvent");
169
+ const llmRequest = requireSingleEvent(events, "LlmRequestEvent");
170
+ const llmResponse = requireSingleEvent(events, "LlmResponseEvent");
171
+ const understudy = requireSingleEvent(events, "UnderstudyClickEvent");
172
+ const understudyCompleted = requireSingleEvent(events, "UnderstudyClickCompletedEvent");
173
+ assertAllParentIdsResolve(events);
174
+ assertNonCdpEventCounts(events, {
175
+ LlmRequestEvent: 1,
176
+ LlmResponseEvent: 1,
177
+ StagehandActCompletedEvent: 1,
178
+ StagehandActEvent: 1,
179
+ UnderstudyClickCompletedEvent: 1,
180
+ UnderstudyClickEvent: 1,
181
+ });
182
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
183
+ expectRootEvent(root);
184
+ expectDirectParent(completed, root);
185
+ expect(llmRequest.eventParentIds).toEqual([root.eventId]);
186
+ expect(llmResponse.eventParentIds).toEqual([root.eventId]);
187
+ expect(understudy.eventParentIds).toEqual([root.eventId]);
188
+ expectDirectParent(understudyCompleted, understudy);
189
+ assertNoFloatingLlmEvents(events);
190
+ assertNoFloatingCdpEvents(events);
191
+ }
192
+ finally {
193
+ await closeV3(v3);
194
+ }
195
+ });
196
+ test("observe and extract emit rooted trees with complete nested llm and cdp events", async () => {
197
+ const observeText = "Flow Logger Observe Button";
198
+ const extractTitle = "Flow Logger Extract Title";
199
+ const llmClient = createScriptedAisdkTestLlmClient({
200
+ jsonResponses: {
201
+ Observation: (options) => ({
202
+ elements: [
203
+ {
204
+ elementId: findLastEncodedId(options),
205
+ description: observeText,
206
+ method: "click",
207
+ arguments: [],
208
+ },
209
+ ],
210
+ }),
211
+ Extraction: {
212
+ title: extractTitle,
213
+ },
214
+ Metadata: {
215
+ completed: true,
216
+ progress: "done",
217
+ },
218
+ },
219
+ });
220
+ const v3 = createRecordedFlowLoggerV3({
221
+ llmClient,
222
+ });
223
+ await v3.init();
224
+ try {
225
+ const page = v3.context.pages()[0];
226
+ await page.goto(encodeHtml(`
227
+ <!doctype html>
228
+ <html>
229
+ <body>
230
+ <button id="observe-target">${observeText}</button>
231
+ <h1>${extractTitle}</h1>
232
+ </body>
233
+ </html>
234
+ `));
235
+ const observeBaseline = await captureFlowEventBaseline(v3);
236
+ const observeResult = await v3.observe(`Find the ${observeText}`);
237
+ expect(observeResult).toHaveLength(1);
238
+ expect(observeResult[0].method).toBe("click");
239
+ const observeEvents = await listRecordedFlowEventsSince(v3, observeBaseline);
240
+ const observeRoot = requireSingleEvent(observeEvents, "StagehandObserveEvent");
241
+ const observeCompleted = requireSingleEvent(observeEvents, "StagehandObserveCompletedEvent");
242
+ const observeLlmRequests = eventsOfType(observeEvents, "LlmRequestEvent");
243
+ const observeLlmResponses = eventsOfType(observeEvents, "LlmResponseEvent");
244
+ assertAllParentIdsResolve(observeEvents);
245
+ assertNonCdpEventCounts(observeEvents, {
246
+ LlmRequestEvent: 1,
247
+ LlmResponseEvent: 1,
248
+ StagehandObserveCompletedEvent: 1,
249
+ StagehandObserveEvent: 1,
250
+ });
251
+ assertSessionIds(observeEvents, v3.flowLoggerContext.sessionId);
252
+ expectRootEvent(observeRoot);
253
+ expectDirectParent(observeCompleted, observeRoot);
254
+ expect(observeLlmRequests).toHaveLength(1);
255
+ expect(observeLlmResponses).toHaveLength(1);
256
+ expect(observeLlmRequests[0].eventParentIds).toEqual([
257
+ observeRoot.eventId,
258
+ ]);
259
+ expect(observeLlmResponses[0].eventParentIds).toEqual([
260
+ observeRoot.eventId,
261
+ ]);
262
+ assertNoFloatingLlmEvents(observeEvents);
263
+ assertNoFloatingCdpEvents(observeEvents);
264
+ const extractBaseline = await captureFlowEventBaseline(v3);
265
+ const extractResult = await v3.extract("Extract the title", z.object({ title: z.string() }));
266
+ expect(extractResult).toEqual({ title: extractTitle });
267
+ const extractEvents = await listRecordedFlowEventsSince(v3, extractBaseline);
268
+ const extractRoot = requireSingleEvent(extractEvents, "StagehandExtractEvent");
269
+ const extractCompleted = requireSingleEvent(extractEvents, "StagehandExtractCompletedEvent");
270
+ const extractLlmRequests = eventsOfType(extractEvents, "LlmRequestEvent");
271
+ const extractLlmResponses = eventsOfType(extractEvents, "LlmResponseEvent");
272
+ assertAllParentIdsResolve(extractEvents);
273
+ assertNonCdpEventCounts(extractEvents, {
274
+ LlmRequestEvent: 2,
275
+ LlmResponseEvent: 2,
276
+ StagehandExtractCompletedEvent: 1,
277
+ StagehandExtractEvent: 1,
278
+ });
279
+ assertSessionIds(extractEvents, v3.flowLoggerContext.sessionId);
280
+ expectRootEvent(extractRoot);
281
+ expectDirectParent(extractCompleted, extractRoot);
282
+ expect(extractLlmRequests).toHaveLength(2);
283
+ expect(extractLlmResponses).toHaveLength(2);
284
+ for (const event of [...extractLlmRequests, ...extractLlmResponses]) {
285
+ expect(event.eventParentIds).toEqual([extractRoot.eventId]);
286
+ }
287
+ assertNoFloatingLlmEvents(extractEvents);
288
+ assertNoFloatingCdpEvents(extractEvents);
289
+ }
290
+ finally {
291
+ await closeV3(v3);
292
+ }
293
+ });
294
+ test("agent.execute -> act carries the full agent -> stagehand -> understudy -> cdp + llm hierarchy", async () => {
295
+ const buttonText = "Agent Act Button";
296
+ const llmClient = createScriptedAisdkTestLlmClient({
297
+ jsonResponses: {
298
+ act: (options) => ({
299
+ elementId: findLastEncodedId(options),
300
+ description: `click ${buttonText}`,
301
+ method: "click",
302
+ arguments: [],
303
+ twoStep: false,
304
+ }),
305
+ },
306
+ generateResponses: [
307
+ toolCallResponse("act", { action: `click the ${buttonText}` }, "act-1"),
308
+ doneToolResponse("finished", true, "done-1"),
309
+ ],
310
+ });
311
+ const v3 = createRecordedFlowLoggerV3({
312
+ experimental: true,
313
+ llmClient,
314
+ });
315
+ await v3.init();
316
+ try {
317
+ const page = v3.context.pages()[0];
318
+ await page.goto(encodeHtml(`
319
+ <!doctype html>
320
+ <html>
321
+ <body>
322
+ <button
323
+ id="agent-act-target"
324
+ onclick="document.body.dataset.agentAct='true'"
325
+ >
326
+ ${buttonText}
327
+ </button>
328
+ </body>
329
+ </html>
330
+ `));
331
+ const baseline = await captureFlowEventBaseline(v3);
332
+ const result = await v3.agent().execute({
333
+ instruction: `Click the ${buttonText} and finish.`,
334
+ maxSteps: 2,
335
+ });
336
+ const events = await listRecordedFlowEventsSince(v3, baseline);
337
+ expect(result.success).toBe(true);
338
+ expect(await page.evaluate(() => document.body.dataset.agentAct ?? "")).toBe("true");
339
+ const agentRoot = assertCompletedEnvelope(events, "AgentExecuteEvent");
340
+ const actRoot = requireSingleEvent(events, "StagehandActEvent");
341
+ const actCompleted = requireSingleEvent(events, "StagehandActCompletedEvent");
342
+ const understudy = requireSingleEvent(events, "UnderstudyClickEvent");
343
+ const understudyCompleted = requireSingleEvent(events, "UnderstudyClickCompletedEvent");
344
+ assertAllParentIdsResolve(events);
345
+ assertNonCdpEventCounts(events, {
346
+ AgentExecuteCompletedEvent: 1,
347
+ AgentExecuteEvent: 1,
348
+ LlmRequestEvent: 3,
349
+ LlmResponseEvent: 3,
350
+ StagehandActCompletedEvent: 1,
351
+ StagehandActEvent: 1,
352
+ UnderstudyClickCompletedEvent: 1,
353
+ UnderstudyClickEvent: 1,
354
+ });
355
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
356
+ expectRootEvent(agentRoot);
357
+ expect(actRoot.eventParentIds).toEqual([agentRoot.eventId]);
358
+ expectDirectParent(actCompleted, actRoot);
359
+ expectDirectParent(understudy, actRoot);
360
+ expectDirectParent(understudyCompleted, understudy);
361
+ expect(directChildrenOfType(events, agentRoot, "LlmRequestEvent")).toHaveLength(2);
362
+ expect(directChildrenOfType(events, agentRoot, "LlmResponseEvent")).toHaveLength(2);
363
+ expect(directChildrenOfType(events, actRoot, "LlmRequestEvent")).toHaveLength(1);
364
+ expect(directChildrenOfType(events, actRoot, "LlmResponseEvent")).toHaveLength(1);
365
+ assertNoFloatingLlmEvents(events);
366
+ assertNoFloatingCdpEvents(events);
367
+ }
368
+ finally {
369
+ await closeV3(v3);
370
+ }
371
+ });
372
+ test("agent.execute -> fillForm carries the observe -> act -> understudy hierarchy with no missing layers", async () => {
373
+ const llmClient = createScriptedAisdkTestLlmClient({
374
+ jsonResponses: {
375
+ Observation: (options) => ({
376
+ elements: [
377
+ {
378
+ elementId: findLastEncodedId(options),
379
+ description: "name input",
380
+ method: "fill",
381
+ arguments: ["hello"],
382
+ },
383
+ ],
384
+ }),
385
+ },
386
+ generateResponses: [
387
+ toolCallResponse("fillForm", {
388
+ fields: [
389
+ {
390
+ action: "type hello into the name field",
391
+ value: "hello",
392
+ },
393
+ ],
394
+ }, "fillform-1"),
395
+ doneToolResponse("finished", true, "done-1"),
396
+ ],
397
+ });
398
+ const v3 = createRecordedFlowLoggerV3({
399
+ experimental: true,
400
+ llmClient,
401
+ });
402
+ await v3.init();
403
+ try {
404
+ const page = v3.context.pages()[0];
405
+ await page.goto(encodeHtml(`
406
+ <!doctype html>
407
+ <html>
408
+ <body>
409
+ <input id="name" />
410
+ </body>
411
+ </html>
412
+ `));
413
+ const baseline = await captureFlowEventBaseline(v3);
414
+ const result = await v3.agent().execute({
415
+ instruction: "Fill the form and finish.",
416
+ maxSteps: 2,
417
+ });
418
+ const events = await listRecordedFlowEventsSince(v3, baseline);
419
+ expect(result.success).toBe(true);
420
+ expect(await page.locator("#name").inputValue()).toBe("hello");
421
+ const agentRoot = assertCompletedEnvelope(events, "AgentExecuteEvent");
422
+ const observeRoot = requireSingleEvent(events, "StagehandObserveEvent");
423
+ const observeCompleted = requireSingleEvent(events, "StagehandObserveCompletedEvent");
424
+ const actRoot = requireSingleEvent(events, "StagehandActEvent");
425
+ const actCompleted = requireSingleEvent(events, "StagehandActCompletedEvent");
426
+ const understudyFill = requireSingleEvent(events, "UnderstudyFillEvent");
427
+ const understudyFillCompleted = requireSingleEvent(events, "UnderstudyFillCompletedEvent");
428
+ assertAllParentIdsResolve(events);
429
+ assertNonCdpEventCounts(events, {
430
+ AgentExecuteCompletedEvent: 1,
431
+ AgentExecuteEvent: 1,
432
+ LlmRequestEvent: 3,
433
+ LlmResponseEvent: 3,
434
+ StagehandActCompletedEvent: 1,
435
+ StagehandActEvent: 1,
436
+ StagehandObserveCompletedEvent: 1,
437
+ StagehandObserveEvent: 1,
438
+ UnderstudyFillCompletedEvent: 1,
439
+ UnderstudyFillEvent: 1,
440
+ });
441
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
442
+ expectRootEvent(agentRoot);
443
+ expect(observeRoot.eventParentIds).toEqual([agentRoot.eventId]);
444
+ expectDirectParent(observeCompleted, observeRoot);
445
+ expect(actRoot.eventParentIds).toEqual([agentRoot.eventId]);
446
+ expectDirectParent(actCompleted, actRoot);
447
+ expectDirectParent(understudyFill, actRoot);
448
+ expectDirectParent(understudyFillCompleted, understudyFill);
449
+ expect(directChildrenOfType(events, observeRoot, "LlmRequestEvent")).toHaveLength(1);
450
+ expect(directChildrenOfType(events, observeRoot, "LlmResponseEvent")).toHaveLength(1);
451
+ expect(directChildrenOfType(events, agentRoot, "LlmRequestEvent")).toHaveLength(2);
452
+ expect(directChildrenOfType(events, agentRoot, "LlmResponseEvent")).toHaveLength(2);
453
+ expect(directChildrenOfType(events, actRoot, "LlmRequestEvent")).toHaveLength(0);
454
+ expect(directChildrenOfType(events, actRoot, "LlmResponseEvent")).toHaveLength(0);
455
+ assertNoFloatingLlmEvents(events);
456
+ assertNoFloatingCdpEvents(events);
457
+ }
458
+ finally {
459
+ await closeV3(v3);
460
+ }
461
+ });
462
+ test("agent.execute -> extract carries the full agent -> extract -> cdp + llm hierarchy", async () => {
463
+ const extractTitle = "Agent Extract Title";
464
+ const llmClient = createScriptedAisdkTestLlmClient({
465
+ jsonResponses: {
466
+ Extraction: {
467
+ title: extractTitle,
468
+ },
469
+ Metadata: {
470
+ completed: true,
471
+ progress: "done",
472
+ },
473
+ },
474
+ generateResponses: [
475
+ toolCallResponse("extract", {
476
+ instruction: "extract the title",
477
+ schema: {
478
+ type: "object",
479
+ properties: {
480
+ title: { type: "string" },
481
+ },
482
+ },
483
+ }, "extract-1"),
484
+ doneToolResponse("finished", true, "done-1"),
485
+ ],
486
+ });
487
+ const v3 = createRecordedFlowLoggerV3({
488
+ experimental: true,
489
+ llmClient,
490
+ });
491
+ await v3.init();
492
+ try {
493
+ const page = v3.context.pages()[0];
494
+ await page.goto(encodeHtml(`
495
+ <!doctype html>
496
+ <html>
497
+ <body>
498
+ <h1>${extractTitle}</h1>
499
+ </body>
500
+ </html>
501
+ `));
502
+ const baseline = await captureFlowEventBaseline(v3);
503
+ const result = await v3.agent().execute({
504
+ instruction: "Extract the title and finish.",
505
+ maxSteps: 2,
506
+ });
507
+ expect(result.success).toBe(true);
508
+ const events = await listRecordedFlowEventsSince(v3, baseline);
509
+ const agentRoot = assertCompletedEnvelope(events, "AgentExecuteEvent");
510
+ const extractRoot = requireSingleEvent(events, "StagehandExtractEvent");
511
+ const extractCompleted = requireSingleEvent(events, "StagehandExtractCompletedEvent");
512
+ assertAllParentIdsResolve(events);
513
+ assertNonCdpEventCounts(events, {
514
+ AgentExecuteCompletedEvent: 1,
515
+ AgentExecuteEvent: 1,
516
+ LlmRequestEvent: 4,
517
+ LlmResponseEvent: 4,
518
+ StagehandExtractCompletedEvent: 1,
519
+ StagehandExtractEvent: 1,
520
+ });
521
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
522
+ expectRootEvent(agentRoot);
523
+ expect(extractRoot.eventParentIds).toEqual([agentRoot.eventId]);
524
+ expectDirectParent(extractCompleted, extractRoot);
525
+ expect(directChildrenOfType(events, agentRoot, "LlmRequestEvent")).toHaveLength(2);
526
+ expect(directChildrenOfType(events, agentRoot, "LlmResponseEvent")).toHaveLength(2);
527
+ expect(directChildrenOfType(events, extractRoot, "LlmRequestEvent")).toHaveLength(2);
528
+ expect(directChildrenOfType(events, extractRoot, "LlmResponseEvent")).toHaveLength(2);
529
+ assertNoFloatingLlmEvents(events);
530
+ assertNoFloatingCdpEvents(events);
531
+ }
532
+ finally {
533
+ await closeV3(v3);
534
+ }
535
+ });
536
+ test("agent.execute nests page events under the agent root and direct page calls root themselves", async () => {
537
+ const agentPageUrl = encodeHtml(`
538
+ <!doctype html>
539
+ <html>
540
+ <body>
541
+ <h1>Agent Flow Logger Page</h1>
542
+ </body>
543
+ </html>
544
+ `);
545
+ const agentLlmClient = createScriptedAisdkTestLlmClient({
546
+ generateResponses: [
547
+ toolCallResponse("goto", { url: agentPageUrl }, "goto-1"),
548
+ toolCallResponse("screenshot", {}, "screenshot-1"),
549
+ doneToolResponse("finished", true, "done-1"),
550
+ ],
551
+ });
552
+ const agentV3 = createRecordedFlowLoggerV3({
553
+ experimental: true,
554
+ llmClient: agentLlmClient,
555
+ });
556
+ await agentV3.init();
557
+ try {
558
+ const baseline = await captureFlowEventBaseline(agentV3);
559
+ const result = await agentV3.agent().execute({
560
+ instruction: "Go to the test page, take a screenshot, and finish.",
561
+ maxSteps: 3,
562
+ });
563
+ expect(result.success).toBe(true);
564
+ expect(result.completed).toBe(true);
565
+ const events = await listRecordedFlowEventsSince(agentV3, baseline);
566
+ const root = assertCompletedEnvelope(events, "AgentExecuteEvent");
567
+ const pageGoto = requireSingleEvent(events, "PageGotoEvent");
568
+ const pageGotoCompleted = requireSingleEvent(events, "PageGotoCompletedEvent");
569
+ const pageScreenshot = requireSingleEvent(events, "PageScreenshotEvent");
570
+ const pageScreenshotCompleted = requireSingleEvent(events, "PageScreenshotCompletedEvent");
571
+ const llmRequests = eventsOfType(events, "LlmRequestEvent");
572
+ const llmResponses = eventsOfType(events, "LlmResponseEvent");
573
+ assertAllParentIdsResolve(events);
574
+ assertNonCdpEventCounts(events, {
575
+ AgentExecuteCompletedEvent: 1,
576
+ AgentExecuteEvent: 1,
577
+ LlmRequestEvent: 3,
578
+ LlmResponseEvent: 3,
579
+ PageGotoCompletedEvent: 1,
580
+ PageGotoEvent: 1,
581
+ PageScreenshotCompletedEvent: 1,
582
+ PageScreenshotEvent: 1,
583
+ });
584
+ assertSessionIds(events, agentV3.flowLoggerContext.sessionId);
585
+ expectRootEvent(root);
586
+ expect(pageGoto.eventParentIds).toEqual([root.eventId]);
587
+ expectDirectParent(pageGotoCompleted, pageGoto);
588
+ expect(pageScreenshot.eventParentIds).toEqual([root.eventId]);
589
+ expectDirectParent(pageScreenshotCompleted, pageScreenshot);
590
+ expect(llmRequests).toHaveLength(3);
591
+ expect(llmResponses).toHaveLength(3);
592
+ for (const event of [...llmRequests, ...llmResponses]) {
593
+ expect(event.eventParentIds).toEqual([root.eventId]);
594
+ }
595
+ assertNoFloatingLlmEvents(events);
596
+ assertNoFloatingCdpEvents(events);
597
+ }
598
+ finally {
599
+ await closeV3(agentV3);
600
+ }
601
+ const directV3 = createRecordedFlowLoggerV3();
602
+ await directV3.init();
603
+ try {
604
+ const page = directV3.context.pages()[0];
605
+ const baseline = await captureFlowEventBaseline(directV3);
606
+ await page.goto(agentPageUrl);
607
+ await page.screenshot({ fullPage: false });
608
+ const events = await listRecordedFlowEventsSince(directV3, baseline);
609
+ const pageGoto = requireSingleEvent(events, "PageGotoEvent");
610
+ const pageGotoCompleted = requireSingleEvent(events, "PageGotoCompletedEvent");
611
+ const pageScreenshot = requireSingleEvent(events, "PageScreenshotEvent");
612
+ const pageScreenshotCompleted = requireSingleEvent(events, "PageScreenshotCompletedEvent");
613
+ assertAllParentIdsResolve(events);
614
+ assertNonCdpEventCounts(events, {
615
+ PageGotoCompletedEvent: 1,
616
+ PageGotoEvent: 1,
617
+ PageScreenshotCompletedEvent: 1,
618
+ PageScreenshotEvent: 1,
619
+ });
620
+ assertSessionIds(events, directV3.flowLoggerContext.sessionId);
621
+ expectRootEvent(pageGoto);
622
+ expectDirectParent(pageGotoCompleted, pageGoto);
623
+ expectRootEvent(pageScreenshot);
624
+ expectDirectParent(pageScreenshotCompleted, pageScreenshot);
625
+ expect(eventsOfType(events, "LlmRequestEvent")).toHaveLength(0);
626
+ expect(eventsOfType(events, "LlmResponseEvent")).toHaveLength(0);
627
+ assertNoFloatingCdpEvents(events);
628
+ }
629
+ finally {
630
+ await closeV3(directV3);
631
+ }
632
+ });
633
+ test("direct page methods, direct understudy calls, and direct sendCDP all attach complete event trees to the session", async () => {
634
+ const v3 = createRecordedFlowLoggerV3();
635
+ await v3.init();
636
+ try {
637
+ const page = v3.context.pages()[0];
638
+ await page.goto(encodeHtml(`
639
+ <!doctype html>
640
+ <html>
641
+ <body>
642
+ <button
643
+ id="direct-click"
644
+ onclick="document.body.dataset.directClick='true'"
645
+ >
646
+ Direct Click
647
+ </button>
648
+ <div id="ready">ready</div>
649
+ </body>
650
+ </html>
651
+ `));
652
+ let baseline = await captureFlowEventBaseline(v3);
653
+ await page.evaluate(() => document.getElementById("ready")?.textContent);
654
+ let events = await listRecordedFlowEventsSince(v3, baseline);
655
+ let root = assertCompletedEnvelope(events, "PageEvaluateEvent");
656
+ assertAllParentIdsResolve(events);
657
+ assertNonCdpEventCounts(events, {
658
+ PageEvaluateCompletedEvent: 1,
659
+ PageEvaluateEvent: 1,
660
+ });
661
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
662
+ expectRootEvent(root);
663
+ expect(eventsOfType(events, "LlmRequestEvent")).toHaveLength(0);
664
+ expect(eventsOfType(events, "LlmResponseEvent")).toHaveLength(0);
665
+ assertNoFloatingCdpEvents(events);
666
+ baseline = await captureFlowEventBaseline(v3);
667
+ await page.snapshot();
668
+ events = await listRecordedFlowEventsSince(v3, baseline);
669
+ root = assertCompletedEnvelope(events, "PageSnapshotEvent");
670
+ assertAllParentIdsResolve(events);
671
+ assertNonCdpEventCounts(events, {
672
+ PageSnapshotCompletedEvent: 1,
673
+ PageSnapshotEvent: 1,
674
+ });
675
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
676
+ expectRootEvent(root);
677
+ expect(eventsOfType(events, "LlmRequestEvent")).toHaveLength(0);
678
+ expect(eventsOfType(events, "LlmResponseEvent")).toHaveLength(0);
679
+ assertNoFloatingCdpEvents(events);
680
+ baseline = await captureFlowEventBaseline(v3);
681
+ await performUnderstudyMethod(page, page.mainFrame(), "click", "/html/body/button", [], 30_000);
682
+ events = await listRecordedFlowEventsSince(v3, baseline);
683
+ root = assertCompletedEnvelope(events, "UnderstudyClickEvent");
684
+ assertAllParentIdsResolve(events);
685
+ assertNonCdpEventCounts(events, {
686
+ UnderstudyClickCompletedEvent: 1,
687
+ UnderstudyClickEvent: 1,
688
+ });
689
+ assertSessionIds(events, v3.flowLoggerContext.sessionId);
690
+ expectRootEvent(root);
691
+ expect(eventsOfType(events, "LlmRequestEvent")).toHaveLength(0);
692
+ expect(eventsOfType(events, "LlmResponseEvent")).toHaveLength(0);
693
+ assertNoFloatingCdpEvents(events);
694
+ expect(await page.evaluate(() => document.body.dataset.directClick ?? "")).toBe("true");
695
+ baseline = await captureFlowEventBaseline(v3);
696
+ const cdpResult = await page.sendCDP("Runtime.evaluate", {
697
+ expression: "2 + 2",
698
+ returnByValue: true,
699
+ });
700
+ events = await listRecordedFlowEventsSince(v3, baseline);
701
+ expect(cdpResult.result?.value).toBe(4);
702
+ expect(eventsOfType(events, "LlmRequestEvent")).toHaveLength(0);
703
+ expect(eventsOfType(events, "LlmResponseEvent")).toHaveLength(0);
704
+ assertAllParentIdsResolve(events);
705
+ assertDirectRootCdpEvents(events, v3.flowLoggerContext.sessionId);
706
+ }
707
+ finally {
708
+ await closeV3(v3);
709
+ }
710
+ });
711
+ });
712
+ //# sourceMappingURL=flowLogger.spec.js.map