libretto 0.5.0 → 0.5.2

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 (122) hide show
  1. package/README.md +109 -35
  2. package/dist/cli/cli.js +22 -97
  3. package/dist/cli/commands/browser.js +86 -59
  4. package/dist/cli/commands/execution.js +199 -86
  5. package/dist/cli/commands/init.js +34 -29
  6. package/dist/cli/commands/logs.js +4 -5
  7. package/dist/cli/commands/shared.js +30 -29
  8. package/dist/cli/commands/snapshot.js +26 -39
  9. package/dist/cli/core/ai-config.js +21 -4
  10. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  11. package/dist/cli/core/browser.js +207 -37
  12. package/dist/cli/core/context.js +4 -1
  13. package/dist/cli/core/session-telemetry.js +434 -174
  14. package/dist/cli/core/session.js +21 -8
  15. package/dist/cli/core/snapshot-analyzer.js +14 -31
  16. package/dist/cli/core/snapshot-api-config.js +2 -6
  17. package/dist/cli/core/telemetry.js +20 -4
  18. package/dist/cli/framework/simple-cli.js +45 -25
  19. package/dist/cli/router.js +14 -21
  20. package/dist/cli/workers/run-integration-runtime.js +24 -5
  21. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  22. package/dist/cli/workers/run-integration-worker.js +1 -4
  23. package/dist/index.d.ts +1 -2
  24. package/dist/index.js +7 -10
  25. package/dist/runtime/download/download.js +5 -1
  26. package/dist/runtime/extract/extract.js +11 -2
  27. package/dist/runtime/network/network.js +8 -1
  28. package/dist/runtime/recovery/agent.js +6 -2
  29. package/dist/runtime/recovery/errors.js +3 -1
  30. package/dist/runtime/recovery/recovery.js +3 -1
  31. package/dist/shared/condense-dom/condense-dom.js +17 -69
  32. package/dist/shared/config/config.d.ts +1 -9
  33. package/dist/shared/config/config.js +0 -18
  34. package/dist/shared/config/index.d.ts +2 -1
  35. package/dist/shared/config/index.js +0 -10
  36. package/dist/shared/debug/pause.js +9 -3
  37. package/dist/shared/dom-semantics.d.ts +8 -0
  38. package/dist/shared/dom-semantics.js +69 -0
  39. package/dist/shared/instrumentation/instrument.js +101 -5
  40. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  41. package/dist/shared/llm/client.js +3 -1
  42. package/dist/shared/logger/index.js +4 -1
  43. package/dist/shared/run/api.js +3 -1
  44. package/dist/shared/run/browser.js +47 -3
  45. package/dist/shared/state/session-state.d.ts +2 -1
  46. package/dist/shared/state/session-state.js +5 -2
  47. package/dist/shared/visualization/ghost-cursor.js +36 -14
  48. package/dist/shared/visualization/highlight.js +9 -6
  49. package/dist/shared/workflow/workflow.d.ts +4 -5
  50. package/dist/shared/workflow/workflow.js +3 -5
  51. package/package.json +6 -2
  52. package/scripts/check-skills-sync.mjs +25 -0
  53. package/scripts/compare-eval-summary.mjs +47 -0
  54. package/scripts/postinstall.mjs +15 -15
  55. package/scripts/prepare-release.sh +97 -0
  56. package/scripts/skills-libretto.mjs +103 -0
  57. package/scripts/summarize-evals.mjs +135 -0
  58. package/scripts/sync-skills.mjs +12 -0
  59. package/skills/libretto/SKILL.md +132 -54
  60. package/skills/libretto/references/action-logs.md +101 -0
  61. package/skills/libretto/references/auth-profiles.md +1 -2
  62. package/skills/libretto/references/code-generation-rules.md +210 -0
  63. package/skills/libretto/references/configuration-file-reference.md +53 -0
  64. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  65. package/skills/libretto/references/site-security-review.md +143 -0
  66. package/src/cli/cli.ts +23 -110
  67. package/src/cli/commands/browser.ts +94 -70
  68. package/src/cli/commands/execution.ts +233 -102
  69. package/src/cli/commands/init.ts +37 -33
  70. package/src/cli/commands/logs.ts +7 -7
  71. package/src/cli/commands/shared.ts +36 -37
  72. package/src/cli/commands/snapshot.ts +44 -59
  73. package/src/cli/core/ai-config.ts +24 -4
  74. package/src/cli/core/api-snapshot-analyzer.ts +17 -6
  75. package/src/cli/core/browser.ts +260 -49
  76. package/src/cli/core/context.ts +7 -2
  77. package/src/cli/core/session-telemetry.ts +449 -197
  78. package/src/cli/core/session.ts +21 -7
  79. package/src/cli/core/snapshot-analyzer.ts +26 -46
  80. package/src/cli/core/snapshot-api-config.ts +170 -175
  81. package/src/cli/core/telemetry.ts +39 -4
  82. package/src/cli/framework/simple-cli.ts +144 -77
  83. package/src/cli/router.ts +13 -21
  84. package/src/cli/workers/run-integration-runtime.ts +36 -9
  85. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  86. package/src/cli/workers/run-integration-worker.ts +1 -4
  87. package/src/index.ts +73 -66
  88. package/src/runtime/download/download.ts +62 -58
  89. package/src/runtime/download/index.ts +5 -5
  90. package/src/runtime/extract/extract.ts +71 -61
  91. package/src/runtime/network/index.ts +3 -3
  92. package/src/runtime/network/network.ts +99 -93
  93. package/src/runtime/recovery/agent.ts +217 -212
  94. package/src/runtime/recovery/errors.ts +107 -104
  95. package/src/runtime/recovery/index.ts +3 -3
  96. package/src/runtime/recovery/recovery.ts +38 -35
  97. package/src/shared/condense-dom/condense-dom.ts +27 -82
  98. package/src/shared/config/config.ts +0 -19
  99. package/src/shared/config/index.ts +0 -5
  100. package/src/shared/debug/pause.ts +57 -51
  101. package/src/shared/dom-semantics.ts +68 -0
  102. package/src/shared/instrumentation/errors.ts +64 -62
  103. package/src/shared/instrumentation/index.ts +5 -5
  104. package/src/shared/instrumentation/instrument.ts +339 -209
  105. package/src/shared/llm/ai-sdk-adapter.ts +58 -55
  106. package/src/shared/llm/client.ts +181 -174
  107. package/src/shared/llm/types.ts +39 -39
  108. package/src/shared/logger/index.ts +11 -4
  109. package/src/shared/logger/logger.ts +312 -306
  110. package/src/shared/logger/sinks.ts +118 -114
  111. package/src/shared/paths/paths.ts +50 -49
  112. package/src/shared/paths/repo-root.ts +17 -17
  113. package/src/shared/run/api.ts +5 -1
  114. package/src/shared/run/browser.ts +65 -3
  115. package/src/shared/state/index.ts +9 -9
  116. package/src/shared/state/session-state.ts +46 -43
  117. package/src/shared/visualization/ghost-cursor.ts +180 -149
  118. package/src/shared/visualization/highlight.ts +89 -86
  119. package/src/shared/visualization/index.ts +13 -13
  120. package/src/shared/workflow/workflow.ts +19 -25
  121. package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
  122. package/skills/libretto/references/user-action-log.md +0 -31
