@runcofactor/cli 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +125 -0
  2. package/dist/api-catalog-GFGCLVS4.js +401 -0
  3. package/dist/api-catalog-GFGCLVS4.js.map +1 -0
  4. package/dist/attachment-SJC7D7FY.js +276 -0
  5. package/dist/attachment-SJC7D7FY.js.map +1 -0
  6. package/dist/auth-EN6NQCJY.js +31 -0
  7. package/dist/auth-EN6NQCJY.js.map +1 -0
  8. package/dist/bin.js +7 -0
  9. package/dist/bin.js.map +1 -0
  10. package/dist/chunk-3CZU7XCS.js +272 -0
  11. package/dist/chunk-3CZU7XCS.js.map +1 -0
  12. package/dist/chunk-4NRDONSC.js +1048 -0
  13. package/dist/chunk-4NRDONSC.js.map +1 -0
  14. package/dist/chunk-4PNOV7OV.js +51980 -0
  15. package/dist/chunk-4PNOV7OV.js.map +1 -0
  16. package/dist/chunk-52IO7ACJ.js +2941 -0
  17. package/dist/chunk-52IO7ACJ.js.map +1 -0
  18. package/dist/chunk-6LEZCKKV.js +663 -0
  19. package/dist/chunk-6LEZCKKV.js.map +1 -0
  20. package/dist/chunk-A4OFTUVV.js +170 -0
  21. package/dist/chunk-A4OFTUVV.js.map +1 -0
  22. package/dist/chunk-B2VISN2V.js +2459 -0
  23. package/dist/chunk-B2VISN2V.js.map +1 -0
  24. package/dist/chunk-CLAWT7BJ.js +271 -0
  25. package/dist/chunk-CLAWT7BJ.js.map +1 -0
  26. package/dist/chunk-GKY2KXO5.js +281 -0
  27. package/dist/chunk-GKY2KXO5.js.map +1 -0
  28. package/dist/chunk-I4QLI3JO.js +413 -0
  29. package/dist/chunk-I4QLI3JO.js.map +1 -0
  30. package/dist/chunk-IMPZAX6L.js +180 -0
  31. package/dist/chunk-IMPZAX6L.js.map +1 -0
  32. package/dist/chunk-J6SH7GQT.js +241 -0
  33. package/dist/chunk-J6SH7GQT.js.map +1 -0
  34. package/dist/chunk-JDBCZ3Z2.js +298 -0
  35. package/dist/chunk-JDBCZ3Z2.js.map +1 -0
  36. package/dist/chunk-JICSYFRJ.js +46 -0
  37. package/dist/chunk-JICSYFRJ.js.map +1 -0
  38. package/dist/chunk-JWHYXEA2.js +32 -0
  39. package/dist/chunk-JWHYXEA2.js.map +1 -0
  40. package/dist/chunk-N7HFALX3.js +88 -0
  41. package/dist/chunk-N7HFALX3.js.map +1 -0
  42. package/dist/chunk-Q4BO4Z5V.js +178 -0
  43. package/dist/chunk-Q4BO4Z5V.js.map +1 -0
  44. package/dist/chunk-QFDOWABX.js +195 -0
  45. package/dist/chunk-QFDOWABX.js.map +1 -0
  46. package/dist/chunk-SJJW77WN.js +605 -0
  47. package/dist/chunk-SJJW77WN.js.map +1 -0
  48. package/dist/chunk-VKVL7WBN.js +50 -0
  49. package/dist/chunk-VKVL7WBN.js.map +1 -0
  50. package/dist/chunk-WOHL4FTB.js +353 -0
  51. package/dist/chunk-WOHL4FTB.js.map +1 -0
  52. package/dist/chunk-XQXVG7RA.js +426 -0
  53. package/dist/chunk-XQXVG7RA.js.map +1 -0
  54. package/dist/chunk-ZD3KHESI.js +94 -0
  55. package/dist/chunk-ZD3KHESI.js.map +1 -0
  56. package/dist/credential-COLTBV6D.js +450 -0
  57. package/dist/credential-COLTBV6D.js.map +1 -0
  58. package/dist/credential-template-EEPYFG7R.js +291 -0
  59. package/dist/credential-template-EEPYFG7R.js.map +1 -0
  60. package/dist/debug-workflow-KOVH5GOP.js +259 -0
  61. package/dist/debug-workflow-KOVH5GOP.js.map +1 -0
  62. package/dist/dist-44PZR7BQ.js +423 -0
  63. package/dist/dist-44PZR7BQ.js.map +1 -0
  64. package/dist/docs-3SSORHB5.js +366 -0
  65. package/dist/docs-3SSORHB5.js.map +1 -0
  66. package/dist/doctor-OTSIWV6G.js +132 -0
  67. package/dist/doctor-OTSIWV6G.js.map +1 -0
  68. package/dist/feature-flag-T2LRZME3.js +283 -0
  69. package/dist/feature-flag-T2LRZME3.js.map +1 -0
  70. package/dist/file-type-S3UUBVCT.js +4205 -0
  71. package/dist/file-type-S3UUBVCT.js.map +1 -0
  72. package/dist/index.d.ts +3 -0
  73. package/dist/index.js +982 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/init-TGWRECT2.js +208 -0
  76. package/dist/init-TGWRECT2.js.map +1 -0
  77. package/dist/install-DXSNWZKO.js +295 -0
  78. package/dist/install-DXSNWZKO.js.map +1 -0
  79. package/dist/issues-XIZOPX6T.js +564 -0
  80. package/dist/issues-XIZOPX6T.js.map +1 -0
  81. package/dist/knobs-NBINZIUB.js +295 -0
  82. package/dist/knobs-NBINZIUB.js.map +1 -0
  83. package/dist/list-3TDHK637.js +59 -0
  84. package/dist/list-3TDHK637.js.map +1 -0
  85. package/dist/local-HICVD6PU.js +100 -0
  86. package/dist/local-HICVD6PU.js.map +1 -0
  87. package/dist/login-UOIUXVUK.js +315 -0
  88. package/dist/login-UOIUXVUK.js.map +1 -0
  89. package/dist/mcp-54IJEOUA.js +1956 -0
  90. package/dist/mcp-54IJEOUA.js.map +1 -0
  91. package/dist/pdf-extract-ABWV3L5X.js +13 -0
  92. package/dist/pdf-extract-ABWV3L5X.js.map +1 -0
  93. package/dist/pdfjs-DQ2YA5S3.js +41561 -0
  94. package/dist/pdfjs-DQ2YA5S3.js.map +1 -0
  95. package/dist/property-UXLOLPTD.js +701 -0
  96. package/dist/property-UXLOLPTD.js.map +1 -0
  97. package/dist/property-skill-guide.md +189 -0
  98. package/dist/psql-BYUUNLGG.js +115 -0
  99. package/dist/psql-BYUUNLGG.js.map +1 -0
  100. package/dist/record-4TR2UIIS.js +233 -0
  101. package/dist/record-4TR2UIIS.js.map +1 -0
  102. package/dist/redis-35VBUYW7.js +116 -0
  103. package/dist/redis-35VBUYW7.js.map +1 -0
  104. package/dist/release-OOLTUBUT.js +354 -0
  105. package/dist/release-OOLTUBUT.js.map +1 -0
  106. package/dist/sandbox-XGSUU6G7.js +2046 -0
  107. package/dist/sandbox-XGSUU6G7.js.map +1 -0
  108. package/dist/scenarios-GWLJEGHA.js +280 -0
  109. package/dist/scenarios-GWLJEGHA.js.map +1 -0
  110. package/dist/session-MYFNZPRP.js +2414 -0
  111. package/dist/session-MYFNZPRP.js.map +1 -0
  112. package/dist/skill-KM34LDMO.js +1839 -0
  113. package/dist/skill-KM34LDMO.js.map +1 -0
  114. package/dist/skills/debug-run/SKILL.md +262 -0
  115. package/dist/skills/debug-run/history/1.0.0.md +244 -0
  116. package/dist/skills/debug-run/history/1.0.1.md +245 -0
  117. package/dist/skills/debug-run/history/1.0.2.md +254 -0
  118. package/dist/skills/debug-run/history/1.1.0.md +262 -0
  119. package/dist/skills/debug-run/version.json +3 -0
  120. package/dist/spec-RA3CFEKG.js +951 -0
  121. package/dist/spec-RA3CFEKG.js.map +1 -0
  122. package/dist/src-LB4HBTJQ.js +1516 -0
  123. package/dist/src-LB4HBTJQ.js.map +1 -0
  124. package/dist/src-N3TPKGTY.js +244 -0
  125. package/dist/src-N3TPKGTY.js.map +1 -0
  126. package/dist/src-UVDX5UP6.js +17 -0
  127. package/dist/src-UVDX5UP6.js.map +1 -0
  128. package/dist/start-PSEQSQA3.js +151 -0
  129. package/dist/start-PSEQSQA3.js.map +1 -0
  130. package/dist/tag-YY6YAABQ.js +22 -0
  131. package/dist/tag-YY6YAABQ.js.map +1 -0
  132. package/dist/task-IQC3YR5K.js +922 -0
  133. package/dist/task-IQC3YR5K.js.map +1 -0
  134. package/dist/test-suite-DZNJFXDW.js +394 -0
  135. package/dist/test-suite-DZNJFXDW.js.map +1 -0
  136. package/dist/trace-HXIW7U2D.js +14 -0
  137. package/dist/trace-HXIW7U2D.js.map +1 -0
  138. package/dist/triage-ORG45R5W.js +16 -0
  139. package/dist/triage-ORG45R5W.js.map +1 -0
  140. package/dist/upgrade-L2VYMP2V.js +61 -0
  141. package/dist/upgrade-L2VYMP2V.js.map +1 -0
  142. package/dist/workflow-XYFH5LTP.js +3708 -0
  143. package/dist/workflow-XYFH5LTP.js.map +1 -0
  144. package/dist/workflow-cache-NQJQFWMV.js +253 -0
  145. package/dist/workflow-cache-NQJQFWMV.js.map +1 -0
  146. package/dist/workflow-debug-BGD4UIXU.js +927 -0
  147. package/dist/workflow-debug-BGD4UIXU.js.map +1 -0
  148. package/dist/workflow-proposals-MTUDX46O.js +382 -0
  149. package/dist/workflow-proposals-MTUDX46O.js.map +1 -0
  150. package/package.json +61 -0
