libretto 0.3.0 → 0.3.1

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 (162) hide show
  1. package/README.md +41 -125
  2. package/dist/cli/cli.js +298 -0
  3. package/dist/cli/commands/ai.js +21 -0
  4. package/dist/cli/commands/browser.js +82 -0
  5. package/dist/cli/commands/execution.js +490 -0
  6. package/dist/cli/commands/init.js +166 -0
  7. package/dist/cli/commands/logs.js +93 -0
  8. package/dist/cli/commands/snapshot.js +106 -0
  9. package/dist/cli/core/ai-config.js +149 -0
  10. package/dist/cli/core/browser.js +652 -0
  11. package/dist/cli/core/context.js +117 -0
  12. package/dist/cli/core/pause-signals.js +29 -0
  13. package/dist/cli/core/session-telemetry.js +491 -0
  14. package/dist/cli/core/session.js +183 -0
  15. package/dist/cli/core/snapshot-analyzer.js +492 -0
  16. package/dist/cli/core/telemetry.js +362 -0
  17. package/dist/cli/index.js +13 -0
  18. package/dist/cli/workers/run-integration-runtime.js +234 -0
  19. package/dist/cli/workers/run-integration-worker-protocol.js +12 -0
  20. package/dist/cli/workers/run-integration-worker.js +67 -0
  21. package/dist/index.cjs +144 -0
  22. package/dist/index.d.cts +21 -0
  23. package/dist/index.d.ts +21 -0
  24. package/dist/index.js +114 -0
  25. package/dist/runtime/download/download.cjs +70 -0
  26. package/dist/runtime/download/download.d.cts +35 -0
  27. package/dist/runtime/download/download.d.ts +35 -0
  28. package/dist/runtime/download/download.js +45 -0
  29. package/dist/runtime/download/index.cjs +30 -0
  30. package/dist/runtime/download/index.d.cts +3 -0
  31. package/dist/runtime/download/index.d.ts +3 -0
  32. package/dist/runtime/download/index.js +8 -0
  33. package/dist/runtime/extract/extract.cjs +88 -0
  34. package/dist/runtime/extract/extract.d.cts +23 -0
  35. package/dist/runtime/extract/extract.d.ts +23 -0
  36. package/dist/runtime/extract/extract.js +64 -0
  37. package/dist/runtime/extract/index.cjs +28 -0
  38. package/dist/runtime/extract/index.d.cts +5 -0
  39. package/dist/runtime/extract/index.d.ts +5 -0
  40. package/dist/runtime/extract/index.js +4 -0
  41. package/dist/runtime/network/index.cjs +28 -0
  42. package/dist/runtime/network/index.d.cts +4 -0
  43. package/dist/runtime/network/index.d.ts +4 -0
  44. package/dist/runtime/network/index.js +6 -0
  45. package/dist/runtime/network/network.cjs +91 -0
  46. package/dist/runtime/network/network.d.cts +28 -0
  47. package/dist/runtime/network/network.d.ts +28 -0
  48. package/dist/runtime/network/network.js +67 -0
  49. package/dist/runtime/recovery/agent.cjs +223 -0
  50. package/dist/runtime/recovery/agent.d.cts +13 -0
  51. package/dist/runtime/recovery/agent.d.ts +13 -0
  52. package/dist/runtime/recovery/agent.js +199 -0
  53. package/dist/runtime/recovery/errors.cjs +124 -0
  54. package/dist/runtime/recovery/errors.d.cts +31 -0
  55. package/dist/runtime/recovery/errors.d.ts +31 -0
  56. package/dist/runtime/recovery/errors.js +100 -0
  57. package/dist/runtime/recovery/index.cjs +34 -0
  58. package/dist/runtime/recovery/index.d.cts +7 -0
  59. package/dist/runtime/recovery/index.d.ts +7 -0
  60. package/dist/runtime/recovery/index.js +10 -0
  61. package/dist/runtime/recovery/recovery.cjs +55 -0
  62. package/dist/runtime/recovery/recovery.d.cts +12 -0
  63. package/dist/runtime/recovery/recovery.d.ts +12 -0
  64. package/dist/runtime/recovery/recovery.js +31 -0
  65. package/dist/shared/config/config.cjs +44 -0
  66. package/dist/shared/config/config.d.cts +10 -0
  67. package/dist/shared/config/config.d.ts +10 -0
  68. package/dist/shared/config/config.js +18 -0
  69. package/dist/shared/config/index.cjs +32 -0
  70. package/dist/shared/config/index.d.cts +1 -0
  71. package/dist/shared/config/index.d.ts +1 -0
  72. package/dist/shared/config/index.js +10 -0
  73. package/dist/shared/debug/index.cjs +30 -0
  74. package/dist/shared/debug/index.d.cts +1 -0
  75. package/dist/shared/debug/index.d.ts +1 -0
  76. package/dist/shared/debug/index.js +5 -0
  77. package/dist/shared/debug/pause.cjs +90 -0
  78. package/dist/shared/debug/pause.d.cts +16 -0
  79. package/dist/shared/debug/pause.d.ts +16 -0
  80. package/dist/shared/debug/pause.js +55 -0
  81. package/dist/shared/instrumentation/errors.cjs +81 -0
  82. package/dist/shared/instrumentation/errors.d.cts +12 -0
  83. package/dist/shared/instrumentation/errors.d.ts +12 -0
  84. package/dist/shared/instrumentation/errors.js +57 -0
  85. package/dist/shared/instrumentation/index.cjs +35 -0
  86. package/dist/shared/instrumentation/index.d.cts +6 -0
  87. package/dist/shared/instrumentation/index.d.ts +6 -0
  88. package/dist/shared/instrumentation/index.js +12 -0
  89. package/dist/shared/instrumentation/instrument.cjs +206 -0
  90. package/dist/shared/instrumentation/instrument.d.cts +32 -0
  91. package/dist/shared/instrumentation/instrument.d.ts +32 -0
  92. package/dist/shared/instrumentation/instrument.js +190 -0
  93. package/dist/shared/llm/ai-sdk-adapter.cjs +67 -0
  94. package/dist/shared/llm/ai-sdk-adapter.d.cts +22 -0
  95. package/dist/shared/llm/ai-sdk-adapter.d.ts +22 -0
  96. package/dist/shared/llm/ai-sdk-adapter.js +43 -0
  97. package/dist/shared/llm/client.cjs +139 -0
  98. package/dist/shared/llm/client.d.cts +6 -0
  99. package/dist/shared/llm/client.d.ts +6 -0
  100. package/dist/shared/llm/client.js +115 -0
  101. package/dist/shared/llm/index.cjs +31 -0
  102. package/dist/shared/llm/index.d.cts +5 -0
  103. package/dist/shared/llm/index.d.ts +5 -0
  104. package/dist/shared/llm/index.js +6 -0
  105. package/dist/shared/llm/types.cjs +16 -0
  106. package/dist/shared/llm/types.d.cts +66 -0
  107. package/dist/shared/llm/types.d.ts +66 -0
  108. package/dist/shared/llm/types.js +0 -0
  109. package/dist/shared/logger/index.cjs +37 -0
  110. package/dist/shared/logger/index.d.cts +2 -0
  111. package/dist/shared/logger/index.d.ts +2 -0
  112. package/dist/shared/logger/index.js +13 -0
  113. package/dist/shared/logger/logger.cjs +232 -0
  114. package/dist/shared/logger/logger.d.cts +86 -0
  115. package/dist/shared/logger/logger.d.ts +86 -0
  116. package/dist/shared/logger/logger.js +207 -0
  117. package/dist/shared/logger/sinks.cjs +160 -0
  118. package/dist/shared/logger/sinks.d.cts +9 -0
  119. package/dist/shared/logger/sinks.d.ts +9 -0
  120. package/dist/shared/logger/sinks.js +124 -0
  121. package/dist/shared/paths/paths.cjs +104 -0
  122. package/dist/shared/paths/paths.d.cts +10 -0
  123. package/dist/shared/paths/paths.d.ts +10 -0
  124. package/dist/shared/paths/paths.js +73 -0
  125. package/dist/shared/run/api.cjs +28 -0
  126. package/dist/shared/run/api.d.cts +2 -0
  127. package/dist/shared/run/api.d.ts +2 -0
  128. package/dist/shared/run/api.js +4 -0
  129. package/dist/shared/run/browser.cjs +98 -0
  130. package/dist/shared/run/browser.d.cts +22 -0
  131. package/dist/shared/run/browser.d.ts +22 -0
  132. package/dist/shared/run/browser.js +74 -0
  133. package/dist/shared/state/index.cjs +38 -0
  134. package/dist/shared/state/index.d.cts +2 -0
  135. package/dist/shared/state/index.d.ts +2 -0
  136. package/dist/shared/state/index.js +16 -0
  137. package/dist/shared/state/session-state.cjs +85 -0
  138. package/dist/shared/state/session-state.d.cts +34 -0
  139. package/dist/shared/state/session-state.d.ts +34 -0
  140. package/dist/shared/state/session-state.js +56 -0
  141. package/dist/shared/visualization/ghost-cursor.cjs +174 -0
  142. package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
  143. package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
  144. package/dist/shared/visualization/ghost-cursor.js +145 -0
  145. package/dist/shared/visualization/highlight.cjs +134 -0
  146. package/dist/shared/visualization/highlight.d.cts +22 -0
  147. package/dist/shared/visualization/highlight.d.ts +22 -0
  148. package/dist/shared/visualization/highlight.js +108 -0
  149. package/dist/shared/visualization/index.cjs +45 -0
  150. package/dist/shared/visualization/index.d.cts +3 -0
  151. package/dist/shared/visualization/index.d.ts +3 -0
  152. package/dist/shared/visualization/index.js +24 -0
  153. package/dist/shared/workflow/workflow.cjs +47 -0
  154. package/dist/shared/workflow/workflow.d.cts +21 -0
  155. package/dist/shared/workflow/workflow.d.ts +21 -0
  156. package/dist/shared/workflow/workflow.js +21 -0
  157. package/package.json +11 -70
  158. package/bin/libretto.mjs +0 -18
  159. package/scripts/postinstall.mjs +0 -48
  160. /package/{skill → .agents/skills/libretto}/SKILL.md +0 -0
  161. /package/{skill → .agents/skills/libretto}/code-generation-rules.md +0 -0
  162. /package/{skill → .agents/skills/libretto}/integration-approach-selection.md +0 -0