@@ -1,3 +1,11 @@
1
+ import {
2
+ filterSemanticClasses,
3
+ INTERACTIVE_ROLE_NAMES,
4
+ INTERACTIVE_TAG_NAMES,
5
+ isObfuscatedClass,
6
+ TEST_ATTRIBUTE_NAMES,
7
+ TRUSTED_ATTRIBUTE_NAMES
8
+ } from "../../shared/dom-semantics.js";
1
9
  async function installSessionTelemetry(options) {
2
10
  const STATIC_EXT_RE = /\.(css|js|png|jpg|jpeg|gif|woff|woff2|ttf|ico|svg)(\?|$)/i;
3
11
  const { context, initialPage, logAction, logNetwork } = options;
@@ -12,7 +20,9 @@ async function installSessionTelemetry(options) {
12
20
  const targetInfo = await cdpSession.send("Target.getTargetInfo");
13
21
  const targetId = targetInfo?.targetInfo?.targetId;
14
22
  if (typeof targetId !== "string" || targetId.length === 0) {
15
- throw new Error(`Could not resolve target id for page at URL "${page.url()}".`);
23
+ throw new Error(
24
+ `Could not resolve target id for page at URL "${page.url()}".`
25
+ );
16
26
  }
17
27
  pageIdCache.set(page, targetId);
18
28
  return targetId;
@@ -150,6 +160,426 @@ async function installSessionTelemetry(options) {
150
160
  }
151
161
  return locator;
152
162
  };
163
+ const createUserDomTrackingInitScript = () => {
164
+ const selectorAttributeNames = [
165
+ "id",
166
+ ...TEST_ATTRIBUTE_NAMES,
167
+ "name",
168
+ "for",
169
+ "role",
170
+ "aria-label",
171
+ "title",
172
+ "placeholder",
173
+ "alt",
174
+ "href",
175
+ "type"
176
+ ];
177
+ return `
178
+ (() => {
179
+ if (window.__btDomListenersInstalled) return;
180
+ window.__btDomListenersInstalled = true;
181
+
182
+ const TEST_ATTRS = new Set(${JSON.stringify([...TEST_ATTRIBUTE_NAMES])});
183
+ const TRUSTED_ATTRS = new Set(${JSON.stringify([...TRUSTED_ATTRIBUTE_NAMES])});
184
+ const INTERACTIVE_TAGS = new Set(${JSON.stringify([...INTERACTIVE_TAG_NAMES])});
185
+ const INTERACTIVE_ROLES = new Set(${JSON.stringify([...INTERACTIVE_ROLE_NAMES])});
186
+ const SELECTOR_ATTRS = ${JSON.stringify(selectorAttributeNames)};
187
+
188
+ ${filterSemanticClasses.toString()}
189
+ ${isObfuscatedClass.toString()}
190
+
191
+ const normalizeWhitespace = (value) =>
192
+ String(value || "").replace(/\\s+/g, " ").trim();
193
+
194
+ const clipText = (value, limit = 120) => {
195
+ const normalized = normalizeWhitespace(value);
196
+ if (!normalized) return "";
197
+ return normalized.length > limit
198
+ ? \`\${normalized.slice(0, limit - 1)}\u2026\`
199
+ : normalized;
200
+ };
201
+
202
+ const cssEscape = (value) => {
203
+ if (globalThis.CSS && typeof globalThis.CSS.escape === "function") {
204
+ return globalThis.CSS.escape(String(value));
205
+ }
206
+ return String(value).replace(/[^a-zA-Z0-9_-]/g, (char) => {
207
+ const hex = char.charCodeAt(0).toString(16);
208
+ return \`\\\\\${hex} \`;
209
+ });
210
+ };
211
+
212
+ const quoteAttrValue = (value) =>
213
+ String(value).replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"');
214
+
215
+ const isElementNode = (value) => value instanceof Element;
216
+
217
+ const isInteractiveElement = (el) => {
218
+ if (!el || !el.tagName) return false;
219
+ const tagName = el.tagName.toLowerCase();
220
+ if (INTERACTIVE_TAGS.has(tagName)) return true;
221
+ if (el.hasAttribute("tabindex") || el.hasAttribute("contenteditable")) {
222
+ return true;
223
+ }
224
+ const role = normalizeWhitespace(el.getAttribute("role")).toLowerCase();
225
+ return Boolean(role) && INTERACTIVE_ROLES.has(role);
226
+ };
227
+
228
+ const getFilteredClassSelector = (el) => {
229
+ const className =
230
+ typeof el.className === "string"
231
+ ? filterSemanticClasses(el.className)
232
+ : "";
233
+ const classes = className
234
+ .split(/\\s+/)
235
+ .filter(Boolean)
236
+ .slice(0, 2);
237
+ if (classes.length === 0) return "";
238
+ return classes.map((cls) => \`.\${cssEscape(cls)}\`).join("");
239
+ };
240
+
241
+ const getMeaningfulAttrValue = (el, attrName) => {
242
+ if (!el.hasAttribute(attrName)) return "";
243
+ const value = clipText(el.getAttribute(attrName) || "", 80);
244
+ if (!value) return "";
245
+ if (attrName === "href" && value.startsWith("javascript:")) return "";
246
+ return value;
247
+ };
248
+
249
+ const buildSelectorForElement = (el) => {
250
+ if (!isElementNode(el)) return "";
251
+ const tagName = el.tagName.toLowerCase();
252
+ const id = clipText(el.id || "", 80);
253
+ if (id) {
254
+ return \`\${tagName}#\${cssEscape(id)}\`;
255
+ }
256
+
257
+ for (const attrName of SELECTOR_ATTRS) {
258
+ if (attrName === "id") continue;
259
+ const value = getMeaningfulAttrValue(el, attrName);
260
+ if (!value) continue;
261
+ if (attrName.startsWith("data-")) {
262
+ return \`\${tagName}[\${attrName}="\${quoteAttrValue(value)}"]\`;
263
+ }
264
+ return \`\${tagName}[\${attrName}="\${quoteAttrValue(value)}"]\`;
265
+ }
266
+
267
+ const classSelector = getFilteredClassSelector(el);
268
+ if (classSelector) {
269
+ return \`\${tagName}\${classSelector}\`;
270
+ }
271
+
272
+ return tagName;
273
+ };
274
+
275
+ const getElementSummary = (el) => {
276
+ if (!isElementNode(el)) return "";
277
+ const selector = buildSelectorForElement(el);
278
+ const role = clipText(el.getAttribute("role") || "", 40);
279
+ const text = getElementText(el);
280
+ const suffix = [];
281
+ if (role) suffix.push(\`role=\${role}\`);
282
+ if (text) suffix.push(\`text="\${text}"\`);
283
+ return suffix.length > 0 ? \`\${selector} [\${suffix.join(", ")}]\` : selector;
284
+ };
285
+
286
+ const getElementText = (el) => {
287
+ if (!isElementNode(el)) return "";
288
+ const tagName = el.tagName.toLowerCase();
289
+ if (tagName === "input") {
290
+ const inputType = normalizeWhitespace(el.getAttribute("type")).toLowerCase();
291
+ if (inputType === "password") return "";
292
+ const value = clipText(el.value || el.getAttribute("value") || "", 80);
293
+ if (value) return value;
294
+ }
295
+
296
+ const attrCandidates = [
297
+ el.getAttribute("aria-label"),
298
+ el.getAttribute("title"),
299
+ el.getAttribute("placeholder"),
300
+ el.getAttribute("alt"),
301
+ el.textContent,
302
+ ];
303
+
304
+ for (const candidate of attrCandidates) {
305
+ const text = clipText(candidate || "", 120);
306
+ if (text.length >= 2) return text;
307
+ }
308
+
309
+ return "";
310
+ };
311
+
312
+ const isMeaningfulAncestor = (el) => {
313
+ if (!isElementNode(el)) return false;
314
+ const tagName = el.tagName.toLowerCase();
315
+ if (isInteractiveElement(el)) return true;
316
+ if (
317
+ [
318
+ "tr",
319
+ "li",
320
+ "td",
321
+ "th",
322
+ "label",
323
+ "section",
324
+ "article",
325
+ "dialog",
326
+ "fieldset",
327
+ "summary",
328
+ ].includes(tagName)
329
+ ) {
330
+ return true;
331
+ }
332
+ if (el.id) return true;
333
+ for (const attrName of TEST_ATTRS) {
334
+ if (el.hasAttribute(attrName)) return true;
335
+ }
336
+ const role = normalizeWhitespace(el.getAttribute("role")).toLowerCase();
337
+ return Boolean(role);
338
+ };
339
+
340
+ const hasStrongSelectorSignal = (el, selector) => {
341
+ if (!isElementNode(el)) return false;
342
+ if (!selector) return false;
343
+ const tagName = el.tagName.toLowerCase();
344
+ if (el.id) return true;
345
+ if (isInteractiveElement(el)) return true;
346
+ if (getFilteredClassSelector(el)) return true;
347
+ for (const attrName of SELECTOR_ATTRS) {
348
+ if (attrName === "id") continue;
349
+ if (getMeaningfulAttrValue(el, attrName)) return true;
350
+ }
351
+ return selector !== tagName;
352
+ };
353
+
354
+ const getAncestorSelectors = (target) => {
355
+ const selectors = [];
356
+ let current = isElementNode(target) ? target : null;
357
+ let depth = 0;
358
+ while (current && depth < 7) {
359
+ const selector = buildSelectorForElement(current);
360
+ if (
361
+ selector &&
362
+ !selectors.includes(selector) &&
363
+ (
364
+ (depth === 0 && hasStrongSelectorSignal(current, selector)) ||
365
+ isMeaningfulAncestor(current)
366
+ )
367
+ ) {
368
+ selectors.push(selector);
369
+ }
370
+ current = current.parentElement;
371
+ depth += 1;
372
+ }
373
+ if (selectors.length === 0 && isElementNode(target)) {
374
+ selectors.push(buildSelectorForElement(target));
375
+ }
376
+ return selectors;
377
+ };
378
+
379
+ const getNearbyText = (target) => {
380
+ let current = isElementNode(target) ? target : null;
381
+ let depth = 0;
382
+ while (current && depth < 6) {
383
+ const text = getElementText(current);
384
+ if (text.length >= 2) return text;
385
+ current = current.parentElement;
386
+ depth += 1;
387
+ }
388
+ return "";
389
+ };
390
+
391
+ const getComposedPathSummary = (event) => {
392
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
393
+ return path
394
+ .filter((entry) => isElementNode(entry))
395
+ .slice(0, 6)
396
+ .map((entry) => getElementSummary(entry));
397
+ };
398
+
399
+ const getCoordinates = (event) => {
400
+ if (!(event instanceof MouseEvent)) return undefined;
401
+ return {
402
+ x: Math.round(event.clientX),
403
+ y: Math.round(event.clientY),
404
+ };
405
+ };
406
+
407
+ const buildActionPayload = (event, action, extra = {}) => {
408
+ const target = isElementNode(event.target) ? event.target : null;
409
+ const ancestorSelectors = getAncestorSelectors(target);
410
+ const bestSemanticSelector = ancestorSelectors[0] || "";
411
+ const targetSelector = target ? buildSelectorForElement(target) : "";
412
+ return {
413
+ action,
414
+ bestSemanticSelector: bestSemanticSelector || undefined,
415
+ targetSelector: targetSelector || undefined,
416
+ ancestorSelectors: ancestorSelectors.length > 0 ? ancestorSelectors : undefined,
417
+ nearbyText: getNearbyText(target) || undefined,
418
+ composedPath: getComposedPathSummary(event),
419
+ coordinates: getCoordinates(event),
420
+ success: true,
421
+ ...extra,
422
+ };
423
+ };
424
+
425
+ let clickTimer = null;
426
+ let pendingClick = null;
427
+
428
+ document.addEventListener(
429
+ "click",
430
+ (event) => {
431
+ if (window.__btApiActionInProgress) return;
432
+ const target = isElementNode(event.target) ? event.target : null;
433
+ if (target?.type === "checkbox") {
434
+ window.__btActionLog(
435
+ JSON.stringify(
436
+ buildActionPayload(event, target.checked ? "check" : "uncheck"),
437
+ ),
438
+ );
439
+ return;
440
+ }
441
+
442
+ pendingClick = buildActionPayload(event, "click");
443
+ if (clickTimer) clearTimeout(clickTimer);
444
+ clickTimer = setTimeout(() => {
445
+ if (pendingClick) {
446
+ window.__btActionLog(JSON.stringify(pendingClick));
447
+ }
448
+ pendingClick = null;
449
+ clickTimer = null;
450
+ }, 200);
451
+ },
452
+ true,
453
+ );
454
+
455
+ document.addEventListener(
456
+ "dblclick",
457
+ (event) => {
458
+ if (window.__btApiActionInProgress) return;
459
+ if (clickTimer) {
460
+ clearTimeout(clickTimer);
461
+ clickTimer = null;
462
+ pendingClick = null;
463
+ }
464
+ window.__btActionLog(
465
+ JSON.stringify(buildActionPayload(event, "dblclick")),
466
+ );
467
+ },
468
+ true,
469
+ );
470
+
471
+ const inputTimers = new WeakMap();
472
+ document.addEventListener(
473
+ "input",
474
+ (event) => {
475
+ if (window.__btApiActionInProgress) return;
476
+ const target = isElementNode(event.target) ? event.target : null;
477
+ if (!target) return;
478
+
479
+ if (target.tagName === "SELECT") {
480
+ window.__btActionLog(
481
+ JSON.stringify(
482
+ buildActionPayload(event, "selectOption", {
483
+ value: target.value,
484
+ }),
485
+ ),
486
+ );
487
+ return;
488
+ }
489
+
490
+ const existing = inputTimers.get(target);
491
+ if (existing) clearTimeout(existing);
492
+ inputTimers.set(
493
+ target,
494
+ setTimeout(() => {
495
+ inputTimers.delete(target);
496
+ window.__btActionLog(
497
+ JSON.stringify(
498
+ buildActionPayload(event, "fill", {
499
+ value: String(target.value || "").slice(0, 100),
500
+ }),
501
+ ),
502
+ );
503
+ }, 500),
504
+ );
505
+ },
506
+ true,
507
+ );
508
+
509
+ const specialKeys = new Set([
510
+ "Enter",
511
+ "Escape",
512
+ "Tab",
513
+ "Backspace",
514
+ "Delete",
515
+ "ArrowUp",
516
+ "ArrowDown",
517
+ "ArrowLeft",
518
+ "ArrowRight",
519
+ "Home",
520
+ "End",
521
+ "PageUp",
522
+ "PageDown",
523
+ "F1",
524
+ "F2",
525
+ "F3",
526
+ "F4",
527
+ "F5",
528
+ "F6",
529
+ "F7",
530
+ "F8",
531
+ "F9",
532
+ "F10",
533
+ "F11",
534
+ "F12",
535
+ ]);
536
+
537
+ document.addEventListener(
538
+ "keydown",
539
+ (event) => {
540
+ if (window.__btApiActionInProgress) return;
541
+ const isShortcut = event.ctrlKey || event.metaKey || event.altKey;
542
+ if (!isShortcut && !specialKeys.has(event.key)) return;
543
+ const keyDesc =
544
+ (event.ctrlKey ? "Ctrl+" : "") +
545
+ (event.metaKey ? "Meta+" : "") +
546
+ (event.altKey ? "Alt+" : "") +
547
+ (event.shiftKey ? "Shift+" : "") +
548
+ event.key;
549
+ window.__btActionLog(
550
+ JSON.stringify(
551
+ buildActionPayload(event, "press", {
552
+ value: keyDesc,
553
+ }),
554
+ ),
555
+ );
556
+ },
557
+ true,
558
+ );
559
+
560
+ let scrollTimer = null;
561
+ document.addEventListener(
562
+ "scroll",
563
+ () => {
564
+ if (window.__btApiActionInProgress) return;
565
+ if (scrollTimer) clearTimeout(scrollTimer);
566
+ scrollTimer = setTimeout(() => {
567
+ scrollTimer = null;
568
+ window.__btActionLog(
569
+ JSON.stringify({
570
+ action: "scroll",
571
+ bestSemanticSelector: "document",
572
+ success: true,
573
+ value: \`y=\${window.scrollY}\`,
574
+ }),
575
+ );
576
+ }, 300);
577
+ },
578
+ true,
579
+ );
580
+ })();
581
+ `;
582
+ };
153
583
  const installUserDomTracking = async (page, pageId) => {
154
584
  if (exposedPages.has(page)) return;
155
585
  exposedPages.add(page);
@@ -161,178 +591,7 @@ async function installSessionTelemetry(options) {
161
591
  ...parsed
162
592
  });
163
593
  });