@@ -0,0 +1,413 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+ import {
3
+ PlaywrightClient
4
+ } from "./chunk-4PNOV7OV.js";
5
+
6
+ // src/local-browser/host.ts
7
+ var HEARTBEAT_INTERVAL_MS = 3e4;
8
+ var RECONNECT_DELAY_MS = 1e3;
9
+ var MAX_RECONNECT_DELAY_MS = 3e4;
10
+ var MAX_RECONNECT_ATTEMPTS = 10;
11
+ var LocalBrowserHost = class {
12
+ options;
13
+ ws = null;
14
+ client;
15
+ heartbeatTimer = null;
16
+ reconnectAttempts = 0;
17
+ isShuttingDown = false;
18
+ constructor(options) {
19
+ this.options = options;
20
+ if (options.existingClient) {
21
+ this.client = options.existingClient;
22
+ } else {
23
+ const logger = {
24
+ debug: (msg, data) => this.log("debug", msg, data),
25
+ info: (msg, data) => this.log("info", msg, data),
26
+ warn: (msg, data) => this.log("warn", msg, data),
27
+ error: (msg, data) => this.log("error", msg, data)
28
+ };
29
+ this.client = new PlaywrightClient({ logger });
30
+ }
31
+ }
32
+ log(level, message, data) {
33
+ if (this.options.onLog) {
34
+ this.options.onLog(level, message, data);
35
+ } else {
36
+ const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
37
+ fn(`[LocalBrowserHost] ${message}`, data ?? "");
38
+ }
39
+ }
40
+ // =========================================================================
41
+ // Lifecycle
42
+ // =========================================================================
43
+ async start() {
44
+ this.log("info", "Starting local browser host", {
45
+ browserMode: this.options.browserMode,
46
+ sessionId: this.options.sessionId
47
+ });
48
+ await this.connectWebSocket();
49
+ if (!this.options.skipBrowserLaunch) {
50
+ const { browserMode, cdpUrl, headless = true, storageStatePath } = this.options;
51
+ await this.client.connect({
52
+ browserMode: headless ? "headless" : "headed",
53
+ cdpUrl: browserMode === "cdp" ? cdpUrl : void 0,
54
+ storageStatePath
55
+ });
56
+ }
57
+ this.sendSessionEvent("browser_ready");
58
+ }
59
+ async stop() {
60
+ this.isShuttingDown = true;
61
+ this.log("info", "Stopping local browser host");
62
+ this.stopHeartbeat();
63
+ if (this.ws) {
64
+ try {
65
+ this.ws.close(1e3, "Shutdown");
66
+ } catch {
67
+ }
68
+ this.ws = null;
69
+ }
70
+ if (!this.options.existingClient) {
71
+ await this.client.disconnect();
72
+ }
73
+ this.log("info", "Local browser host stopped");
74
+ }
75
+ // =========================================================================
76
+ // WebSocket Connection
77
+ // =========================================================================
78
+ async connectWebSocket() {
79
+ return new Promise((resolve, reject) => {
80
+ const wsUrl = `${this.options.apiUrl.replace("http", "ws")}/local-browser/sessions/${this.options.sessionId}/connect?token=${this.options.wsToken}`;
81
+ this.log("info", "Connecting to cloud API", { url: wsUrl.replace(/token=.*/, "token=***") });
82
+ const ws = new WebSocket(wsUrl);
83
+ ws.onopen = () => {
84
+ this.log("info", "Connected to cloud API");
85
+ this.ws = ws;
86
+ this.reconnectAttempts = 0;
87
+ this.startHeartbeat();
88
+ resolve();
89
+ };
90
+ ws.onmessage = (event) => {
91
+ this.handleMessage(event.data);
92
+ };
93
+ ws.onerror = (event) => {
94
+ this.log("error", "WebSocket error", event);
95
+ };
96
+ ws.onclose = () => {
97
+ this.log("info", "WebSocket closed");
98
+ this.stopHeartbeat();
99
+ this.ws = null;
100
+ if (!this.isShuttingDown) {
101
+ this.scheduleReconnect();
102
+ }
103
+ };
104
+ setTimeout(() => {
105
+ if (!this.ws) {
106
+ reject(new Error("WebSocket connection timeout"));
107
+ }
108
+ }, 3e4);
109
+ });
110
+ }
111
+ scheduleReconnect() {
112
+ if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
113
+ this.log("error", "Max reconnection attempts reached, giving up");
114
+ this.stop();
115
+ return;
116
+ }
117
+ const delay = Math.min(
118
+ RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),
119
+ MAX_RECONNECT_DELAY_MS
120
+ );
121
+ this.reconnectAttempts++;
122
+ this.log("info", `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
123
+ setTimeout(async () => {
124
+ try {
125
+ await this.connectWebSocket();
126
+ this.sendSessionEvent("connected");
127
+ this.sendSessionEvent("browser_ready");
128
+ } catch (error) {
129
+ this.log("error", "Reconnection failed", error);
130
+ this.scheduleReconnect();
131
+ }
132
+ }, delay);
133
+ }
134
+ // =========================================================================
135
+ // Heartbeat
136
+ // =========================================================================
137
+ startHeartbeat() {
138
+ this.stopHeartbeat();
139
+ this.heartbeatTimer = setInterval(() => {
140
+ if (this.ws?.readyState === WebSocket.OPEN) {
141
+ const ping = {
142
+ type: "heartbeat",
143
+ id: crypto.randomUUID(),
144
+ timestamp: Date.now(),
145
+ direction: "pong"
146
+ };
147
+ this.ws.send(JSON.stringify(ping));
148
+ }
149
+ }, HEARTBEAT_INTERVAL_MS);
150
+ }
151
+ stopHeartbeat() {
152
+ if (this.heartbeatTimer) {
153
+ clearInterval(this.heartbeatTimer);
154
+ this.heartbeatTimer = null;
155
+ }
156
+ }
157
+ // =========================================================================
158
+ // Message Handling
159
+ // =========================================================================
160
+ handleMessage(data) {
161
+ try {
162
+ const message = JSON.parse(data);
163
+ if (message.type === "heartbeat" && message.direction === "ping") {
164
+ const pong = {
165
+ type: "heartbeat",
166
+ id: crypto.randomUUID(),
167
+ timestamp: Date.now(),
168
+ direction: "pong"
169
+ };
170
+ this.ws?.send(JSON.stringify(pong));
171
+ return;
172
+ }
173
+ if (message.type === "command") {
174
+ this.handleCommand(message);
175
+ return;
176
+ }
177
+ this.log("debug", "Received unknown message type", message);
178
+ } catch (error) {
179
+ this.log("error", "Failed to parse message", { error, data });
180
+ }
181
+ }
182
+ async handleCommand(command) {
183
+ const startTime = Date.now();
184
+ if (this.options.shouldExecute && !this.options.shouldExecute()) {
185
+ const response = {
186
+ type: "response",
187
+ id: crypto.randomUUID(),
188
+ timestamp: Date.now(),
189
+ requestId: command.id,
190
+ success: false,
191
+ error: "paused",
192
+ contextId: command.contextId
193
+ };
194
+ this.ws?.send(JSON.stringify(response));
195
+ this.log("debug", `Command rejected (paused): ${command.method}`, { id: command.id });
196
+ return;
197
+ }
198
+ this.log("debug", `Executing command: ${command.method}`, { id: command.id });
199
+ try {
200
+ const result = await this.executeMethod(command.method, command.args);
201
+ const response = {
202
+ type: "response",
203
+ id: crypto.randomUUID(),
204
+ timestamp: Date.now(),
205
+ requestId: command.id,
206
+ success: true,
207
+ result,
208
+ contextId: command.contextId
209
+ };
210
+ this.ws?.send(JSON.stringify(response));
211
+ this.log("debug", `Command completed: ${command.method}`, {
212
+ id: command.id,
213
+ durationMs: Date.now() - startTime
214
+ });
215
+ } catch (error) {
216
+ const errorMessage = error instanceof Error ? error.message : String(error);
217
+ const response = {
218
+ type: "response",
219
+ id: crypto.randomUUID(),
220
+ timestamp: Date.now(),
221
+ requestId: command.id,
222
+ success: false,
223
+ error: errorMessage,
224
+ stack: error instanceof Error ? error.stack : void 0,
225
+ contextId: command.contextId
226
+ };
227
+ this.ws?.send(JSON.stringify(response));
228
+ this.log("error", `Command failed: ${command.method}`, {
229
+ id: command.id,
230
+ error: errorMessage
231
+ });
232
+ }
233
+ }
234
+ sendSessionEvent(event, error) {
235
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
236
+ const message = {
237
+ type: "session",
238
+ id: crypto.randomUUID(),
239
+ timestamp: Date.now(),
240
+ event,
241
+ browserMode: this.options.browserMode,
242
+ error
243
+ };
244
+ this.ws.send(JSON.stringify(message));
245
+ }
246
+ // =========================================================================
247
+ // Method Execution — Delegate to PlaywrightClient
248
+ // =========================================================================
249
+ /**
250
+ * Maps incoming WebSocket command method names to PlaywrightClient methods.
251
+ * The client implements IBrowserClient, so all standard browser operations
252
+ * are available and behave identically to the cloud environment.
253
+ */
254
+ async executeMethod(method, args) {
255
+ const client = this.client;
256
+ switch (method) {
257
+ // Lifecycle
258
+ case "connect":
259
+ return client.connect(args[0]);
260
+ case "disconnect":
261
+ return client.disconnect();
262
+ // Navigation
263
+ case "navigate":
264
+ return client.navigate(args[0], args[1]);
265
+ case "navigateBack":
266
+ return client.navigateBack(args[0]);
267
+ // Page Inspection
268
+ case "snapshot":
269
+ return client.snapshot(args[0]);
270
+ case "takeScreenshot":
271
+ return client.takeScreenshot(args[0]);
272
+ case "evaluate":
273
+ return client.evaluate(args[0], args[1]);
274
+ case "runCode":
275
+ return client.runCode(args[0], args[1]);
276
+ case "consoleMessages":
277
+ return client.consoleMessages(args[0]);
278
+ case "networkRequests":
279
+ return client.networkRequests(args[0]);
280
+ // Interaction
281
+ case "click":
282
+ return client.click(args[0], args[1], args[2]);
283
+ case "clickAtCoordinates":
284
+ return client.clickAtCoordinates(
285
+ args[0],
286
+ args[1],
287
+ args[2],
288
+ args[3]
289
+ );
290
+ case "moveToCoordinates":
291
+ return client.moveToCoordinates(
292
+ args[0],
293
+ args[1],
294
+ args[2],
295
+ args[3]
296
+ );
297
+ case "dragCoordinates":
298
+ return client.dragCoordinates(
299
+ args[0],
300
+ args[1],
301
+ args[2],
302
+ args[3],
303
+ args[4],
304
+ args[5]
305
+ );
306
+ case "hover":
307
+ return client.hover(args[0], args[1], args[2]);
308
+ case "drag":
309
+ return client.drag(
310
+ args[0],
311
+ args[1],
312
+ args[2],
313
+ args[3],
314
+ args[4]
315
+ );
316
+ case "type":
317
+ return client.type(
318
+ args[0],
319
+ args[1],
320
+ args[2],
321
+ args[3],
322
+ args[4]
323
+ );
324
+ case "pressKey":
325
+ return client.pressKey(args[0], args[1]);
326
+ case "fillForm":
327
+ return client.fillForm(args[0], args[1]);
328
+ case "selectOption":
329
+ return client.selectOption(
330
+ args[0],
331
+ args[1],
332
+ args[2],
333
+ args[3]
334
+ );
335
+ case "fileUpload":
336
+ return client.fileUpload(args[0], args[1]);
337
+ // Scroll
338
+ case "scroll":
339
+ return client.scroll(
340
+ args[0],
341
+ args[1],
342
+ args[2],
343
+ args[3],
344
+ args[4]
345
+ );
346
+ // Dialogs
347
+ case "handleDialog":
348
+ return client.handleDialog(
349
+ args[0],
350
+ args[1],
351
+ args[2]
352
+ );
353
+ // Waiting
354
+ case "waitFor":
355
+ return client.waitFor(args[0]);
356
+ // Browser Management
357
+ case "close":
358
+ return client.close(args[0]);
359
+ case "resize":
360
+ return client.resize(args[0], args[1], args[2]);
361
+ case "tabs":
362
+ return client.tabs(
363
+ args[0],
364
+ args[1],
365
+ args[2]
366
+ );
367
+ // Context Management
368
+ case "swapContext":
369
+ return client.swapContext?.(args[0]);
370
+ // Storage & Page Info
371
+ case "getStorageState":
372
+ return client.getStorageState(args[0]);
373
+ case "getCurrentUrl":
374
+ return client.getCurrentUrl(args[0]);
375
+ case "getTitle":
376
+ return client.getTitle(args[0]);
377
+ case "getLinks":
378
+ return client.getLinks(args[0]);
379
+ case "getElementBoundingBox":
380
+ return client.getElementBoundingBox(args[0], args[1]);
381
+ // Tracing
382
+ case "startTracing":
383
+ return client.startTracing?.(args[0]);
384
+ case "stopTracing":
385
+ return client.stopTracing?.(args[0]);
386
+ // Video
387
+ case "isVideoRecordingEnabled":
388
+ return client.isVideoRecordingEnabled?.() ?? false;
389
+ case "saveVideo":
390
+ return client.saveVideo?.() ?? null;
391
+ case "getVideoPath":
392
+ return null;
393
+ // Screencast
394
+ case "startScreencast":
395
+ return client.startScreencast?.(args[0], args[1]);
396
+ case "stopScreencast":
397
+ return client.stopScreencast?.();
398
+ case "isScreencastActive":
399
+ return client.isScreencastActive?.() ?? false;
400
+ default:
401
+ throw new Error(`Unknown method: ${method}`);
402
+ }
403
+ }
404
+ };
405
+
406
+ // src/session/types.ts
407
+ var USER_PAUSED_ERROR = "USER_PAUSED";
408
+
409
+ export {
410
+ LocalBrowserHost,
411
+ USER_PAUSED_ERROR
412
+ };
413
+ //# sourceMappingURL=chunk-I4QLI3JO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/local-browser/host.ts","../src/session/types.ts"],"sourcesContent":["/**\n * Local Browser Host\n *\n * Manages a local browser instance and handles commands from the cloud API\n * via WebSocket. Delegates all browser automation to PlaywrightClient from\n * @chatsdet/browser-core, ensuring the agent experience is identical\n * regardless of whether the browser is local or cloud.\n *\n * @module local-browser-host\n */\n\nimport { PlaywrightClient, type IBrowserClient, type BrowserLogger } from '@chatsdet/browser-core';\nimport type {\n BrowserCommand,\n BrowserResponse,\n HeartbeatMessage,\n SessionMessage,\n LocalBrowserMode,\n} from './protocol';\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 1000;\nconst MAX_RECONNECT_DELAY_MS = 30_000;\nconst MAX_RECONNECT_ATTEMPTS = 10;\n\ninterface LocalBrowserHostOptions {\n apiUrl: string;\n wsToken: string;\n sessionId: string;\n browserMode: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n onLog?: (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: unknown) => void;\n /** Inject an existing PlaywrightClient instead of creating a new one */\n existingClient?: PlaywrightClient;\n /** When true, skip browser launch in start() — used when browser is already managed externally */\n skipBrowserLaunch?: boolean;\n /** Called before executing a command — return false to reject with a 'paused' error */\n shouldExecute?: () => boolean;\n}\n\n/**\n * LocalBrowserHost manages the WebSocket connection to the cloud API and\n * delegates all browser operations to a shared PlaywrightClient instance.\n */\nexport class LocalBrowserHost {\n private options: LocalBrowserHostOptions;\n private ws: WebSocket | null = null;\n private client: PlaywrightClient;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private reconnectAttempts = 0;\n private isShuttingDown = false;\n\n constructor(options: LocalBrowserHostOptions) {\n this.options = options;\n if (options.existingClient) {\n this.client = options.existingClient;\n } else {\n const logger: BrowserLogger = {\n debug: (msg: string, data?: Record<string, unknown>) => this.log('debug', msg, data),\n info: (msg: string, data?: Record<string, unknown>) => this.log('info', msg, data),\n warn: (msg: string, data?: Record<string, unknown>) => this.log('warn', msg, data),\n error: (msg: string, data?: Record<string, unknown>) => this.log('error', msg, data),\n };\n this.client = new PlaywrightClient({ logger });\n }\n }\n\n private log(level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: unknown) {\n if (this.options.onLog) {\n this.options.onLog(level, message, data);\n } else {\n const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;\n fn(`[LocalBrowserHost] ${message}`, data ?? '');\n }\n }\n\n // =========================================================================\n // Lifecycle\n // =========================================================================\n\n async start(): Promise<void> {\n this.log('info', 'Starting local browser host', {\n browserMode: this.options.browserMode,\n sessionId: this.options.sessionId,\n });\n\n // Connect to WebSocket first\n await this.connectWebSocket();\n\n if (!this.options.skipBrowserLaunch) {\n // Launch browser via PlaywrightClient\n const { browserMode, cdpUrl, headless = true, storageStatePath } = this.options;\n await this.client.connect({\n browserMode: headless ? 'headless' : 'headed',\n cdpUrl: browserMode === 'cdp' ? cdpUrl : undefined,\n storageStatePath,\n });\n }\n\n // Notify cloud that browser is ready\n this.sendSessionEvent('browser_ready');\n }\n\n async stop(): Promise<void> {\n this.isShuttingDown = true;\n this.log('info', 'Stopping local browser host');\n\n this.stopHeartbeat();\n\n if (this.ws) {\n try {\n this.ws.close(1000, 'Shutdown');\n } catch {\n /* intentionally empty */\n }\n this.ws = null;\n }\n\n // Only disconnect client if we own it (not injected externally)\n if (!this.options.existingClient) {\n await this.client.disconnect();\n }\n this.log('info', 'Local browser host stopped');\n }\n\n // =========================================================================\n // WebSocket Connection\n // =========================================================================\n\n private async connectWebSocket(): Promise<void> {\n return new Promise((resolve, reject) => {\n const wsUrl = `${this.options.apiUrl.replace('http', 'ws')}/local-browser/sessions/${this.options.sessionId}/connect?token=${this.options.wsToken}`;\n\n this.log('info', 'Connecting to cloud API', { url: wsUrl.replace(/token=.*/, 'token=***') });\n\n const ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n this.log('info', 'Connected to cloud API');\n this.ws = ws;\n this.reconnectAttempts = 0;\n this.startHeartbeat();\n resolve();\n };\n\n ws.onmessage = (event) => {\n this.handleMessage(event.data as string);\n };\n\n ws.onerror = (event) => {\n this.log('error', 'WebSocket error', event);\n };\n\n ws.onclose = () => {\n this.log('info', 'WebSocket closed');\n this.stopHeartbeat();\n this.ws = null;\n\n if (!this.isShuttingDown) {\n this.scheduleReconnect();\n }\n };\n\n // Timeout after 30 seconds\n setTimeout(() => {\n if (!this.ws) {\n reject(new Error('WebSocket connection timeout'));\n }\n }, 30_000);\n });\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n this.log('error', 'Max reconnection attempts reached, giving up');\n this.stop();\n return;\n }\n\n const delay = Math.min(\n RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),\n MAX_RECONNECT_DELAY_MS\n );\n\n this.reconnectAttempts++;\n this.log('info', `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n\n setTimeout(async () => {\n try {\n await this.connectWebSocket();\n this.sendSessionEvent('connected');\n this.sendSessionEvent('browser_ready');\n } catch (error) {\n this.log('error', 'Reconnection failed', error);\n this.scheduleReconnect();\n }\n }, delay);\n }\n\n // =========================================================================\n // Heartbeat\n // =========================================================================\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n const ping: HeartbeatMessage = {\n type: 'heartbeat',\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n direction: 'pong',\n };\n this.ws.send(JSON.stringify(ping));\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n // =========================================================================\n // Message Handling\n // =========================================================================\n\n private handleMessage(data: string): void {\n try {\n const message = JSON.parse(data);\n\n if (message.type === 'heartbeat' && message.direction === 'ping') {\n const pong: HeartbeatMessage = {\n type: 'heartbeat',\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n direction: 'pong',\n };\n this.ws?.send(JSON.stringify(pong));\n return;\n }\n\n if (message.type === 'command') {\n this.handleCommand(message as BrowserCommand);\n return;\n }\n\n this.log('debug', 'Received unknown message type', message);\n } catch (error) {\n this.log('error', 'Failed to parse message', { error, data });\n }\n }\n\n private async handleCommand(command: BrowserCommand): Promise<void> {\n const startTime = Date.now();\n\n // Check if execution is allowed (e.g. session may be paused by CLI taking control)\n if (this.options.shouldExecute && !this.options.shouldExecute()) {\n const response: BrowserResponse = {\n type: 'response',\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: false,\n error: 'paused',\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n this.log('debug', `Command rejected (paused): ${command.method}`, { id: command.id });\n return;\n }\n\n this.log('debug', `Executing command: ${command.method}`, { id: command.id });\n\n try {\n const result = await this.executeMethod(command.method, command.args);\n const response: BrowserResponse = {\n type: 'response',\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: true,\n result,\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log('debug', `Command completed: ${command.method}`, {\n id: command.id,\n durationMs: Date.now() - startTime,\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const response: BrowserResponse = {\n type: 'response',\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: false,\n error: errorMessage,\n stack: error instanceof Error ? error.stack : undefined,\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log('error', `Command failed: ${command.method}`, {\n id: command.id,\n error: errorMessage,\n });\n }\n }\n\n private sendSessionEvent(\n event: 'connected' | 'disconnected' | 'browser_ready' | 'browser_closed' | 'error',\n error?: string\n ): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n\n const message: SessionMessage = {\n type: 'session',\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n event,\n browserMode: this.options.browserMode,\n error,\n };\n this.ws.send(JSON.stringify(message));\n }\n\n // =========================================================================\n // Method Execution — Delegate to PlaywrightClient\n // =========================================================================\n\n /**\n * Maps incoming WebSocket command method names to PlaywrightClient methods.\n * The client implements IBrowserClient, so all standard browser operations\n * are available and behave identically to the cloud environment.\n */\n private async executeMethod(method: string, args: unknown[]): Promise<unknown> {\n const client = this.client as IBrowserClient;\n\n switch (method) {\n // Lifecycle\n case 'connect':\n return client.connect(args[0] as any);\n case 'disconnect':\n return client.disconnect();\n\n // Navigation\n case 'navigate':\n return client.navigate(args[0] as string, args[1] as any);\n case 'navigateBack':\n return client.navigateBack(args[0] as any);\n\n // Page Inspection\n case 'snapshot':\n return client.snapshot(args[0] as any);\n case 'takeScreenshot':\n return client.takeScreenshot(args[0] as any);\n case 'evaluate':\n return client.evaluate(args[0] as string, args[1] as any);\n case 'runCode':\n return client.runCode(args[0] as string, args[1] as any);\n case 'consoleMessages':\n return client.consoleMessages(args[0] as any);\n case 'networkRequests':\n return client.networkRequests(args[0] as any);\n\n // Interaction\n case 'click':\n return client.click(args[0] as string, args[1] as string, args[2] as any);\n case 'clickAtCoordinates':\n return client.clickAtCoordinates(\n args[0] as number,\n args[1] as number,\n args[2] as string,\n args[3] as any\n );\n case 'moveToCoordinates':\n return client.moveToCoordinates(\n args[0] as number,\n args[1] as number,\n args[2] as string,\n args[3] as any\n );\n case 'dragCoordinates':\n return client.dragCoordinates(\n args[0] as number,\n args[1] as number,\n args[2] as number,\n args[3] as number,\n args[4] as string,\n args[5] as any\n );\n case 'hover':\n return client.hover(args[0] as string, args[1] as string, args[2] as any);\n case 'drag':\n return client.drag(\n args[0] as string,\n args[1] as string,\n args[2] as string,\n args[3] as string,\n args[4] as any\n );\n case 'type':\n return client.type(\n args[0] as string,\n args[1] as string,\n args[2] as string,\n args[3] as boolean,\n args[4] as any\n );\n case 'pressKey':\n return client.pressKey(args[0] as string, args[1] as any);\n case 'fillForm':\n return client.fillForm(args[0] as any[], args[1] as any);\n case 'selectOption':\n return client.selectOption(\n args[0] as string,\n args[1] as string,\n args[2] as string,\n args[3] as any\n );\n case 'fileUpload':\n return client.fileUpload(args[0] as string[], args[1] as any);\n\n // Scroll\n case 'scroll':\n return client.scroll(\n args[0] as any,\n args[1] as any,\n args[2] as any,\n args[3] as any,\n args[4] as any\n );\n\n // Dialogs\n case 'handleDialog':\n return client.handleDialog(\n args[0] as 'accept' | 'dismiss',\n args[1] as string,\n args[2] as any\n );\n\n // Waiting\n case 'waitFor':\n return client.waitFor(args[0] as any);\n\n // Browser Management\n case 'close':\n return client.close(args[0] as any);\n case 'resize':\n return client.resize(args[0] as number, args[1] as number, args[2] as any);\n case 'tabs':\n return client.tabs(\n args[0] as 'list' | 'new' | 'close' | 'select',\n args[1] as number,\n args[2] as any\n );\n\n // Context Management\n case 'swapContext':\n return client.swapContext?.(args[0] as any);\n\n // Storage & Page Info\n case 'getStorageState':\n return client.getStorageState(args[0] as any);\n case 'getCurrentUrl':\n return client.getCurrentUrl(args[0] as any);\n case 'getTitle':\n return client.getTitle(args[0] as any);\n case 'getLinks':\n return client.getLinks(args[0] as any);\n case 'getElementBoundingBox':\n return client.getElementBoundingBox(args[0] as string, args[1] as any);\n\n // Tracing\n case 'startTracing':\n return client.startTracing?.(args[0] as any);\n case 'stopTracing':\n return client.stopTracing?.(args[0] as any);\n\n // Video\n case 'isVideoRecordingEnabled':\n return client.isVideoRecordingEnabled?.() ?? false;\n case 'saveVideo':\n return client.saveVideo?.() ?? null;\n case 'getVideoPath':\n return null;\n\n // Screencast\n case 'startScreencast':\n return client.startScreencast?.(args[0] as any, args[1] as any);\n case 'stopScreencast':\n return client.stopScreencast?.();\n case 'isScreencastActive':\n return client.isScreencastActive?.() ?? false;\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n }\n}\n","/**\n * Shared types for the session daemon and CLI client.\n *\n * @module\n */\n\n/** Persisted in the pidfile at ~/.config/canary-cli/daemon.json */\nexport interface DaemonState {\n pid: number;\n port: number;\n startedAt: string;\n}\n\n/** Who currently controls the browser session */\nexport type SessionController = 'cli' | 'cloud';\n\n/** Session-level lock — one controller at a time owns the browser */\nexport interface SessionLock {\n controller: SessionController;\n acquiredAt: string;\n /** If cloud was paused by a CLI force-take, the cloud can resume later */\n cloudPaused: boolean;\n}\n\n/** Session metadata returned by the daemon */\nexport interface SessionInfo {\n id: string;\n name: string;\n mode: 'headed' | 'headless' | 'attached';\n credentialName?: string;\n url?: string;\n startedAt: string;\n /** ISO timestamp of the last action dispatched to this session. Refreshed on tool/lock/swap calls. */\n lastActivityAt: string;\n lock: SessionLock;\n cloudRelay?: { localBrowserSessionId: string; connected: boolean };\n}\n\n/** Configuration for connecting a session to the cloud API via WebSocket relay */\nexport interface CloudRelayConfig {\n apiUrl: string;\n token: string;\n}\n\n/** Body for POST /sessions */\nexport interface CreateSessionRequest {\n headless?: boolean;\n storageStatePath?: string;\n name?: string;\n url?: string;\n credentialName?: string;\n /** When present, the daemon connects this session to the cloud API via WebSocket */\n cloudRelay?: CloudRelayConfig;\n /**\n * Attach to the user's running Chrome via chrome-devtools-mcp.\n * Mutually exclusive with headless/storageStatePath/credentialName/url/cloudRelay.\n */\n attachRealChrome?: boolean;\n}\n\n/** Standard daemon response envelope */\nexport interface DaemonResponse<T = unknown> {\n ok: boolean;\n data?: T;\n error?: string;\n}\n\n/** Body for POST /sessions/:id/swap-context */\nexport interface SwapContextRequest {\n storageStatePath?: string;\n credentialName?: string;\n}\n\n/** Error code returned when the user has paused the agent via the browser badge */\nexport const USER_PAUSED_ERROR = 'USER_PAUSED' as const;\n\n/** Tool execution response */\nexport interface ToolResponse {\n ok: boolean;\n text?: string;\n images?: Array<{ data: string; mimeType: string }>;\n error?: string;\n /** Structured error code for programmatic handling */\n errorCode?: string;\n}\n\n/** Response for GET /sessions/:id/storage-state */\nexport interface StorageStateResponse {\n ok: boolean;\n data?: unknown;\n error?: string;\n}\n\n/* ── Captured interaction event types ──────────────────────────────── */\n\n// Authoritative home is @chatsdet/recording; re-exported here so existing imports\n// via `@/session/types` keep working.\nexport type { CapturedInteractionEvent, TakeoverEvent } from '@chatsdet/recording';\n"],"mappings":"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","names":[]}
@@ -0,0 +1,180 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+
3
+ // ../recording/src/capture-script.ts
4
+ var INTERACTION_CAPTURE_SCRIPT = `
5
+ (function() {
6
+ if (window.__canaryRecordedEvents) return;
7
+ window.__canaryRecordedEvents = [];
8
+
9
+ function push(event) {
10
+ window.__canaryRecordedEvents.push(event);
11
+ }
12
+
13
+ // --- Input debouncing ---
14
+ // Accumulate input per element, flush after 500ms idle as a single event
15
+ var inputTimers = new Map();
16
+ var inputState = new Map();
17
+
18
+ function getElementKey(el) {
19
+ return (el.id || '') + '|' + (el.getAttribute('aria-label') || '') + '|' + (el.tagName || '') + '|' + (el.getAttribute('name') || '');
20
+ }
21
+
22
+ function flushInput(key) {
23
+ var state = inputState.get(key);
24
+ if (!state) return;
25
+ inputTimers.delete(key);
26
+ inputState.delete(key);
27
+ push({
28
+ type: 'input',
29
+ ts: state.ts,
30
+ tagName: state.tagName,
31
+ id: state.id || undefined,
32
+ inputType: state.inputType || undefined,
33
+ ariaLabel: state.ariaLabel || undefined,
34
+ value: state.value,
35
+ isFinal: true,
36
+ });
37
+ }
38
+
39
+ function flushAllInputs() {
40
+ inputTimers.forEach(function(timer) { clearTimeout(timer); });
41
+ inputState.forEach(function(_, key) { flushInput(key); });
42
+ }
43
+
44
+ document.addEventListener('click', function(e) {
45
+ var el = e.target;
46
+ push({
47
+ type: 'click',
48
+ ts: Date.now(),
49
+ x: e.clientX,
50
+ y: e.clientY,
51
+ tagName: el.tagName || '',
52
+ id: el.id || undefined,
53
+ testId: el.getAttribute('data-testid') || undefined,
54
+ ariaLabel: el.getAttribute('aria-label') || undefined,
55
+ role: el.getAttribute('role') || undefined,
56
+ textContent: (el.textContent || '').slice(0, 200).trim() || undefined,
57
+ className: el.className && typeof el.className === 'string' ? el.className.slice(0, 200) : undefined,
58
+ });
59
+ }, true);
60
+
61
+ document.addEventListener('input', function(e) {
62
+ var el = e.target;
63
+ var isPassword = el.type === 'password';
64
+ var key = getElementKey(el);
65
+
66
+ // Clear previous timer for this element
67
+ var prevTimer = inputTimers.get(key);
68
+ if (prevTimer) clearTimeout(prevTimer);
69
+
70
+ // Update accumulated state
71
+ inputState.set(key, {
72
+ ts: Date.now(),
73
+ tagName: el.tagName || '',
74
+ id: el.id || undefined,
75
+ inputType: el.type || undefined,
76
+ ariaLabel: el.getAttribute('aria-label') || undefined,
77
+ value: isPassword ? '***' : (el.value || ''),
78
+ });
79
+
80
+ // Schedule flush after 500ms idle
81
+ inputTimers.set(key, setTimeout(function() { flushInput(key); }, 500));
82
+ }, true);
83
+
84
+ // --- Change events for selects, checkboxes, radios ---
85
+ document.addEventListener('change', function(e) {
86
+ var el = e.target;
87
+ var tagName = el.tagName || '';
88
+ var inputType = (el.type || '').toLowerCase();
89
+
90
+ // Only capture selects, checkboxes, and radios (text inputs are handled by input debouncing)
91
+ var isSelect = tagName === 'SELECT';
92
+ var isCheckOrRadio = inputType === 'checkbox' || inputType === 'radio';
93
+ if (!isSelect && !isCheckOrRadio) return;
94
+
95
+ var value;
96
+ if (isCheckOrRadio) {
97
+ value = el.checked ? 'checked' : 'unchecked';
98
+ } else {
99
+ value = el.value || '';
100
+ }
101
+
102
+ push({
103
+ type: 'change',
104
+ ts: Date.now(),
105
+ tagName: tagName,
106
+ id: el.id || undefined,
107
+ inputType: inputType || undefined,
108
+ ariaLabel: el.getAttribute('aria-label') || undefined,
109
+ value: value,
110
+ });
111
+ }, true);
112
+
113
+ document.addEventListener('keydown', function(e) {
114
+ if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {
115
+ push({
116
+ type: 'keydown',
117
+ ts: Date.now(),
118
+ key: e.key,
119
+ });
120
+ }
121
+ }, true);
122
+
123
+ // --- SPA navigation: patch pushState/replaceState ---
124
+ var origPushState = history.pushState;
125
+ var origReplaceState = history.replaceState;
126
+
127
+ history.pushState = function() {
128
+ origPushState.apply(this, arguments);
129
+ push({
130
+ type: 'navigation',
131
+ ts: Date.now(),
132
+ url: window.location.href,
133
+ navigationType: 'pushState',
134
+ });
135
+ };
136
+
137
+ history.replaceState = function() {
138
+ origReplaceState.apply(this, arguments);
139
+ push({
140
+ type: 'navigation',
141
+ ts: Date.now(),
142
+ url: window.location.href,
143
+ navigationType: 'replaceState',
144
+ });
145
+ };
146
+
147
+ window.addEventListener('popstate', function() {
148
+ push({
149
+ type: 'navigation',
150
+ ts: Date.now(),
151
+ url: window.location.href,
152
+ navigationType: 'popstate',
153
+ });
154
+ });
155
+
156
+ window.addEventListener('beforeunload', function() {
157
+ flushAllInputs();
158
+ push({
159
+ type: 'navigation',
160
+ ts: Date.now(),
161
+ url: window.location.href,
162
+ navigationType: 'beforeunload',
163
+ });
164
+ });
165
+
166
+ // Exposed for discardCapturedEvents \u2014 cancels pending debounce timers
167
+ // without flushing their events, then clears the array.
168
+ window.__canaryFlushAndDiscard = function() {
169
+ inputTimers.forEach(function(timer) { clearTimeout(timer); });
170
+ inputTimers.clear();
171
+ inputState.clear();
172
+ window.__canaryRecordedEvents = [];
173
+ };
174
+ })();
175
+ `;
176
+
177
+ export {
178
+ INTERACTION_CAPTURE_SCRIPT
179
+ };
180
+ //# sourceMappingURL=chunk-IMPZAX6L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../recording/src/capture-script.ts"],"sourcesContent":["/**\n * In-page interaction capture script injected via addInitScript.\n * Captures user interactions to window.__canaryRecordedEvents.\n *\n * @module record-interaction-script\n */\n\nexport const INTERACTION_CAPTURE_SCRIPT = `\n(function() {\n if (window.__canaryRecordedEvents) return;\n window.__canaryRecordedEvents = [];\n\n function push(event) {\n window.__canaryRecordedEvents.push(event);\n }\n\n // --- Input debouncing ---\n // Accumulate input per element, flush after 500ms idle as a single event\n var inputTimers = new Map();\n var inputState = new Map();\n\n function getElementKey(el) {\n return (el.id || '') + '|' + (el.getAttribute('aria-label') || '') + '|' + (el.tagName || '') + '|' + (el.getAttribute('name') || '');\n }\n\n function flushInput(key) {\n var state = inputState.get(key);\n if (!state) return;\n inputTimers.delete(key);\n inputState.delete(key);\n push({\n type: 'input',\n ts: state.ts,\n tagName: state.tagName,\n id: state.id || undefined,\n inputType: state.inputType || undefined,\n ariaLabel: state.ariaLabel || undefined,\n value: state.value,\n isFinal: true,\n });\n }\n\n function flushAllInputs() {\n inputTimers.forEach(function(timer) { clearTimeout(timer); });\n inputState.forEach(function(_, key) { flushInput(key); });\n }\n\n document.addEventListener('click', function(e) {\n var el = e.target;\n push({\n type: 'click',\n ts: Date.now(),\n x: e.clientX,\n y: e.clientY,\n tagName: el.tagName || '',\n id: el.id || undefined,\n testId: el.getAttribute('data-testid') || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n role: el.getAttribute('role') || undefined,\n textContent: (el.textContent || '').slice(0, 200).trim() || undefined,\n className: el.className && typeof el.className === 'string' ? el.className.slice(0, 200) : undefined,\n });\n }, true);\n\n document.addEventListener('input', function(e) {\n var el = e.target;\n var isPassword = el.type === 'password';\n var key = getElementKey(el);\n\n // Clear previous timer for this element\n var prevTimer = inputTimers.get(key);\n if (prevTimer) clearTimeout(prevTimer);\n\n // Update accumulated state\n inputState.set(key, {\n ts: Date.now(),\n tagName: el.tagName || '',\n id: el.id || undefined,\n inputType: el.type || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n value: isPassword ? '***' : (el.value || ''),\n });\n\n // Schedule flush after 500ms idle\n inputTimers.set(key, setTimeout(function() { flushInput(key); }, 500));\n }, true);\n\n // --- Change events for selects, checkboxes, radios ---\n document.addEventListener('change', function(e) {\n var el = e.target;\n var tagName = el.tagName || '';\n var inputType = (el.type || '').toLowerCase();\n\n // Only capture selects, checkboxes, and radios (text inputs are handled by input debouncing)\n var isSelect = tagName === 'SELECT';\n var isCheckOrRadio = inputType === 'checkbox' || inputType === 'radio';\n if (!isSelect && !isCheckOrRadio) return;\n\n var value;\n if (isCheckOrRadio) {\n value = el.checked ? 'checked' : 'unchecked';\n } else {\n value = el.value || '';\n }\n\n push({\n type: 'change',\n ts: Date.now(),\n tagName: tagName,\n id: el.id || undefined,\n inputType: inputType || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n value: value,\n });\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {\n push({\n type: 'keydown',\n ts: Date.now(),\n key: e.key,\n });\n }\n }, true);\n\n // --- SPA navigation: patch pushState/replaceState ---\n var origPushState = history.pushState;\n var origReplaceState = history.replaceState;\n\n history.pushState = function() {\n origPushState.apply(this, arguments);\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'pushState',\n });\n };\n\n history.replaceState = function() {\n origReplaceState.apply(this, arguments);\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'replaceState',\n });\n };\n\n window.addEventListener('popstate', function() {\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'popstate',\n });\n });\n\n window.addEventListener('beforeunload', function() {\n flushAllInputs();\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'beforeunload',\n });\n });\n\n // Exposed for discardCapturedEvents — cancels pending debounce timers\n // without flushing their events, then clears the array.\n window.__canaryFlushAndDiscard = function() {\n inputTimers.forEach(function(timer) { clearTimeout(timer); });\n inputTimers.clear();\n inputState.clear();\n window.__canaryRecordedEvents = [];\n };\n})();\n`;\n"],"mappings":";;;AAOO,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}