@@ -0,0 +1,117 @@
1
+ import { Logger, createFileLogSink } from "../../shared/logger/index.js";
2
+ import { spawnSync } from "node:child_process";
3
+ import { cwd } from "node:process";
4
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ import { validateSessionName } from "./session.js";
7
+ function getRepoRoot() {
8
+ const override = process.env.LIBRETTO_REPO_ROOT?.trim();
9
+ if (override) {
10
+ return resolve(override);
11
+ }
12
+ const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
13
+ encoding: "utf-8"
14
+ });
15
+ if (result.status === 0 && result.stdout) {
16
+ return result.stdout.trim();
17
+ }
18
+ return cwd();
19
+ }
20
+ const REPO_ROOT = getRepoRoot();
21
+ const LIBRETTO_CONFIG_DIR = join(REPO_ROOT, ".libretto");
22
+ const LIBRETTO_CONFIG_PATH = join(LIBRETTO_CONFIG_DIR, "config.json");
23
+ const PROFILES_DIR = join(LIBRETTO_CONFIG_DIR, "profiles");
24
+ const LIBRETTO_SESSIONS_DIR = join(LIBRETTO_CONFIG_DIR, "sessions");
25
+ const LIBRETTO_GITIGNORE_PATH = join(LIBRETTO_CONFIG_DIR, ".gitignore");
26
+ const LIBRETTO_GITIGNORE_CONTENT = [
27
+ "# Local libretto runtime state",
28
+ "sessions/",
29
+ "profiles/",
30
+ ""
31
+ ].join("\n");
32
+ function getSessionDir(session) {
33
+ return join(LIBRETTO_SESSIONS_DIR, session);
34
+ }
35
+ function getSessionStatePath(session) {
36
+ return join(getSessionDir(session), "state.json");
37
+ }
38
+ function getSessionLogsPath(session) {
39
+ return join(getSessionDir(session), "logs.jsonl");
40
+ }
41
+ function getSessionNetworkLogPath(session) {
42
+ return join(getSessionDir(session), "network.jsonl");
43
+ }
44
+ function getSessionActionsLogPath(session) {
45
+ return join(getSessionDir(session), "actions.jsonl");
46
+ }
47
+ function getSessionSnapshotsDir(session) {
48
+ return join(getSessionDir(session), "snapshots");
49
+ }
50
+ function getSessionSnapshotRunDir(session, snapshotRunId) {
51
+ return join(getSessionSnapshotsDir(session), snapshotRunId);
52
+ }
53
+ function ensureLibrettoSetup() {
54
+ mkdirSync(LIBRETTO_CONFIG_DIR, { recursive: true });
55
+ mkdirSync(LIBRETTO_SESSIONS_DIR, { recursive: true });
56
+ mkdirSync(PROFILES_DIR, { recursive: true });
57
+ if (!existsSync(LIBRETTO_GITIGNORE_PATH)) {
58
+ writeFileSync(LIBRETTO_GITIGNORE_PATH, LIBRETTO_GITIGNORE_CONTENT, "utf-8");
59
+ }
60
+ }
61
+ function createLoggerForSession(session) {
62
+ validateSessionName(session);
63
+ const sessionDir = getSessionDir(session);
64
+ mkdirSync(sessionDir, { recursive: true });
65
+ const logFilePath = getSessionLogsPath(session);
66
+ return new Logger(["libretto-cli"], [createFileLogSink({ filePath: logFilePath })]);
67
+ }
68
+ async function closeLogger(logger) {
69
+ if (!logger) return;
70
+ await logger.close();
71
+ }
72
+ async function withSessionLogger(session, run) {
73
+ const logger = createLoggerForSession(session);
74
+ try {
75
+ return await run(logger);
76
+ } finally {
77
+ await closeLogger(logger);
78
+ }
79
+ }
80
+ let llmClientFactory = null;
81
+ function setLLMClientFactory(factory) {
82
+ llmClientFactory = factory;
83
+ }
84
+ function getLLMClientFactory() {
85
+ return llmClientFactory;
86
+ }
87
+ function maybeConfigureLLMClientFactoryFromEnv() {
88
+ if (llmClientFactory) return;
89
+ const hasAnyCreds = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
90
+ if (!hasAnyCreds) return;
91
+ setLLMClientFactory(async (_logger, model) => {
92
+ const { createLLMClient } = await import("../../shared/llm/index.js");
93
+ return createLLMClient(model);
94
+ });
95
+ }
96
+ export {
97
+ LIBRETTO_CONFIG_DIR,
98
+ LIBRETTO_CONFIG_PATH,
99
+ LIBRETTO_GITIGNORE_PATH,
100
+ LIBRETTO_SESSIONS_DIR,
101
+ PROFILES_DIR,
102
+ REPO_ROOT,
103
+ closeLogger,
104
+ createLoggerForSession,
105
+ ensureLibrettoSetup,
106
+ getLLMClientFactory,
107
+ getSessionActionsLogPath,
108
+ getSessionDir,
109
+ getSessionLogsPath,
110
+ getSessionNetworkLogPath,
111
+ getSessionSnapshotRunDir,
112
+ getSessionSnapshotsDir,
113
+ getSessionStatePath,
114
+ maybeConfigureLLMClientFactoryFromEnv,
115
+ setLLMClientFactory,
116
+ withSessionLogger
117
+ };
@@ -0,0 +1,29 @@
1
+ import { existsSync } from "node:fs";
2
+ import { unlink } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { getSessionDir } from "./context.js";
5
+ function getPauseSignalPaths(session) {
6
+ const sessionDir = getSessionDir(session);
7
+ return {
8
+ pausedSignalPath: join(sessionDir, `${session}.paused`),
9
+ resumeSignalPath: join(sessionDir, `${session}.resume`),
10
+ completedSignalPath: join(sessionDir, `${session}.completed`),
11
+ failedSignalPath: join(sessionDir, `${session}.failed`),
12
+ outputSignalPath: join(sessionDir, `${session}.output`)
13
+ };
14
+ }
15
+ async function removeSignalIfExists(path) {
16
+ if (!existsSync(path)) return;
17
+ try {
18
+ await unlink(path);
19
+ } catch (error) {
20
+ const code = error.code;
21
+ if (code !== "ENOENT") {
22
+ throw error;
23
+ }
24
+ }
25
+ }
26
+ export {
27
+ getPauseSignalPaths,
28
+ removeSignalIfExists
29
+ };
@@ -0,0 +1,491 @@
1
+ async function installSessionTelemetry(options) {
2
+ const STATIC_EXT_RE = /\.(css|js|png|jpg|jpeg|gif|woff|woff2|ttf|ico|svg)(\?|$)/i;
3
+ const { context, initialPage, logAction, logNetwork } = options;
4
+ const includeUserDomActions = options.includeUserDomActions ?? false;
5
+ const pageIdCache = /* @__PURE__ */ new WeakMap();
6
+ const wrappedPages = /* @__PURE__ */ new WeakSet();
7
+ const exposedPages = /* @__PURE__ */ new WeakSet();
8
+ const resolvePageId = async (page) => {
9
+ if (pageIdCache.has(page)) return pageIdCache.get(page);
10
+ const cdpSession = await context.newCDPSession(page);
11
+ try {
12
+ const targetInfo = await cdpSession.send("Target.getTargetInfo");
13
+ const targetId = targetInfo?.targetInfo?.targetId;
14
+ if (typeof targetId !== "string" || targetId.length === 0) {
15
+ throw new Error(`Could not resolve target id for page at URL "${page.url()}".`);
16
+ }
17
+ pageIdCache.set(page, targetId);
18
+ return targetId;
19
+ } finally {
20
+ await cdpSession.detach();
21
+ }
22
+ };
23
+ const emitAction = (entry) => {
24
+ logAction({
25
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
26
+ ...entry
27
+ });
28
+ };
29
+ const emitNetwork = (entry) => {
30
+ logNetwork({
31
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
32
+ ...entry
33
+ });
34
+ };
35
+ const markApiActionInProgress = async (page, inProgress) => {
36
+ await page.evaluate((flag) => {
37
+ window.__btApiActionInProgress = flag;
38
+ }, inProgress);
39
+ };
40
+ const wrapLocator = (locator, page, pageId) => {
41
+ if (locator.__librettoActionLogged) return locator;
42
+ locator.__librettoActionLogged = true;
43
+ const locatorActionMethods = [
44
+ "click",
45
+ "dblclick",
46
+ "fill",
47
+ "type",
48
+ "press",
49
+ "check",
50
+ "uncheck",
51
+ "selectOption",
52
+ "hover",
53
+ "focus",
54
+ "scrollIntoViewIfNeeded",
55
+ "waitFor",
56
+ "innerHTML",
57
+ "innerText",
58
+ "textContent",
59
+ "inputValue",
60
+ "isChecked",
61
+ "isDisabled",
62
+ "isEditable",
63
+ "isEnabled",
64
+ "isHidden",
65
+ "isVisible",
66
+ "count",
67
+ "boundingBox",
68
+ "screenshot",
69
+ "evaluate",
70
+ "evaluateAll",
71
+ "evaluateHandle",
72
+ "getAttribute",
73
+ "dispatchEvent",
74
+ "setInputFiles",
75
+ "selectText",
76
+ "dragTo",
77
+ "highlight",
78
+ "tap"
79
+ ];
80
+ const locatorReturningMethods = [
81
+ "first",
82
+ "last",
83
+ "locator",
84
+ "getByRole",
85
+ "getByText",
86
+ "getByLabel",
87
+ "getByPlaceholder",
88
+ "getByAltText",
89
+ "getByTitle",
90
+ "getByTestId",
91
+ "filter",
92
+ "and",
93
+ "or"
94
+ ];
95
+ for (const actMethod of locatorActionMethods) {
96
+ if (typeof locator[actMethod] !== "function") continue;
97
+ const originalAction = locator[actMethod].bind(locator);
98
+ locator[actMethod] = async (...actionArgs) => {
99
+ const start = Date.now();
100
+ await markApiActionInProgress(page, true);
101
+ try {
102
+ const result = await originalAction(...actionArgs);
103
+ emitAction({
104
+ pageId,
105
+ action: actMethod,
106
+ source: "agent",
107
+ selector: "locator",
108
+ value: actionArgs[0] !== void 0 ? String(actionArgs[0]).slice(0, 100) : void 0,
109
+ duration: Date.now() - start,
110
+ success: true
111
+ });
112
+ return result;
113
+ } catch (error) {
114
+ emitAction({
115
+ pageId,
116
+ action: actMethod,
117
+ source: "agent",
118
+ selector: "locator",
119
+ duration: Date.now() - start,
120
+ success: false,
121
+ error: error?.message ?? String(error)
122
+ });
123
+ throw error;
124
+ } finally {
125
+ await markApiActionInProgress(page, false);
126
+ }
127
+ };
128
+ }
129
+ for (const method of locatorReturningMethods) {
130
+ if (typeof locator[method] !== "function") continue;
131
+ const originalMethod = locator[method].bind(locator);
132
+ locator[method] = (...args) => {
133
+ const child = originalMethod(...args);
134
+ return wrapLocator(child, page, pageId);
135
+ };
136
+ }
137
+ if (typeof locator.nth === "function") {
138
+ const originalNth = locator.nth.bind(locator);
139
+ locator.nth = (index) => {
140
+ const child = originalNth(index);
141
+ return wrapLocator(child, page, pageId);
142
+ };
143
+ }
144
+ if (typeof locator.all === "function") {
145
+ const originalAll = locator.all.bind(locator);
146
+ locator.all = async () => {
147
+ const items = await originalAll();
148
+ return items.map((item) => wrapLocator(item, page, pageId));
149
+ };
150
+ }
151
+ return locator;
152
+ };
153
+ const installUserDomTracking = async (page, pageId) => {
154
+ if (exposedPages.has(page)) return;
155
+ exposedPages.add(page);
156
+ await page.exposeFunction("__btActionLog", (jsonStr) => {
157
+ const parsed = JSON.parse(jsonStr);
158
+ emitAction({
159
+ pageId,
160
+ source: "user",
161
+ ...parsed
162
+ });
163
+ });
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
+ });
336
+ };
337
+ const wrapPageActions = (page, pageId) => {
338
+ if (wrappedPages.has(page)) return;
339
+ wrappedPages.add(page);
340
+ const pageActions = [
341
+ "click",
342
+ "dblclick",
343
+ "fill",
344
+ "type",
345
+ "press",
346
+ "check",
347
+ "uncheck",
348
+ "selectOption",
349
+ "hover",
350
+ "focus"
351
+ ];
352
+ const navActions = ["goto", "reload", "goBack", "goForward"];
353
+ const locatorFactories = [
354
+ "locator",
355
+ "getByRole",
356
+ "getByText",
357
+ "getByLabel",
358
+ "getByPlaceholder",
359
+ "getByAltText",
360
+ "getByTitle",
361
+ "getByTestId"
362
+ ];
363
+ for (const method of pageActions) {
364
+ const originalMethod = page[method].bind(page);
365
+ page[method] = async (...args) => {
366
+ const start = Date.now();
367
+ await markApiActionInProgress(page, true);
368
+ try {
369
+ const result = await originalMethod(...args);
370
+ emitAction({
371
+ pageId,
372
+ action: method,
373
+ source: "agent",
374
+ selector: typeof args[0] === "string" ? args[0] : void 0,
375
+ value: args[1] !== void 0 ? String(args[1]).slice(0, 100) : void 0,
376
+ duration: Date.now() - start,
377
+ success: true
378
+ });
379
+ return result;
380
+ } catch (error) {
381
+ emitAction({
382
+ pageId,
383
+ action: method,
384
+ source: "agent",
385
+ selector: typeof args[0] === "string" ? args[0] : void 0,
386
+ duration: Date.now() - start,
387
+ success: false,
388
+ error: error?.message ?? String(error)
389
+ });
390
+ throw error;
391
+ } finally {
392
+ await markApiActionInProgress(page, false);
393
+ }
394
+ };
395
+ }
396
+ for (const method of navActions) {
397
+ const originalMethod = page[method].bind(page);
398
+ page[method] = async (...args) => {
399
+ const start = Date.now();
400
+ try {
401
+ const result = await originalMethod(...args);
402
+ emitAction({
403
+ pageId,
404
+ action: method,
405
+ source: "agent",
406
+ url: typeof args[0] === "string" ? args[0] : page.url(),
407
+ duration: Date.now() - start,
408
+ success: true
409
+ });
410
+ return result;
411
+ } catch (error) {
412
+ emitAction({
413
+ pageId,
414
+ action: method,
415
+ source: "agent",
416
+ url: typeof args[0] === "string" ? args[0] : void 0,
417
+ duration: Date.now() - start,
418
+ success: false,
419
+ error: error?.message ?? String(error)
420
+ });
421
+ throw error;
422
+ }
423
+ };
424
+ }
425
+ for (const factory of locatorFactories) {
426
+ const originalFactory = page[factory].bind(page);
427
+ page[factory] = (...factoryArgs) => {
428
+ const locator = originalFactory(...factoryArgs);
429
+ return wrapLocator(locator, page, pageId);
430
+ };
431
+ }
432
+ };
433
+ const installForPage = async (page) => {
434
+ const pageId = await resolvePageId(page);
435
+ wrapPageActions(page, pageId);
436
+ if (includeUserDomActions) {
437
+ await installUserDomTracking(page, pageId);
438
+ }
439
+ page.on("response", async (response) => {
440
+ const request = response.request();
441
+ const url = request.url();
442
+ if (STATIC_EXT_RE.test(url) || url.startsWith("chrome-extension://")) return;
443
+ emitNetwork({
444
+ pageId,
445
+ method: request.method(),
446
+ url,
447
+ status: response.status(),
448
+ contentType: response.headers()["content-type"] ?? null,
449
+ postData: request.method() === "POST" || request.method() === "PUT" || request.method() === "PATCH" ? (request.postData() ?? "").substring(0, 2e3) : void 0,
450
+ responseBody: null,
451
+ size: null,
452
+ durationMs: null
453
+ });
454
+ });
455
+ page.on("framenavigated", (frame) => {
456
+ if (frame !== page.mainFrame()) return;
457
+ emitAction({
458
+ pageId,
459
+ action: "navigate",
460
+ source: "agent",
461
+ url: frame.url(),
462
+ success: true
463
+ });
464
+ });
465
+ page.on("popup", (popup) => {
466
+ emitAction({
467
+ pageId,
468
+ action: "popup",
469
+ source: "agent",
470
+ url: popup.url(),
471
+ success: true
472
+ });
473
+ });
474
+ page.on("dialog", (dialog) => {
475
+ emitAction({
476
+ pageId,
477
+ action: "dialog",
478
+ source: "agent",
479
+ value: `${dialog.type()}: ${dialog.message().slice(0, 500)}`,
480
+ success: true
481
+ });
482
+ });
483
+ };
484
+ await installForPage(initialPage);
485
+ context.on("page", (newPage) => {
486
+ void installForPage(newPage);
487
+ });
488
+ }
489
+ export {
490
+ installSessionTelemetry
491
+ };