164
- await page.addInitScript(() => {
165
- if (window.__btDomListenersInstalled) return;
166
- window.__btDomListenersInstalled = true;
167
- const identify = (el) => {
168
- if (!el || !el.tagName) return "";
169
- const testId = el.getAttribute("data-testid");
170
- if (testId) return `[data-testid="${testId}"]`;
171
- const role = el.getAttribute("role") || "";
172
- const id = el.id;
173
- if (role && id) return `${role}#${id}`;
174
- const label = el.getAttribute("aria-label") || (el.textContent || "").trim().slice(0, 30) || "";
175
- if (role && label) return `${role} "${label}"`;
176
- const tag = el.tagName.toLowerCase();
177
- const cls = el.className && typeof el.className === "string" ? "." + el.className.trim().split(/\s+/).slice(0, 2).join(".") : "";
178
- return `${tag}${cls}`;
179
- };
180
- let clickTimer = null;
181
- let pendingClick = null;
182
- document.addEventListener(
183
- "click",
184
- (event) => {
185
- if (window.__btApiActionInProgress) return;
186
- const target = event.target;
187
- const selector = identify(target);
188
- if (target?.type === "checkbox") {
189
- window.__btActionLog(
190
- JSON.stringify({
191
- action: target.checked ? "check" : "uncheck",
192
- selector,
193
- success: true
194
- })
195
- );
196
- return;
197
- }
198
- pendingClick = { selector };
199
- if (clickTimer) clearTimeout(clickTimer);
200
- clickTimer = setTimeout(() => {
201
- if (pendingClick) {
202
- window.__btActionLog(
203
- JSON.stringify({
204
- action: "click",
205
- selector: pendingClick.selector,
206
- success: true
207
- })
208
- );
209
- }
210
- pendingClick = null;
211
- clickTimer = null;
212
- }, 200);
213
- },
214
- true
215
- );
216
- document.addEventListener(
217
- "dblclick",
218
- (event) => {
219
- if (window.__btApiActionInProgress) return;
220
- if (clickTimer) {
221
- clearTimeout(clickTimer);
222
- clickTimer = null;
223
- pendingClick = null;
224
- }
225
- const selector = identify(event.target);
226
- window.__btActionLog(
227
- JSON.stringify({ action: "dblclick", selector, success: true })
228
- );
229
- },
230
- true
231
- );
232
- const inputTimers = /* @__PURE__ */ new WeakMap();
233
- document.addEventListener(
234
- "input",
235
- (event) => {
236
- if (window.__btApiActionInProgress) return;
237
- const target = event.target;
238
- const selector = identify(target);
239
- if (target.tagName === "SELECT") {
240
- window.__btActionLog(
241
- JSON.stringify({
242
- action: "selectOption",
243
- selector,
244
- value: target.value,
245
- success: true
246
- })
247
- );
248
- return;
249
- }
250
- const existing = inputTimers.get(target);
251
- if (existing) clearTimeout(existing);
252
- inputTimers.set(
253
- target,
254
- setTimeout(() => {
255
- inputTimers.delete(target);
256
- window.__btActionLog(
257
- JSON.stringify({
258
- action: "fill",
259
- selector,
260
- value: (target.value || "").slice(0, 100),
261
- success: true
262
- })
263
- );
264
- }, 500)
265
- );
266
- },
267
- true
268
- );
269
- const specialKeys = /* @__PURE__ */ new Set([
270
- "Enter",
271
- "Escape",
272
- "Tab",
273
- "Backspace",
274
- "Delete",
275
- "ArrowUp",
276
- "ArrowDown",
277
- "ArrowLeft",
278
- "ArrowRight",
279
- "Home",
280
- "End",
281
- "PageUp",
282
- "PageDown",
283
- "F1",
284
- "F2",
285
- "F3",
286
- "F4",
287
- "F5",
288
- "F6",
289
- "F7",
290
- "F8",
291
- "F9",
292
- "F10",
293
- "F11",
294
- "F12"
295
- ]);
296
- document.addEventListener(
297
- "keydown",
298
- (event) => {
299
- if (window.__btApiActionInProgress) return;
300
- const isShortcut = event.ctrlKey || event.metaKey || event.altKey;
301
- if (!isShortcut && !specialKeys.has(event.key)) return;
302
- const selector = identify(event.target);
303
- const keyDesc = (event.ctrlKey ? "Ctrl+" : "") + (event.metaKey ? "Meta+" : "") + (event.altKey ? "Alt+" : "") + (event.shiftKey ? "Shift+" : "") + event.key;
304
- window.__btActionLog(
305
- JSON.stringify({
306
- action: "press",
307
- selector,
308
- value: keyDesc,
309
- success: true
310
- })
311
- );
312
- },
313
- true
314
- );
315
- let scrollTimer = null;
316
- document.addEventListener(
317
- "scroll",
318
- () => {
319
- if (window.__btApiActionInProgress) return;
320
- if (scrollTimer) clearTimeout(scrollTimer);
321
- scrollTimer = setTimeout(() => {
322
- scrollTimer = null;
323
- window.__btActionLog(
324
- JSON.stringify({
325
- action: "scroll",
326
- selector: "document",
327
- value: `y=${window.scrollY}`,
328
- success: true
329
- })
330
- );
331
- }, 300);
332
- },
333
- true
334
- );
335
- });
594
+ await page.addInitScript({ content: createUserDomTrackingInitScript() });
336
595
  };
