@jshookmcp/jshook 0.2.8 → 0.2.9

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 (157) hide show
  1. package/README.md +36 -5
  2. package/README.zh.md +36 -5
  3. package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-BNk-EoBt.mjs} +3 -3
  4. package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-Cq8q01kp.mjs} +5 -5
  5. package/dist/ConsoleMonitor-CPVQW1Y-.mjs +2201 -0
  6. package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-BNPxu0RH.mjs} +1 -1
  7. package/dist/DetailedDataManager-BQQcxh64.mjs +217 -0
  8. package/dist/EventBus-DgPmwpeu.mjs +141 -0
  9. package/dist/EvidenceGraphBridge-SFesNera.mjs +153 -0
  10. package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-CWYgw0YW.mjs} +13 -6
  11. package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-gzWtkKuf.mjs} +1 -1
  12. package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-B9gZCdFP.mjs} +3 -3
  13. package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-BLDH0dCv.mjs} +4 -4
  14. package/dist/HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs +639 -0
  15. package/dist/InstrumentationSession-CvPC7Jwy.mjs +244 -0
  16. package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CbVdCIJF.mjs} +3 -3
  17. package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-BsDZbLYm.mjs} +81 -78
  18. package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-Bcpml6II.mjs} +44 -18
  19. package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-dZtA1ZGn.mjs} +14 -53
  20. package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-B-FjA2mJ.mjs} +1 -1
  21. package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-D1lzJ_VG.mjs} +2 -2
  22. package/dist/PageController-Bqm2kZ_X.mjs +417 -0
  23. package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-BOhyVsjx.mjs} +4 -4
  24. package/dist/PrerequisiteError-Dl33Svkz.mjs +20 -0
  25. package/dist/ResponseBuilder-D3iFYx2N.mjs +143 -0
  26. package/dist/ReverseEvidenceGraph-Dlsk94LC.mjs +269 -0
  27. package/dist/ScriptManager-aHHq0X7U.mjs +3000 -0
  28. package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-CqdIFlQl.mjs} +2 -2
  29. package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-DhFaPvRO.mjs} +3 -3
  30. package/dist/ToolCatalog-C0JGZoOm.mjs +582 -0
  31. package/dist/ToolError-jh9whhMd.mjs +15 -0
  32. package/dist/ToolProbe-oC7aPrkv.mjs +45 -0
  33. package/dist/ToolRegistry-BjaF4oNz.mjs +131 -0
  34. package/dist/ToolRouter.policy-BWV67ZK-.mjs +304 -0
  35. package/dist/TraceRecorder-DgxyVbdQ.mjs +519 -0
  36. package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-CePkipZY.mjs} +1 -1
  37. package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-BvKs-gxc.mjs} +2 -2
  38. package/dist/WorkflowEngine-CuvkZtWu.mjs +598 -0
  39. package/dist/analysis-CL9uACt9.mjs +463 -0
  40. package/dist/antidebug-CqDTB_uk.mjs +1081 -0
  41. package/dist/artifactRetention-CFEprwPw.mjs +591 -0
  42. package/dist/artifacts-Bk2-_uPq.mjs +59 -0
  43. package/dist/betterSqlite3-0pqusHHH.mjs +74 -0
  44. package/dist/binary-instrument-CXfpx6fT.mjs +979 -0
  45. package/dist/bind-helpers-xFfRF-qm.mjs +22 -0
  46. package/dist/boringssl-inspector-BH2D3VKc.mjs +180 -0
  47. package/dist/browser-BpOr5PEx.mjs +4082 -0
  48. package/dist/concurrency-Bt0yv1kJ.mjs +41 -0
  49. package/dist/{constants-CCvsN80K.mjs → constants-B0OANIBL.mjs} +88 -46
  50. package/dist/coordination-qUbyF8KU.mjs +259 -0
  51. package/dist/debugger-gnKxRSN0.mjs +1271 -0
  52. package/dist/definitions-6M-eejaT.mjs +53 -0
  53. package/dist/definitions-B18eyf0B.mjs +18 -0
  54. package/dist/definitions-B3QdlrHv.mjs +34 -0
  55. package/dist/definitions-B4rAvHNZ.mjs +63 -0
  56. package/dist/definitions-BB_4jnmy.mjs +37 -0
  57. package/dist/definitions-BMfYXoNC.mjs +43 -0
  58. package/dist/definitions-Beid2EB3.mjs +27 -0
  59. package/dist/definitions-C1UvM5Iy.mjs +126 -0
  60. package/dist/definitions-CXEI7QC72.mjs +216 -0
  61. package/dist/definitions-C_4r7Fo-2.mjs +14 -0
  62. package/dist/definitions-CkFDALoa.mjs +26 -0
  63. package/dist/definitions-Cke7zEb8.mjs +94 -0
  64. package/dist/definitions-ClJLzsJQ.mjs +25 -0
  65. package/dist/definitions-Cq-zroAU.mjs +28 -0
  66. package/dist/definitions-Cy3Sl6gV.mjs +34 -0
  67. package/dist/definitions-D3VsGcvz.mjs +47 -0
  68. package/dist/definitions-DVGfrn7y.mjs +96 -0
  69. package/dist/definitions-LKpC3-nL.mjs +9 -0
  70. package/dist/definitions-bAhHQJq9.mjs +359 -0
  71. package/dist/encoding-Bvz5jLRv.mjs +1065 -0
  72. package/dist/evidence-graph-bridge-C_fv9PuC.mjs +135 -0
  73. package/dist/{factory-CibqTNC8.mjs → factory-DxlGh9Xf.mjs} +37 -52
  74. package/dist/graphql-DYWzJ29s.mjs +1026 -0
  75. package/dist/handlers-9sAbfIg-.mjs +2552 -0
  76. package/dist/handlers-Bl8zkwz1.mjs +2716 -0
  77. package/dist/handlers-C67ktuRN.mjs +710 -0
  78. package/dist/handlers-C87g8oCe.mjs +276 -0
  79. package/dist/handlers-CTsDAO6p.mjs +681 -0
  80. package/dist/handlers-Cgyg6c0U.mjs +645 -0
  81. package/dist/handlers-D6j6yka7.mjs +2124 -0
  82. package/dist/handlers-DdFzXLvF.mjs +446 -0
  83. package/dist/handlers-DeLOCd5m.mjs +799 -0
  84. package/dist/handlers-DlCJN4Td.mjs +757 -0
  85. package/dist/handlers-DxGIq15_2.mjs +917 -0
  86. package/dist/handlers-U6L4xhuF.mjs +585 -0
  87. package/dist/handlers-tB9Mp9ZK.mjs +84 -0
  88. package/dist/handlers-tiy7EIBp.mjs +572 -0
  89. package/dist/handlers.impl-DS0d9fUw.mjs +761 -0
  90. package/dist/hooks-CzCWByww.mjs +898 -0
  91. package/dist/index.mjs +377 -155
  92. package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
  93. package/dist/maintenance-P7ePRXQC.mjs +830 -0
  94. package/dist/manifest-2ToTpjv8.mjs +106 -0
  95. package/dist/manifest-3g71z6Bg.mjs +79 -0
  96. package/dist/manifest-82baTv4U.mjs +45 -0
  97. package/dist/manifest-B3QVVeBS.mjs +82 -0
  98. package/dist/manifest-BB2J8IMJ.mjs +149 -0
  99. package/dist/manifest-BKbgbSiY.mjs +60 -0
  100. package/dist/manifest-Bcf-TJzH.mjs +848 -0
  101. package/dist/manifest-BmtZzQiQ2.mjs +45 -0
  102. package/dist/manifest-Bnd7kqEY.mjs +55 -0
  103. package/dist/manifest-BqQX6OQC2.mjs +65 -0
  104. package/dist/manifest-BqrQ4Tpj.mjs +81 -0
  105. package/dist/manifest-Br4RPFt5.mjs +370 -0
  106. package/dist/manifest-C5qDjysN.mjs +107 -0
  107. package/dist/manifest-C9RT5nk32.mjs +34 -0
  108. package/dist/manifest-CAhOuvSl.mjs +204 -0
  109. package/dist/manifest-CBYWCUBJ.mjs +51 -0
  110. package/dist/manifest-CFADCRa1.mjs +37 -0
  111. package/dist/manifest-CQVhavRF.mjs +114 -0
  112. package/dist/manifest-CT7zZBV1.mjs +48 -0
  113. package/dist/manifest-CV12bcrF.mjs +121 -0
  114. package/dist/manifest-CXsRWjjI.mjs +224 -0
  115. package/dist/manifest-CZLUCfG02.mjs +95 -0
  116. package/dist/manifest-D6phHKFd.mjs +131 -0
  117. package/dist/manifest-DCyjf4n2.mjs +294 -0
  118. package/dist/manifest-DHsnKgP6.mjs +60 -0
  119. package/dist/manifest-Df_dliIe.mjs +55 -0
  120. package/dist/manifest-Dh8WBmEW.mjs +129 -0
  121. package/dist/manifest-DhKRAT8_.mjs +92 -0
  122. package/dist/manifest-DlpTj4ic2.mjs +193 -0
  123. package/dist/manifest-DrbmZcFl2.mjs +253 -0
  124. package/dist/manifest-DuwHjUa5.mjs +70 -0
  125. package/dist/manifest-DzwvxPJX.mjs +38 -0
  126. package/dist/manifest-NXctwWQq.mjs +68 -0
  127. package/dist/manifest-Sc_0JQ13.mjs +418 -0
  128. package/dist/manifest-gZ4s_UtG.mjs +96 -0
  129. package/dist/manifest-qSleDqdO.mjs +1023 -0
  130. package/dist/modules-C184v-S9.mjs +11365 -0
  131. package/dist/mojo-ipc-B_H61Afw.mjs +525 -0
  132. package/dist/network-671Cw6hV.mjs +3346 -0
  133. package/dist/{artifacts-BbdOMET5.mjs → outputPaths-B1uGmrWZ.mjs} +219 -212
  134. package/dist/parse-args-BlRjqlkL.mjs +39 -0
  135. package/dist/platform-WmNn8Sxb.mjs +2070 -0
  136. package/dist/process-QcbIy5Zq.mjs +1401 -0
  137. package/dist/proxy-DqNs0bAd.mjs +170 -0
  138. package/dist/registry-D-6e18lB.mjs +34 -0
  139. package/dist/response-BQVP-xUn.mjs +28 -0
  140. package/dist/server/plugin-api.mjs +2 -2
  141. package/dist/shared-state-board-DV-dpHFJ.mjs +586 -0
  142. package/dist/sourcemap-Dq8ez8vS.mjs +650 -0
  143. package/dist/ssrf-policy-ZaUfvhq7.mjs +166 -0
  144. package/dist/streaming-BUQ0VJsg.mjs +725 -0
  145. package/dist/tool-builder-DCbIC5Eo.mjs +186 -0
  146. package/dist/transform-CiYJfNX0.mjs +1007 -0
  147. package/dist/types-Bx92KJfT.mjs +4 -0
  148. package/dist/wasm-DQTnHDs4.mjs +531 -0
  149. package/dist/workflow-f3xJOcjx.mjs +725 -0
  150. package/package.json +16 -16
  151. package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
  152. package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
  153. package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-CDe5WPSV.mjs} +0 -0
  154. package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-Bo4T3bz8.mjs} +0 -0
  155. package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-CwVLVdDM.mjs} +0 -0
  156. package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-DVkj9kpI.mjs} +0 -0
  157. package/dist/{types-BBjOqye-.mjs → types-CPhOReNX.mjs} +1 -1