337
596
  const wrapPageActions = (page, pageId) => {
338
597
  if (wrappedPages.has(page)) return;
@@ -439,7 +698,8 @@ async function installSessionTelemetry(options) {
439
698
  page.on("response", async (response) => {
440
699
  const request = response.request();
441
700
  const url = request.url();
442
- if (STATIC_EXT_RE.test(url) || url.startsWith("chrome-extension://")) return;
701
+ if (STATIC_EXT_RE.test(url) || url.startsWith("chrome-extension://"))
702
+ return;
443
703
  emitNetwork({
444
704
  pageId,
445
705
  method: request.method(),
@@ -18,9 +18,16 @@ import {
18
18
  serializeSessionState
19
19
  } from "../../shared/state/index.js";
20
20
  const SESSION_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
21
- const SESSION_DEFAULT = "default";
22
21
  const SESSION_DEV_SERVER = "dev-server";
23
22
  const SESSION_BROWSER_AGENT = "browser-agent";
23
+ function generateSessionName() {
24
+ const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
25
+ let id = "";
26
+ for (let i = 0; i < 4; i++) {
27
+ id += chars[Math.floor(Math.random() * chars.length)];
28
+ }
29
+ return `ses-${id}`;
30
+ }
24
31
  function logFileForSession(session) {
25
32
  validateSessionName(session);
26
33
  const dir = getSessionDir(session);
@@ -108,7 +115,10 @@ function readSessionStateOrThrow(session) {
108
115
  throwSessionNotFoundError(session);
109
116
  }
110
117
  try {
111
- return parseSessionStateContent(readFileSync(stateFile, "utf-8"), stateFile);
118
+ return parseSessionStateContent(
119
+ readFileSync(stateFile, "utf-8"),
120
+ stateFile
121
+ );
112
122
  } catch (err) {
113
123
  throw new Error(
114
124
  `Could not read session state for "${session}": ${err instanceof Error ? err.message : String(err)}`
@@ -147,15 +157,18 @@ function setSessionStatus(session, status, logger) {
147
157
  const state = readSessionState(session, logger);
148
158
  if (!state) return;
149
159
  if (state.status === status) return;
150
- writeSessionState({
151
- ...state,
152
- status
153
- }, logger);
160
+ writeSessionState(
161
+ {
162
+ ...state,
163
+ status
164
+ },
165
+ logger
166
+ );
154
167
  }
155
168
  function assertSessionAvailableForStart(session, logger) {
156
169
  const existingState = readSessionState(session, logger);
157
170
  if (!existingState) return;
158
- if (!isPidRunning(existingState.pid)) {
171
+ if (existingState.pid == null || !isPidRunning(existingState.pid)) {
159
172
  setSessionStatus(session, "exited", logger);
160
173
  return;
161
174
  }
@@ -166,12 +179,12 @@ function assertSessionAvailableForStart(session, logger) {
166
179
  }
167
180
  export {
168
181
  SESSION_BROWSER_AGENT,
169
- SESSION_DEFAULT,
170
182
  SESSION_DEV_SERVER,
171
183
  SESSION_STATE_VERSION,
172
184
  assertSessionAvailableForStart,
173
185
  assertSessionStateExistsOrThrow,
174
186
  clearSessionState,
187
+ generateSessionName,
175
188
  getStateFilePath,
176
189
  listSessionsWithStateFile,
177
190
  logFileForSession,