@@ -0,0 +1,4082 @@
1
+ import { t as logger } from "./logger-Dh_xb7_2.mjs";
2
+ import { C as CAPTCHA_MAX_TIMEOUT_MS, D as CAPTCHA_SOLVER_BASE_URL, E as CAPTCHA_RESULT_TIMEOUT_MS, O as CAPTCHA_SUBMIT_TIMEOUT_MS, S as CAPTCHA_MAX_RETRIES, T as CAPTCHA_POLL_INTERVAL_MS, b as CAPTCHA_DEFAULT_RETRIES, vn as SCRIPTS_MAX_CAP, w as CAPTCHA_MIN_TIMEOUT_MS, x as CAPTCHA_DEFAULT_TIMEOUT_MS } from "./constants-B0OANIBL.mjs";
3
+ import { t as DetailedDataManager } from "./DetailedDataManager-BQQcxh64.mjs";
4
+ import { c as getConfig, l as projectRoot, o as resolveOutputDirectory, s as resolveScreenshotOutputPath } from "./outputPaths-B1uGmrWZ.mjs";
5
+ import { c as AICaptchaDetector, l as CamoufoxBrowserManager, n as StealthScripts } from "./modules-C184v-S9.mjs";
6
+ import { t as PrerequisiteError } from "./PrerequisiteError-Dl33Svkz.mjs";
7
+ import { n as isBetterSqlite3RelatedError, t as formatBetterSqlite3Error } from "./betterSqlite3-0pqusHHH.mjs";
8
+ import { t as cdpLimit } from "./concurrency-Bt0yv1kJ.mjs";
9
+ import { a as argString, i as argObject, n as argEnum, o as argStringArray, r as argNumber, s as argStringRequired, t as argBool } from "./parse-args-BlRjqlkL.mjs";
10
+ import "./definitions-bAhHQJq9.mjs";
11
+ import { t as R } from "./ResponseBuilder-D3iFYx2N.mjs";
12
+ import { join } from "path";
13
+ import { randomUUID } from "node:crypto";
14
+ import { readFile, writeFile } from "fs/promises";
15
+ //#region src/server/domains/browser/handlers/browser-control.ts
16
+ const projectEnvPath = join(projectRoot, ".env");
17
+ const CHROME_CHANNELS = new Set([
18
+ "stable",
19
+ "beta",
20
+ "dev",
21
+ "canary"
22
+ ]);
23
+ var BrowserControlHandlers = class {
24
+ constructor(deps) {
25
+ this.deps = deps;
26
+ }
27
+ markMonitoringContextChanged(context) {
28
+ try {
29
+ this.deps.consoleMonitor.markContextChanged();
30
+ } catch (error) {
31
+ logger.warn(`[${context}] Failed to mark monitoring context as stale: ${error instanceof Error ? error.message : String(error)}`);
32
+ }
33
+ }
34
+ async syncTabRegistryWithCollectorPages(context) {
35
+ try {
36
+ const resolvedPages = await this.deps.collector.listResolvedPages();
37
+ this.deps.getTabRegistry().reconcilePages(resolvedPages.map((entry) => entry.page), resolvedPages.map(({ page: _page, ...meta }) => meta));
38
+ } catch (error) {
39
+ logger.warn(`[${context}] Failed to sync attached tabs into TabRegistry: ${error instanceof Error ? error.message : String(error)}`);
40
+ }
41
+ }
42
+ parseHeadlessArg(value) {
43
+ if (typeof value === "boolean") return value;
44
+ if (typeof value === "number") {
45
+ if (value === 1) return true;
46
+ if (value === 0) return false;
47
+ return;
48
+ }
49
+ if (typeof value === "string") {
50
+ const normalized = value.trim().toLowerCase();
51
+ if ([
52
+ "true",
53
+ "1",
54
+ "yes",
55
+ "on"
56
+ ].includes(normalized)) return true;
57
+ if ([
58
+ "false",
59
+ "0",
60
+ "no",
61
+ "off"
62
+ ].includes(normalized)) return false;
63
+ }
64
+ }
65
+ parseChromeConnectRequest(args) {
66
+ const channelValue = argString(args, "channel");
67
+ if (channelValue && !CHROME_CHANNELS.has(channelValue)) throw new Error(`Invalid channel "${channelValue}". Expected one of: stable, beta, dev, canary.`);
68
+ return {
69
+ browserURL: argString(args, "browserURL"),
70
+ wsEndpoint: argString(args, "wsEndpoint"),
71
+ autoConnect: argBool(args, "autoConnect"),
72
+ userDataDir: argString(args, "userDataDir"),
73
+ channel: channelValue
74
+ };
75
+ }
76
+ hasChromeConnectRequest(request) {
77
+ return Boolean(request.browserURL || request.wsEndpoint || request.autoConnect || request.userDataDir || request.channel);
78
+ }
79
+ describeChromeConnectRequest(request) {
80
+ if (request.wsEndpoint) return request.wsEndpoint;
81
+ if (request.browserURL) return request.browserURL;
82
+ if (request.userDataDir) return `autoConnect:${request.userDataDir}`;
83
+ return `autoConnect:${request.channel ?? "stable"}`;
84
+ }
85
+ isAutoConnectRequest(request) {
86
+ return Boolean(request.autoConnect || request.userDataDir || request.channel);
87
+ }
88
+ getAutoConnectApprovalHint(request) {
89
+ if (!this.isAutoConnectRequest(request)) return null;
90
+ return "Chrome 144+ autoConnect may prompt for manual approval. Switch to Chrome and click Allow for this client if prompted.";
91
+ }
92
+ shouldAttemptLinuxHeadfulFallback(headlessArg, error) {
93
+ const requestedHeadful = headlessArg === false || headlessArg === void 0 && process.env.PUPPETEER_HEADLESS === "false";
94
+ const linuxRuntime = process.platform === "linux" || process.env.JSHOOK_FORCE_LINUX_FALLBACK === "true";
95
+ if (!requestedHeadful || !linuxRuntime) return false;
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ return /Missing X server|cannot open display|Failed to launch the browser process|ozone|No protocol specified|X11|Wayland|DevToolsActivePort/i.test(message);
98
+ }
99
+ async persistHeadlessEnv(value) {
100
+ try {
101
+ let envContent = "";
102
+ try {
103
+ envContent = await readFile(projectEnvPath, "utf-8");
104
+ } catch (error) {
105
+ if (error?.code !== "ENOENT") throw error;
106
+ }
107
+ const nextLine = `PUPPETEER_HEADLESS=${value}`;
108
+ await writeFile(projectEnvPath, /^PUPPETEER_HEADLESS=.*$/m.test(envContent) ? envContent.replace(/^PUPPETEER_HEADLESS=.*$/m, nextLine) : `${envContent.trimEnd()}\n${nextLine}\n`, "utf-8");
109
+ } catch (error) {
110
+ logger.warn(`Failed to persist PUPPETEER_HEADLESS=${value} to .env: ${String(error)}`);
111
+ }
112
+ }
113
+ async handleBrowserLaunch(args) {
114
+ try {
115
+ if (argString(args, "driver", "chrome") === "camoufox") {
116
+ if (argString(args, "mode", "launch") === "connect") {
117
+ const wsEndpoint = argString(args, "wsEndpoint");
118
+ if (!wsEndpoint) return R.fail("wsEndpoint is required for connect mode. Use camoufox_server_launch first to get a wsEndpoint.").json();
119
+ return R.ok().merge({
120
+ driver: "camoufox",
121
+ mode: "connect",
122
+ wsEndpoint,
123
+ message: "Connected to Camoufox server. Use page_navigate to begin."
124
+ }).json();
125
+ }
126
+ return R.ok().merge({
127
+ driver: "camoufox",
128
+ mode: "launch",
129
+ message: "Camoufox (Firefox) browser launched",
130
+ note: "Use page_navigate to begin. CDP debugger is limited in Firefox; network_enable and console_monitor({ action: \"enable\" }) use Playwright events and are fully supported."
131
+ }).json();
132
+ }
133
+ if (argString(args, "mode", "launch") === "connect") {
134
+ const connectRequest = this.parseChromeConnectRequest(args);
135
+ if (!this.hasChromeConnectRequest(connectRequest)) return R.fail("browserURL, wsEndpoint, autoConnect, userDataDir, or channel is required for chrome connect mode.").json();
136
+ await this.deps.collector.connect(connectRequest);
137
+ const status = await this.deps.collector.getStatus();
138
+ return R.ok().merge({
139
+ driver: "chrome",
140
+ mode: "connect",
141
+ endpoint: this.describeChromeConnectRequest(connectRequest),
142
+ autoConnect: this.isAutoConnectRequest(connectRequest),
143
+ channel: connectRequest.channel ?? null,
144
+ userDataDir: connectRequest.userDataDir ?? null,
145
+ manualApprovalMayBeRequired: this.isAutoConnectRequest(connectRequest),
146
+ approvalHint: this.getAutoConnectApprovalHint(connectRequest),
147
+ message: "Connected to existing Chrome browser successfully",
148
+ status
149
+ }).json();
150
+ }
151
+ const chromeHeadless = this.parseHeadlessArg(args.headless);
152
+ try {
153
+ await this.deps.collector.init(chromeHeadless);
154
+ } catch (error) {
155
+ if (!this.shouldAttemptLinuxHeadfulFallback(chromeHeadless, error)) throw error;
156
+ const reason = error instanceof Error ? error.message : String(error);
157
+ logger.warn(`Headful launch failed on Linux, fallback to headless=true: ${reason}`);
158
+ process.env.PUPPETEER_HEADLESS = "true";
159
+ await this.persistHeadlessEnv("true");
160
+ await this.deps.collector.init(true);
161
+ const fallbackStatus = await this.deps.collector.getStatus();
162
+ return R.ok().merge({
163
+ driver: "chrome",
164
+ message: "Browser launched with Linux fallback (headless=true)",
165
+ status: fallbackStatus,
166
+ fallback: {
167
+ applied: true,
168
+ reason: "Headful browser is unavailable in current Linux runtime; switched to headless and updated .env",
169
+ newEnv: "PUPPETEER_HEADLESS=true"
170
+ }
171
+ }).json();
172
+ }
173
+ const status = await this.deps.collector.getStatus();
174
+ return R.ok().merge({
175
+ driver: "chrome",
176
+ message: "Browser launched successfully",
177
+ status
178
+ }).json();
179
+ } catch (error) {
180
+ return R.fail(error).json();
181
+ }
182
+ }
183
+ async handleBrowserClose(_args) {
184
+ try {
185
+ await this.deps.collector.close();
186
+ return R.ok().set("message", "Browser closed successfully").json();
187
+ } catch (e) {
188
+ return R.fail(e).json();
189
+ }
190
+ }
191
+ async handleBrowserStatus(_args) {
192
+ try {
193
+ const status = await this.deps.collector.getStatus();
194
+ return R.ok().merge({
195
+ driver: "chrome",
196
+ ...status
197
+ }).json();
198
+ } catch (e) {
199
+ return R.fail(e).json();
200
+ }
201
+ }
202
+ async handleBrowserListTabs(args) {
203
+ const connectRequest = this.parseChromeConnectRequest(args);
204
+ try {
205
+ if (this.hasChromeConnectRequest(connectRequest)) await this.deps.collector.connect(connectRequest);
206
+ const pages = await this.deps.collector.listPages();
207
+ const registry = this.deps.getTabRegistry();
208
+ await this.syncTabRegistryWithCollectorPages("browser_list_tabs");
209
+ const enrichedPages = pages.map((page) => {
210
+ const tab = registry.getTabByIndex(page.index);
211
+ return {
212
+ ...page,
213
+ pageId: tab?.pageId ?? null,
214
+ aliases: tab?.aliases ?? []
215
+ };
216
+ });
217
+ const currentInfo = registry.getContextMeta();
218
+ return R.ok().merge({
219
+ count: pages.length,
220
+ pages: enrichedPages,
221
+ currentPageId: currentInfo.pageId,
222
+ currentIndex: currentInfo.tabIndex,
223
+ autoConnect: this.isAutoConnectRequest(connectRequest),
224
+ manualApprovalMayBeRequired: this.isAutoConnectRequest(connectRequest),
225
+ approvalHint: this.getAutoConnectApprovalHint(connectRequest),
226
+ hint: "Use browser_select_tab(index=N) to switch to a specific tab"
227
+ }).json();
228
+ } catch (error) {
229
+ return R.fail(error).set("hint", "Make sure browser is attached via browser_attach first, or provide browserURL/autoConnect. Chrome 144+ autoConnect may require manual approval in the Chrome window.").set("approvalHint", this.getAutoConnectApprovalHint(connectRequest)).json();
230
+ }
231
+ }
232
+ async handleBrowserSelectTab(args) {
233
+ try {
234
+ const index = argNumber(args, "index");
235
+ const urlPattern = argString(args, "urlPattern");
236
+ const titlePattern = argString(args, "titlePattern");
237
+ const registry = this.deps.getTabRegistry();
238
+ if (index !== void 0) {
239
+ const clearedTarget = await this.deps.clearAttachedTargetContext("browser_select_tab");
240
+ await this.deps.collector.selectPage(index);
241
+ const pages = await this.deps.collector.listPages();
242
+ await this.syncTabRegistryWithCollectorPages("browser_select_tab");
243
+ const selected = pages[index];
244
+ const tab = registry.setCurrentByIndex(index);
245
+ if (tab?.pageId || clearedTarget.detached) this.markMonitoringContextChanged("browser_select_tab");
246
+ return R.ok().merge({
247
+ selectedIndex: index,
248
+ selectedPageId: tab?.pageId ?? null,
249
+ url: selected?.url,
250
+ title: selected?.title,
251
+ contextSwitched: true,
252
+ detachedCdpTarget: clearedTarget.detached,
253
+ detachedCdpTargetId: clearedTarget.targetId,
254
+ monitoringBindingDeferred: Boolean(tab?.pageId),
255
+ networkMonitoringEnabled: false,
256
+ consoleMonitoringEnabled: false
257
+ }).json();
258
+ }
259
+ const pages = await this.deps.collector.listPages();
260
+ let matchIndex = -1;
261
+ for (const page of pages) {
262
+ if (urlPattern && page.url.includes(urlPattern)) {
263
+ matchIndex = page.index;
264
+ break;
265
+ }
266
+ if (titlePattern && page.title.includes(titlePattern)) {
267
+ matchIndex = page.index;
268
+ break;
269
+ }
270
+ }
271
+ if (matchIndex === -1) return R.fail("No matching tab found").set("availablePages", pages).json();
272
+ const clearedTarget = await this.deps.clearAttachedTargetContext("browser_select_tab");
273
+ await this.deps.collector.selectPage(matchIndex);
274
+ await this.syncTabRegistryWithCollectorPages("browser_select_tab");
275
+ const selected = pages[matchIndex];
276
+ const tab = registry.setCurrentByIndex(matchIndex);
277
+ if (tab?.pageId || clearedTarget.detached) this.markMonitoringContextChanged("browser_select_tab");
278
+ return R.ok().merge({
279
+ selectedIndex: matchIndex,
280
+ selectedPageId: tab?.pageId ?? null,
281
+ url: selected?.url,
282
+ title: selected?.title,
283
+ contextSwitched: true,
284
+ detachedCdpTarget: clearedTarget.detached,
285
+ detachedCdpTargetId: clearedTarget.targetId,
286
+ monitoringBindingDeferred: Boolean(tab?.pageId),
287
+ networkMonitoringEnabled: false,
288
+ consoleMonitoringEnabled: false
289
+ }).json();
290
+ } catch (error) {
291
+ return R.fail(error).json();
292
+ }
293
+ }
294
+ async handleBrowserAttach(args) {
295
+ let connectRequest = null;
296
+ try {
297
+ connectRequest = this.parseChromeConnectRequest(args);
298
+ if (!this.hasChromeConnectRequest(connectRequest)) return R.fail("browserURL, wsEndpoint, autoConnect, userDataDir, or channel is required").json();
299
+ const clearedTarget = await this.deps.clearAttachedTargetContext("browser_attach");
300
+ await this.deps.collector.connect(connectRequest);
301
+ const rawPageIndex = args.pageIndex;
302
+ const pageIndex = typeof rawPageIndex === "number" ? rawPageIndex : typeof rawPageIndex === "string" && rawPageIndex.trim() !== "" ? Number(rawPageIndex) : 0;
303
+ const selectedIndex = Number.isFinite(pageIndex) ? pageIndex : 0;
304
+ const pages = await this.deps.collector.listPages();
305
+ if (pages.length > 0 && selectedIndex < pages.length) await this.deps.collector.selectPage(selectedIndex);
306
+ else if (pages.length > 0) {
307
+ await this.deps.collector.selectPage(0);
308
+ logger.warn(`[browser_attach] pageIndex ${selectedIndex} out of range (0-${pages.length - 1}), fell back to 0`);
309
+ }
310
+ const registry = this.deps.getTabRegistry();
311
+ await this.syncTabRegistryWithCollectorPages("browser_attach");
312
+ const actualIndex = pages.length > 0 ? Math.min(selectedIndex, pages.length - 1) : 0;
313
+ const tab = pages.length > 0 ? registry.setCurrentByIndex(actualIndex) : null;
314
+ const selected = pages[actualIndex];
315
+ const pageHandleReady = Boolean(tab?.pageId);
316
+ if (pageHandleReady) this.markMonitoringContextChanged("browser_attach");
317
+ const status = await this.deps.collector.getStatus();
318
+ return R.ok().merge({
319
+ message: "Attached to existing browser successfully",
320
+ endpoint: this.describeChromeConnectRequest(connectRequest),
321
+ autoConnect: this.isAutoConnectRequest(connectRequest),
322
+ channel: connectRequest.channel ?? null,
323
+ userDataDir: connectRequest.userDataDir ?? null,
324
+ manualApprovalMayBeRequired: this.isAutoConnectRequest(connectRequest),
325
+ approvalHint: this.getAutoConnectApprovalHint(connectRequest),
326
+ selectedIndex: actualIndex,
327
+ selectedPageId: tab?.pageId ?? null,
328
+ currentUrl: selected?.url ?? null,
329
+ currentTitle: selected?.title ?? null,
330
+ totalPages: pages.length,
331
+ contextSwitched: pages.length > 0,
332
+ detachedCdpTarget: clearedTarget.detached,
333
+ detachedCdpTargetId: clearedTarget.targetId,
334
+ monitoringBindingDeferred: pageHandleReady,
335
+ networkMonitoringEnabled: false,
336
+ consoleMonitoringEnabled: false,
337
+ takeoverReady: pageHandleReady,
338
+ note: pageHandleReady ? "Monitoring will auto-rebind on the next console/network operation for the selected tab." : "Connected to existing Chrome, but the selected tab does not currently expose a stable Puppeteer Page handle. Tab discovery still works; try selecting a different tab or navigate the tab and retry.",
339
+ status
340
+ }).json();
341
+ } catch (error) {
342
+ return R.fail(error).set("approvalHint", this.getAutoConnectApprovalHint(connectRequest ?? {})).json();
343
+ }
344
+ }
345
+ };
346
+ //#endregion
347
+ //#region src/server/domains/browser/handlers/camoufox-browser.ts
348
+ function extractCamoufoxServerConfig(args) {
349
+ const addons = argStringArray(args, "addons");
350
+ const excludeAddons = argStringArray(args, "excludeAddons");
351
+ const fonts = argStringArray(args, "fonts");
352
+ return {
353
+ headless: argBool(args, "headless", true),
354
+ os: argString(args, "os", "windows"),
355
+ geoip: argBool(args, "geoip", false),
356
+ humanize: argBool(args, "humanize", false),
357
+ proxy: argString(args, "proxy") || void 0,
358
+ blockImages: argBool(args, "blockImages", false),
359
+ blockWebrtc: argBool(args, "blockWebrtc", false),
360
+ blockWebgl: argBool(args, "blockWebgl", false),
361
+ locale: argString(args, "locale") || void 0,
362
+ addons: addons.length > 0 ? addons : void 0,
363
+ fonts: fonts.length > 0 ? fonts : void 0,
364
+ excludeAddons: excludeAddons.length > 0 ? excludeAddons : void 0,
365
+ customFontsOnly: argBool(args, "customFontsOnly", false),
366
+ screen: args.screen,
367
+ window: args.window,
368
+ fingerprint: argObject(args, "fingerprint"),
369
+ webglConfig: argObject(args, "webglConfig"),
370
+ firefoxUserPrefs: argObject(args, "firefoxUserPrefs"),
371
+ mainWorldEval: argBool(args, "mainWorldEval", false),
372
+ enableCache: argBool(args, "enableCache", false)
373
+ };
374
+ }
375
+ /**
376
+ * Check if camoufox-js is available and has all required dependencies.
377
+ * Returns error message if not available, null if available.
378
+ */
379
+ async function checkCamoufoxDependencies() {
380
+ try {
381
+ await import("camoufox-js");
382
+ return null;
383
+ } catch (error) {
384
+ const errorMsg = error instanceof Error ? error.message : String(error);
385
+ if (isBetterSqlite3RelatedError(error)) return `Camoufox requires the same native SQLite backend used by trace tooling. ${formatBetterSqlite3Error(error)}`;
386
+ if (errorMsg.includes("Cannot find package 'camoufox-js'")) return "camoufox-js package is not installed. Run: pnpm add camoufox-js && npx camoufox-js fetch";
387
+ return `Camoufox dependencies check failed: ${errorMsg}`;
388
+ }
389
+ }
390
+ var CamoufoxBrowserHandlers = class {
391
+ constructor(deps) {
392
+ this.deps = deps;
393
+ }
394
+ async handleCamoufoxServerLaunch(args) {
395
+ try {
396
+ const depError = await checkCamoufoxDependencies();
397
+ if (depError) {
398
+ logger.warn(`Camoufox dependencies not available: ${depError}`);
399
+ return R.fail(depError).set("hint", "Camoufox is optional. Use browser_launch with Chrome driver instead, or install dependencies.").json();
400
+ }
401
+ const port = argNumber(args, "port");
402
+ const ws_path = argString(args, "ws_path");
403
+ const config = extractCamoufoxServerConfig(args);
404
+ let camoufoxManager = this.deps.getCamoufoxManager();
405
+ if (!camoufoxManager) {
406
+ camoufoxManager = new CamoufoxBrowserManager(config);
407
+ this.deps.setCamoufoxManager(camoufoxManager);
408
+ }
409
+ const wsEndpoint = await camoufoxManager.launchAsServer(port, ws_path);
410
+ return R.ok().merge({
411
+ wsEndpoint,
412
+ config: {
413
+ os: config.os,
414
+ headless: config.headless,
415
+ geoip: config.geoip,
416
+ locale: config.locale,
417
+ blockWebgl: config.blockWebgl
418
+ },
419
+ message: "Camoufox server launched. Connect with: browser_launch(driver=\"camoufox\", mode=\"connect\", wsEndpoint=<wsEndpoint>)"
420
+ }).json();
421
+ } catch (error) {
422
+ return R.fail(error).set("hint", "Try running: npx camoufox-js fetch to download browser binaries").json();
423
+ }
424
+ }
425
+ async handleCamoufoxServerClose(_args) {
426
+ try {
427
+ const camoufoxManager = this.deps.getCamoufoxManager();
428
+ if (!camoufoxManager) return R.fail("No camoufox server is running.").json();
429
+ await camoufoxManager.closeBrowserServer();
430
+ return R.ok().set("message", "Camoufox server closed.").json();
431
+ } catch (e) {
432
+ return R.fail(e).json();
433
+ }
434
+ }
435
+ async handleCamoufoxServerStatus(_args) {
436
+ try {
437
+ const wsEndpoint = this.deps.getCamoufoxManager()?.getBrowserServerEndpoint() ?? null;
438
+ return R.ok().merge({
439
+ running: wsEndpoint !== null,
440
+ wsEndpoint
441
+ }).json();
442
+ } catch (e) {
443
+ return R.fail(e).json();
444
+ }
445
+ }
446
+ };
447
+ //#endregion
448
+ //#region src/server/domains/browser/handlers/page-navigation.ts
449
+ var PageNavigationHandlers = class {
450
+ constructor(deps) {
451
+ this.deps = deps;
452
+ }
453
+ async handlePageNavigate(args) {
454
+ try {
455
+ const url = argString(args, "url", "");
456
+ const rawWaitUntil = argString(args, "waitUntil", "networkidle");
457
+ const timeout = argNumber(args, "timeout");
458
+ const enableNetworkMonitoring = argBool(args, "enableNetworkMonitoring");
459
+ if (this.deps.getActiveDriver() === "camoufox") {
460
+ const playwrightWaitUntil = rawWaitUntil === "networkidle2" ? "networkidle" : rawWaitUntil;
461
+ const page = await this.deps.getCamoufoxPage();
462
+ await page.goto(url, {
463
+ waitUntil: playwrightWaitUntil,
464
+ timeout
465
+ });
466
+ this.deps.consoleMonitor.setPlaywrightPage(page);
467
+ if (enableNetworkMonitoring) await this.deps.consoleMonitor.enable({
468
+ enableNetwork: true,
469
+ enableExceptions: true
470
+ });
471
+ const navigatedUrl = page.url();
472
+ this.deps.eventBus?.emit("browser:navigated", {
473
+ url: navigatedUrl,
474
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
475
+ });
476
+ return R.ok().build({
477
+ driver: "camoufox",
478
+ url: navigatedUrl,
479
+ title: await page.title(),
480
+ network_monitoring: { enabled: this.deps.consoleMonitor.isNetworkEnabled() }
481
+ });
482
+ }
483
+ if (enableNetworkMonitoring) await this.deps.consoleMonitor.enable({
484
+ enableNetwork: true,
485
+ enableExceptions: true
486
+ });
487
+ const waitUntil = {
488
+ networkidle: "networkidle2",
489
+ commit: "load"
490
+ }[rawWaitUntil] || rawWaitUntil;
491
+ await this.deps.pageController.navigate(url, {
492
+ waitUntil,
493
+ timeout
494
+ });
495
+ const currentUrl = await this.deps.pageController.getURL();
496
+ const title = await this.deps.pageController.getTitle();
497
+ this.deps.eventBus?.emit("browser:navigated", {
498
+ url: currentUrl,
499
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
500
+ });
501
+ return R.ok().build({
502
+ url: currentUrl,
503
+ title,
504
+ network_monitoring: { enabled: this.deps.consoleMonitor.isNetworkEnabled() }
505
+ });
506
+ } catch (e) {
507
+ return R.fail(e).build();
508
+ }
509
+ }
510
+ async handlePageReload(_args) {
511
+ try {
512
+ if (this.deps.getActiveDriver() === "camoufox") {
513
+ await (await this.deps.getCamoufoxPage()).reload();
514
+ return R.ok().build({
515
+ message: "Page reloaded",
516
+ driver: "camoufox"
517
+ });
518
+ }
519
+ await this.deps.pageController.reload();
520
+ return R.ok().build({ message: "Page reloaded" });
521
+ } catch (e) {
522
+ return R.fail(e).build();
523
+ }
524
+ }
525
+ async handlePageBack(args) {
526
+ try {
527
+ const timeout = argNumber(args, "timeout", 1e4);
528
+ if (this.deps.getActiveDriver() === "camoufox") {
529
+ const page = await this.deps.getCamoufoxPage();
530
+ await page.goBack();
531
+ return R.ok().build({
532
+ url: page.url(),
533
+ driver: "camoufox"
534
+ });
535
+ }
536
+ await this.deps.pageController.goBack(timeout);
537
+ const url = await this.deps.pageController.getURL();
538
+ return R.ok().build({ url });
539
+ } catch (e) {
540
+ return R.fail(e).build();
541
+ }
542
+ }
543
+ async handlePageForward(args) {
544
+ try {
545
+ const timeout = argNumber(args, "timeout", 1e4);
546
+ if (this.deps.getActiveDriver() === "camoufox") {
547
+ const page = await this.deps.getCamoufoxPage();
548
+ await page.goForward();
549
+ return R.ok().build({
550
+ url: page.url(),
551
+ driver: "camoufox"
552
+ });
553
+ }
554
+ await this.deps.pageController.goForward(timeout);
555
+ const url = await this.deps.pageController.getURL();
556
+ return R.ok().build({ url });
557
+ } catch (e) {
558
+ return R.fail(e).build();
559
+ }
560
+ }
561
+ };
562
+ //#endregion
563
+ //#region src/server/domains/browser/handlers/page-interaction.ts
564
+ var PageInteractionHandlers = class {
565
+ constructor(deps) {
566
+ this.deps = deps;
567
+ }
568
+ toErrorMessage(error) {
569
+ if (error instanceof Error) return error.message;
570
+ return typeof error === "string" ? error : "";
571
+ }
572
+ parseNumberArg(value, options = {}) {
573
+ let parsed;
574
+ if (typeof value === "number" && Number.isFinite(value)) parsed = value;
575
+ else if (typeof value === "string") {
576
+ const trimmed = value.trim();
577
+ if (trimmed.length > 0) {
578
+ const n = Number(trimmed);
579
+ if (Number.isFinite(n)) parsed = n;
580
+ }
581
+ }
582
+ if (parsed === void 0) parsed = options.defaultValue;
583
+ if (parsed === void 0) return;
584
+ if (options.integer) parsed = Math.trunc(parsed);
585
+ if (typeof options.min === "number") parsed = Math.max(options.min, parsed);
586
+ if (typeof options.max === "number") parsed = Math.min(options.max, parsed);
587
+ return parsed;
588
+ }
589
+ parseMouseButton(value) {
590
+ if (typeof value === "string") {
591
+ const normalized = value.trim().toLowerCase();
592
+ if (normalized === "left" || normalized === "right" || normalized === "middle") return normalized;
593
+ }
594
+ return "left";
595
+ }
596
+ async getCamoufoxInteractionContext(frameOptions) {
597
+ const page = await this.deps.getCamoufoxPage();
598
+ if (!frameOptions?.frameUrl && !frameOptions?.frameSelector) return page;
599
+ return await this.deps.pageController.resolveFrame(page, frameOptions);
600
+ }
601
+ async handlePageClick(args) {
602
+ try {
603
+ const selector = argString(args, "selector", "");
604
+ const button = this.parseMouseButton(args.button);
605
+ const clickCount = this.parseNumberArg(args.clickCount, {
606
+ defaultValue: 1,
607
+ min: 1,
608
+ max: 10,
609
+ integer: true
610
+ });
611
+ const delay = this.parseNumberArg(args.delay, {
612
+ min: 0,
613
+ max: 6e4,
614
+ integer: true
615
+ });
616
+ const frameUrl = argString(args, "frameUrl");
617
+ const frameSelector = argString(args, "frameSelector");
618
+ const frameOptions = frameUrl || frameSelector ? {
619
+ frameUrl: frameUrl || void 0,
620
+ frameSelector: frameSelector || void 0
621
+ } : void 0;
622
+ if (!selector || typeof selector !== "string" || selector.trim().length === 0) return R.fail("selector parameter is required").build();
623
+ if (this.deps.getActiveDriver() === "camoufox") {
624
+ await (await this.getCamoufoxInteractionContext(frameOptions)).click(selector, {
625
+ button,
626
+ clickCount,
627
+ delay
628
+ });
629
+ return R.ok().build({
630
+ driver: "camoufox",
631
+ message: `Clicked: ${selector}`,
632
+ ...frameOptions ? { frame: frameOptions } : {}
633
+ });
634
+ }
635
+ try {
636
+ if (frameOptions) await this.deps.pageController.click(selector, {
637
+ button,
638
+ clickCount,
639
+ delay
640
+ }, frameOptions);
641
+ else await this.deps.pageController.click(selector, {
642
+ button,
643
+ clickCount,
644
+ delay
645
+ });
646
+ } catch (error) {
647
+ const msg = this.toErrorMessage(error);
648
+ if (msg.includes("detached") || msg.includes("timed out") || msg.includes("Execution context was destroyed") || msg.includes("callFunctionOn") || msg.includes("Target closed")) return R.ok().build({
649
+ message: `Clicked ${selector} - navigation triggered`,
650
+ navigated: true,
651
+ ...frameOptions ? { frame: frameOptions } : {}
652
+ });
653
+ throw error;
654
+ }
655
+ return R.ok().build({
656
+ message: `Clicked: ${selector}`,
657
+ ...frameOptions ? { frame: frameOptions } : {}
658
+ });
659
+ } catch (e) {
660
+ return R.fail(e).build();
661
+ }
662
+ }
663
+ async handlePageType(args) {
664
+ try {
665
+ const selector = argString(args, "selector", "");
666
+ const text = argString(args, "text", "");
667
+ const delay = argNumber(args, "delay");
668
+ const frameUrl = argString(args, "frameUrl");
669
+ const frameSelector = argString(args, "frameSelector");
670
+ const frameOptions = frameUrl || frameSelector ? {
671
+ frameUrl: frameUrl || void 0,
672
+ frameSelector: frameSelector || void 0
673
+ } : void 0;
674
+ if (this.deps.getActiveDriver() === "camoufox") {
675
+ await (await this.getCamoufoxInteractionContext(frameOptions)).fill(selector, text);
676
+ return R.ok().build({
677
+ driver: "camoufox",
678
+ message: `Typed into ${selector}`,
679
+ ...frameOptions ? { frame: frameOptions } : {}
680
+ });
681
+ }
682
+ if (frameOptions) await this.deps.pageController.type(selector, text, { delay }, frameOptions);
683
+ else await this.deps.pageController.type(selector, text, { delay });
684
+ return R.ok().build({
685
+ message: `Typed into ${selector}`,
686
+ ...frameOptions ? { frame: frameOptions } : {}
687
+ });
688
+ } catch (e) {
689
+ return R.fail(e).build();
690
+ }
691
+ }
692
+ async handlePageSelect(args) {
693
+ try {
694
+ const selector = argString(args, "selector", "");
695
+ const values = argStringArray(args, "values");
696
+ const frameUrl = argString(args, "frameUrl");
697
+ const frameSelector = argString(args, "frameSelector");
698
+ const frameOptions = frameUrl || frameSelector ? {
699
+ frameUrl: frameUrl || void 0,
700
+ frameSelector: frameSelector || void 0
701
+ } : void 0;
702
+ if (this.deps.getActiveDriver() === "camoufox") {
703
+ await (await this.getCamoufoxInteractionContext(frameOptions)).selectOption(selector, values);
704
+ return R.ok().build({
705
+ driver: "camoufox",
706
+ message: `Selected in ${selector}: ${values.join(", ")}`,
707
+ ...frameOptions ? { frame: frameOptions } : {}
708
+ });
709
+ }
710
+ if (frameOptions) await this.deps.pageController.select(selector, values, frameOptions);
711
+ else await this.deps.pageController.select(selector, values);
712
+ return R.ok().build({
713
+ message: `Selected in ${selector}: ${values.join(", ")}`,
714
+ ...frameOptions ? { frame: frameOptions } : {}
715
+ });
716
+ } catch (e) {
717
+ return R.fail(e).build();
718
+ }
719
+ }
720
+ async handlePageHover(args) {
721
+ try {
722
+ const selector = argString(args, "selector", "");
723
+ const frameUrl = argString(args, "frameUrl");
724
+ const frameSelector = argString(args, "frameSelector");
725
+ const frameOptions = frameUrl || frameSelector ? {
726
+ frameUrl: frameUrl || void 0,
727
+ frameSelector: frameSelector || void 0
728
+ } : void 0;
729
+ if (this.deps.getActiveDriver() === "camoufox") {
730
+ await (await this.getCamoufoxInteractionContext(frameOptions)).hover(selector);
731
+ return R.ok().build({
732
+ driver: "camoufox",
733
+ message: `Hovered: ${selector}`,
734
+ ...frameOptions ? { frame: frameOptions } : {}
735
+ });
736
+ }
737
+ if (frameOptions) await this.deps.pageController.hover(selector, frameOptions);
738
+ else await this.deps.pageController.hover(selector);
739
+ return R.ok().build({
740
+ message: `Hovered: ${selector}`,
741
+ ...frameOptions ? { frame: frameOptions } : {}
742
+ });
743
+ } catch (e) {
744
+ return R.fail(e).build();
745
+ }
746
+ }
747
+ async handlePageScroll(args) {
748
+ try {
749
+ const x = argNumber(args, "x", 0);
750
+ const y = argNumber(args, "y", 0);
751
+ if (this.deps.getActiveDriver() === "camoufox") {
752
+ await (await this.deps.getCamoufoxPage()).evaluate((position) => {
753
+ window.scrollTo(position.x || 0, position.y || 0);
754
+ }, {
755
+ x,
756
+ y
757
+ });
758
+ return R.ok().build({
759
+ driver: "camoufox",
760
+ message: `Scrolled to: x=${x || 0}, y=${y || 0}`
761
+ });
762
+ }
763
+ await this.deps.pageController.scroll({
764
+ x,
765
+ y
766
+ });
767
+ return R.ok().build({ message: `Scrolled to: x=${x || 0}, y=${y || 0}` });
768
+ } catch (e) {
769
+ return R.fail(e).build();
770
+ }
771
+ }
772
+ async handlePagePressKey(args) {
773
+ try {
774
+ const key = argString(args, "key", "");
775
+ if (this.deps.getActiveDriver() === "camoufox") {
776
+ await (await this.deps.getCamoufoxPage()).keyboard.press(key);
777
+ return R.ok().build({
778
+ driver: "camoufox",
779
+ key
780
+ });
781
+ }
782
+ await this.deps.pageController.pressKey(key);
783
+ return R.ok().build({ key });
784
+ } catch (e) {
785
+ return R.fail(e).build();
786
+ }
787
+ }
788
+ };
789
+ //#endregion
790
+ //#region src/server/domains/browser/handlers/evaluation-utils.ts
791
+ /** Recursively remove keys listed in `fields` from any nested object/array. */
792
+ function filterFields(value, fields) {
793
+ if (Array.isArray(value)) return value.map((item) => filterFields(item, fields));
794
+ if (value !== null && typeof value === "object") {
795
+ const obj = value;
796
+ const out = {};
797
+ for (const [key, nested] of Object.entries(obj)) if (!fields.has(key)) out[key] = filterFields(nested, fields);
798
+ return out;
799
+ }
800
+ return value;
801
+ }
802
+ /**
803
+ * Recursively replace base64 payloads with a short placeholder.
804
+ * Catches: data:[mime];base64,<payload> and bare strings >500 chars of [A-Za-z0-9+/=]
805
+ */
806
+ function stripBase64Values(value) {
807
+ if (typeof value === "string") {
808
+ if (/^data:[a-z+-]+\/[a-z+-]+;base64,/i.test(value)) return `[base64 ~${Math.round(value.length / 1024)}KB stripped]`;
809
+ if (value.length > 500 && /^[A-Za-z0-9+/=\r\n]+$/.test(value.replace(/\s/g, ""))) return `[base64 ~${value.length}chars stripped]`;
810
+ return value;
811
+ }
812
+ if (Array.isArray(value)) return value.map((item) => stripBase64Values(item));
813
+ if (value !== null && typeof value === "object") {
814
+ const obj = value;
815
+ const out = {};
816
+ for (const [key, nested] of Object.entries(obj)) out[key] = stripBase64Values(nested);
817
+ return out;
818
+ }
819
+ return value;
820
+ }
821
+ function applyEvaluationPostFilters(raw, detailedDataManager, options) {
822
+ let result = options.autoSummarize ? detailedDataManager.smartHandle(raw, options.maxSize) : raw;
823
+ if (options.fieldFilter && options.fieldFilter.length > 0) result = filterFields(result, new Set(options.fieldFilter));
824
+ if (options.stripBase64) result = stripBase64Values(result);
825
+ return result;
826
+ }
827
+ //#endregion
828
+ //#region src/server/domains/browser/handlers/page-evaluation.ts
829
+ var PageEvaluationHandlers = class {
830
+ constructor(deps) {
831
+ this.deps = deps;
832
+ }
833
+ async getCamoufoxEvaluationContext(frameOptions) {
834
+ const page = await this.deps.getCamoufoxPage();
835
+ if (!frameOptions?.frameUrl && !frameOptions?.frameSelector) return page;
836
+ return await this.deps.pageController.resolveFrame(page, frameOptions);
837
+ }
838
+ async handlePageEvaluate(args) {
839
+ try {
840
+ const code = argString(args, "script", "") || argString(args, "code", "");
841
+ const autoSummarize = argBool(args, "autoSummarize", true);
842
+ const maxSize = argNumber(args, "maxSize", 51200);
843
+ const fieldFilterArg = argStringArray(args, "fieldFilter");
844
+ const doStripBase64 = argBool(args, "stripBase64", false);
845
+ const frameUrl = argString(args, "frameUrl");
846
+ const frameSelector = argString(args, "frameSelector");
847
+ const frameOptions = frameUrl || frameSelector ? {
848
+ frameUrl: frameUrl || void 0,
849
+ frameSelector: frameSelector || void 0
850
+ } : void 0;
851
+ if (this.deps.getActiveDriver() === "camoufox") {
852
+ const context = await this.getCamoufoxEvaluationContext(frameOptions);
853
+ const evaluateExpression = new Function(`return (${code})`);
854
+ const processedResult = applyEvaluationPostFilters(await context.evaluate(evaluateExpression), this.deps.detailedDataManager, {
855
+ autoSummarize,
856
+ maxSize,
857
+ fieldFilter: fieldFilterArg ?? void 0,
858
+ stripBase64: doStripBase64
859
+ });
860
+ return R.ok().build({
861
+ driver: "camoufox",
862
+ ...frameOptions ? { frame: frameOptions } : {},
863
+ result: processedResult
864
+ });
865
+ }
866
+ const processedResult = applyEvaluationPostFilters(frameOptions ? await this.deps.pageController.evaluate(code, frameOptions) : await this.deps.pageController.evaluate(code), this.deps.detailedDataManager, {
867
+ autoSummarize,
868
+ maxSize,
869
+ fieldFilter: fieldFilterArg ?? void 0,
870
+ stripBase64: doStripBase64
871
+ });
872
+ return R.ok().build({
873
+ ...frameOptions ? { frame: frameOptions } : {},
874
+ result: processedResult
875
+ });
876
+ } catch (e) {
877
+ return R.fail(e).build();
878
+ }
879
+ }
880
+ async handlePageScreenshot(args) {
881
+ try {
882
+ const requestedPath = argString(args, "path");
883
+ const type = argString(args, "type", "png");
884
+ const quality = argNumber(args, "quality");
885
+ const fullPage = argBool(args, "fullPage", false);
886
+ const clipArg = argObject(args, "clip");
887
+ const rawSelector = args.selector;
888
+ const selectors = [];
889
+ if (Array.isArray(rawSelector)) for (const s of rawSelector) {
890
+ const trimmed = typeof s === "string" ? s.trim() : "";
891
+ if (trimmed.length > 0 && trimmed.toLowerCase() !== "all") selectors.push(trimmed);
892
+ }
893
+ else if (typeof rawSelector === "string") {
894
+ const trimmed = rawSelector.trim();
895
+ if (trimmed.length > 0 && trimmed.toLowerCase() !== "all") selectors.push(trimmed);
896
+ }
897
+ if (selectors.length > 1) return this._screenshotBatch(selectors, requestedPath, type, quality);
898
+ const selector = selectors[0] ?? "";
899
+ const { absolutePath, displayPath, pathRewritten } = await resolveScreenshotOutputPath({
900
+ requestedPath,
901
+ type,
902
+ fallbackName: selector ? "element" : clipArg ? "region" : "page",
903
+ fallbackDir: "screenshots/manual"
904
+ });
905
+ if (this.deps.getActiveDriver() === "camoufox") {
906
+ const page = await this.deps.getCamoufoxPage();
907
+ let buffer;
908
+ if (selector) {
909
+ const element = await page.$(selector);
910
+ if (!element) return R.fail(`Element not found: ${selector}`).build();
911
+ buffer = await element.screenshot({
912
+ path: absolutePath,
913
+ type,
914
+ quality
915
+ });
916
+ } else buffer = await page.screenshot({
917
+ path: absolutePath,
918
+ type,
919
+ quality,
920
+ fullPage: clipArg ? false : fullPage
921
+ });
922
+ return R.ok().build({
923
+ driver: "camoufox",
924
+ selector: selector || void 0,
925
+ clip: clipArg || void 0,
926
+ message: `Screenshot taken: ${displayPath}`,
927
+ path: displayPath,
928
+ pathRewritten,
929
+ size: buffer?.length ?? 0
930
+ });
931
+ }
932
+ let buffer;
933
+ if (selector) {
934
+ const element = await (await this.deps.pageController.getPage()).$(selector);
935
+ if (!element) return R.fail(`Element not found: ${selector}`).build();
936
+ buffer = await element.screenshot({
937
+ path: absolutePath,
938
+ type,
939
+ quality
940
+ });
941
+ } else buffer = await this.deps.pageController.screenshot({
942
+ path: absolutePath,
943
+ type,
944
+ quality,
945
+ fullPage: clipArg ? false : fullPage,
946
+ clip: clipArg
947
+ });
948
+ return R.ok().build({
949
+ selector: selector || void 0,
950
+ clip: clipArg || void 0,
951
+ message: `Screenshot taken: ${displayPath}`,
952
+ path: displayPath,
953
+ pathRewritten,
954
+ size: buffer.length
955
+ });
956
+ } catch (e) {
957
+ return R.fail(e).build();
958
+ }
959
+ }
960
+ /** Take one screenshot per selector and return all results. */
961
+ async _screenshotBatch(selectors, requestedPath, type, quality) {
962
+ const isCamoufox = this.deps.getActiveDriver() === "camoufox";
963
+ const results = [];
964
+ for (const selector of selectors) {
965
+ const { absolutePath, displayPath } = await resolveScreenshotOutputPath({
966
+ requestedPath: requestedPath ? requestedPath.replace(/(\.\w+)$/, `-${selector.replace(/[^a-zA-Z0-9]/g, "_")}$1`) : void 0,
967
+ type,
968
+ fallbackName: `element-${selector.replace(/[^a-zA-Z0-9]/g, "_")}`,
969
+ fallbackDir: "screenshots/manual"
970
+ });
971
+ try {
972
+ let size = 0;
973
+ if (isCamoufox) {
974
+ const element = await (await this.deps.getCamoufoxPage()).$(selector);
975
+ if (!element) {
976
+ results.push({
977
+ selector,
978
+ success: false,
979
+ error: `Element not found: ${selector}`
980
+ });
981
+ continue;
982
+ }
983
+ size = (await element.screenshot({
984
+ path: absolutePath,
985
+ type,
986
+ quality
987
+ }))?.length ?? 0;
988
+ } else {
989
+ const element = await (await this.deps.pageController.getPage()).$(selector);
990
+ if (!element) {
991
+ results.push({
992
+ selector,
993
+ success: false,
994
+ error: `Element not found: ${selector}`
995
+ });
996
+ continue;
997
+ }
998
+ size = (await element.screenshot({
999
+ path: absolutePath,
1000
+ type,
1001
+ quality
1002
+ })).length;
1003
+ }
1004
+ results.push({
1005
+ selector,
1006
+ success: true,
1007
+ path: displayPath,
1008
+ size
1009
+ });
1010
+ } catch (err) {
1011
+ results.push({
1012
+ selector,
1013
+ success: false,
1014
+ error: String(err)
1015
+ });
1016
+ }
1017
+ }
1018
+ return R.ok().build({
1019
+ mode: "batch",
1020
+ total: selectors.length,
1021
+ succeeded: results.filter((r) => r.success).length,
1022
+ results
1023
+ });
1024
+ }
1025
+ async handlePageInjectScript(args) {
1026
+ try {
1027
+ const script = argString(args, "script", "");
1028
+ await this.deps.pageController.injectScript(script);
1029
+ return R.ok().build({ message: "Script injected" });
1030
+ } catch (e) {
1031
+ return R.fail(e).build();
1032
+ }
1033
+ }
1034
+ async handlePageWaitForSelector(args) {
1035
+ try {
1036
+ const selector = argString(args, "selector", "");
1037
+ const timeout = argNumber(args, "timeout");
1038
+ if (this.deps.getActiveDriver() === "camoufox") {
1039
+ const page = await this.deps.getCamoufoxPage();
1040
+ try {
1041
+ await page.waitForSelector(selector, { timeout: timeout || 3e4 });
1042
+ const element = await page.evaluate((sel) => {
1043
+ const el = document.querySelector(sel);
1044
+ if (!el) return null;
1045
+ return {
1046
+ tagName: el.tagName.toLowerCase(),
1047
+ id: el.id || void 0,
1048
+ className: el.className || void 0,
1049
+ textContent: el.textContent?.trim().substring(0, 100) || void 0,
1050
+ attributes: Array.from(el.attributes).reduce((acc, attr) => {
1051
+ acc[attr.name] = attr.value;
1052
+ return acc;
1053
+ }, {})
1054
+ };
1055
+ }, selector);
1056
+ return R.ok().build({
1057
+ driver: "camoufox",
1058
+ element,
1059
+ message: `Selector appeared: ${selector}`
1060
+ });
1061
+ } catch {
1062
+ return R.fail(`Timeout waiting for selector: ${selector}`).build({ driver: "camoufox" });
1063
+ }
1064
+ }
1065
+ const result = await this.deps.pageController.waitForSelector(selector, timeout);
1066
+ return R.ok().merge(result).build();
1067
+ } catch (e) {
1068
+ return R.fail(e).build();
1069
+ }
1070
+ }
1071
+ };
1072
+ //#endregion
1073
+ //#region src/server/domains/browser/handlers/page-data.ts
1074
+ var PageDataHandlers = class {
1075
+ constructor(deps) {
1076
+ this.deps = deps;
1077
+ }
1078
+ async handleGetContent(_args) {
1079
+ try {
1080
+ const html = await this.deps.pageController.getContent();
1081
+ return R.ok().build({ html });
1082
+ } catch (e) {
1083
+ return R.fail(e).build();
1084
+ }
1085
+ }
1086
+ async handleGetTitle(_args) {
1087
+ try {
1088
+ const title = await this.deps.pageController.getTitle();
1089
+ return R.ok().build({ title });
1090
+ } catch (e) {
1091
+ return R.fail(e).build();
1092
+ }
1093
+ }
1094
+ async handleGetUrl(_args) {
1095
+ try {
1096
+ const url = await this.deps.pageController.getURL();
1097
+ return R.ok().build({ url });
1098
+ } catch (e) {
1099
+ return R.fail(e).build();
1100
+ }
1101
+ }
1102
+ async handleGetText(args) {
1103
+ try {
1104
+ const selector = argString(args, "selector", "");
1105
+ const text = await this.deps.pageController.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent || ""`);
1106
+ return R.ok().build({
1107
+ selector,
1108
+ text
1109
+ });
1110
+ } catch (e) {
1111
+ return R.fail(e).build();
1112
+ }
1113
+ }
1114
+ async handleGetOuterHtml(args) {
1115
+ try {
1116
+ const selector = argString(args, "selector", "");
1117
+ const html = await this.deps.pageController.evaluate(`document.querySelector(${JSON.stringify(selector)})?.outerHTML || ""`);
1118
+ return R.ok().build({
1119
+ selector,
1120
+ html
1121
+ });
1122
+ } catch (e) {
1123
+ return R.fail(e).build();
1124
+ }
1125
+ }
1126
+ async handleGetScrollPosition(_args) {
1127
+ try {
1128
+ const pos = await this.deps.pageController.evaluate(`({
1129
+ scrollX: window.scrollX,
1130
+ scrollY: window.scrollY,
1131
+ maxScrollX: document.documentElement.scrollWidth - window.innerWidth,
1132
+ maxScrollY: document.documentElement.scrollHeight - window.innerHeight
1133
+ })`);
1134
+ return R.ok().build(pos);
1135
+ } catch (e) {
1136
+ return R.fail(e).build();
1137
+ }
1138
+ }
1139
+ async handlePageSetCookies(args) {
1140
+ try {
1141
+ const cookies = args.cookies;
1142
+ await this.deps.pageController.setCookies(cookies);
1143
+ return R.ok().build({ message: `Set ${cookies.length} cookies` });
1144
+ } catch (e) {
1145
+ return R.fail(e).build();
1146
+ }
1147
+ }
1148
+ async handlePageGetCookies(_args) {
1149
+ try {
1150
+ const cookies = await this.deps.pageController.getCookies();
1151
+ return R.ok().build({
1152
+ count: cookies.length,
1153
+ cookies
1154
+ });
1155
+ } catch (e) {
1156
+ return R.fail(e).build();
1157
+ }
1158
+ }
1159
+ async getPageCookieCount() {
1160
+ return (await this.deps.pageController.getCookies()).length;
1161
+ }
1162
+ async handlePageClearCookies(_args) {
1163
+ try {
1164
+ await this.deps.pageController.clearCookies();
1165
+ return R.ok().build({ message: "Cookies cleared" });
1166
+ } catch (e) {
1167
+ return R.fail(e).build();
1168
+ }
1169
+ }
1170
+ async handlePageSetViewport(args) {
1171
+ try {
1172
+ const width = argNumber(args, "width", 0);
1173
+ const height = argNumber(args, "height", 0);
1174
+ await this.deps.pageController.setViewport(width, height);
1175
+ return R.ok().build({ viewport: {
1176
+ width,
1177
+ height
1178
+ } });
1179
+ } catch (e) {
1180
+ return R.fail(e).build();
1181
+ }
1182
+ }
1183
+ async handlePageEmulateDevice(args) {
1184
+ try {
1185
+ const device = argString(args, "device", "");
1186
+ await this.deps.pageController.emulateDevice(device);
1187
+ return R.ok().build({ device });
1188
+ } catch (e) {
1189
+ return R.fail(e).build();
1190
+ }
1191
+ }
1192
+ async handlePageGetLocalStorage(_args) {
1193
+ try {
1194
+ const storage = await this.deps.pageController.getLocalStorage();
1195
+ return R.ok().build({
1196
+ count: Object.keys(storage).length,
1197
+ storage
1198
+ });
1199
+ } catch (e) {
1200
+ return R.fail(e).build();
1201
+ }
1202
+ }
1203
+ async handlePageSetLocalStorage(args) {
1204
+ try {
1205
+ const key = argString(args, "key", "");
1206
+ const value = argString(args, "value", "");
1207
+ await this.deps.pageController.setLocalStorage(key, value);
1208
+ return R.ok().build({ key });
1209
+ } catch (e) {
1210
+ return R.fail(e).build();
1211
+ }
1212
+ }
1213
+ };
1214
+ //#endregion
1215
+ //#region src/server/domains/browser/handlers/console-handlers.ts
1216
+ var ConsoleHandlers = class {
1217
+ constructor(deps) {
1218
+ this.deps = deps;
1219
+ }
1220
+ async handleConsoleMonitor(args) {
1221
+ try {
1222
+ if (argString(args, "action") === "enable") {
1223
+ await this.deps.consoleMonitor.enable();
1224
+ return R.ok().build({ message: "Console monitoring enabled" });
1225
+ } else {
1226
+ await this.deps.consoleMonitor.disable();
1227
+ return R.ok().build({ message: "Console monitoring disabled" });
1228
+ }
1229
+ } catch (e) {
1230
+ return R.fail(e).build();
1231
+ }
1232
+ }
1233
+ async handleConsoleGetLogs(args) {
1234
+ try {
1235
+ const type = argString(args, "type");
1236
+ const limit = argNumber(args, "limit");
1237
+ const since = argNumber(args, "since");
1238
+ const logs = this.deps.consoleMonitor.getLogs({
1239
+ type,
1240
+ limit,
1241
+ since
1242
+ });
1243
+ const result = {
1244
+ count: logs.length,
1245
+ logs
1246
+ };
1247
+ const processedResult = this.deps.detailedDataManager.smartHandle(result, 51200);
1248
+ return R.ok().merge(processedResult).build();
1249
+ } catch (e) {
1250
+ return R.fail(e).build();
1251
+ }
1252
+ }
1253
+ async handleConsoleExecute(args) {
1254
+ try {
1255
+ const expression = argString(args, "expression", "");
1256
+ const result = await this.deps.consoleMonitor.execute(expression);
1257
+ return R.ok().build({ result });
1258
+ } catch (e) {
1259
+ return R.fail(e).build();
1260
+ }
1261
+ }
1262
+ };
1263
+ //#endregion
1264
+ //#region src/server/domains/browser/handlers/script-management.ts
1265
+ var ScriptManagementHandlers = class {
1266
+ constructor(deps) {
1267
+ this.deps = deps;
1268
+ }
1269
+ async handleGetAllScripts(args) {
1270
+ try {
1271
+ const includeSource = argBool(args, "includeSource", false);
1272
+ const MAX_SCRIPTS_CAP = SCRIPTS_MAX_CAP;
1273
+ const maxScripts = Math.min(argNumber(args, "maxScripts", includeSource ? 200 : 1e3), MAX_SCRIPTS_CAP);
1274
+ const scripts = await this.deps.scriptManager.getAllScripts(includeSource, maxScripts);
1275
+ const data = {
1276
+ count: scripts.length,
1277
+ scripts
1278
+ };
1279
+ const processed = this.deps.detailedDataManager.smartHandle(data);
1280
+ return R.ok().merge(processed).build();
1281
+ } catch (e) {
1282
+ return R.fail(e).build();
1283
+ }
1284
+ }
1285
+ async handleGetScriptSource(args) {
1286
+ try {
1287
+ const scriptId = argString(args, "scriptId");
1288
+ const url = argString(args, "url");
1289
+ const preview = argBool(args, "preview", true);
1290
+ const maxLines = argNumber(args, "maxLines", 100);
1291
+ const startLine = argNumber(args, "startLine");
1292
+ const endLine = argNumber(args, "endLine");
1293
+ const script = await this.deps.scriptManager.getScriptSource(scriptId, url);
1294
+ if (!script) return R.fail("Script not found").build();
1295
+ if (preview || startLine !== void 0 || endLine !== void 0) {
1296
+ const source = script.source || "";
1297
+ const lines = source.split("\n");
1298
+ const totalLines = lines.length;
1299
+ const size = source.length;
1300
+ let previewContent;
1301
+ let actualStartLine;
1302
+ let actualEndLine;
1303
+ if (startLine !== void 0 && endLine !== void 0) {
1304
+ actualStartLine = Math.max(1, startLine);
1305
+ actualEndLine = Math.min(totalLines, endLine);
1306
+ previewContent = lines.slice(actualStartLine - 1, actualEndLine).join("\n");
1307
+ } else {
1308
+ actualStartLine = 1;
1309
+ actualEndLine = Math.min(maxLines, totalLines);
1310
+ previewContent = lines.slice(0, maxLines).join("\n");
1311
+ }
1312
+ const result = {
1313
+ scriptId: script.scriptId,
1314
+ url: script.url,
1315
+ preview: true,
1316
+ totalLines,
1317
+ size,
1318
+ sizeKB: (size / 1024).toFixed(1) + "KB",
1319
+ showingLines: `${actualStartLine}-${actualEndLine}`,
1320
+ content: previewContent,
1321
+ hint: size > 51200 ? `Script is large (${(size / 1024).toFixed(1)}KB). Use startLine/endLine to get specific sections, or set preview=false to get full source (will return detailId).` : "Set preview=false to get full source"
1322
+ };
1323
+ return R.ok().build(result);
1324
+ }
1325
+ const processedScript = this.deps.detailedDataManager.smartHandle(script, 51200);
1326
+ return R.ok().merge(processedScript).build();
1327
+ } catch (e) {
1328
+ return R.fail(e).build();
1329
+ }
1330
+ }
1331
+ };
1332
+ //#endregion
1333
+ //#region src/server/domains/browser/handlers/captcha-handlers.ts
1334
+ var CaptchaHandlers = class {
1335
+ constructor(deps) {
1336
+ this.deps = deps;
1337
+ }
1338
+ async handleCaptchaDetect(_args) {
1339
+ try {
1340
+ const page = await this.deps.pageController.getPage();
1341
+ const result = await this.deps.captchaDetector.detect(page);
1342
+ return R.ok().build({
1343
+ captcha_detected: result.detected,
1344
+ captcha_info: result
1345
+ });
1346
+ } catch (e) {
1347
+ return R.fail(e).build();
1348
+ }
1349
+ }
1350
+ async handleCaptchaWait(args) {
1351
+ try {
1352
+ const timeout = argNumber(args, "timeout", this.deps.captchaTimeout);
1353
+ const page = await this.deps.pageController.getPage();
1354
+ logger.info("Waiting for CAPTCHA to be solved...");
1355
+ if (!await this.deps.captchaDetector.waitForCompletion(page, timeout)) return R.fail("CAPTCHA wait timed out").build();
1356
+ return R.ok().build({ message: "CAPTCHA solved" });
1357
+ } catch (e) {
1358
+ return R.fail(e).build();
1359
+ }
1360
+ }
1361
+ async handleCaptchaConfig(args) {
1362
+ try {
1363
+ if (args.autoDetectCaptcha !== void 0) this.deps.setAutoDetectCaptcha(argBool(args, "autoDetectCaptcha", false));
1364
+ if (args.autoSwitchHeadless !== void 0) this.deps.setAutoSwitchHeadless(argBool(args, "autoSwitchHeadless", false));
1365
+ if (args.captchaTimeout !== void 0) this.deps.setCaptchaTimeout(argNumber(args, "captchaTimeout", 0));
1366
+ return R.ok().build({ config: {
1367
+ autoDetectCaptcha: this.deps.autoDetectCaptcha,
1368
+ autoSwitchHeadless: this.deps.autoSwitchHeadless,
1369
+ captchaTimeout: this.deps.captchaTimeout
1370
+ } });
1371
+ } catch (e) {
1372
+ return R.fail(e).build();
1373
+ }
1374
+ }
1375
+ };
1376
+ //#endregion
1377
+ //#region src/server/domains/browser/handlers/stealth-injection.ts
1378
+ /** Module-level jitter configuration shared across handler calls. */
1379
+ const jitterOptions = {
1380
+ enabled: true,
1381
+ minDelayMs: 20,
1382
+ maxDelayMs: 80,
1383
+ burstMode: false
1384
+ };
1385
+ let fingerprintManagerInstance = null;
1386
+ async function getFingerprintManager() {
1387
+ if (fingerprintManagerInstance) return fingerprintManagerInstance;
1388
+ try {
1389
+ fingerprintManagerInstance = (await import("./FingerprintManager-gzWtkKuf.mjs")).FingerprintManager.getInstance();
1390
+ return fingerprintManagerInstance;
1391
+ } catch {
1392
+ return null;
1393
+ }
1394
+ }
1395
+ var StealthInjectionHandlers = class {
1396
+ constructor(deps) {
1397
+ this.deps = deps;
1398
+ }
1399
+ async handleStealthInject(_args) {
1400
+ try {
1401
+ if (this.deps.getActiveDriver() === "camoufox") return R.ok().build({
1402
+ driver: "camoufox",
1403
+ message: "Camoufox uses C++ engine-level fingerprint spoofing — JS-layer stealth scripts are not needed and have been skipped."
1404
+ });
1405
+ const page = await this.deps.pageController.getPage();
1406
+ const fm = await getFingerprintManager();
1407
+ let fingerprintApplied = false;
1408
+ if (fm?.isAvailable()) try {
1409
+ let profile = fm.getActiveProfile();
1410
+ if (!profile) profile = await fm.generateFingerprint();
1411
+ if (profile) {
1412
+ await fm.injectFingerprint(page, profile);
1413
+ fingerprintApplied = true;
1414
+ }
1415
+ } catch (err) {
1416
+ logger.warn("Fingerprint injection failed, falling back to StealthScripts:", err);
1417
+ }
1418
+ await StealthScripts.injectAll(page);
1419
+ return R.ok().build({
1420
+ message: "Stealth scripts injected successfully",
1421
+ fingerprintApplied,
1422
+ _nextStepHint: "Stealth patches are now active. Next: navigate to your target URL with page_navigate. Do NOT call stealth_inject again — it only needs to run once per page."
1423
+ });
1424
+ } catch (e) {
1425
+ return R.fail(e).build();
1426
+ }
1427
+ }
1428
+ async handleStealthSetUserAgent(args) {
1429
+ try {
1430
+ const platform = argString(args, "platform", "windows");
1431
+ const page = await this.deps.pageController.getPage();
1432
+ await StealthScripts.setRealisticUserAgent(page, platform);
1433
+ return R.ok().build({
1434
+ platform,
1435
+ message: `User-Agent set for ${platform}`,
1436
+ _nextStepHint: "User-Agent is now configured. Next: call stealth_inject to apply all anti-detection patches, then page_navigate to your target URL."
1437
+ });
1438
+ } catch (e) {
1439
+ return R.fail(e).build();
1440
+ }
1441
+ }
1442
+ async handleStealthConfigureJitter(args) {
1443
+ try {
1444
+ if (args.enabled !== void 0) jitterOptions.enabled = Boolean(args.enabled);
1445
+ if (typeof args.minDelayMs === "number") jitterOptions.minDelayMs = args.minDelayMs;
1446
+ if (typeof args.maxDelayMs === "number") jitterOptions.maxDelayMs = args.maxDelayMs;
1447
+ if (args.burstMode !== void 0) jitterOptions.burstMode = Boolean(args.burstMode);
1448
+ return R.ok().build({
1449
+ jitterOptions,
1450
+ message: `CDP timing jitter ${jitterOptions.enabled ? "enabled" : "disabled"}: ${jitterOptions.minDelayMs}-${jitterOptions.maxDelayMs}ms${jitterOptions.burstMode ? " (burst mode)" : ""}`
1451
+ });
1452
+ } catch (e) {
1453
+ return R.fail(e).build();
1454
+ }
1455
+ }
1456
+ async handleStealthGenerateFingerprint(args) {
1457
+ try {
1458
+ if (this.deps.getActiveDriver() === "camoufox") try {
1459
+ const fingerprints = await import("camoufox-js/fingerprints");
1460
+ const os = argString(args, "os", "windows");
1461
+ const fp = await fingerprints.generateFingerprint(os);
1462
+ return R.ok().build({
1463
+ fingerprint: fp,
1464
+ driver: "camoufox",
1465
+ message: "Fingerprint generated using camoufox native engine. Apply via browser_launch(fingerprint=...) before launching."
1466
+ });
1467
+ } catch (err) {
1468
+ return R.fail(`Camoufox fingerprint generation failed: ${err instanceof Error ? err.message : String(err)}`).build();
1469
+ }
1470
+ const fm = await getFingerprintManager();
1471
+ if (!fm?.isAvailable()) return R.fail("fingerprint-generator/fingerprint-injector packages are not installed. Install them with: pnpm add fingerprint-generator fingerprint-injector").build();
1472
+ const profile = await fm.generateFingerprint({
1473
+ os: args.os,
1474
+ browser: args.browser ?? "chrome",
1475
+ locale: args.locale ?? "en-US"
1476
+ });
1477
+ return R.ok().build({
1478
+ profile,
1479
+ message: "Fingerprint generated and cached. It will be auto-applied on next stealth_inject."
1480
+ });
1481
+ } catch (e) {
1482
+ return R.fail(e).build();
1483
+ }
1484
+ }
1485
+ async handleStealthVerify(_args) {
1486
+ try {
1487
+ const page = await this.deps.pageController.getPage();
1488
+ const result = await new (await (import("./StealthVerifier-Bo4T3bz8.mjs"))).StealthVerifier().verify(page);
1489
+ return R.ok().merge(result).build();
1490
+ } catch (err) {
1491
+ return R.fail(`Stealth verification failed: ${err instanceof Error ? err.message : String(err)}`).build();
1492
+ }
1493
+ }
1494
+ async handleCamoufoxGeolocation(args) {
1495
+ try {
1496
+ const locale = argString(args, "locale");
1497
+ if (!locale) return R.fail("locale is required (e.g. \"en-US\", \"zh-CN\")").build();
1498
+ let geo;
1499
+ try {
1500
+ geo = await (await import("camoufox-js/locale")).getGeolocation(locale);
1501
+ } catch (err) {
1502
+ return R.fail(`Camoufox locale module unavailable: ${err instanceof Error ? err.message : String(err)}. Ensure camoufox-js is installed.`).build();
1503
+ }
1504
+ let publicIp = null;
1505
+ const proxy = argString(args, "proxy");
1506
+ if (proxy) try {
1507
+ publicIp = await (await import("camoufox-js/ip")).publicIP(proxy);
1508
+ } catch {}
1509
+ return R.ok().build({
1510
+ locale,
1511
+ geolocation: geo,
1512
+ publicIp
1513
+ });
1514
+ } catch (e) {
1515
+ return R.fail(e).build();
1516
+ }
1517
+ }
1518
+ };
1519
+ //#endregion
1520
+ //#region src/server/domains/browser/handlers/framework-state.ts
1521
+ var FrameworkStateHandlers = class {
1522
+ constructor(deps) {
1523
+ this.deps = deps;
1524
+ }
1525
+ async handleFrameworkStateExtract(args) {
1526
+ const framework = argString(args, "framework", "auto");
1527
+ const selector = argString(args, "selector", "");
1528
+ const maxDepth = argNumber(args, "maxDepth", 5);
1529
+ try {
1530
+ const page = await this.deps.getActivePage();
1531
+ try {
1532
+ const cdp = await page.createCDPSession();
1533
+ await Promise.race([cdp.send("Runtime.evaluate", {
1534
+ expression: "1",
1535
+ returnByValue: true
1536
+ }), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("cdp_unreachable")), 3e3))]);
1537
+ } catch {
1538
+ throw new PrerequisiteError("CDP session unresponsive — the debugger may be blocking page evaluation. Call debugger_lifecycle({ action: 'disable' })() before framework_state_extract, or run it before debugger_lifecycle({ action: 'enable' }).");
1539
+ }
1540
+ const evalPromise = page.evaluate((opts) => {
1541
+ const win = window;
1542
+ function safeSerialize(val, depth = 0) {
1543
+ if (depth > 4) return "[deep]";
1544
+ if (val === null || val === void 0) return val;
1545
+ if (typeof val === "function") return "[Function]";
1546
+ if (typeof val !== "object") return val;
1547
+ if (Array.isArray(val)) return val.slice(0, 20).map((v) => safeSerialize(v, depth + 1));
1548
+ try {
1549
+ const out = {};
1550
+ let count = 0;
1551
+ for (const k of Object.keys(val)) {
1552
+ if (count++ > 30) {
1553
+ out["__truncated__"] = true;
1554
+ break;
1555
+ }
1556
+ out[k] = safeSerialize(val[k], depth + 1);
1557
+ }
1558
+ return out;
1559
+ } catch {
1560
+ return "[unserializable]";
1561
+ }
1562
+ }
1563
+ const getRootEl = () => {
1564
+ if (opts.selector) return document.querySelector(opts.selector) ?? document.body;
1565
+ return document.getElementById("root") ?? document.getElementById("app") ?? document.querySelector("[data-reactroot]") ?? document.body;
1566
+ };
1567
+ const extractReact = () => {
1568
+ const rootObj = getRootEl();
1569
+ const fiberKey = Object.keys(rootObj).find((k) => k.startsWith("__reactFiber") || k.startsWith("__reactInternalInstance") || k.startsWith("__reactFiberContainer"));
1570
+ if (!fiberKey) return null;
1571
+ const states = [];
1572
+ const visited = /* @__PURE__ */ new WeakSet();
1573
+ const visitFiber = (fiber, depth) => {
1574
+ if (!fiber || depth > opts.maxDepth || visited.has(fiber)) return;
1575
+ visited.add(fiber);
1576
+ if (fiber["memoizedState"]) {
1577
+ const stateList = [];
1578
+ let s = fiber["memoizedState"];
1579
+ let guard = 0;
1580
+ while (s && guard++ < 20) {
1581
+ const queue = s["queue"];
1582
+ const val = s["memoizedState"] !== void 0 ? s["memoizedState"] : queue?.["lastRenderedState"];
1583
+ if (val !== void 0) stateList.push(safeSerialize(val));
1584
+ s = s["next"] ?? null;
1585
+ }
1586
+ if (stateList.length > 0) {
1587
+ const fiberType = fiber["type"];
1588
+ const componentName = typeof fiberType === "object" && fiberType !== null ? String(fiberType["name"] ?? "anonymous") : typeof fiberType === "string" ? fiberType : "anonymous";
1589
+ states.push({
1590
+ component: componentName,
1591
+ state: stateList
1592
+ });
1593
+ }
1594
+ }
1595
+ visitFiber(fiber["child"] ?? null, depth + 1);
1596
+ visitFiber(fiber["sibling"] ?? null, depth + 1);
1597
+ };
1598
+ visitFiber(rootObj[fiberKey] ?? null, 0);
1599
+ return states;
1600
+ };
1601
+ const extractVue3 = () => {
1602
+ const rootObj = getRootEl();
1603
+ const vueKey = Object.keys(rootObj).find((k) => k === "__vueParentComponent" || k === "__vue_app__" || k.startsWith("__vue"));
1604
+ if (!vueKey) return null;
1605
+ const comp = rootObj[vueKey];
1606
+ if (!comp) return null;
1607
+ const states = [];
1608
+ const visited = /* @__PURE__ */ new WeakSet();
1609
+ const visitComp = (c, depth) => {
1610
+ if (!c || depth > opts.maxDepth || visited.has(c)) return;
1611
+ visited.add(c);
1612
+ const setupState = safeSerialize(c["setupState"] ?? c["ctx"]);
1613
+ const data = safeSerialize(c["$data"] ?? c["data"]);
1614
+ if (setupState || data) {
1615
+ const compType = c["type"];
1616
+ states.push({
1617
+ component: compType?.["__name"] ?? "unknown",
1618
+ setupState,
1619
+ data
1620
+ });
1621
+ }
1622
+ const children = c["subTree"]?.["children"];
1623
+ if (Array.isArray(children)) {
1624
+ for (const child of children) if (child?.["component"]) visitComp(child["component"], depth + 1);
1625
+ }
1626
+ };
1627
+ visitComp(comp, 0);
1628
+ return states;
1629
+ };
1630
+ const extractVue2 = () => {
1631
+ const rootObj = getRootEl();
1632
+ const vueKey = Object.keys(rootObj).find((k) => k === "__vue__");
1633
+ if (!vueKey) return null;
1634
+ const vm = rootObj[vueKey];
1635
+ if (!vm) return null;
1636
+ const states = [];
1637
+ const visited = /* @__PURE__ */ new WeakSet();
1638
+ const visitVm = (v, depth) => {
1639
+ if (!v || depth > opts.maxDepth || visited.has(v)) return;
1640
+ visited.add(v);
1641
+ const options = v["$options"];
1642
+ states.push({
1643
+ component: options?.["name"] ?? "unknown",
1644
+ data: safeSerialize(v["$data"])
1645
+ });
1646
+ const children = v["$children"];
1647
+ if (Array.isArray(children)) for (const child of children) visitVm(child, depth + 1);
1648
+ };
1649
+ visitVm(vm, 0);
1650
+ return states;
1651
+ };
1652
+ const extractSvelte = () => {
1653
+ const states = [];
1654
+ const visited = /* @__PURE__ */ new WeakSet();
1655
+ const svelteEls = document.querySelectorAll("[class]");
1656
+ const candidates = [getRootEl(), ...Array.from(svelteEls)];
1657
+ let foundAny = false;
1658
+ for (const el of candidates) {
1659
+ const obj = el;
1660
+ if (!Object.keys(obj).some((k) => k === "$$" || k === "__svelte_meta" || k.startsWith("__s"))) continue;
1661
+ foundAny = true;
1662
+ const ctx = obj["$$"];
1663
+ if (!ctx || visited.has(ctx)) continue;
1664
+ visited.add(ctx);
1665
+ const componentName = (obj["__svelte_meta"]?.["loc"])?.["file"];
1666
+ const ctxArray = ctx["ctx"];
1667
+ const stateObj = {};
1668
+ if (Array.isArray(ctxArray)) {
1669
+ let idx = 0;
1670
+ for (const val of ctxArray.slice(0, 20)) {
1671
+ if (val !== void 0 && typeof val !== "function") stateObj[`$${idx}`] = safeSerialize(val);
1672
+ idx++;
1673
+ }
1674
+ }
1675
+ const fragment = ctx["fragment"];
1676
+ if (Object.keys(stateObj).length > 0 || fragment) states.push({
1677
+ component: componentName ?? el.tagName?.toLowerCase() ?? "svelte-component",
1678
+ state: [stateObj],
1679
+ ...componentName ? { file: componentName } : {}
1680
+ });
1681
+ if (states.length >= 50) break;
1682
+ }
1683
+ return foundAny ? states : null;
1684
+ };
1685
+ const extractSolid = () => {
1686
+ const states = [];
1687
+ const dx = win["_$DX"];
1688
+ const hy = win["_$HY"];
1689
+ if (!dx && !hy) {
1690
+ if (!document.querySelector("[data-hk]")) return null;
1691
+ states.push({
1692
+ component: "SolidRoot",
1693
+ state: [{ _note: "Solid detected via hydration markers; install solid-devtools for full state extraction" }]
1694
+ });
1695
+ return states;
1696
+ }
1697
+ if (dx) {
1698
+ const roots = dx["roots"];
1699
+ if (roots && typeof roots === "object") {
1700
+ const entries = roots instanceof Map ? Array.from(roots.values()) : Object.values(roots);
1701
+ let count = 0;
1702
+ for (const root of entries) {
1703
+ if (count++ >= opts.maxDepth * 10) break;
1704
+ const name = root["name"] ?? "SolidComponent";
1705
+ const value = root["value"] ?? root["state"];
1706
+ states.push({
1707
+ component: name,
1708
+ state: value ? [safeSerialize(value)] : []
1709
+ });
1710
+ }
1711
+ }
1712
+ }
1713
+ if (hy && states.length === 0) states.push({
1714
+ component: "SolidHydration",
1715
+ state: [safeSerialize(hy)]
1716
+ });
1717
+ return states.length > 0 ? states : null;
1718
+ };
1719
+ const extractPreact = () => {
1720
+ const rootObj = getRootEl();
1721
+ const rootKeys = Object.keys(rootObj);
1722
+ if (rootKeys.some((k) => k.startsWith("__reactFiber") || k.startsWith("__reactInternalInstance"))) return null;
1723
+ if (!rootKeys.some((k) => k === "__k" || k === "__e" || k === "_dom")) return null;
1724
+ const states = [];
1725
+ const visited = /* @__PURE__ */ new WeakSet();
1726
+ const visitVNode = (vnode, depth) => {
1727
+ if (!vnode || depth > opts.maxDepth || visited.has(vnode)) return;
1728
+ visited.add(vnode);
1729
+ const component = vnode["__c"];
1730
+ if (component) {
1731
+ const compState = component["state"];
1732
+ const compProps = component["props"];
1733
+ const hooks = component["__H"];
1734
+ const hookStates = [];
1735
+ if (hooks) {
1736
+ const list = hooks["__"];
1737
+ if (Array.isArray(list)) for (const h of list.slice(0, 20)) {
1738
+ const val = h["__"] ?? h["_value"];
1739
+ if (val !== void 0) hookStates.push(safeSerialize(val));
1740
+ }
1741
+ }
1742
+ const typeName = vnode["type"];
1743
+ const name = typeof typeName === "function" ? typeName["displayName"] ?? typeName["name"] ?? "PreactComponent" : typeof typeName === "string" ? typeName : "PreactComponent";
1744
+ if (compState || hookStates.length > 0) states.push({
1745
+ component: String(name),
1746
+ state: hookStates.length > 0 ? hookStates : compState ? [safeSerialize(compState)] : [],
1747
+ ...compProps ? { props: safeSerialize(compProps) } : {}
1748
+ });
1749
+ }
1750
+ const children = vnode["__k"];
1751
+ if (Array.isArray(children)) {
1752
+ for (const child of children) if (child) visitVNode(child, depth + 1);
1753
+ }
1754
+ };
1755
+ const rootVNode = rootObj["__k"];
1756
+ if (Array.isArray(rootVNode)) {
1757
+ for (const vn of rootVNode) if (vn) visitVNode(vn, 0);
1758
+ } else if (rootObj["_children"]) {
1759
+ const alt = rootObj["_children"];
1760
+ if (Array.isArray(alt)) {
1761
+ for (const vn of alt) if (vn) visitVNode(vn, 0);
1762
+ }
1763
+ }
1764
+ return states.length > 0 ? states : null;
1765
+ };
1766
+ const extractMetaFramework = () => {
1767
+ const nextData = win["__NEXT_DATA__"];
1768
+ if (nextData) return {
1769
+ framework: "nextjs",
1770
+ route: nextData["page"],
1771
+ buildId: nextData["buildId"],
1772
+ runtimeConfig: safeSerialize(nextData["runtimeConfig"]),
1773
+ props: safeSerialize(nextData["props"])
1774
+ };
1775
+ const nuxt = win["__NUXT__"];
1776
+ if (nuxt) {
1777
+ if (nuxt["config"] !== void 0 || nuxt["_errors"] !== void 0) return {
1778
+ framework: "nuxt3",
1779
+ state: safeSerialize(nuxt["state"]),
1780
+ config: safeSerialize(nuxt["config"]),
1781
+ payload: safeSerialize(nuxt["data"])
1782
+ };
1783
+ return {
1784
+ framework: "nuxt2",
1785
+ state: safeSerialize(nuxt["state"]),
1786
+ serverRendered: nuxt["serverRendered"]
1787
+ };
1788
+ }
1789
+ return null;
1790
+ };
1791
+ const rootObj = getRootEl();
1792
+ const keys = Object.keys(rootObj);
1793
+ const hasReactMarker = keys.some((k) => k.startsWith("__reactFiber") || k.startsWith("__reactInternalInstance") || k.startsWith("__reactFiberContainer"));
1794
+ const hasVue3Marker = keys.some((k) => k === "__vueParentComponent" || k === "__vue_app__");
1795
+ const hasVue2Marker = keys.some((k) => k === "__vue__");
1796
+ const hasSvelteMarker = keys.some((k) => k === "$$" || k === "__svelte_meta" || k.startsWith("__s"));
1797
+ const hasSolidMarker = win["_$DX"] !== void 0 || win["_$HY"] !== void 0 || Boolean(document.querySelector("[data-hk]"));
1798
+ const hasPreactMarker = keys.some((k) => k === "__k" || k === "__e" || k === "_dom" || k === "_children");
1799
+ let detectedFramework = opts.framework;
1800
+ if (detectedFramework === "preact" && hasReactMarker) detectedFramework = "react";
1801
+ if (detectedFramework === "auto") {
1802
+ if (hasReactMarker) detectedFramework = "react";
1803
+ else if (hasVue3Marker) detectedFramework = "vue3";
1804
+ else if (hasVue2Marker) detectedFramework = "vue2";
1805
+ else if (hasSvelteMarker) detectedFramework = "svelte";
1806
+ else if (hasSolidMarker) detectedFramework = "solid";
1807
+ else if (hasPreactMarker) detectedFramework = "preact";
1808
+ }
1809
+ let states = null;
1810
+ if (detectedFramework === "react" || detectedFramework === "auto") states = extractReact();
1811
+ if (!states && (detectedFramework === "vue3" || detectedFramework === "auto")) states = extractVue3();
1812
+ if (!states && (detectedFramework === "vue2" || detectedFramework === "auto")) states = extractVue2();
1813
+ if (!states && (detectedFramework === "svelte" || detectedFramework === "auto")) states = extractSvelte();
1814
+ if (!states && (detectedFramework === "solid" || detectedFramework === "auto")) states = extractSolid();
1815
+ if (!states && (detectedFramework === "preact" || detectedFramework === "auto")) states = extractPreact();
1816
+ const meta = extractMetaFramework();
1817
+ return {
1818
+ detected: detectedFramework,
1819
+ states: states ?? [],
1820
+ found: states !== null && states.length > 0,
1821
+ ...meta ? { meta } : {}
1822
+ };
1823
+ }, {
1824
+ framework,
1825
+ selector,
1826
+ maxDepth
1827
+ });
1828
+ const result = await Promise.race([evalPromise, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("page.evaluate timed out after 30000ms")), 3e4))]);
1829
+ return R.ok().build(result);
1830
+ } catch (error) {
1831
+ return R.fail(error).build();
1832
+ }
1833
+ }
1834
+ };
1835
+ //#endregion
1836
+ //#region src/server/domains/browser/handlers/indexeddb-dump.ts
1837
+ var IndexedDBDumpHandlers = class {
1838
+ constructor(deps) {
1839
+ this.deps = deps;
1840
+ }
1841
+ async handleIndexedDBDump(args) {
1842
+ const database = argString(args, "database", "");
1843
+ const store = argString(args, "store", "");
1844
+ const maxRecords = argNumber(args, "maxRecords", 100);
1845
+ try {
1846
+ const result = await (await this.deps.getActivePage()).evaluate(async (opts) => {
1847
+ const dbList = await indexedDB.databases();
1848
+ const output = {};
1849
+ for (const dbInfo of dbList) {
1850
+ if (!dbInfo.name) continue;
1851
+ if (opts.database && dbInfo.name !== opts.database) continue;
1852
+ const dbName = dbInfo.name;
1853
+ let db;
1854
+ try {
1855
+ db = await new Promise((resolve, reject) => {
1856
+ const req = dbInfo.version ? indexedDB.open(dbName, dbInfo.version) : indexedDB.open(dbName);
1857
+ req.addEventListener("success", () => resolve(req.result), { once: true });
1858
+ req.addEventListener("error", () => reject(req.error), { once: true });
1859
+ });
1860
+ } catch {
1861
+ output[dbName] = { __error__: ["failed to open"] };
1862
+ continue;
1863
+ }
1864
+ const storeNames = Array.from(db.objectStoreNames);
1865
+ const dbData = {};
1866
+ for (const storeName of storeNames) {
1867
+ if (opts.store && storeName !== opts.store) continue;
1868
+ try {
1869
+ dbData[storeName] = await new Promise((resolve, reject) => {
1870
+ try {
1871
+ const req = db.transaction(storeName, "readonly").objectStore(storeName).getAll();
1872
+ req.addEventListener("success", () => resolve(req.result.slice(0, opts.maxRecords)), { once: true });
1873
+ req.addEventListener("error", () => reject(req.error), { once: true });
1874
+ } catch (e) {
1875
+ reject(e);
1876
+ }
1877
+ });
1878
+ } catch {
1879
+ dbData[storeName] = ["__error reading store__"];
1880
+ }
1881
+ }
1882
+ db.close();
1883
+ output[dbName] = dbData;
1884
+ }
1885
+ return output;
1886
+ }, {
1887
+ database,
1888
+ store,
1889
+ maxRecords
1890
+ });
1891
+ return R.ok().build(result);
1892
+ } catch (error) {
1893
+ return R.fail(error).build();
1894
+ }
1895
+ }
1896
+ };
1897
+ //#endregion
1898
+ //#region src/server/domains/browser/handlers/detailed-data.ts
1899
+ var DetailedDataHandlers = class {
1900
+ constructor(deps) {
1901
+ this.deps = deps;
1902
+ }
1903
+ async handleGetDetailedData(args) {
1904
+ try {
1905
+ const detailId = argString(args, "detailId", "");
1906
+ const path = argString(args, "path");
1907
+ const data = this.deps.detailedDataManager.retrieve(detailId, path);
1908
+ return R.ok().build({
1909
+ detailId,
1910
+ path: path || "full",
1911
+ data
1912
+ });
1913
+ } catch (error) {
1914
+ return R.fail(error).set("hint", "DetailId may have expired (TTL: 10 minutes) or is invalid").build();
1915
+ }
1916
+ }
1917
+ };
1918
+ //#endregion
1919
+ //#region src/server/domains/browser/handlers/target-evaluation.ts
1920
+ var TargetEvaluationHandlers = class {
1921
+ constructor(deps) {
1922
+ this.deps = deps;
1923
+ }
1924
+ async handleBrowserEvaluateCdpTarget(args) {
1925
+ try {
1926
+ const code = argString(args, "script", "") || argString(args, "code", "");
1927
+ const autoSummarize = argBool(args, "autoSummarize", true);
1928
+ const maxSize = argNumber(args, "maxSize", 51200);
1929
+ const fieldFilterArg = argStringArray(args, "fieldFilter");
1930
+ const doStripBase64 = argBool(args, "stripBase64", false);
1931
+ const returnByValue = argBool(args, "returnByValue", true);
1932
+ const awaitPromise = argBool(args, "awaitPromise", true);
1933
+ if (!code) return R.fail("code is required").build();
1934
+ const activeTarget = this.deps.pageController.getAttachedTargetInfo();
1935
+ if (!activeTarget) return R.fail("No CDP target is currently attached. Call browser_attach_cdp_target(targetId=\"...\") first.").build();
1936
+ const processedResult = applyEvaluationPostFilters(await this.deps.pageController.evaluateAttachedTarget(code, {
1937
+ returnByValue,
1938
+ awaitPromise
1939
+ }), this.deps.detailedDataManager, {
1940
+ autoSummarize,
1941
+ maxSize,
1942
+ fieldFilter: fieldFilterArg ?? void 0,
1943
+ stripBase64: doStripBase64
1944
+ });
1945
+ return R.ok().build({
1946
+ target: activeTarget,
1947
+ result: processedResult
1948
+ });
1949
+ } catch (error) {
1950
+ logger.error("Failed to evaluate in CDP target:", error);
1951
+ return R.fail(error instanceof Error ? error.message : String(error)).build();
1952
+ }
1953
+ }
1954
+ };
1955
+ //#endregion
1956
+ //#region src/server/domains/browser/handlers/target-control.ts
1957
+ var TargetControlHandlers = class {
1958
+ constructor(deps) {
1959
+ this.deps = deps;
1960
+ }
1961
+ markMonitoringContextChanged(context) {
1962
+ try {
1963
+ this.deps.consoleMonitor.markContextChanged();
1964
+ } catch (error) {
1965
+ logger.warn(`[${context}] Failed to mark monitoring context as stale: ${error instanceof Error ? error.message : String(error)}`);
1966
+ }
1967
+ }
1968
+ safeOrigin(url) {
1969
+ if (!url) return null;
1970
+ try {
1971
+ return new URL(url).origin;
1972
+ } catch {
1973
+ return null;
1974
+ }
1975
+ }
1976
+ async clearAttachedTargetContext(context) {
1977
+ const activeTarget = this.deps.collector.getAttachedTargetInfo();
1978
+ if (!activeTarget) return {
1979
+ detached: false,
1980
+ targetId: null,
1981
+ type: null
1982
+ };
1983
+ const detached = await this.deps.collector.detachCdpTarget();
1984
+ if (detached) logger.info(`[${context}] Detached active CDP target ${activeTarget.targetId} before switching page context`);
1985
+ return {
1986
+ detached,
1987
+ targetId: activeTarget.targetId,
1988
+ type: activeTarget.type ?? null
1989
+ };
1990
+ }
1991
+ async handleBrowserListCdpTargets(args) {
1992
+ try {
1993
+ const type = argString(args, "type");
1994
+ const types = argStringArray(args, "types");
1995
+ const targetId = argString(args, "targetId");
1996
+ const urlPattern = argString(args, "urlPattern");
1997
+ const titlePattern = argString(args, "titlePattern");
1998
+ const attachedOnly = argBool(args, "attachedOnly", false);
1999
+ const discoverOOPIF = argBool(args, "discoverOOPIF", true);
2000
+ const targets = await this.deps.collector.listCdpTargets({
2001
+ type: type ?? void 0,
2002
+ types: types ?? void 0,
2003
+ targetId: targetId ?? void 0,
2004
+ urlPattern: urlPattern ?? void 0,
2005
+ titlePattern: titlePattern ?? void 0,
2006
+ attachedOnly,
2007
+ discoverOOPIF
2008
+ });
2009
+ const activeTarget = this.deps.collector.getAttachedTargetInfo();
2010
+ const contextMeta = this.deps.getTabRegistry().getContextMeta();
2011
+ const pages = await this.deps.collector.listPages();
2012
+ const currentTab = typeof contextMeta.tabIndex === "number" ? pages[contextMeta.tabIndex] : void 0;
2013
+ const currentTabUrl = currentTab?.url ?? null;
2014
+ const currentTabOrigin = this.safeOrigin(currentTabUrl);
2015
+ const enrichedTargets = targets.map((target) => {
2016
+ const targetUrl = target.url;
2017
+ const targetOrigin = this.safeOrigin(targetUrl);
2018
+ const currentTabMatch = currentTabUrl !== null && targetUrl === currentTabUrl;
2019
+ const sameOriginAsCurrentTab = currentTabOrigin !== null && targetOrigin !== null && currentTabOrigin === targetOrigin;
2020
+ const isActiveTarget = activeTarget?.targetId === target.targetId;
2021
+ const relationHints = [];
2022
+ if (isActiveTarget) relationHints.push("active_target");
2023
+ if (currentTabMatch) relationHints.push("matches_current_tab_url");
2024
+ if (!currentTabMatch && sameOriginAsCurrentTab) relationHints.push("same_origin_as_current_tab");
2025
+ if (target.openerId && activeTarget?.targetId === target.openerId) relationHints.push("opened_by_active_target");
2026
+ if (target.openerId && !relationHints.includes("opened_by_active_target")) relationHints.push("has_opener_target");
2027
+ if (target.openerFrameId) relationHints.push("has_opener_frame");
2028
+ return {
2029
+ ...target,
2030
+ isActiveTarget,
2031
+ matchesCurrentTabUrl: currentTabMatch,
2032
+ sameOriginAsCurrentTab,
2033
+ relationHints
2034
+ };
2035
+ });
2036
+ return R.ok().build({
2037
+ count: enrichedTargets.length,
2038
+ activeTarget,
2039
+ currentTab: currentTab ? {
2040
+ index: currentTab.index,
2041
+ url: currentTab.url,
2042
+ title: currentTab.title
2043
+ } : null,
2044
+ filters: {
2045
+ type: type ?? null,
2046
+ types: types ?? null,
2047
+ targetId: targetId ?? null,
2048
+ urlPattern: urlPattern ?? null,
2049
+ titlePattern: titlePattern ?? null,
2050
+ attachedOnly
2051
+ },
2052
+ targets: enrichedTargets
2053
+ });
2054
+ } catch (error) {
2055
+ logger.error("Failed to list CDP targets:", error);
2056
+ return R.fail(error instanceof Error ? error.message : String(error)).build();
2057
+ }
2058
+ }
2059
+ async handleBrowserAttachCdpTarget(args) {
2060
+ try {
2061
+ const targetId = argString(args, "targetId");
2062
+ if (!targetId) throw new Error("targetId is required");
2063
+ const target = await this.deps.collector.attachCdpTarget(targetId);
2064
+ this.markMonitoringContextChanged("browser_attach_cdp_target");
2065
+ return R.ok().build({
2066
+ attached: true,
2067
+ target
2068
+ });
2069
+ } catch (error) {
2070
+ logger.error("Failed to attach CDP target:", error);
2071
+ return R.fail(error instanceof Error ? error.message : String(error)).build();
2072
+ }
2073
+ }
2074
+ async handleBrowserDetachCdpTarget(_args) {
2075
+ try {
2076
+ const activeTarget = this.deps.collector.getAttachedTargetInfo();
2077
+ const detached = await this.deps.collector.detachCdpTarget();
2078
+ if (detached) this.markMonitoringContextChanged("browser_detach_cdp_target");
2079
+ return R.ok().build({
2080
+ detached,
2081
+ targetId: activeTarget?.targetId ?? null
2082
+ });
2083
+ } catch (error) {
2084
+ logger.error("Failed to detach CDP target:", error);
2085
+ return R.fail(error instanceof Error ? error.message : String(error)).build();
2086
+ }
2087
+ }
2088
+ };
2089
+ //#endregion
2090
+ //#region src/server/domains/browser/handlers/js-heap.ts
2091
+ const NODE_TYPE_NAMES = [
2092
+ "hidden",
2093
+ "array",
2094
+ "string",
2095
+ "object",
2096
+ "code",
2097
+ "closure",
2098
+ "regexp",
2099
+ "number",
2100
+ "native",
2101
+ "synthetic",
2102
+ "concatenated string",
2103
+ "sliced string",
2104
+ "symbol",
2105
+ "bigint"
2106
+ ];
2107
+ function isRecord$1(value) {
2108
+ return typeof value === "object" && value !== null;
2109
+ }
2110
+ function isCDPPageLike(value) {
2111
+ return isRecord$1(value) && typeof value.createCDPSession === "function";
2112
+ }
2113
+ function isHeapSnapshotChunk(value) {
2114
+ return isRecord$1(value) && typeof value.chunk === "string";
2115
+ }
2116
+ var JSHeapSearchHandlers = class {
2117
+ detailedDataManager;
2118
+ constructor(deps) {
2119
+ this.deps = deps;
2120
+ this.detailedDataManager = DetailedDataManager.getInstance();
2121
+ }
2122
+ async handleJSHeapSearch(args) {
2123
+ const pattern = argString(args, "pattern", "");
2124
+ const maxResults = argNumber(args, "maxResults", 50);
2125
+ const caseSensitive = argBool(args, "caseSensitive", false);
2126
+ if (!pattern) return R.fail("pattern is required").build();
2127
+ return cdpLimit(async () => {
2128
+ let cdpSession = null;
2129
+ let ownedSession = false;
2130
+ try {
2131
+ const page = await this.deps.getActivePage();
2132
+ if (!isCDPPageLike(page)) throw new Error("Active page does not support CDP session creation");
2133
+ cdpSession = await page.createCDPSession();
2134
+ ownedSession = true;
2135
+ logger.info("[js_heap_search] Taking heap snapshot", {
2136
+ patternLength: pattern.length,
2137
+ caseSensitive,
2138
+ maxResults
2139
+ });
2140
+ await cdpSession.send("HeapProfiler.enable");
2141
+ const snapshotChunks = [];
2142
+ let snapshotSize = 0;
2143
+ cdpSession.on("HeapProfiler.addHeapSnapshotChunk", (params) => {
2144
+ if (isHeapSnapshotChunk(params)) {
2145
+ snapshotChunks.push(params.chunk);
2146
+ snapshotSize += params.chunk.length;
2147
+ }
2148
+ });
2149
+ await cdpSession.send("HeapProfiler.takeHeapSnapshot", {
2150
+ reportProgress: false,
2151
+ treatGlobalObjectsAsRoots: true,
2152
+ captureNumericValue: false
2153
+ });
2154
+ await cdpSession.send("HeapProfiler.disable");
2155
+ logger.info(`[js_heap_search] Snapshot size: ${(snapshotSize / 1024).toFixed(1)} KB`);
2156
+ const snapshotData = snapshotChunks.join("");
2157
+ snapshotChunks.length = 0;
2158
+ const matches = this.searchSnapshot(snapshotData, pattern, maxResults, caseSensitive);
2159
+ const result = {
2160
+ success: true,
2161
+ pattern,
2162
+ caseSensitive,
2163
+ snapshotSizeKB: Math.round(snapshotSize / 1024),
2164
+ matchCount: matches.length,
2165
+ truncated: matches.length >= maxResults,
2166
+ matches,
2167
+ tip: matches.length > 0 ? "Use page_evaluate to inspect the objects at the paths found. E.g., eval the objectPath as a JS expression." : "No matches found. The value may be encrypted, compressed, or stored in a non-string form."
2168
+ };
2169
+ return R.ok().build(this.detailedDataManager.smartHandle(result, 51200));
2170
+ } catch (error) {
2171
+ logger.error("[js_heap_search] Error:", error);
2172
+ return R.fail(error).build();
2173
+ } finally {
2174
+ if (ownedSession && cdpSession) try {
2175
+ await cdpSession.detach();
2176
+ } catch {}
2177
+ }
2178
+ });
2179
+ }
2180
+ searchSnapshot(snapshotData, pattern, maxResults, caseSensitive) {
2181
+ try {
2182
+ let parsed;
2183
+ try {
2184
+ parsed = JSON.parse(snapshotData);
2185
+ } catch {
2186
+ return [];
2187
+ }
2188
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
2189
+ const snapshot = parsed;
2190
+ const stringsRaw = snapshot.strings;
2191
+ const nodesRaw = snapshot.nodes;
2192
+ const snapshotMeta = typeof snapshot.snapshot === "object" && snapshot.snapshot !== null ? snapshot.snapshot : null;
2193
+ const meta = snapshotMeta && typeof snapshotMeta.meta === "object" && snapshotMeta.meta !== null ? snapshotMeta.meta : null;
2194
+ const nodeFieldsRaw = meta?.node_fields;
2195
+ const nodeTypesRaw = meta?.node_types;
2196
+ if (!Array.isArray(nodeFieldsRaw) || !Array.isArray(stringsRaw) || !Array.isArray(nodesRaw)) return [];
2197
+ const nodeFieldCount = nodeFieldsRaw.length;
2198
+ if (nodeFieldCount === 0) return [];
2199
+ const typeIdx = nodeFieldsRaw.indexOf("type");
2200
+ const nameIdx = nodeFieldsRaw.indexOf("name");
2201
+ const idIdx = nodeFieldsRaw.indexOf("id");
2202
+ if (typeIdx < 0 || nameIdx < 0) return [];
2203
+ const nodeTypeTable = Array.isArray(nodeTypesRaw) && Array.isArray(nodeTypesRaw[0]) ? nodeTypesRaw[0] : [];
2204
+ const searchStr = caseSensitive ? pattern : pattern.toLowerCase();
2205
+ const matches = [];
2206
+ const nodeCount = Math.floor(nodesRaw.length / nodeFieldCount);
2207
+ const stringsArr = stringsRaw;
2208
+ for (let i = 0; i < nodeCount && matches.length < maxResults; i++) {
2209
+ const base = i * nodeFieldCount;
2210
+ const typeOrdinal = nodesRaw[base + typeIdx];
2211
+ const nameOrdinal = nodesRaw[base + nameIdx];
2212
+ if (typeof nameOrdinal !== "number" || nameOrdinal < 0 || nameOrdinal >= stringsArr.length) continue;
2213
+ const tableName = nodeTypeTable[typeOrdinal];
2214
+ const nodeTypeName = (typeof tableName === "string" ? tableName : void 0) ?? NODE_TYPE_NAMES[typeOrdinal] ?? `type_${typeOrdinal}`;
2215
+ if (nodeTypeName !== "string" && nodeTypeName !== "concatenated string" && nodeTypeName !== "sliced string") continue;
2216
+ const value = stringsArr[nameOrdinal];
2217
+ if (typeof value !== "string") continue;
2218
+ if (!(caseSensitive ? value : value.toLowerCase()).includes(searchStr)) continue;
2219
+ const rawId = idIdx >= 0 ? nodesRaw[base + idIdx] : void 0;
2220
+ const nodeId = rawId !== void 0 ? rawId : i;
2221
+ matches.push({
2222
+ nodeId,
2223
+ nodeType: nodeTypeName,
2224
+ value: value.length > 200 ? `${value.slice(0, 200)}…` : value,
2225
+ objectPath: `[HeapNode #${nodeId}]`,
2226
+ nameHint: value.slice(0, 80)
2227
+ });
2228
+ }
2229
+ return matches;
2230
+ } catch (error) {
2231
+ logger.warn("[js_heap_search] Snapshot parse error:", error);
2232
+ return [];
2233
+ }
2234
+ }
2235
+ };
2236
+ //#endregion
2237
+ //#region src/server/domains/browser/handlers/tab-workflow.ts
2238
+ const TAB_ACTIONS = new Set([
2239
+ "alias_bind",
2240
+ "alias_open",
2241
+ "navigate",
2242
+ "wait_for",
2243
+ "context_set",
2244
+ "context_get",
2245
+ "transfer",
2246
+ "list",
2247
+ "clear"
2248
+ ]);
2249
+ function isRecord(value) {
2250
+ return typeof value === "object" && value !== null;
2251
+ }
2252
+ function isTabAction(value) {
2253
+ return typeof value === "string" && TAB_ACTIONS.has(value);
2254
+ }
2255
+ function isTabPageLike(value) {
2256
+ return isRecord(value) && typeof value.goto === "function" && typeof value.waitForSelector === "function" && typeof value.evaluate === "function" && typeof value.url === "function" && typeof value.title === "function";
2257
+ }
2258
+ function isCamoufoxPageLike(value) {
2259
+ if (!isTabPageLike(value) || !isRecord(value)) return false;
2260
+ return typeof value.context === "function";
2261
+ }
2262
+ function isBrowserLike(value) {
2263
+ return isRecord(value) && typeof value.newPage === "function" && typeof value.pages === "function";
2264
+ }
2265
+ function readRequiredString(value) {
2266
+ return typeof value === "string" && value.length > 0 ? value : null;
2267
+ }
2268
+ function readAliasIndex(value) {
2269
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2270
+ if (typeof value === "string" && value.trim() !== "") {
2271
+ const parsed = Number(value);
2272
+ if (Number.isFinite(parsed)) return parsed;
2273
+ }
2274
+ return null;
2275
+ }
2276
+ function readTimeout(value, fallback) {
2277
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
2278
+ return fallback;
2279
+ }
2280
+ var TabWorkflowHandlers = class {
2281
+ constructor(deps) {
2282
+ this.deps = deps;
2283
+ }
2284
+ get registry() {
2285
+ return this.deps.getTabRegistry();
2286
+ }
2287
+ async handleTabWorkflow(args) {
2288
+ const action = args.action;
2289
+ try {
2290
+ if (!isTabAction(action)) return R.fail(`Unknown action: "${String(action)}". Valid: list, alias_bind, alias_open, navigate, wait_for, context_set, context_get, transfer, clear`).build();
2291
+ switch (action) {
2292
+ case "list": return this.listAliases();
2293
+ case "clear": return this.clearState();
2294
+ case "alias_bind": return await this.aliasBind(args);
2295
+ case "alias_open": return await this.aliasOpen(args);
2296
+ case "navigate": return await this.navigateAlias(args);
2297
+ case "wait_for": return await this.waitFor(args);
2298
+ case "context_set": return this.contextSet(args);
2299
+ case "context_get": return this.contextGet(args);
2300
+ case "transfer": return await this.transfer(args);
2301
+ }
2302
+ } catch (err) {
2303
+ const errorMsg = err instanceof Error ? err.message : String(err);
2304
+ logger.error("[tab_workflow] Action failed", {
2305
+ action: typeof action === "string" ? action : String(action),
2306
+ alias: typeof args.alias === "string" ? args.alias : void 0,
2307
+ fromAlias: typeof args.fromAlias === "string" ? args.fromAlias : void 0,
2308
+ error: errorMsg
2309
+ });
2310
+ return R.fail(errorMsg).build();
2311
+ }
2312
+ }
2313
+ listAliases() {
2314
+ const info = this.registry.getCurrentTabInfo(this.deps.getActiveDriver());
2315
+ const context = this.registry.getSharedContextMap();
2316
+ return R.ok().build({
2317
+ aliases: info.aliases,
2318
+ staleAliases: info.staleAliases,
2319
+ currentPageId: info.currentPageId,
2320
+ currentIndex: info.currentIndex,
2321
+ currentUrl: info.url,
2322
+ context
2323
+ });
2324
+ }
2325
+ clearState() {
2326
+ this.registry.clear();
2327
+ return R.ok().build({ cleared: true });
2328
+ }
2329
+ async aliasBind(args) {
2330
+ const alias = readRequiredString(args.alias);
2331
+ const index = readAliasIndex(args.index);
2332
+ if (!alias) return R.fail("alias is required").build();
2333
+ if (index === null) return R.fail("index is required").build();
2334
+ await this.reconcilePages();
2335
+ const pageId = this.registry.bindAliasByIndex(alias, index);
2336
+ if (!pageId) return R.fail(`No active page at index ${index}. Use browser_list_tabs to check available pages.`).build();
2337
+ return R.ok().build({ bound: {
2338
+ alias,
2339
+ index,
2340
+ pageId
2341
+ } });
2342
+ }
2343
+ async aliasOpen(args) {
2344
+ const alias = readRequiredString(args.alias);
2345
+ const url = readRequiredString(args.url);
2346
+ if (!alias) return R.fail("alias is required").build();
2347
+ if (!url) return R.fail("url is required").build();
2348
+ if (this.deps.getActiveDriver() === "camoufox") {
2349
+ const currentPage = await this.deps.getCamoufoxPage();
2350
+ if (!isCamoufoxPageLike(currentPage)) return R.fail("Cannot open new tab: camoufox page context not accessible").build();
2351
+ const context = currentPage.context();
2352
+ const newPage = await context.newPage();
2353
+ await newPage.goto(url, { waitUntil: "domcontentloaded" });
2354
+ const idx = context.pages().indexOf(newPage);
2355
+ const pageTitle = await newPage.title();
2356
+ const pageId = this.registry.registerPage(newPage, {
2357
+ index: idx,
2358
+ url: newPage.url(),
2359
+ title: pageTitle
2360
+ });
2361
+ this.registry.bindAlias(alias, pageId);
2362
+ return R.ok().build({
2363
+ alias,
2364
+ index: idx,
2365
+ pageId,
2366
+ url: newPage.url(),
2367
+ title: pageTitle
2368
+ });
2369
+ }
2370
+ const browser = await this.getBrowserFromController();
2371
+ if (!browser) return R.fail("Cannot open new tab: browser instance not accessible via PageController").build();
2372
+ const newPage = await browser.newPage();
2373
+ await newPage.goto(url, { waitUntil: "domcontentloaded" });
2374
+ const idx = (await browser.pages()).indexOf(newPage);
2375
+ const pageTitle = await newPage.title();
2376
+ const pageId = this.registry.registerPage(newPage, {
2377
+ index: idx,
2378
+ url: newPage.url(),
2379
+ title: pageTitle
2380
+ });
2381
+ this.registry.bindAlias(alias, pageId);
2382
+ return R.ok().build({
2383
+ alias,
2384
+ index: idx,
2385
+ pageId,
2386
+ url: newPage.url(),
2387
+ title: pageTitle
2388
+ });
2389
+ }
2390
+ async navigateAlias(args) {
2391
+ const alias = readRequiredString(args.alias);
2392
+ const url = readRequiredString(args.url);
2393
+ if (!alias) return R.fail("alias is required").build();
2394
+ if (!url) return R.fail("url is required").build();
2395
+ const page = await this.getPageByAlias(alias);
2396
+ if (!page) return R.fail(`No tab found for alias "${alias}". Use alias_bind or alias_open first.`).build();
2397
+ await page.goto(url, { waitUntil: "domcontentloaded" });
2398
+ return R.ok().build({
2399
+ alias,
2400
+ navigated: url,
2401
+ currentUrl: page.url()
2402
+ });
2403
+ }
2404
+ async waitFor(args) {
2405
+ const alias = readRequiredString(args.alias);
2406
+ const selector = readRequiredString(args.selector);
2407
+ const text = readRequiredString(args.waitForText);
2408
+ const timeoutMs = readTimeout(args.timeoutMs, 1e4);
2409
+ if (!alias) return R.fail("alias is required").build();
2410
+ if (!selector && !text) return R.fail("selector or waitForText is required").build();
2411
+ const page = await this.getPageByAlias(alias);
2412
+ if (!page) return R.fail(`No tab found for alias "${alias}"`).build();
2413
+ if (selector) {
2414
+ await page.waitForSelector(selector, { timeout: timeoutMs });
2415
+ return R.ok().build({
2416
+ alias,
2417
+ waitedFor: selector,
2418
+ found: true
2419
+ });
2420
+ }
2421
+ const waitText = text;
2422
+ const start = Date.now();
2423
+ while (Date.now() - start < timeoutMs) {
2424
+ const bodyTextValue = await page.evaluate(() => document.body.innerText);
2425
+ if ((typeof bodyTextValue === "string" ? bodyTextValue : String(bodyTextValue ?? "")).includes(waitText)) return R.ok().build({
2426
+ alias,
2427
+ waitedForText: waitText,
2428
+ found: true
2429
+ });
2430
+ await new Promise((r) => setTimeout(r, 500));
2431
+ }
2432
+ return R.fail(`Timeout waiting for text "${waitText}" in tab "${alias}"`).build();
2433
+ }
2434
+ contextSet(args) {
2435
+ const key = readRequiredString(args.key);
2436
+ const value = args.value;
2437
+ if (!key) return R.fail("key is required").build();
2438
+ this.registry.setSharedContext(key, value);
2439
+ return R.ok().build({ set: {
2440
+ key,
2441
+ value
2442
+ } });
2443
+ }
2444
+ contextGet(args) {
2445
+ const key = readRequiredString(args.key);
2446
+ if (!key) return R.fail("key is required").build();
2447
+ const { value, found } = this.registry.getSharedContext(key);
2448
+ return R.ok().build({
2449
+ key,
2450
+ value,
2451
+ found
2452
+ });
2453
+ }
2454
+ async transfer(args) {
2455
+ const fromAlias = readRequiredString(args.fromAlias);
2456
+ const key = readRequiredString(args.key);
2457
+ const expression = readRequiredString(args.expression);
2458
+ if (!fromAlias) return R.fail("fromAlias is required").build();
2459
+ if (!key) return R.fail("key is required").build();
2460
+ if (!expression) return R.fail("expression is required").build();
2461
+ const page = await this.getPageByAlias(fromAlias);
2462
+ if (!page) return R.fail(`No tab found for alias "${fromAlias}"`).build();
2463
+ const value = await page.evaluate(expression);
2464
+ this.registry.setSharedContext(key, value);
2465
+ return R.ok().build({ transferred: {
2466
+ fromAlias,
2467
+ key,
2468
+ value
2469
+ } });
2470
+ }
2471
+ async getPageByAlias(alias) {
2472
+ const pageId = this.registry.resolveAlias(alias);
2473
+ if (!pageId) return null;
2474
+ const page = this.registry.getPageById(pageId);
2475
+ if (page && isTabPageLike(page)) return page;
2476
+ await this.reconcilePages();
2477
+ const retryPage = this.registry.getPageById(pageId);
2478
+ if (retryPage && isTabPageLike(retryPage)) return retryPage;
2479
+ return null;
2480
+ }
2481
+ async reconcilePages() {
2482
+ if (this.deps.getActiveDriver() === "camoufox") {
2483
+ const page = await this.deps.getCamoufoxPage();
2484
+ if (isCamoufoxPageLike(page)) {
2485
+ const pages = page.context().pages();
2486
+ const meta = await Promise.all(pages.map(async (p, i) => ({
2487
+ index: i,
2488
+ url: p.url(),
2489
+ title: await p.title()
2490
+ })));
2491
+ this.registry.reconcilePages(pages, meta);
2492
+ }
2493
+ return;
2494
+ }
2495
+ const browser = await this.getBrowserFromController();
2496
+ if (!browser) return;
2497
+ const pages = await browser.pages();
2498
+ const meta = await Promise.all(pages.map(async (p, i) => ({
2499
+ index: i,
2500
+ url: p.url(),
2501
+ title: await p.title()
2502
+ })));
2503
+ this.registry.reconcilePages(pages, meta);
2504
+ }
2505
+ async getBrowserFromController() {
2506
+ const pc = this.deps.getPageController();
2507
+ if (!isRecord(pc) || typeof pc.getBrowser !== "function") return null;
2508
+ const browser = await pc.getBrowser();
2509
+ return isBrowserLike(browser) ? browser : null;
2510
+ }
2511
+ };
2512
+ //#endregion
2513
+ //#region src/server/domains/browser/handlers/jsdom-tools.ts
2514
+ /**
2515
+ * jsdom-tools.ts — Headless DOM analysis tools backed by the `jsdom` package.
2516
+ *
2517
+ * Provides 5 tools for offline HTML parsing, DOM querying, sandboxed script
2518
+ * execution, serialization and cookie management. Each call creates or
2519
+ * references a session keyed by UUID. Sessions auto-expire after
2520
+ * {@link SESSION_TTL_MS} of inactivity.
2521
+ */
2522
+ const RUN_SCRIPTS_MODES = new Set([
2523
+ "none",
2524
+ "outside-only",
2525
+ "dangerously"
2526
+ ]);
2527
+ const COOKIE_ACTIONS = new Set([
2528
+ "get",
2529
+ "set",
2530
+ "clear"
2531
+ ]);
2532
+ /** Maximum HTML input size to prevent unbounded memory allocation. */
2533
+ const MAX_HTML_SIZE_BYTES = 10 * 1024 * 1024;
2534
+ /** Session lifetime in milliseconds. Configurable via env in future. */
2535
+ const SESSION_TTL_MS = 600 * 1e3;
2536
+ var JsdomHandlers = class {
2537
+ sessions = /* @__PURE__ */ new Map();
2538
+ createSessionId() {
2539
+ return randomUUID();
2540
+ }
2541
+ scheduleExpiry(sessionId) {
2542
+ const timer = setTimeout(() => {
2543
+ logger.debug(`JSDOM session ${sessionId} expired after ${SESSION_TTL_MS}ms`);
2544
+ this.closeSession(sessionId);
2545
+ }, SESSION_TTL_MS);
2546
+ timer.unref?.();
2547
+ return timer;
2548
+ }
2549
+ refreshSessionExpiry(sessionId, session) {
2550
+ clearTimeout(session.timer);
2551
+ session.timer = this.scheduleExpiry(sessionId);
2552
+ }
2553
+ getSession(sessionId) {
2554
+ const session = this.sessions.get(sessionId);
2555
+ if (!session) throw new Error(`JSDOM session not found or expired: ${sessionId}`);
2556
+ this.refreshSessionExpiry(sessionId, session);
2557
+ return session;
2558
+ }
2559
+ closeSession(sessionId) {
2560
+ const session = this.sessions.get(sessionId);
2561
+ if (!session) return;
2562
+ clearTimeout(session.timer);
2563
+ try {
2564
+ session.dom.window.close();
2565
+ } catch (err) {
2566
+ logger.debug(`JSDOM window close error: ${String(err)}`);
2567
+ }
2568
+ this.sessions.delete(sessionId);
2569
+ }
2570
+ /** Close all active sessions. Called on server shutdown. */
2571
+ closeAll() {
2572
+ for (const id of Array.from(this.sessions.keys())) this.closeSession(id);
2573
+ }
2574
+ async handleJsdomParse(args) {
2575
+ try {
2576
+ const html = argStringRequired(args, "html");
2577
+ if (Buffer.byteLength(html, "utf8") > MAX_HTML_SIZE_BYTES) return R.fail(`HTML input exceeds ${MAX_HTML_SIZE_BYTES / 1024 / 1024}MB limit. Provide smaller HTML or use a URL.`).build();
2578
+ const url = argString(args, "url", "about:blank");
2579
+ const contentType = argString(args, "contentType", "text/html");
2580
+ const runScripts = argEnum(args, "runScripts", RUN_SCRIPTS_MODES, "none");
2581
+ const includeNodeLocations = argBool(args, "includeNodeLocations", false);
2582
+ const pretendToBeVisual = argBool(args, "pretendToBeVisual", false);
2583
+ const referrer = argString(args, "referrer", "");
2584
+ const options = {
2585
+ url,
2586
+ contentType,
2587
+ includeNodeLocations,
2588
+ pretendToBeVisual,
2589
+ storageQuota: argNumber(args, "storageQuotaBytes", 1e6)
2590
+ };
2591
+ if (runScripts !== "none") options.runScripts = runScripts;
2592
+ if (referrer) options.referrer = referrer;
2593
+ const { JSDOM } = await import("jsdom");
2594
+ const dom = new JSDOM(html, options);
2595
+ const sessionId = this.createSessionId();
2596
+ const session = {
2597
+ dom,
2598
+ url,
2599
+ runScripts,
2600
+ includeNodeLocations,
2601
+ createdAt: Date.now(),
2602
+ timer: this.scheduleExpiry(sessionId)
2603
+ };
2604
+ this.sessions.set(sessionId, session);
2605
+ const doc = dom.window.document;
2606
+ return R.ok().set("sessionId", sessionId).set("title", doc.title || "").set("url", url).set("contentType", contentType).set("runScripts", runScripts).set("ttlMs", SESSION_TTL_MS).set("activeSessions", this.sessions.size).set("stats", {
2607
+ elements: doc.getElementsByTagName("*").length,
2608
+ scripts: doc.getElementsByTagName("script").length,
2609
+ links: doc.getElementsByTagName("a").length,
2610
+ images: doc.getElementsByTagName("img").length,
2611
+ stylesheets: doc.querySelectorAll("link[rel=\"stylesheet\"], style").length
2612
+ }).build();
2613
+ } catch (error) {
2614
+ return R.fail(error).build();
2615
+ }
2616
+ }
2617
+ async handleJsdomQuery(args) {
2618
+ try {
2619
+ const sessionId = argStringRequired(args, "sessionId");
2620
+ const selector = argStringRequired(args, "selector");
2621
+ const maxResults = argNumber(args, "maxResults", 50);
2622
+ const includeHtml = argBool(args, "includeHtml", false);
2623
+ const includeText = argBool(args, "includeText", true);
2624
+ const includeLocation = argBool(args, "includeLocation", false);
2625
+ const attributes = argStringArray(args, "attributes");
2626
+ const session = this.getSession(sessionId);
2627
+ const doc = session.dom.window.document;
2628
+ const all = Array.from(doc.querySelectorAll(selector));
2629
+ const results = all.slice(0, maxResults).map((el) => {
2630
+ const item = { tag: el.tagName.toLowerCase() };
2631
+ if (attributes.length > 0) {
2632
+ const picked = {};
2633
+ for (const name of attributes) picked[name] = el.getAttribute(name);
2634
+ item.attributes = picked;
2635
+ } else {
2636
+ const full = {};
2637
+ for (const attr of Array.from(el.attributes)) full[attr.name] = attr.value;
2638
+ item.attributes = full;
2639
+ }
2640
+ if (includeText) item.text = (el.textContent ?? "").trim();
2641
+ if (includeHtml) item.html = el.outerHTML;
2642
+ if (includeLocation && session.includeNodeLocations) try {
2643
+ item.location = session.dom.nodeLocation(el) ?? null;
2644
+ } catch {
2645
+ item.location = null;
2646
+ }
2647
+ return item;
2648
+ });
2649
+ return R.ok().set("sessionId", sessionId).set("selector", selector).set("matched", all.length).set("returned", results.length).set("results", results).build();
2650
+ } catch (error) {
2651
+ return R.fail(error).build();
2652
+ }
2653
+ }
2654
+ async handleJsdomExecute(args) {
2655
+ try {
2656
+ const sessionId = argStringRequired(args, "sessionId");
2657
+ const code = argStringRequired(args, "code");
2658
+ const timeoutHintMs = argNumber(args, "timeoutMs", 5e3);
2659
+ const session = this.getSession(sessionId);
2660
+ if (session.runScripts === "none") return R.fail("JSDOM session was created with runScripts=\"none\". Re-parse with runScripts=\"outside-only\" or \"dangerously\" to execute code.").build();
2661
+ const logs = [];
2662
+ const window = session.dom.window;
2663
+ const originalConsole = window.console;
2664
+ window.console = createCapturingConsole(originalConsole, logs);
2665
+ let result;
2666
+ let errorMessage = null;
2667
+ try {
2668
+ result = window.eval(code);
2669
+ } catch (err) {
2670
+ errorMessage = err instanceof Error ? err.message : String(err);
2671
+ } finally {
2672
+ window.console = originalConsole;
2673
+ }
2674
+ if (errorMessage !== null) return R.fail(errorMessage).set("consoleLogs", logs).build();
2675
+ return R.ok().set("sessionId", sessionId).set("result", safeSerialize(result)).set("consoleLogs", logs).set("timeoutHintMs", timeoutHintMs).build();
2676
+ } catch (error) {
2677
+ return R.fail(error).build();
2678
+ }
2679
+ }
2680
+ async handleJsdomSerialize(args) {
2681
+ try {
2682
+ const sessionId = argStringRequired(args, "sessionId");
2683
+ const pretty = argBool(args, "pretty", false);
2684
+ const fragment = argString(args, "selector", "");
2685
+ const session = this.getSession(sessionId);
2686
+ let html;
2687
+ if (fragment) {
2688
+ const element = session.dom.window.document.querySelector(fragment);
2689
+ if (!element) return R.fail(`No element matches selector: ${fragment}`).build();
2690
+ html = element.outerHTML;
2691
+ } else html = session.dom.serialize();
2692
+ if (pretty) html = prettyPrintHtml(html);
2693
+ return R.ok().set("sessionId", sessionId).set("bytes", Buffer.byteLength(html, "utf8")).set("pretty", pretty).set("html", html).build();
2694
+ } catch (error) {
2695
+ return R.fail(error).build();
2696
+ }
2697
+ }
2698
+ async handleJsdomCookies(args) {
2699
+ try {
2700
+ const sessionId = argStringRequired(args, "sessionId");
2701
+ const action = argEnum(args, "action", COOKIE_ACTIONS, "get");
2702
+ const session = this.getSession(sessionId);
2703
+ const jar = session.dom.cookieJar;
2704
+ const cookieUrl = argString(args, "url", session.url);
2705
+ if (action === "get") {
2706
+ const cookies = await jar.getCookies(cookieUrl);
2707
+ return R.ok().set("sessionId", sessionId).set("url", cookieUrl).set("cookies", cookies.map(serializeCookie)).build();
2708
+ }
2709
+ if (action === "set") {
2710
+ const cookie = argObject(args, "cookie");
2711
+ if (!cookie) return R.fail("cookie object required for action=\"set\"").build();
2712
+ const cookieStr = typeof cookie.raw === "string" ? cookie.raw : buildCookieString(cookie);
2713
+ await jar.setCookie(cookieStr, cookieUrl);
2714
+ return R.ok().set("sessionId", sessionId).set("action", "set").set("cookie", cookieStr).build();
2715
+ }
2716
+ const store = jar.store;
2717
+ if (store && typeof store.removeAllCookies === "function") await new Promise((resolve, reject) => store.removeAllCookies((err) => err ? reject(err) : resolve()));
2718
+ return R.ok().set("sessionId", sessionId).set("action", "clear").build();
2719
+ } catch (error) {
2720
+ return R.fail(error).build();
2721
+ }
2722
+ }
2723
+ };
2724
+ function serializeCookie(c) {
2725
+ return {
2726
+ key: c.key ?? "",
2727
+ value: c.value ?? "",
2728
+ domain: c.domain ?? null,
2729
+ path: c.path ?? null,
2730
+ expires: c.expires instanceof Date ? c.expires.toISOString() : c.expires ?? null,
2731
+ httpOnly: c.httpOnly === true,
2732
+ secure: c.secure === true,
2733
+ sameSite: c.sameSite ?? null
2734
+ };
2735
+ }
2736
+ function buildCookieString(cookie) {
2737
+ const name = String(cookie.name ?? cookie.key ?? "");
2738
+ const value = String(cookie.value ?? "");
2739
+ if (!name) throw new Error("cookie.name (or cookie.key) is required");
2740
+ const parts = [`${name}=${value}`];
2741
+ if (typeof cookie.domain === "string") parts.push(`Domain=${cookie.domain}`);
2742
+ if (typeof cookie.path === "string") parts.push(`Path=${cookie.path}`);
2743
+ if (typeof cookie.expires === "string") parts.push(`Expires=${cookie.expires}`);
2744
+ if (typeof cookie.maxAge === "number") parts.push(`Max-Age=${cookie.maxAge}`);
2745
+ if (cookie.secure === true) parts.push("Secure");
2746
+ if (cookie.httpOnly === true) parts.push("HttpOnly");
2747
+ if (typeof cookie.sameSite === "string") parts.push(`SameSite=${cookie.sameSite}`);
2748
+ return parts.join("; ");
2749
+ }
2750
+ function createCapturingConsole(original, logs) {
2751
+ const levels = [
2752
+ "log",
2753
+ "info",
2754
+ "warn",
2755
+ "error",
2756
+ "debug",
2757
+ "trace"
2758
+ ];
2759
+ const proxy = {};
2760
+ for (const level of levels) proxy[level] = (...callArgs) => {
2761
+ logs.push({
2762
+ level,
2763
+ args: callArgs.map((x) => safeSerialize(x))
2764
+ });
2765
+ const orig = original?.[level];
2766
+ if (typeof orig === "function") try {
2767
+ orig.apply(original, callArgs);
2768
+ } catch {}
2769
+ };
2770
+ return proxy;
2771
+ }
2772
+ function safeSerialize(value) {
2773
+ if (value === null || value === void 0) return value;
2774
+ const t = typeof value;
2775
+ if (t === "string" || t === "number" || t === "boolean") return value;
2776
+ if (t === "bigint") return `${value.toString()}n`;
2777
+ if (t === "function") return `[Function: ${value.name || "anonymous"}]`;
2778
+ if (t === "symbol") return String(value);
2779
+ try {
2780
+ return JSON.parse(JSON.stringify(value));
2781
+ } catch {
2782
+ return String(value);
2783
+ }
2784
+ }
2785
+ function prettyPrintHtml(html) {
2786
+ return html.replace(/>(?=<)/g, ">\n");
2787
+ }
2788
+ //#endregion
2789
+ //#region src/modules/browser/TabRegistry.ts
2790
+ /**
2791
+ * TabRegistry — unified tab/page state for all browser tools.
2792
+ *
2793
+ * Replaces the fragmented model where tab_workflow kept its own alias→index map
2794
+ * and browser_control used collector.listPages()/selectPage() independently.
2795
+ *
2796
+ * Key properties:
2797
+ * - pageId is session-stable (survives tab open/close of other tabs)
2798
+ * - Alias binds to pageId, not index
2799
+ * - Stale detection when a previously registered page disappears
2800
+ * - Single source of truth for "current page"
2801
+ */
2802
+ let globalIdCounter = 0;
2803
+ var TabRegistry = class {
2804
+ pageIdByHandle = /* @__PURE__ */ new WeakMap();
2805
+ tabsById = /* @__PURE__ */ new Map();
2806
+ aliasToPageId = /* @__PURE__ */ new Map();
2807
+ currentPageId = null;
2808
+ sharedContext = /* @__PURE__ */ new Map();
2809
+ /**
2810
+ * Register a page and get a stable pageId.
2811
+ * If the page object was previously registered, returns its existing pageId.
2812
+ */
2813
+ registerPage(page, meta) {
2814
+ const handle = page;
2815
+ const existingId = this.pageIdByHandle.get(handle);
2816
+ if (existingId) {
2817
+ const existing = this.tabsById.get(existingId);
2818
+ if (existing) {
2819
+ existing.meta = meta;
2820
+ existing.stale = false;
2821
+ } else this.tabsById.set(existingId, {
2822
+ page,
2823
+ meta,
2824
+ stale: false
2825
+ });
2826
+ return existingId;
2827
+ }
2828
+ globalIdCounter += 1;
2829
+ const pageId = `tab-${globalIdCounter}`;
2830
+ this.pageIdByHandle.set(handle, pageId);
2831
+ this.tabsById.set(pageId, {
2832
+ page,
2833
+ meta,
2834
+ stale: false
2835
+ });
2836
+ logger.debug(`[TabRegistry] Registered page ${pageId} (index=${meta.index}, url=${meta.url})`);
2837
+ return pageId;
2838
+ }
2839
+ /**
2840
+ * Reconcile the registry with a fresh pages list.
2841
+ * - New pages get registered
2842
+ * - Missing pages get marked stale
2843
+ * - Index updates are applied
2844
+ * Returns the full tab list.
2845
+ */
2846
+ reconcilePages(pages, metaList) {
2847
+ const activeIds = /* @__PURE__ */ new Set();
2848
+ for (let i = 0; i < pages.length; i++) {
2849
+ const page = pages[i];
2850
+ const meta = metaList[i] ?? {
2851
+ index: i,
2852
+ url: "",
2853
+ title: ""
2854
+ };
2855
+ const pageId = this.registerPage(page, {
2856
+ ...meta,
2857
+ index: i
2858
+ });
2859
+ activeIds.add(pageId);
2860
+ }
2861
+ for (const [pageId, entry] of this.tabsById) if (!activeIds.has(pageId) && !entry.stale) {
2862
+ entry.stale = true;
2863
+ logger.debug(`[TabRegistry] Page ${pageId} marked stale`);
2864
+ }
2865
+ if (this.currentPageId && !activeIds.has(this.currentPageId)) {
2866
+ logger.debug(`[TabRegistry] Current page ${this.currentPageId} is stale, clearing`);
2867
+ this.currentPageId = null;
2868
+ }
2869
+ return this.listTabs();
2870
+ }
2871
+ /** Bind an alias to a pageId. */
2872
+ bindAlias(alias, pageId) {
2873
+ if (!this.tabsById.has(pageId)) return false;
2874
+ this.aliasToPageId.set(alias, pageId);
2875
+ return true;
2876
+ }
2877
+ /** Bind an alias to a page by its current index. */
2878
+ bindAliasByIndex(alias, index) {
2879
+ for (const [pageId, entry] of this.tabsById) if (entry.meta.index === index && !entry.stale) {
2880
+ this.aliasToPageId.set(alias, pageId);
2881
+ return pageId;
2882
+ }
2883
+ return null;
2884
+ }
2885
+ /** Remove an alias binding. */
2886
+ unbindAlias(alias) {
2887
+ return this.aliasToPageId.delete(alias);
2888
+ }
2889
+ /** Resolve an alias to its pageId. Returns null if alias not found or page is stale. */
2890
+ resolveAlias(alias) {
2891
+ const pageId = this.aliasToPageId.get(alias);
2892
+ if (!pageId) return null;
2893
+ const entry = this.tabsById.get(pageId);
2894
+ if (!entry || entry.stale) return null;
2895
+ return pageId;
2896
+ }
2897
+ /** Get the page object by pageId. Returns null if not found or stale. */
2898
+ getPageById(pageId) {
2899
+ const entry = this.tabsById.get(pageId);
2900
+ if (!entry || entry.stale) return null;
2901
+ return entry.page;
2902
+ }
2903
+ /** Get full tab descriptor by pageId. */
2904
+ getTabById(pageId) {
2905
+ const entry = this.tabsById.get(pageId);
2906
+ if (!entry) return null;
2907
+ const aliases = this.getAliasesForPageId(pageId);
2908
+ return {
2909
+ pageId,
2910
+ index: entry.meta.index,
2911
+ url: entry.meta.url,
2912
+ title: entry.meta.title,
2913
+ page: entry.page,
2914
+ aliases,
2915
+ stale: entry.stale
2916
+ };
2917
+ }
2918
+ /** Get tab by current index. */
2919
+ getTabByIndex(index) {
2920
+ for (const [pageId, entry] of this.tabsById) if (entry.meta.index === index && !entry.stale) return this.getTabById(pageId);
2921
+ return null;
2922
+ }
2923
+ /** Find a tab matching a predicate. */
2924
+ findTab(predicate) {
2925
+ for (const [pageId] of this.tabsById) {
2926
+ const tab = this.getTabById(pageId);
2927
+ if (tab && predicate(tab)) return tab;
2928
+ }
2929
+ return null;
2930
+ }
2931
+ /** Set the current page by pageId. */
2932
+ setCurrentPageId(pageId) {
2933
+ const entry = this.tabsById.get(pageId);
2934
+ if (!entry || entry.stale) return false;
2935
+ this.currentPageId = pageId;
2936
+ return true;
2937
+ }
2938
+ /** Set the current page by index. Returns the tab descriptor or null. */
2939
+ setCurrentByIndex(index) {
2940
+ const tab = this.getTabByIndex(index);
2941
+ if (tab) this.currentPageId = tab.pageId;
2942
+ return tab;
2943
+ }
2944
+ /** Get the current pageId. */
2945
+ getCurrentPageId() {
2946
+ return this.currentPageId;
2947
+ }
2948
+ /** Get current page object. */
2949
+ getCurrentPage() {
2950
+ if (!this.currentPageId) return null;
2951
+ return this.getPageById(this.currentPageId);
2952
+ }
2953
+ /** Get full info about the current tab, suitable for tool responses. */
2954
+ getCurrentTabInfo(driver) {
2955
+ const allAliases = [];
2956
+ const staleAliases = [];
2957
+ for (const [alias, pageId] of this.aliasToPageId) {
2958
+ const entry = this.tabsById.get(pageId);
2959
+ const stale = !entry || entry.stale;
2960
+ allAliases.push({
2961
+ alias,
2962
+ pageId,
2963
+ index: entry?.meta.index ?? null,
2964
+ stale
2965
+ });
2966
+ if (stale) staleAliases.push(alias);
2967
+ }
2968
+ const currentEntry = this.currentPageId ? this.tabsById.get(this.currentPageId) : null;
2969
+ const current = currentEntry && !currentEntry.stale ? currentEntry : null;
2970
+ return {
2971
+ driver,
2972
+ currentPageId: current ? this.currentPageId : null,
2973
+ currentIndex: current?.meta.index ?? null,
2974
+ url: current?.meta.url ?? null,
2975
+ title: current?.meta.title ?? null,
2976
+ aliases: allAliases,
2977
+ staleAliases
2978
+ };
2979
+ }
2980
+ /** Get a compact context snapshot for tool response enrichment. */
2981
+ getContextMeta() {
2982
+ const currentEntry = this.currentPageId ? this.tabsById.get(this.currentPageId) : null;
2983
+ const current = currentEntry && !currentEntry.stale ? currentEntry : null;
2984
+ return {
2985
+ url: current?.meta.url ?? null,
2986
+ title: current?.meta.title ?? null,
2987
+ tabIndex: current?.meta.index ?? null,
2988
+ pageId: current ? this.currentPageId : null
2989
+ };
2990
+ }
2991
+ /** List all non-stale tabs. */
2992
+ listTabs() {
2993
+ const result = [];
2994
+ for (const [pageId, entry] of this.tabsById) if (!entry.stale) {
2995
+ const aliases = this.getAliasesForPageId(pageId);
2996
+ result.push({
2997
+ pageId,
2998
+ index: entry.meta.index,
2999
+ url: entry.meta.url,
3000
+ title: entry.meta.title,
3001
+ page: entry.page,
3002
+ aliases,
3003
+ stale: false
3004
+ });
3005
+ }
3006
+ return result.toSorted((a, b) => a.index - b.index);
3007
+ }
3008
+ /** List all tabs including stale ones. */
3009
+ listAllTabs() {
3010
+ const result = [];
3011
+ for (const [pageId, entry] of this.tabsById) {
3012
+ const aliases = this.getAliasesForPageId(pageId);
3013
+ result.push({
3014
+ pageId,
3015
+ index: entry.meta.index,
3016
+ url: entry.meta.url,
3017
+ title: entry.meta.title,
3018
+ page: entry.page,
3019
+ aliases,
3020
+ stale: entry.stale
3021
+ });
3022
+ }
3023
+ return result.toSorted((a, b) => a.index - b.index);
3024
+ }
3025
+ setSharedContext(key, value) {
3026
+ this.sharedContext.set(key, value);
3027
+ }
3028
+ getSharedContext(key) {
3029
+ return {
3030
+ value: this.sharedContext.get(key) ?? null,
3031
+ found: this.sharedContext.has(key)
3032
+ };
3033
+ }
3034
+ getSharedContextMap() {
3035
+ const result = {};
3036
+ this.sharedContext.forEach((v, k) => {
3037
+ result[k] = v;
3038
+ });
3039
+ return result;
3040
+ }
3041
+ /** Clear all state. */
3042
+ clear() {
3043
+ this.tabsById.clear();
3044
+ this.aliasToPageId.clear();
3045
+ this.sharedContext.clear();
3046
+ this.currentPageId = null;
3047
+ }
3048
+ getAliasesForPageId(pageId) {
3049
+ const aliases = [];
3050
+ for (const [alias, pid] of this.aliasToPageId) if (pid === pageId) aliases.push(alias);
3051
+ return aliases;
3052
+ }
3053
+ };
3054
+ //#endregion
3055
+ //#region src/server/domains/browser/handlers/facade-initializer.ts
3056
+ function initializeBrowserHandlerModules(deps) {
3057
+ const commonDeps = {
3058
+ getActiveDriver: deps.getActiveDriver,
3059
+ getCamoufoxPage: deps.getCamoufoxPage
3060
+ };
3061
+ const tabRegistry = new TabRegistry();
3062
+ const targetControl = new TargetControlHandlers({
3063
+ collector: deps.collector,
3064
+ consoleMonitor: deps.consoleMonitor,
3065
+ getTabRegistry: () => tabRegistry
3066
+ });
3067
+ return {
3068
+ tabRegistry,
3069
+ targetControl,
3070
+ browserControl: new BrowserControlHandlers({
3071
+ collector: deps.collector,
3072
+ pageController: deps.pageController,
3073
+ consoleMonitor: deps.consoleMonitor,
3074
+ getActiveDriver: deps.getActiveDriver,
3075
+ getCamoufoxManager: deps.getCamoufoxManager,
3076
+ getCamoufoxPage: deps.getCamoufoxPage,
3077
+ getTabRegistry: () => tabRegistry,
3078
+ clearAttachedTargetContext: (context) => targetControl.clearAttachedTargetContext(context)
3079
+ }),
3080
+ camoufoxBrowser: new CamoufoxBrowserHandlers({
3081
+ getCamoufoxManager: deps.getCamoufoxManager,
3082
+ setCamoufoxManager: deps.setCamoufoxManager,
3083
+ closeCamoufox: deps.closeCamoufox
3084
+ }),
3085
+ pageNavigation: new PageNavigationHandlers({
3086
+ pageController: deps.pageController,
3087
+ consoleMonitor: deps.consoleMonitor,
3088
+ eventBus: deps.eventBus,
3089
+ ...commonDeps
3090
+ }),
3091
+ pageInteraction: new PageInteractionHandlers({
3092
+ pageController: deps.pageController,
3093
+ ...commonDeps
3094
+ }),
3095
+ pageEvaluation: new PageEvaluationHandlers({
3096
+ pageController: deps.pageController,
3097
+ detailedDataManager: deps.detailedDataManager,
3098
+ ...commonDeps
3099
+ }),
3100
+ targetEvaluation: new TargetEvaluationHandlers({
3101
+ pageController: deps.pageController,
3102
+ detailedDataManager: deps.detailedDataManager
3103
+ }),
3104
+ pageData: new PageDataHandlers({
3105
+ pageController: deps.pageController,
3106
+ ...commonDeps
3107
+ }),
3108
+ consoleHandlers: new ConsoleHandlers({
3109
+ consoleMonitor: deps.consoleMonitor,
3110
+ detailedDataManager: deps.detailedDataManager
3111
+ }),
3112
+ scriptManagement: new ScriptManagementHandlers({
3113
+ scriptManager: deps.scriptManager,
3114
+ detailedDataManager: deps.detailedDataManager
3115
+ }),
3116
+ captchaHandlers: new CaptchaHandlers({
3117
+ pageController: deps.pageController,
3118
+ captchaDetector: deps.captchaDetector,
3119
+ autoDetectCaptcha: deps.getAutoDetectCaptcha(),
3120
+ autoSwitchHeadless: deps.getAutoSwitchHeadless(),
3121
+ captchaTimeout: deps.getCaptchaTimeout(),
3122
+ setAutoDetectCaptcha: deps.setAutoDetectCaptcha,
3123
+ setAutoSwitchHeadless: deps.setAutoSwitchHeadless,
3124
+ setCaptchaTimeout: deps.setCaptchaTimeout
3125
+ }),
3126
+ stealthInjection: new StealthInjectionHandlers({
3127
+ pageController: deps.pageController,
3128
+ ...commonDeps
3129
+ }),
3130
+ frameworkState: new FrameworkStateHandlers({ getActivePage: () => deps.collector.getActivePage() }),
3131
+ indexedDBDump: new IndexedDBDumpHandlers({ getActivePage: () => deps.collector.getActivePage() }),
3132
+ jsHeapSearch: new JSHeapSearchHandlers({
3133
+ getActivePage: () => deps.collector.getActivePage(),
3134
+ getActiveDriver: deps.getActiveDriver
3135
+ }),
3136
+ tabWorkflow: new TabWorkflowHandlers({
3137
+ getActiveDriver: deps.getActiveDriver,
3138
+ getCamoufoxPage: deps.getCamoufoxPage,
3139
+ getPageController: () => deps.pageController,
3140
+ getTabRegistry: () => tabRegistry
3141
+ }),
3142
+ detailedData: new DetailedDataHandlers({ detailedDataManager: deps.detailedDataManager }),
3143
+ jsdomHandlers: new JsdomHandlers()
3144
+ };
3145
+ }
3146
+ //#endregion
3147
+ //#region src/server/domains/browser/handlers/human-behavior.ts
3148
+ /** Cubic Bezier: P(t) = (1-t)^3·P0 + 3(1-t)^2·t·P1 + 3(1-t)·t^2·P2 + t^3·P3 */
3149
+ function cubicBezier(p0, p1, p2, p3, t) {
3150
+ const u = 1 - t;
3151
+ const uu = u * u;
3152
+ const uuu = uu * u;
3153
+ const tt = t * t;
3154
+ const ttt = tt * t;
3155
+ return {
3156
+ x: uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x,
3157
+ y: uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y
3158
+ };
3159
+ }
3160
+ /** Generate control points with lateral offset for natural curve feel. */
3161
+ function generateControlPoints(from, to) {
3162
+ const dx = to.x - from.x;
3163
+ const dy = to.y - from.y;
3164
+ const perpX = -dy;
3165
+ const perpY = dx;
3166
+ const len = Math.sqrt(perpX * perpX + perpY * perpY) || 1;
3167
+ const offset1 = (Math.random() - .5) * .4;
3168
+ const offset2 = (Math.random() - .5) * .4;
3169
+ return [{
3170
+ x: from.x + dx * .3 + perpX / len * Math.abs(dx + dy) * offset1,
3171
+ y: from.y + dy * .3 + perpY / len * Math.abs(dx + dy) * offset1
3172
+ }, {
3173
+ x: from.x + dx * .7 + perpX / len * Math.abs(dx + dy) * offset2,
3174
+ y: from.y + dy * .7 + perpY / len * Math.abs(dx + dy) * offset2
3175
+ }];
3176
+ }
3177
+ /** Easing functions for speed curves. */
3178
+ function easeT(t, curve) {
3179
+ switch (curve) {
3180
+ case "linear": return t;
3181
+ case "ease-in": return t * t;
3182
+ case "ease-out": return 1 - (1 - t) * (1 - t);
3183
+ default: return t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
3184
+ }
3185
+ }
3186
+ function sleep$1(ms) {
3187
+ return new Promise((resolve) => setTimeout(resolve, ms));
3188
+ }
3189
+ async function handleHumanMouse(args, collector) {
3190
+ try {
3191
+ const page = await collector.getActivePage();
3192
+ if (!page) return R.fail("No active page. Use browser_launch or browser_attach first.").build();
3193
+ let toX = argNumber(args, "toX");
3194
+ let toY = argNumber(args, "toY");
3195
+ const selector = argString(args, "selector");
3196
+ if (selector) {
3197
+ const box = await page.evaluate((sel) => {
3198
+ const el = document.querySelector(sel);
3199
+ if (!el) return null;
3200
+ const rect = el.getBoundingClientRect();
3201
+ return {
3202
+ x: rect.x + rect.width / 2,
3203
+ y: rect.y + rect.height / 2
3204
+ };
3205
+ }, selector);
3206
+ if (!box) return R.fail(`Selector not found: ${selector}`).build();
3207
+ toX = box.x;
3208
+ toY = box.y;
3209
+ }
3210
+ if (toX === void 0 || toY === void 0) return R.fail("Either selector or toX/toY coordinates are required").build();
3211
+ const fromX = argNumber(args, "fromX", 0);
3212
+ const fromY = argNumber(args, "fromY", 0);
3213
+ const steps = Math.max(1, Math.min(argNumber(args, "steps", 24), 500));
3214
+ const durationMs = Math.max(10, Math.min(argNumber(args, "durationMs", 600), 3e4));
3215
+ const jitterPx = Math.max(0, Math.min(argNumber(args, "jitterPx", 1.5), 20));
3216
+ const curve = argString(args, "curve", "ease");
3217
+ const shouldClick = argBool(args, "click", false);
3218
+ const from = {
3219
+ x: fromX,
3220
+ y: fromY
3221
+ };
3222
+ const to = {
3223
+ x: toX,
3224
+ y: toY
3225
+ };
3226
+ const [cp1, cp2] = generateControlPoints(from, to);
3227
+ const stepDelay = durationMs / steps;
3228
+ for (let i = 0; i <= steps; i++) {
3229
+ const pt = cubicBezier(from, cp1, cp2, to, easeT(i / steps, curve));
3230
+ if (i > 0 && i < steps) {
3231
+ pt.x += (Math.random() - .5) * 2 * jitterPx;
3232
+ pt.y += (Math.random() - .5) * 2 * jitterPx;
3233
+ }
3234
+ pt.x = Math.max(0, pt.x);
3235
+ pt.y = Math.max(0, pt.y);
3236
+ await page.mouse.move(pt.x, pt.y);
3237
+ await sleep$1(stepDelay * (.8 + Math.random() * .4));
3238
+ }
3239
+ if (shouldClick) await page.mouse.click(toX, toY);
3240
+ return R.ok().build({
3241
+ from: {
3242
+ x: fromX,
3243
+ y: fromY
3244
+ },
3245
+ to: {
3246
+ x: toX,
3247
+ y: toY
3248
+ },
3249
+ steps,
3250
+ durationMs,
3251
+ clicked: shouldClick
3252
+ });
3253
+ } catch (e) {
3254
+ return R.fail(e).build();
3255
+ }
3256
+ }
3257
+ async function handleHumanScroll(args, collector) {
3258
+ try {
3259
+ const page = await collector.getActivePage();
3260
+ if (!page) return R.fail("No active page.").build();
3261
+ const distance = Math.max(1, Math.min(argNumber(args, "distance", 500), 1e4));
3262
+ const direction = argString(args, "direction", "down");
3263
+ const segments = Math.max(1, Math.min(argNumber(args, "segments", 8), 200));
3264
+ const pauseMs = Math.max(0, Math.min(argNumber(args, "pauseMs", 80), 5e3));
3265
+ const jitter = Math.max(0, Math.min(argNumber(args, "jitter", .3), 1));
3266
+ const selector = argString(args, "selector");
3267
+ const isVertical = direction === "up" || direction === "down";
3268
+ const sign = direction === "down" || direction === "right" ? 1 : -1;
3269
+ let scrolled = 0;
3270
+ for (let i = 0; i < segments; i++) {
3271
+ const decel = 1 - i / segments * .4;
3272
+ const segmentDist = distance / segments * decel * (1 + (Math.random() - .5) * jitter * 2);
3273
+ const actualDist = Math.min(segmentDist, distance - scrolled);
3274
+ if (actualDist <= 0) break;
3275
+ const deltaX = isVertical ? 0 : actualDist * sign;
3276
+ const deltaY = isVertical ? actualDist * sign : 0;
3277
+ if (selector) await page.evaluate((sel, dx, dy) => {
3278
+ const el = document.querySelector(sel);
3279
+ if (el) el.scrollBy({
3280
+ left: dx,
3281
+ top: dy,
3282
+ behavior: "auto"
3283
+ });
3284
+ }, selector, deltaX, deltaY);
3285
+ else await page.evaluate((dx, dy) => window.scrollBy({
3286
+ left: dx,
3287
+ top: dy,
3288
+ behavior: "auto"
3289
+ }), deltaX, deltaY);
3290
+ scrolled += actualDist;
3291
+ await sleep$1(pauseMs * (.5 + Math.random()));
3292
+ }
3293
+ return R.ok().build({
3294
+ direction,
3295
+ requestedDistance: distance,
3296
+ actualScrolled: Math.round(scrolled),
3297
+ segments
3298
+ });
3299
+ } catch (e) {
3300
+ return R.fail(e).build();
3301
+ }
3302
+ }
3303
+ async function handleHumanTyping(args, collector) {
3304
+ try {
3305
+ const page = await collector.getActivePage();
3306
+ if (!page) return R.fail("No active page.").build();
3307
+ const selector = argString(args, "selector", "");
3308
+ const text = argString(args, "text", "");
3309
+ const wpm = Math.max(10, Math.min(argNumber(args, "wpm", 90), 300));
3310
+ const errorRate = Math.max(0, Math.min(argNumber(args, "errorRate", .02), .3));
3311
+ const correctDelayMs = Math.max(50, Math.min(argNumber(args, "correctDelayMs", 200), 2e3));
3312
+ const clearFirst = argBool(args, "clearFirst", false);
3313
+ if (!selector || !text) return R.fail("selector and text are required").build();
3314
+ const avgDelayMs = 6e4 / (wpm * 5);
3315
+ await page.click(selector);
3316
+ if (clearFirst) await page.evaluate((sel) => {
3317
+ const el = document.querySelector(sel);
3318
+ if (el) el.value = "";
3319
+ }, selector);
3320
+ let typoCount = 0;
3321
+ for (const char of text) {
3322
+ if (Math.random() < errorRate && char !== " ") {
3323
+ const wrongChar = String.fromCharCode(char.charCodeAt(0) + (Math.random() > .5 ? 1 : -1));
3324
+ await page.keyboard.type(wrongChar, { delay: 0 });
3325
+ await sleep$1(correctDelayMs * (.5 + Math.random()));
3326
+ await page.keyboard.press("Backspace");
3327
+ await sleep$1(50 + Math.random() * 50);
3328
+ typoCount++;
3329
+ }
3330
+ await page.keyboard.type(char, { delay: 0 });
3331
+ let delay = avgDelayMs * (.5 + Math.random());
3332
+ if (char === " " || ".,:;!?".includes(char)) delay *= 1.5 + Math.random() * .5;
3333
+ await sleep$1(delay);
3334
+ }
3335
+ return R.ok().build({
3336
+ selector,
3337
+ length: text.length,
3338
+ wpm,
3339
+ typosSimulated: typoCount,
3340
+ errorRate
3341
+ });
3342
+ } catch (e) {
3343
+ return R.fail(e).build();
3344
+ }
3345
+ }
3346
+ //#endregion
3347
+ //#region src/server/domains/browser/handlers/captcha-solver.ts
3348
+ function sleep(ms) {
3349
+ return new Promise((resolve) => setTimeout(resolve, ms));
3350
+ }
3351
+ function normalizeSolverMode(rawMode) {
3352
+ const value = typeof rawMode === "string" ? rawMode.toLowerCase() : "";
3353
+ if (value === "hook") return "hook";
3354
+ if (value === "external_service") return "external_service";
3355
+ if (value === "2captcha" || value === "anticaptcha" || value === "capsolver") return "external_service";
3356
+ return "manual";
3357
+ }
3358
+ function normalizeChallengeTypeHint(rawType) {
3359
+ const value = typeof rawType === "string" ? rawType.toLowerCase() : "";
3360
+ if (value === "image") return "image";
3361
+ if (value === "widget" || value === "recaptcha_v2" || value === "recaptcha_v3" || value === "hcaptcha" || value === "funcaptcha" || value === "turnstile") return "widget";
3362
+ if (value === "browser_check" || value === "managed_widget") return "browser_check";
3363
+ return "auto";
3364
+ }
3365
+ function resolveLegacyServiceOverride(rawProvider) {
3366
+ if (typeof rawProvider !== "string" || !rawProvider.trim()) return;
3367
+ return rawProvider.trim().toLowerCase();
3368
+ }
3369
+ function resolveExternalServiceName(args) {
3370
+ const legacyOverride = resolveLegacyServiceOverride(args.provider);
3371
+ const configured = (process.env.CAPTCHA_PROVIDER || "").trim().toLowerCase();
3372
+ return legacyOverride || configured || "2captcha";
3373
+ }
3374
+ async function solveWith2Captcha(apiKey, params, timeoutMs) {
3375
+ const start = Date.now();
3376
+ const baseUrl = CAPTCHA_SOLVER_BASE_URL;
3377
+ if (!baseUrl) throw new Error("CAPTCHA_SOLVER_BASE_URL must be configured before using external_service mode.");
3378
+ const submitBody = {
3379
+ key: apiKey,
3380
+ json: 1
3381
+ };
3382
+ if (params.taskKind === "turnstile" || params.taskKind === "recaptcha_v2" || params.taskKind === "hcaptcha") {
3383
+ submitBody.method = params.taskKind === "turnstile" ? "turnstile" : params.taskKind === "hcaptcha" ? "hcaptcha" : "userrecaptcha";
3384
+ submitBody.sitekey = params.siteKey;
3385
+ submitBody.pageurl = params.pageUrl;
3386
+ } else {
3387
+ submitBody.method = "base64";
3388
+ submitBody.body = params.imageBase64;
3389
+ }
3390
+ const submitData = await (await fetch(`${baseUrl}/in.php`, {
3391
+ method: "POST",
3392
+ headers: { "Content-Type": "application/json" },
3393
+ body: JSON.stringify(submitBody),
3394
+ signal: AbortSignal.timeout(CAPTCHA_SUBMIT_TIMEOUT_MS)
3395
+ })).json();
3396
+ if (submitData.status !== 1) throw new Error(`2captcha submit failed: ${JSON.stringify(submitData)}`);
3397
+ const taskId = submitData.request;
3398
+ const pollInterval = CAPTCHA_POLL_INTERVAL_MS;
3399
+ while (true) {
3400
+ const remaining = timeoutMs - (Date.now() - start);
3401
+ if (remaining <= 0) break;
3402
+ await sleep(Math.min(pollInterval, remaining));
3403
+ if (Date.now() - start >= timeoutMs) break;
3404
+ const resultUrl = new URL(`${baseUrl}/res.php`);
3405
+ resultUrl.searchParams.set("key", apiKey);
3406
+ resultUrl.searchParams.set("action", "get");
3407
+ resultUrl.searchParams.set("id", taskId);
3408
+ resultUrl.searchParams.set("json", "1");
3409
+ const resultData = await (await fetch(resultUrl.toString(), { signal: AbortSignal.timeout(CAPTCHA_RESULT_TIMEOUT_MS) })).json();
3410
+ if (resultData.status === 1) return {
3411
+ token: resultData.request,
3412
+ challengeType: params.taskKind === "image" ? "image" : "widget",
3413
+ mode: "external_service",
3414
+ durationMs: Date.now() - start
3415
+ };
3416
+ if (resultData.request !== "CAPCHA_NOT_READY") throw new Error(`2captcha solve failed: ${JSON.stringify(resultData)}`);
3417
+ }
3418
+ throw new Error(`2captcha solve timeout after ${timeoutMs}ms`);
3419
+ }
3420
+ async function handleCaptchaVisionSolve(args, collector) {
3421
+ const page = await collector.getActivePage();
3422
+ if (!page) return R.fail("No active page.").build();
3423
+ const mode = normalizeSolverMode(args.mode ?? args.provider ?? process.env.CAPTCHA_PROVIDER);
3424
+ const externalService = resolveExternalServiceName(args);
3425
+ const apiKey = argString(args, "apiKey", "") || process.env.CAPTCHA_API_KEY || "";
3426
+ const challengeTypeHint = normalizeChallengeTypeHint(args.challengeType ?? args.typeHint);
3427
+ const timeoutMs = Math.min(Math.max(argNumber(args, "timeoutMs", CAPTCHA_DEFAULT_TIMEOUT_MS), CAPTCHA_MIN_TIMEOUT_MS), CAPTCHA_MAX_TIMEOUT_MS);
3428
+ const maxRetries = Math.min(Math.max(argNumber(args, "maxRetries", CAPTCHA_DEFAULT_RETRIES), 0), CAPTCHA_MAX_RETRIES);
3429
+ let challengeType = challengeTypeHint;
3430
+ let taskKind = challengeTypeHint === "image" ? "image" : "recaptcha_v2";
3431
+ let siteKey = argString(args, "siteKey");
3432
+ const pageUrl = argString(args, "pageUrl", "") || page.url();
3433
+ if (challengeType === "auto") {
3434
+ const detected = await page.evaluate(() => {
3435
+ if (document.querySelector("[data-sitekey]")) {
3436
+ const sk = document.querySelector("[data-sitekey]")?.getAttribute("data-sitekey") || "";
3437
+ if (document.querySelector(".cf-turnstile")) return {
3438
+ challengeType: "widget",
3439
+ taskKind: "turnstile",
3440
+ siteKey: sk
3441
+ };
3442
+ if (document.querySelector(".h-captcha")) return {
3443
+ challengeType: "widget",
3444
+ taskKind: "hcaptcha",
3445
+ siteKey: sk
3446
+ };
3447
+ return {
3448
+ challengeType: "widget",
3449
+ taskKind: "recaptcha_v2",
3450
+ siteKey: sk
3451
+ };
3452
+ }
3453
+ if (document.querySelector("iframe[src*=\"recaptcha\"]")) return {
3454
+ challengeType: "widget",
3455
+ taskKind: "recaptcha_v2",
3456
+ siteKey: ""
3457
+ };
3458
+ if (document.querySelector("iframe[src*=\"hcaptcha\"]")) return {
3459
+ challengeType: "widget",
3460
+ taskKind: "hcaptcha",
3461
+ siteKey: ""
3462
+ };
3463
+ if (document.querySelector(".cf-turnstile")) return {
3464
+ challengeType: "widget",
3465
+ taskKind: "turnstile",
3466
+ siteKey: ""
3467
+ };
3468
+ return {
3469
+ challengeType: "image",
3470
+ taskKind: "image",
3471
+ siteKey: ""
3472
+ };
3473
+ });
3474
+ challengeType = detected.challengeType;
3475
+ taskKind = detected.taskKind;
3476
+ if (!siteKey && detected.siteKey) siteKey = detected.siteKey;
3477
+ } else if (challengeType === "image") taskKind = "image";
3478
+ else taskKind = "recaptcha_v2";
3479
+ if (mode === "manual") return R.ok().build({
3480
+ mode: "manual",
3481
+ challengeType,
3482
+ siteKey: siteKey ?? null,
3483
+ instruction: "Please solve the CAPTCHA manually in the browser, then continue.",
3484
+ hint: "Configure an external solver service and CAPTCHA_API_KEY to automate this flow."
3485
+ });
3486
+ if (!apiKey) return R.fail("External solver credentials are required. Set CAPTCHA_API_KEY.").build();
3487
+ let lastError = null;
3488
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
3489
+ let result;
3490
+ if (externalService === "2captcha") result = await solveWith2Captcha(apiKey, {
3491
+ taskKind,
3492
+ siteKey,
3493
+ pageUrl
3494
+ }, timeoutMs);
3495
+ else if (externalService === "anticaptcha" || externalService === "capsolver") throw new Error("The selected external solver service is not yet implemented. Currently only the configured primary service and manual mode are supported.");
3496
+ else throw new Error("Unsupported external solver service.");
3497
+ return R.ok().build({
3498
+ token: result.token,
3499
+ challengeType: result.challengeType,
3500
+ mode: result.mode,
3501
+ durationMs: result.durationMs,
3502
+ attempt: attempt + 1
3503
+ });
3504
+ } catch (error) {
3505
+ lastError = error instanceof Error ? error : new Error(String(error));
3506
+ logger.warn(`[captcha] Attempt ${attempt + 1} failed: ${lastError.message}`);
3507
+ }
3508
+ return R.fail(lastError ?? /* @__PURE__ */ new Error("All attempts failed")).merge({
3509
+ challengeType,
3510
+ mode,
3511
+ maxRetries,
3512
+ suggestion: "Try manual mode or adjust the external solver configuration."
3513
+ }).build();
3514
+ }
3515
+ async function handleWidgetChallengeSolve(args, collector) {
3516
+ const page = await collector.getActivePage();
3517
+ if (!page) return R.fail("No active page.").build();
3518
+ const mode = normalizeSolverMode(args.mode ?? args.provider ?? process.env.CAPTCHA_PROVIDER);
3519
+ const externalService = resolveExternalServiceName(args);
3520
+ const apiKey = argString(args, "apiKey", "") || process.env.CAPTCHA_API_KEY || "";
3521
+ const timeoutMs = Math.min(Math.max(argNumber(args, "timeoutMs", 12e4), 5e3), 6e5);
3522
+ const injectToken = argBool(args, "injectToken", true);
3523
+ let siteKey = argString(args, "siteKey");
3524
+ const pageUrl = argString(args, "pageUrl", "") || page.url();
3525
+ if (!siteKey) siteKey = await page.evaluate(() => {
3526
+ return document.querySelector(".cf-turnstile[data-sitekey], [data-sitekey]")?.getAttribute("data-sitekey") ?? "";
3527
+ }) || void 0;
3528
+ if (!siteKey) return R.fail("Could not detect the widget siteKey. Provide it manually or ensure the page exposes a site key.").build();
3529
+ if (mode === "hook") {
3530
+ const hookTimeoutMs = Math.min(timeoutMs, 3e4);
3531
+ const token = await page.evaluate((hookTimeout) => {
3532
+ return new Promise((resolve, reject) => {
3533
+ const timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("Hook timeout")), hookTimeout);
3534
+ const origCallbacks = window.__turnstile_callbacks;
3535
+ if (origCallbacks) for (const [key, cb] of Object.entries(origCallbacks)) origCallbacks[key] = (captchaToken) => {
3536
+ clearTimeout(timeout);
3537
+ resolve(captchaToken);
3538
+ cb(captchaToken);
3539
+ };
3540
+ else {
3541
+ clearTimeout(timeout);
3542
+ reject(/* @__PURE__ */ new Error("No widget callbacks found. Try external_service mode instead."));
3543
+ }
3544
+ });
3545
+ }, hookTimeoutMs).catch(() => null);
3546
+ if (token) return R.ok().build({
3547
+ token,
3548
+ method: "hook",
3549
+ challengeType: "widget",
3550
+ siteKey
3551
+ });
3552
+ }
3553
+ if (mode === "manual") return R.ok().build({
3554
+ mode: "manual",
3555
+ challengeType: "widget",
3556
+ siteKey,
3557
+ pageUrl,
3558
+ instruction: "Please complete the widget challenge manually."
3559
+ });
3560
+ if (externalService !== "2captcha") return R.fail("The selected external solver service is not implemented for this widget flow. Currently only the configured primary service, manual mode, and hook mode are supported.").build();
3561
+ if (!apiKey) return R.fail("External solver credentials are required.").build();
3562
+ try {
3563
+ const result = await solveWith2Captcha(apiKey, {
3564
+ taskKind: "turnstile",
3565
+ siteKey,
3566
+ pageUrl
3567
+ }, timeoutMs);
3568
+ if (injectToken && result.token) await page.evaluate((token) => {
3569
+ document.querySelectorAll("input[name*=\"turnstile\"], input[name*=\"cf-turnstile\"]").forEach((input) => {
3570
+ input.value = token;
3571
+ });
3572
+ if (window.turnstile?.getResponse) {}
3573
+ }, result.token);
3574
+ return R.ok().build({
3575
+ token: result.token,
3576
+ challengeType: result.challengeType,
3577
+ siteKey,
3578
+ mode: result.mode,
3579
+ durationMs: result.durationMs,
3580
+ injected: injectToken
3581
+ });
3582
+ } catch (error) {
3583
+ return R.fail(error).merge({
3584
+ siteKey,
3585
+ mode,
3586
+ suggestion: "Try manual mode or hook mode."
3587
+ }).build();
3588
+ }
3589
+ }
3590
+ //#endregion
3591
+ //#region src/server/domains/browser/handlers/camoufox-flow.ts
3592
+ function extractCamoufoxConfig(args) {
3593
+ const addons = argStringArray(args, "addons");
3594
+ const excludeAddons = argStringArray(args, "excludeAddons");
3595
+ const fonts = argStringArray(args, "fonts");
3596
+ return {
3597
+ headless: argBool(args, "headless", true),
3598
+ os: argString(args, "os", "windows"),
3599
+ geoip: argBool(args, "geoip", false),
3600
+ humanize: argBool(args, "humanize", false),
3601
+ proxy: argString(args, "proxy") || void 0,
3602
+ blockImages: argBool(args, "blockImages", false),
3603
+ blockWebrtc: argBool(args, "blockWebrtc", false),
3604
+ blockWebgl: argBool(args, "blockWebgl", false),
3605
+ locale: argString(args, "locale") || void 0,
3606
+ addons: addons.length > 0 ? addons : void 0,
3607
+ fonts: fonts.length > 0 ? fonts : void 0,
3608
+ excludeAddons: excludeAddons.length > 0 ? excludeAddons : void 0,
3609
+ customFontsOnly: argBool(args, "customFontsOnly", false),
3610
+ screen: args.screen,
3611
+ window: args.window,
3612
+ fingerprint: argObject(args, "fingerprint"),
3613
+ webglConfig: argObject(args, "webglConfig"),
3614
+ firefoxUserPrefs: argObject(args, "firefoxUserPrefs"),
3615
+ mainWorldEval: argBool(args, "mainWorldEval", false),
3616
+ enableCache: argBool(args, "enableCache", false)
3617
+ };
3618
+ }
3619
+ async function handleCamoufoxLaunchFlow(context, args) {
3620
+ try {
3621
+ const config = extractCamoufoxConfig(args);
3622
+ if (argString(args, "mode", "launch") === "connect") {
3623
+ const wsEndpoint = argString(args, "wsEndpoint");
3624
+ if (!wsEndpoint) return R.fail("wsEndpoint is required for connect mode.").build();
3625
+ const manager = new CamoufoxBrowserManager(config);
3626
+ await manager.connectToServer(wsEndpoint);
3627
+ context.setCamoufoxManager(manager);
3628
+ context.setActiveDriver("camoufox");
3629
+ context.clearCamoufoxPage();
3630
+ return R.ok().build({
3631
+ driver: "camoufox",
3632
+ mode: "connect",
3633
+ wsEndpoint,
3634
+ message: "Connected to Camoufox server."
3635
+ });
3636
+ }
3637
+ const manager = new CamoufoxBrowserManager(config);
3638
+ await manager.launch();
3639
+ context.setCamoufoxManager(manager);
3640
+ context.setActiveDriver("camoufox");
3641
+ context.clearCamoufoxPage();
3642
+ return R.ok().build({
3643
+ driver: "camoufox",
3644
+ mode: "launch",
3645
+ config: {
3646
+ os: config.os,
3647
+ headless: config.headless,
3648
+ geoip: config.geoip,
3649
+ humanize: config.humanize,
3650
+ locale: config.locale,
3651
+ blockWebgl: config.blockWebgl,
3652
+ blockImages: config.blockImages,
3653
+ blockWebrtc: config.blockWebrtc
3654
+ },
3655
+ message: "Camoufox (Firefox) browser launched"
3656
+ });
3657
+ } catch (e) {
3658
+ return R.fail(e).build();
3659
+ }
3660
+ }
3661
+ function normalizeWaitUntil(waitUntil) {
3662
+ if (waitUntil === "networkidle2") return "networkidle";
3663
+ if (waitUntil === "load") return "load";
3664
+ if (waitUntil === "domcontentloaded") return "domcontentloaded";
3665
+ if (waitUntil === "commit") return "commit";
3666
+ return "networkidle";
3667
+ }
3668
+ async function handleCamoufoxNavigateFlow(context, args) {
3669
+ try {
3670
+ const url = argString(args, "url", "");
3671
+ const rawWaitUntil = argString(args, "waitUntil", "networkidle");
3672
+ const timeout = argNumber(args, "timeout");
3673
+ const page = await context.getCamoufoxPage();
3674
+ await page.goto(url, {
3675
+ waitUntil: normalizeWaitUntil(rawWaitUntil),
3676
+ timeout
3677
+ });
3678
+ context.setConsoleMonitorPage(page);
3679
+ return R.ok().build({
3680
+ driver: "camoufox",
3681
+ url: page.url(),
3682
+ title: await page.title()
3683
+ });
3684
+ } catch (e) {
3685
+ return R.fail(e).build();
3686
+ }
3687
+ }
3688
+ //#endregion
3689
+ //#region src/server/domains/browser/handlers.impl.ts
3690
+ var BrowserToolHandlers = class {
3691
+ collector;
3692
+ pageController;
3693
+ scriptManager;
3694
+ consoleMonitor;
3695
+ captchaDetector;
3696
+ detailedDataManager;
3697
+ camoufoxManager = null;
3698
+ activeDriver = "chrome";
3699
+ camoufoxPage = null;
3700
+ autoDetectCaptcha = true;
3701
+ autoSwitchHeadless = true;
3702
+ captchaTimeout = 3e5;
3703
+ browserControl;
3704
+ targetControl;
3705
+ camoufoxBrowser;
3706
+ pageNavigation;
3707
+ pageInteraction;
3708
+ pageEvaluation;
3709
+ targetEvaluation;
3710
+ pageData;
3711
+ consoleHandlers;
3712
+ scriptManagement;
3713
+ captchaHandlers;
3714
+ stealthInjection;
3715
+ frameworkState;
3716
+ indexedDBDump;
3717
+ jsHeapSearch;
3718
+ tabWorkflow;
3719
+ detailedData;
3720
+ jsdomHandlers;
3721
+ _tabRegistry;
3722
+ constructor(collector, pageController, scriptManager, consoleMonitor, eventBus) {
3723
+ this.collector = collector;
3724
+ this.pageController = pageController;
3725
+ this.scriptManager = scriptManager;
3726
+ this.consoleMonitor = consoleMonitor;
3727
+ const screenshotDir = resolveOutputDirectory(getConfig().paths.captchaScreenshotDir, "screenshots/captcha");
3728
+ this.captchaDetector = new AICaptchaDetector(screenshotDir);
3729
+ this.detailedDataManager = DetailedDataManager.getInstance();
3730
+ const modules = initializeBrowserHandlerModules({
3731
+ collector: this.collector,
3732
+ pageController: this.pageController,
3733
+ scriptManager: this.scriptManager,
3734
+ consoleMonitor: this.consoleMonitor,
3735
+ eventBus,
3736
+ captchaDetector: this.captchaDetector,
3737
+ detailedDataManager: this.detailedDataManager,
3738
+ getActiveDriver: () => this.activeDriver,
3739
+ getCamoufoxPage: () => this.getCamoufoxPage(),
3740
+ getCamoufoxManager: () => this.camoufoxManager,
3741
+ setCamoufoxManager: (manager) => {
3742
+ this.camoufoxManager = manager;
3743
+ },
3744
+ closeCamoufox: () => this.closeCamoufox(),
3745
+ getAutoDetectCaptcha: () => this.autoDetectCaptcha,
3746
+ getAutoSwitchHeadless: () => this.autoSwitchHeadless,
3747
+ getCaptchaTimeout: () => this.captchaTimeout,
3748
+ setAutoDetectCaptcha: (value) => {
3749
+ this.autoDetectCaptcha = value;
3750
+ },
3751
+ setAutoSwitchHeadless: (value) => {
3752
+ this.autoSwitchHeadless = value;
3753
+ },
3754
+ setCaptchaTimeout: (value) => {
3755
+ this.captchaTimeout = value;
3756
+ }
3757
+ });
3758
+ this.browserControl = modules.browserControl;
3759
+ this.targetControl = modules.targetControl;
3760
+ this.camoufoxBrowser = modules.camoufoxBrowser;
3761
+ this.pageNavigation = modules.pageNavigation;
3762
+ this.pageInteraction = modules.pageInteraction;
3763
+ this.pageEvaluation = modules.pageEvaluation;
3764
+ this.targetEvaluation = modules.targetEvaluation;
3765
+ this.pageData = modules.pageData;
3766
+ this.consoleHandlers = modules.consoleHandlers;
3767
+ this.scriptManagement = modules.scriptManagement;
3768
+ this.captchaHandlers = modules.captchaHandlers;
3769
+ this.stealthInjection = modules.stealthInjection;
3770
+ this.frameworkState = modules.frameworkState;
3771
+ this.indexedDBDump = modules.indexedDBDump;
3772
+ this.jsHeapSearch = modules.jsHeapSearch;
3773
+ this.tabWorkflow = modules.tabWorkflow;
3774
+ this.detailedData = modules.detailedData;
3775
+ this.jsdomHandlers = modules.jsdomHandlers;
3776
+ this._tabRegistry = modules.tabRegistry;
3777
+ }
3778
+ /** Get the shared TabRegistry for context enrichment. */
3779
+ getTabRegistry() {
3780
+ return this._tabRegistry;
3781
+ }
3782
+ /** Get or create camoufox page (Playwright Page). */
3783
+ async getCamoufoxPage() {
3784
+ if (!this.camoufoxManager) throw new Error("Camoufox browser not launched. Call browser_launch(driver=\"camoufox\") first.");
3785
+ if (!this.camoufoxPage) this.camoufoxPage = await this.camoufoxManager.newPage();
3786
+ return this.camoufoxPage;
3787
+ }
3788
+ async closeCamoufox() {
3789
+ try {
3790
+ await this.consoleMonitor.disable();
3791
+ } catch (error) {
3792
+ logger.warn(`Failed to reset console monitor before closing Camoufox: ${String(error)}`);
3793
+ }
3794
+ this.consoleMonitor.clearPlaywrightPage();
3795
+ if (this.camoufoxManager) {
3796
+ await this.camoufoxManager.close();
3797
+ this.camoufoxManager = null;
3798
+ this.camoufoxPage = null;
3799
+ }
3800
+ }
3801
+ async handleBrowserLaunch(args) {
3802
+ if (argString(args, "driver", "chrome") === "camoufox") return this.handleCamoufoxLaunch(args);
3803
+ if (this.activeDriver === "camoufox" && this.camoufoxManager) await this.closeCamoufox();
3804
+ this.activeDriver = "chrome";
3805
+ return this.browserControl.handleBrowserLaunch(args);
3806
+ }
3807
+ async handleBrowserClose(args) {
3808
+ if (this.activeDriver === "camoufox" && this.camoufoxManager) {
3809
+ await this.closeCamoufox();
3810
+ await this.browserControl.handleBrowserClose(args);
3811
+ this.activeDriver = "chrome";
3812
+ return { content: [{
3813
+ type: "text",
3814
+ text: JSON.stringify({
3815
+ success: true,
3816
+ message: "Camoufox browser closed"
3817
+ }, null, 2)
3818
+ }] };
3819
+ }
3820
+ return this.browserControl.handleBrowserClose(args);
3821
+ }
3822
+ async handleBrowserStatus(args) {
3823
+ if (this.activeDriver === "camoufox") {
3824
+ const running = !!this.camoufoxManager?.getBrowser();
3825
+ return { content: [{
3826
+ type: "text",
3827
+ text: JSON.stringify({
3828
+ success: true,
3829
+ driver: "camoufox",
3830
+ running,
3831
+ hasActivePage: !!this.camoufoxPage
3832
+ }, null, 2)
3833
+ }] };
3834
+ }
3835
+ return this.browserControl.handleBrowserStatus(args);
3836
+ }
3837
+ async handleBrowserListTabs(args) {
3838
+ return this.browserControl.handleBrowserListTabs(args);
3839
+ }
3840
+ async handleBrowserListCdpTargets(args) {
3841
+ return this.targetControl.handleBrowserListCdpTargets(args);
3842
+ }
3843
+ async handleBrowserSelectTab(args) {
3844
+ return this.browserControl.handleBrowserSelectTab(args);
3845
+ }
3846
+ async handleBrowserAttachCdpTarget(args) {
3847
+ return this.targetControl.handleBrowserAttachCdpTarget(args);
3848
+ }
3849
+ async handleBrowserDetachCdpTarget(args) {
3850
+ return this.targetControl.handleBrowserDetachCdpTarget(args);
3851
+ }
3852
+ async handleBrowserEvaluateCdpTarget(args) {
3853
+ return this.targetEvaluation.handleBrowserEvaluateCdpTarget(args);
3854
+ }
3855
+ async handleBrowserAttach(args) {
3856
+ if (this.activeDriver === "camoufox" && this.camoufoxManager) await this.closeCamoufox();
3857
+ this.activeDriver = "chrome";
3858
+ return this.browserControl.handleBrowserAttach(args);
3859
+ }
3860
+ async handleCamoufoxServerDispatch(args) {
3861
+ switch (String(args["action"] ?? "")) {
3862
+ case "close": return this.camoufoxBrowser.handleCamoufoxServerClose(args);
3863
+ case "status": return this.camoufoxBrowser.handleCamoufoxServerStatus(args);
3864
+ default: return this.camoufoxBrowser.handleCamoufoxServerLaunch(args);
3865
+ }
3866
+ }
3867
+ async handleCamoufoxServerLaunch(args) {
3868
+ return this.camoufoxBrowser.handleCamoufoxServerLaunch(args);
3869
+ }
3870
+ async handleCamoufoxServerClose(args) {
3871
+ return this.camoufoxBrowser.handleCamoufoxServerClose(args);
3872
+ }
3873
+ async handleCamoufoxServerStatus(args) {
3874
+ return this.camoufoxBrowser.handleCamoufoxServerStatus(args);
3875
+ }
3876
+ async handlePageNavigate(args) {
3877
+ if (this.activeDriver === "camoufox") return this.handleCamoufoxNavigate(args);
3878
+ return this.pageNavigation.handlePageNavigate(args);
3879
+ }
3880
+ async handlePageReload(args) {
3881
+ return this.pageNavigation.handlePageReload(args);
3882
+ }
3883
+ async handlePageBack(args) {
3884
+ return this.pageNavigation.handlePageBack(args);
3885
+ }
3886
+ async handlePageForward(args) {
3887
+ return this.pageNavigation.handlePageForward(args);
3888
+ }
3889
+ async handlePageClick(args) {
3890
+ return this.pageInteraction.handlePageClick(args);
3891
+ }
3892
+ async handlePageType(args) {
3893
+ return this.pageInteraction.handlePageType(args);
3894
+ }
3895
+ async handlePageSelect(args) {
3896
+ return this.pageInteraction.handlePageSelect(args);
3897
+ }
3898
+ async handlePageHover(args) {
3899
+ return this.pageInteraction.handlePageHover(args);
3900
+ }
3901
+ async handlePageScroll(args) {
3902
+ return this.pageInteraction.handlePageScroll(args);
3903
+ }
3904
+ async handlePagePressKey(args) {
3905
+ return this.pageInteraction.handlePagePressKey(args);
3906
+ }
3907
+ async handlePageEvaluate(args) {
3908
+ return this.pageEvaluation.handlePageEvaluate(args);
3909
+ }
3910
+ async handlePageScreenshot(args) {
3911
+ return this.pageEvaluation.handlePageScreenshot(args);
3912
+ }
3913
+ async handlePageInjectScript(args) {
3914
+ return this.pageEvaluation.handlePageInjectScript(args);
3915
+ }
3916
+ async handlePageWaitForSelector(args) {
3917
+ return this.pageEvaluation.handlePageWaitForSelector(args);
3918
+ }
3919
+ async handlePageCookiesDispatch(args) {
3920
+ const action = String(args["action"] ?? "");
3921
+ switch (action) {
3922
+ case "get": return this.pageData.handlePageGetCookies(args);
3923
+ case "set": return this.pageData.handlePageSetCookies(args);
3924
+ case "clear": {
3925
+ const expectedCount = args["expectedCount"];
3926
+ if (typeof expectedCount !== "number" || expectedCount < 0) return {
3927
+ content: [{
3928
+ type: "text",
3929
+ text: "action=clear requires expectedCount (number). Call action=get first to obtain the current cookie count."
3930
+ }],
3931
+ isError: true
3932
+ };
3933
+ const current = await this.pageData.getPageCookieCount();
3934
+ if (current !== expectedCount) return {
3935
+ content: [{
3936
+ type: "text",
3937
+ text: `Cookie count mismatch: expected ${expectedCount} but found ${current}. Call action=get to refresh, then retry with the correct count.`
3938
+ }],
3939
+ isError: true
3940
+ };
3941
+ return this.pageData.handlePageClearCookies(args);
3942
+ }
3943
+ default: return {
3944
+ content: [{
3945
+ type: "text",
3946
+ text: `Invalid action: "${action}". Expected one of: get, set, clear`
3947
+ }],
3948
+ isError: true
3949
+ };
3950
+ }
3951
+ }
3952
+ async handlePageSetViewport(args) {
3953
+ return this.pageData.handlePageSetViewport(args);
3954
+ }
3955
+ async handlePageEmulateDevice(args) {
3956
+ return this.pageData.handlePageEmulateDevice(args);
3957
+ }
3958
+ async handlePageLocalStorageDispatch(args) {
3959
+ const action = String(args["action"] ?? "");
3960
+ switch (action) {
3961
+ case "get": return this.pageData.handlePageGetLocalStorage(args);
3962
+ case "set": return this.pageData.handlePageSetLocalStorage(args);
3963
+ default: return {
3964
+ content: [{
3965
+ type: "text",
3966
+ text: `Invalid action: "${action}". Expected one of: get, set`
3967
+ }],
3968
+ isError: true
3969
+ };
3970
+ }
3971
+ }
3972
+ async handleConsoleMonitor(args) {
3973
+ return this.consoleHandlers.handleConsoleMonitor(args);
3974
+ }
3975
+ async handleConsoleGetLogs(args) {
3976
+ return this.consoleHandlers.handleConsoleGetLogs(args);
3977
+ }
3978
+ async handleConsoleExecute(args) {
3979
+ return this.consoleHandlers.handleConsoleExecute(args);
3980
+ }
3981
+ async handleGetAllScripts(args) {
3982
+ return this.scriptManagement.handleGetAllScripts(args);
3983
+ }
3984
+ async handleGetScriptSource(args) {
3985
+ return this.scriptManagement.handleGetScriptSource(args);
3986
+ }
3987
+ async handleCaptchaDetect(args) {
3988
+ return this.captchaHandlers.handleCaptchaDetect(args);
3989
+ }
3990
+ async handleCaptchaWait(args) {
3991
+ return this.captchaHandlers.handleCaptchaWait(args);
3992
+ }
3993
+ async handleCaptchaConfig(args) {
3994
+ return this.captchaHandlers.handleCaptchaConfig(args);
3995
+ }
3996
+ async handleStealthInject(args) {
3997
+ return this.stealthInjection.handleStealthInject(args);
3998
+ }
3999
+ async handleStealthSetUserAgent(args) {
4000
+ return this.stealthInjection.handleStealthSetUserAgent(args);
4001
+ }
4002
+ async handleStealthConfigureJitter(args) {
4003
+ return this.stealthInjection.handleStealthConfigureJitter(args);
4004
+ }
4005
+ async handleStealthGenerateFingerprint(args) {
4006
+ return this.stealthInjection.handleStealthGenerateFingerprint(args);
4007
+ }
4008
+ async handleStealthVerify(args) {
4009
+ return this.stealthInjection.handleStealthVerify(args);
4010
+ }
4011
+ async handleCamoufoxGeolocation(args) {
4012
+ return this.stealthInjection.handleCamoufoxGeolocation(args);
4013
+ }
4014
+ async handleFrameworkStateExtract(args) {
4015
+ return this.frameworkState.handleFrameworkStateExtract(args);
4016
+ }
4017
+ async handleIndexedDBDump(args) {
4018
+ return this.indexedDBDump.handleIndexedDBDump(args);
4019
+ }
4020
+ async handleJSHeapSearch(args) {
4021
+ return this.jsHeapSearch.handleJSHeapSearch(args);
4022
+ }
4023
+ async handleTabWorkflow(args) {
4024
+ return this.tabWorkflow.handleTabWorkflow(args);
4025
+ }
4026
+ async handleGetDetailedData(args) {
4027
+ return this.detailedData.handleGetDetailedData(args);
4028
+ }
4029
+ async handleCamoufoxLaunch(args) {
4030
+ return handleCamoufoxLaunchFlow({
4031
+ setCamoufoxManager: (manager) => {
4032
+ this.camoufoxManager = manager;
4033
+ },
4034
+ setActiveDriver: (driver) => {
4035
+ this.activeDriver = driver;
4036
+ },
4037
+ clearCamoufoxPage: () => {
4038
+ this.camoufoxPage = null;
4039
+ }
4040
+ }, args);
4041
+ }
4042
+ async handleCamoufoxNavigate(args) {
4043
+ return handleCamoufoxNavigateFlow({
4044
+ getCamoufoxPage: () => this.getCamoufoxPage(),
4045
+ setConsoleMonitorPage: (page) => {
4046
+ this.consoleMonitor.setPlaywrightPage(page);
4047
+ }
4048
+ }, args);
4049
+ }
4050
+ async handleHumanMouse(args) {
4051
+ return handleHumanMouse(args, this.collector);
4052
+ }
4053
+ async handleHumanScroll(args) {
4054
+ return handleHumanScroll(args, this.collector);
4055
+ }
4056
+ async handleHumanTyping(args) {
4057
+ return handleHumanTyping(args, this.collector);
4058
+ }
4059
+ async handleCaptchaVisionSolve(args) {
4060
+ return handleCaptchaVisionSolve(args, this.collector);
4061
+ }
4062
+ async handleWidgetChallengeSolve(args) {
4063
+ return handleWidgetChallengeSolve(args, this.collector);
4064
+ }
4065
+ async handleJsdomParse(args) {
4066
+ return this.jsdomHandlers.handleJsdomParse(args);
4067
+ }
4068
+ async handleJsdomQuery(args) {
4069
+ return this.jsdomHandlers.handleJsdomQuery(args);
4070
+ }
4071
+ async handleJsdomExecute(args) {
4072
+ return this.jsdomHandlers.handleJsdomExecute(args);
4073
+ }
4074
+ async handleJsdomSerialize(args) {
4075
+ return this.jsdomHandlers.handleJsdomSerialize(args);
4076
+ }
4077
+ async handleJsdomCookies(args) {
4078
+ return this.jsdomHandlers.handleJsdomCookies(args);
4079
+ }
4080
+ };
4081
+ //#endregion
4082
+ export { BrowserToolHandlers };