agent-web-interface 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/src/browser/ensure-browser.d.ts +39 -0
  4. package/dist/src/browser/ensure-browser.d.ts.map +1 -0
  5. package/dist/src/browser/ensure-browser.js +65 -0
  6. package/dist/src/browser/ensure-browser.js.map +1 -0
  7. package/dist/src/browser/index.d.ts +8 -0
  8. package/dist/src/browser/index.d.ts.map +1 -0
  9. package/dist/src/browser/index.js +8 -0
  10. package/dist/src/browser/index.js.map +1 -0
  11. package/dist/src/browser/page-network-tracker.d.ts +96 -0
  12. package/dist/src/browser/page-network-tracker.d.ts.map +1 -0
  13. package/dist/src/browser/page-network-tracker.js +235 -0
  14. package/dist/src/browser/page-network-tracker.js.map +1 -0
  15. package/dist/src/browser/page-registry.d.ts +137 -0
  16. package/dist/src/browser/page-registry.d.ts.map +1 -0
  17. package/dist/src/browser/page-registry.js +194 -0
  18. package/dist/src/browser/page-registry.js.map +1 -0
  19. package/dist/src/browser/page-stabilization.d.ts +35 -0
  20. package/dist/src/browser/page-stabilization.d.ts.map +1 -0
  21. package/dist/src/browser/page-stabilization.js +42 -0
  22. package/dist/src/browser/page-stabilization.js.map +1 -0
  23. package/dist/src/browser/session-manager.d.ts +336 -0
  24. package/dist/src/browser/session-manager.d.ts.map +1 -0
  25. package/dist/src/browser/session-manager.js +964 -0
  26. package/dist/src/browser/session-manager.js.map +1 -0
  27. package/dist/src/cdp/cdp-client.interface.d.ts +193 -0
  28. package/dist/src/cdp/cdp-client.interface.d.ts.map +1 -0
  29. package/dist/src/cdp/cdp-client.interface.js +9 -0
  30. package/dist/src/cdp/cdp-client.interface.js.map +1 -0
  31. package/dist/src/cdp/index.d.ts +9 -0
  32. package/dist/src/cdp/index.d.ts.map +1 -0
  33. package/dist/src/cdp/index.js +8 -0
  34. package/dist/src/cdp/index.js.map +1 -0
  35. package/dist/src/cdp/puppeteer-cdp-client.d.ts +97 -0
  36. package/dist/src/cdp/puppeteer-cdp-client.d.ts.map +1 -0
  37. package/dist/src/cdp/puppeteer-cdp-client.js +273 -0
  38. package/dist/src/cdp/puppeteer-cdp-client.js.map +1 -0
  39. package/dist/src/cli/args.d.ts +35 -0
  40. package/dist/src/cli/args.d.ts.map +1 -0
  41. package/dist/src/cli/args.js +76 -0
  42. package/dist/src/cli/args.js.map +1 -0
  43. package/dist/src/delta/dom-stabilizer.d.ts +46 -0
  44. package/dist/src/delta/dom-stabilizer.d.ts.map +1 -0
  45. package/dist/src/delta/dom-stabilizer.js +121 -0
  46. package/dist/src/delta/dom-stabilizer.js.map +1 -0
  47. package/dist/src/delta/index.d.ts +8 -0
  48. package/dist/src/delta/index.d.ts.map +1 -0
  49. package/dist/src/delta/index.js +7 -0
  50. package/dist/src/delta/index.js.map +1 -0
  51. package/dist/src/factpack/action-selector.d.ts +36 -0
  52. package/dist/src/factpack/action-selector.d.ts.map +1 -0
  53. package/dist/src/factpack/action-selector.js +367 -0
  54. package/dist/src/factpack/action-selector.js.map +1 -0
  55. package/dist/src/factpack/dialog-detector.d.ts +19 -0
  56. package/dist/src/factpack/dialog-detector.d.ts.map +1 -0
  57. package/dist/src/factpack/dialog-detector.js +354 -0
  58. package/dist/src/factpack/dialog-detector.js.map +1 -0
  59. package/dist/src/factpack/form-detector.d.ts +28 -0
  60. package/dist/src/factpack/form-detector.d.ts.map +1 -0
  61. package/dist/src/factpack/form-detector.js +555 -0
  62. package/dist/src/factpack/form-detector.js.map +1 -0
  63. package/dist/src/factpack/index.d.ts +32 -0
  64. package/dist/src/factpack/index.d.ts.map +1 -0
  65. package/dist/src/factpack/index.js +73 -0
  66. package/dist/src/factpack/index.js.map +1 -0
  67. package/dist/src/factpack/page-classifier.d.ts +22 -0
  68. package/dist/src/factpack/page-classifier.d.ts.map +1 -0
  69. package/dist/src/factpack/page-classifier.js +526 -0
  70. package/dist/src/factpack/page-classifier.js.map +1 -0
  71. package/dist/src/factpack/types.d.ts +307 -0
  72. package/dist/src/factpack/types.d.ts.map +1 -0
  73. package/dist/src/factpack/types.js +12 -0
  74. package/dist/src/factpack/types.js.map +1 -0
  75. package/dist/src/form/dependency-tracker.d.ts +108 -0
  76. package/dist/src/form/dependency-tracker.d.ts.map +1 -0
  77. package/dist/src/form/dependency-tracker.js +298 -0
  78. package/dist/src/form/dependency-tracker.js.map +1 -0
  79. package/dist/src/form/field-extractor.d.ts +32 -0
  80. package/dist/src/form/field-extractor.d.ts.map +1 -0
  81. package/dist/src/form/field-extractor.js +544 -0
  82. package/dist/src/form/field-extractor.js.map +1 -0
  83. package/dist/src/form/form-detector.d.ts +103 -0
  84. package/dist/src/form/form-detector.d.ts.map +1 -0
  85. package/dist/src/form/form-detector.js +704 -0
  86. package/dist/src/form/form-detector.js.map +1 -0
  87. package/dist/src/form/form-state.d.ts +23 -0
  88. package/dist/src/form/form-state.d.ts.map +1 -0
  89. package/dist/src/form/form-state.js +39 -0
  90. package/dist/src/form/form-state.js.map +1 -0
  91. package/dist/src/form/index.d.ts +23 -0
  92. package/dist/src/form/index.d.ts.map +1 -0
  93. package/dist/src/form/index.js +27 -0
  94. package/dist/src/form/index.js.map +1 -0
  95. package/dist/src/form/runtime-value-reader.d.ts +72 -0
  96. package/dist/src/form/runtime-value-reader.d.ts.map +1 -0
  97. package/dist/src/form/runtime-value-reader.js +232 -0
  98. package/dist/src/form/runtime-value-reader.js.map +1 -0
  99. package/dist/src/form/types.d.ts +384 -0
  100. package/dist/src/form/types.d.ts.map +1 -0
  101. package/dist/src/form/types.js +17 -0
  102. package/dist/src/form/types.js.map +1 -0
  103. package/dist/src/index.d.ts +8 -0
  104. package/dist/src/index.d.ts.map +1 -0
  105. package/dist/src/index.js +212 -0
  106. package/dist/src/index.js.map +1 -0
  107. package/dist/src/lib/constants.d.ts +27 -0
  108. package/dist/src/lib/constants.d.ts.map +1 -0
  109. package/dist/src/lib/constants.js +63 -0
  110. package/dist/src/lib/constants.js.map +1 -0
  111. package/dist/src/lib/index.d.ts +12 -0
  112. package/dist/src/lib/index.d.ts.map +1 -0
  113. package/dist/src/lib/index.js +17 -0
  114. package/dist/src/lib/index.js.map +1 -0
  115. package/dist/src/lib/regions.d.ts +29 -0
  116. package/dist/src/lib/regions.d.ts.map +1 -0
  117. package/dist/src/lib/regions.js +93 -0
  118. package/dist/src/lib/regions.js.map +1 -0
  119. package/dist/src/lib/scoring.d.ts +47 -0
  120. package/dist/src/lib/scoring.d.ts.map +1 -0
  121. package/dist/src/lib/scoring.js +79 -0
  122. package/dist/src/lib/scoring.js.map +1 -0
  123. package/dist/src/lib/selectors.d.ts +42 -0
  124. package/dist/src/lib/selectors.d.ts.map +1 -0
  125. package/dist/src/lib/selectors.js +138 -0
  126. package/dist/src/lib/selectors.js.map +1 -0
  127. package/dist/src/lib/text-utils.d.ts +155 -0
  128. package/dist/src/lib/text-utils.d.ts.map +1 -0
  129. package/dist/src/lib/text-utils.js +391 -0
  130. package/dist/src/lib/text-utils.js.map +1 -0
  131. package/dist/src/observation/eid-linker.d.ts +104 -0
  132. package/dist/src/observation/eid-linker.d.ts.map +1 -0
  133. package/dist/src/observation/eid-linker.js +403 -0
  134. package/dist/src/observation/eid-linker.js.map +1 -0
  135. package/dist/src/observation/index.d.ts +12 -0
  136. package/dist/src/observation/index.d.ts.map +1 -0
  137. package/dist/src/observation/index.js +15 -0
  138. package/dist/src/observation/index.js.map +1 -0
  139. package/dist/src/observation/observation-accumulator.d.ts +58 -0
  140. package/dist/src/observation/observation-accumulator.d.ts.map +1 -0
  141. package/dist/src/observation/observation-accumulator.js +213 -0
  142. package/dist/src/observation/observation-accumulator.js.map +1 -0
  143. package/dist/src/observation/observation.types.d.ts +139 -0
  144. package/dist/src/observation/observation.types.d.ts.map +1 -0
  145. package/dist/src/observation/observation.types.js +59 -0
  146. package/dist/src/observation/observation.types.js.map +1 -0
  147. package/dist/src/observation/observer-script.d.ts +19 -0
  148. package/dist/src/observation/observer-script.d.ts.map +1 -0
  149. package/dist/src/observation/observer-script.js +569 -0
  150. package/dist/src/observation/observer-script.js.map +1 -0
  151. package/dist/src/query/index.d.ts +9 -0
  152. package/dist/src/query/index.d.ts.map +1 -0
  153. package/dist/src/query/index.js +10 -0
  154. package/dist/src/query/index.js.map +1 -0
  155. package/dist/src/query/query-engine.d.ts +111 -0
  156. package/dist/src/query/query-engine.d.ts.map +1 -0
  157. package/dist/src/query/query-engine.js +509 -0
  158. package/dist/src/query/query-engine.js.map +1 -0
  159. package/dist/src/query/types/index.d.ts +5 -0
  160. package/dist/src/query/types/index.d.ts.map +1 -0
  161. package/dist/src/query/types/index.js +5 -0
  162. package/dist/src/query/types/index.js.map +1 -0
  163. package/dist/src/query/types/query.types.d.ts +141 -0
  164. package/dist/src/query/types/query.types.d.ts.map +1 -0
  165. package/dist/src/query/types/query.types.js +19 -0
  166. package/dist/src/query/types/query.types.js.map +1 -0
  167. package/dist/src/renderer/budget-manager.d.ts +46 -0
  168. package/dist/src/renderer/budget-manager.d.ts.map +1 -0
  169. package/dist/src/renderer/budget-manager.js +133 -0
  170. package/dist/src/renderer/budget-manager.js.map +1 -0
  171. package/dist/src/renderer/constants.d.ts +38 -0
  172. package/dist/src/renderer/constants.d.ts.map +1 -0
  173. package/dist/src/renderer/constants.js +29 -0
  174. package/dist/src/renderer/constants.js.map +1 -0
  175. package/dist/src/renderer/index.d.ts +12 -0
  176. package/dist/src/renderer/index.d.ts.map +1 -0
  177. package/dist/src/renderer/index.js +16 -0
  178. package/dist/src/renderer/index.js.map +1 -0
  179. package/dist/src/renderer/section-renderers.d.ts +42 -0
  180. package/dist/src/renderer/section-renderers.d.ts.map +1 -0
  181. package/dist/src/renderer/section-renderers.js +252 -0
  182. package/dist/src/renderer/section-renderers.js.map +1 -0
  183. package/dist/src/renderer/token-counter.d.ts +45 -0
  184. package/dist/src/renderer/token-counter.d.ts.map +1 -0
  185. package/dist/src/renderer/token-counter.js +65 -0
  186. package/dist/src/renderer/token-counter.js.map +1 -0
  187. package/dist/src/renderer/types.d.ts +71 -0
  188. package/dist/src/renderer/types.d.ts.map +1 -0
  189. package/dist/src/renderer/types.js +7 -0
  190. package/dist/src/renderer/types.js.map +1 -0
  191. package/dist/src/renderer/xml-renderer.d.ts +42 -0
  192. package/dist/src/renderer/xml-renderer.d.ts.map +1 -0
  193. package/dist/src/renderer/xml-renderer.js +103 -0
  194. package/dist/src/renderer/xml-renderer.js.map +1 -0
  195. package/dist/src/server/index.d.ts +8 -0
  196. package/dist/src/server/index.d.ts.map +1 -0
  197. package/dist/src/server/index.js +8 -0
  198. package/dist/src/server/index.js.map +1 -0
  199. package/dist/src/server/mcp-server.d.ts +59 -0
  200. package/dist/src/server/mcp-server.d.ts.map +1 -0
  201. package/dist/src/server/mcp-server.js +140 -0
  202. package/dist/src/server/mcp-server.js.map +1 -0
  203. package/dist/src/server/server-config.d.ts +41 -0
  204. package/dist/src/server/server-config.d.ts.map +1 -0
  205. package/dist/src/server/server-config.js +80 -0
  206. package/dist/src/server/server-config.js.map +1 -0
  207. package/dist/src/server/session-store.d.ts +148 -0
  208. package/dist/src/server/session-store.d.ts.map +1 -0
  209. package/dist/src/server/session-store.js +224 -0
  210. package/dist/src/server/session-store.js.map +1 -0
  211. package/dist/src/shared/errors/browser-session.error.d.ts +102 -0
  212. package/dist/src/shared/errors/browser-session.error.d.ts.map +1 -0
  213. package/dist/src/shared/errors/browser-session.error.js +153 -0
  214. package/dist/src/shared/errors/browser-session.error.js.map +1 -0
  215. package/dist/src/shared/errors/index.d.ts +5 -0
  216. package/dist/src/shared/errors/index.d.ts.map +1 -0
  217. package/dist/src/shared/errors/index.js +5 -0
  218. package/dist/src/shared/errors/index.js.map +1 -0
  219. package/dist/src/shared/services/dom-transformer.service.d.ts +71 -0
  220. package/dist/src/shared/services/dom-transformer.service.d.ts.map +1 -0
  221. package/dist/src/shared/services/dom-transformer.service.js +190 -0
  222. package/dist/src/shared/services/dom-transformer.service.js.map +1 -0
  223. package/dist/src/shared/services/index.d.ts +7 -0
  224. package/dist/src/shared/services/index.d.ts.map +1 -0
  225. package/dist/src/shared/services/index.js +7 -0
  226. package/dist/src/shared/services/index.js.map +1 -0
  227. package/dist/src/shared/services/logging.service.d.ts +154 -0
  228. package/dist/src/shared/services/logging.service.d.ts.map +1 -0
  229. package/dist/src/shared/services/logging.service.js +267 -0
  230. package/dist/src/shared/services/logging.service.js.map +1 -0
  231. package/dist/src/shared/services/selector-builder.service.d.ts +53 -0
  232. package/dist/src/shared/services/selector-builder.service.d.ts.map +1 -0
  233. package/dist/src/shared/services/selector-builder.service.js +240 -0
  234. package/dist/src/shared/services/selector-builder.service.js.map +1 -0
  235. package/dist/src/shared/types/base.types.d.ts +45 -0
  236. package/dist/src/shared/types/base.types.d.ts.map +1 -0
  237. package/dist/src/shared/types/base.types.js +8 -0
  238. package/dist/src/shared/types/base.types.js.map +1 -0
  239. package/dist/src/shared/types/index.d.ts +5 -0
  240. package/dist/src/shared/types/index.d.ts.map +1 -0
  241. package/dist/src/shared/types/index.js +5 -0
  242. package/dist/src/shared/types/index.js.map +1 -0
  243. package/dist/src/snapshot/element-resolver.d.ts +102 -0
  244. package/dist/src/snapshot/element-resolver.d.ts.map +1 -0
  245. package/dist/src/snapshot/element-resolver.js +379 -0
  246. package/dist/src/snapshot/element-resolver.js.map +1 -0
  247. package/dist/src/snapshot/extractors/attribute-extractor.d.ts +40 -0
  248. package/dist/src/snapshot/extractors/attribute-extractor.d.ts.map +1 -0
  249. package/dist/src/snapshot/extractors/attribute-extractor.js +237 -0
  250. package/dist/src/snapshot/extractors/attribute-extractor.js.map +1 -0
  251. package/dist/src/snapshot/extractors/ax-extractor.d.ts +36 -0
  252. package/dist/src/snapshot/extractors/ax-extractor.d.ts.map +1 -0
  253. package/dist/src/snapshot/extractors/ax-extractor.js +144 -0
  254. package/dist/src/snapshot/extractors/ax-extractor.js.map +1 -0
  255. package/dist/src/snapshot/extractors/dom-extractor.d.ts +21 -0
  256. package/dist/src/snapshot/extractors/dom-extractor.d.ts.map +1 -0
  257. package/dist/src/snapshot/extractors/dom-extractor.js +137 -0
  258. package/dist/src/snapshot/extractors/dom-extractor.js.map +1 -0
  259. package/dist/src/snapshot/extractors/grouping-resolver.d.ts +39 -0
  260. package/dist/src/snapshot/extractors/grouping-resolver.d.ts.map +1 -0
  261. package/dist/src/snapshot/extractors/grouping-resolver.js +260 -0
  262. package/dist/src/snapshot/extractors/grouping-resolver.js.map +1 -0
  263. package/dist/src/snapshot/extractors/index.d.ts +19 -0
  264. package/dist/src/snapshot/extractors/index.d.ts.map +1 -0
  265. package/dist/src/snapshot/extractors/index.js +27 -0
  266. package/dist/src/snapshot/extractors/index.js.map +1 -0
  267. package/dist/src/snapshot/extractors/label-resolver.d.ts +44 -0
  268. package/dist/src/snapshot/extractors/label-resolver.d.ts.map +1 -0
  269. package/dist/src/snapshot/extractors/label-resolver.js +173 -0
  270. package/dist/src/snapshot/extractors/label-resolver.js.map +1 -0
  271. package/dist/src/snapshot/extractors/layout-extractor.d.ts +52 -0
  272. package/dist/src/snapshot/extractors/layout-extractor.d.ts.map +1 -0
  273. package/dist/src/snapshot/extractors/layout-extractor.js +382 -0
  274. package/dist/src/snapshot/extractors/layout-extractor.js.map +1 -0
  275. package/dist/src/snapshot/extractors/locator-builder.d.ts +27 -0
  276. package/dist/src/snapshot/extractors/locator-builder.d.ts.map +1 -0
  277. package/dist/src/snapshot/extractors/locator-builder.js +223 -0
  278. package/dist/src/snapshot/extractors/locator-builder.js.map +1 -0
  279. package/dist/src/snapshot/extractors/region-resolver.d.ts +31 -0
  280. package/dist/src/snapshot/extractors/region-resolver.d.ts.map +1 -0
  281. package/dist/src/snapshot/extractors/region-resolver.js +168 -0
  282. package/dist/src/snapshot/extractors/region-resolver.js.map +1 -0
  283. package/dist/src/snapshot/extractors/state-extractor.d.ts +30 -0
  284. package/dist/src/snapshot/extractors/state-extractor.d.ts.map +1 -0
  285. package/dist/src/snapshot/extractors/state-extractor.js +181 -0
  286. package/dist/src/snapshot/extractors/state-extractor.js.map +1 -0
  287. package/dist/src/snapshot/extractors/types.d.ts +213 -0
  288. package/dist/src/snapshot/extractors/types.d.ts.map +1 -0
  289. package/dist/src/snapshot/extractors/types.js +145 -0
  290. package/dist/src/snapshot/extractors/types.js.map +1 -0
  291. package/dist/src/snapshot/index.d.ts +14 -0
  292. package/dist/src/snapshot/index.d.ts.map +1 -0
  293. package/dist/src/snapshot/index.js +18 -0
  294. package/dist/src/snapshot/index.js.map +1 -0
  295. package/dist/src/snapshot/snapshot-compiler.d.ts +73 -0
  296. package/dist/src/snapshot/snapshot-compiler.d.ts.map +1 -0
  297. package/dist/src/snapshot/snapshot-compiler.js +763 -0
  298. package/dist/src/snapshot/snapshot-compiler.js.map +1 -0
  299. package/dist/src/snapshot/snapshot-health.d.ts +97 -0
  300. package/dist/src/snapshot/snapshot-health.d.ts.map +1 -0
  301. package/dist/src/snapshot/snapshot-health.js +214 -0
  302. package/dist/src/snapshot/snapshot-health.js.map +1 -0
  303. package/dist/src/snapshot/snapshot-store.d.ts +137 -0
  304. package/dist/src/snapshot/snapshot-store.d.ts.map +1 -0
  305. package/dist/src/snapshot/snapshot-store.js +202 -0
  306. package/dist/src/snapshot/snapshot-store.js.map +1 -0
  307. package/dist/src/snapshot/snapshot.types.d.ts +250 -0
  308. package/dist/src/snapshot/snapshot.types.d.ts.map +1 -0
  309. package/dist/src/snapshot/snapshot.types.js +54 -0
  310. package/dist/src/snapshot/snapshot.types.js.map +1 -0
  311. package/dist/src/state/actionables-filter.d.ts +47 -0
  312. package/dist/src/state/actionables-filter.d.ts.map +1 -0
  313. package/dist/src/state/actionables-filter.js +173 -0
  314. package/dist/src/state/actionables-filter.js.map +1 -0
  315. package/dist/src/state/atoms-extractor.d.ts +23 -0
  316. package/dist/src/state/atoms-extractor.d.ts.map +1 -0
  317. package/dist/src/state/atoms-extractor.js +160 -0
  318. package/dist/src/state/atoms-extractor.js.map +1 -0
  319. package/dist/src/state/constants.d.ts +125 -0
  320. package/dist/src/state/constants.d.ts.map +1 -0
  321. package/dist/src/state/constants.js +131 -0
  322. package/dist/src/state/constants.js.map +1 -0
  323. package/dist/src/state/diff-engine.d.ts +23 -0
  324. package/dist/src/state/diff-engine.d.ts.map +1 -0
  325. package/dist/src/state/diff-engine.js +475 -0
  326. package/dist/src/state/diff-engine.js.map +1 -0
  327. package/dist/src/state/element-identity.d.ts +75 -0
  328. package/dist/src/state/element-identity.d.ts.map +1 -0
  329. package/dist/src/state/element-identity.js +129 -0
  330. package/dist/src/state/element-identity.js.map +1 -0
  331. package/dist/src/state/element-ref.types.d.ts +135 -0
  332. package/dist/src/state/element-ref.types.d.ts.map +1 -0
  333. package/dist/src/state/element-ref.types.js +13 -0
  334. package/dist/src/state/element-ref.types.js.map +1 -0
  335. package/dist/src/state/element-registry.d.ts +118 -0
  336. package/dist/src/state/element-registry.d.ts.map +1 -0
  337. package/dist/src/state/element-registry.js +222 -0
  338. package/dist/src/state/element-registry.js.map +1 -0
  339. package/dist/src/state/health.types.d.ts +93 -0
  340. package/dist/src/state/health.types.d.ts.map +1 -0
  341. package/dist/src/state/health.types.js +56 -0
  342. package/dist/src/state/health.types.js.map +1 -0
  343. package/dist/src/state/layer-detector.d.ts +23 -0
  344. package/dist/src/state/layer-detector.d.ts.map +1 -0
  345. package/dist/src/state/layer-detector.js +368 -0
  346. package/dist/src/state/layer-detector.js.map +1 -0
  347. package/dist/src/state/locator-generator.d.ts +21 -0
  348. package/dist/src/state/locator-generator.d.ts.map +1 -0
  349. package/dist/src/state/locator-generator.js +137 -0
  350. package/dist/src/state/locator-generator.js.map +1 -0
  351. package/dist/src/state/state-manager.d.ts +104 -0
  352. package/dist/src/state/state-manager.d.ts.map +1 -0
  353. package/dist/src/state/state-manager.js +618 -0
  354. package/dist/src/state/state-manager.js.map +1 -0
  355. package/dist/src/state/state-renderer.d.ts +63 -0
  356. package/dist/src/state/state-renderer.d.ts.map +1 -0
  357. package/dist/src/state/state-renderer.js +340 -0
  358. package/dist/src/state/state-renderer.js.map +1 -0
  359. package/dist/src/state/types.d.ts +353 -0
  360. package/dist/src/state/types.d.ts.map +1 -0
  361. package/dist/src/state/types.js +8 -0
  362. package/dist/src/state/types.js.map +1 -0
  363. package/dist/src/tools/browser-tools.d.ts +140 -0
  364. package/dist/src/tools/browser-tools.d.ts.map +1 -0
  365. package/dist/src/tools/browser-tools.js +657 -0
  366. package/dist/src/tools/browser-tools.js.map +1 -0
  367. package/dist/src/tools/errors.d.ts +63 -0
  368. package/dist/src/tools/errors.d.ts.map +1 -0
  369. package/dist/src/tools/errors.js +86 -0
  370. package/dist/src/tools/errors.js.map +1 -0
  371. package/dist/src/tools/execute-action.d.ts +135 -0
  372. package/dist/src/tools/execute-action.d.ts.map +1 -0
  373. package/dist/src/tools/execute-action.js +579 -0
  374. package/dist/src/tools/execute-action.js.map +1 -0
  375. package/dist/src/tools/form-tools.d.ts +109 -0
  376. package/dist/src/tools/form-tools.d.ts.map +1 -0
  377. package/dist/src/tools/form-tools.js +434 -0
  378. package/dist/src/tools/form-tools.js.map +1 -0
  379. package/dist/src/tools/index.d.ts +11 -0
  380. package/dist/src/tools/index.d.ts.map +1 -0
  381. package/dist/src/tools/index.js +49 -0
  382. package/dist/src/tools/index.js.map +1 -0
  383. package/dist/src/tools/response-builder.d.ts +98 -0
  384. package/dist/src/tools/response-builder.d.ts.map +1 -0
  385. package/dist/src/tools/response-builder.js +219 -0
  386. package/dist/src/tools/response-builder.js.map +1 -0
  387. package/dist/src/tools/tool-schemas.d.ts +2482 -0
  388. package/dist/src/tools/tool-schemas.d.ts.map +1 -0
  389. package/dist/src/tools/tool-schemas.js +606 -0
  390. package/dist/src/tools/tool-schemas.js.map +1 -0
  391. package/dist/vitest.config.d.ts +3 -0
  392. package/dist/vitest.config.d.ts.map +1 -0
  393. package/dist/vitest.config.js +16 -0
  394. package/dist/vitest.config.js.map +1 -0
  395. package/package.json +76 -0
@@ -0,0 +1,964 @@
1
+ /**
2
+ * Session Manager
3
+ *
4
+ * Manages Puppeteer browser lifecycle with a single shared BrowserContext.
5
+ * All pages share cookies/storage within the context.
6
+ */
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ import puppeteer, { TargetType, } from 'puppeteer-core';
11
+ import { PuppeteerCdpClient } from '../cdp/puppeteer-cdp-client.js';
12
+ import { PageRegistry } from './page-registry.js';
13
+ import { getLogger } from '../shared/services/logging.service.js';
14
+ import { BrowserSessionError } from '../shared/errors/browser-session.error.js';
15
+ import { observationAccumulator } from '../observation/index.js';
16
+ import { waitForNetworkQuiet, NAVIGATION_NETWORK_IDLE_TIMEOUT_MS } from './page-stabilization.js';
17
+ import { getOrCreateTracker, removeTracker } from './page-network-tracker.js';
18
+ /** Default user data directory for persistent browser profiles */
19
+ const DEFAULT_USER_DATA_DIR = path.join(os.homedir(), '.cache', 'agent-web-interface', 'chrome-profile');
20
+ /**
21
+ * Extract a meaningful error message from any thrown value.
22
+ * Exported for testing.
23
+ */
24
+ export function extractErrorMessage(error) {
25
+ if (error instanceof Error) {
26
+ return error.message || error.name || 'Unknown Error';
27
+ }
28
+ if (typeof error === 'string') {
29
+ return error;
30
+ }
31
+ if (error && typeof error === 'object') {
32
+ // Check common error-like properties
33
+ const obj = error;
34
+ if (typeof obj.message === 'string')
35
+ return obj.message;
36
+ if (typeof obj.error === 'string')
37
+ return obj.error;
38
+ if (typeof obj.reason === 'string')
39
+ return obj.reason;
40
+ // Try to stringify, but handle circular refs
41
+ try {
42
+ const str = JSON.stringify(error);
43
+ return str !== '{}' ? str : `Unknown error object: ${Object.keys(obj).join(', ') || 'empty'}`;
44
+ }
45
+ catch {
46
+ return `Non-serializable error: ${Object.prototype.toString.call(error)}`;
47
+ }
48
+ }
49
+ return String(error);
50
+ }
51
+ /** Default CDP port for browser automation */
52
+ const DEFAULT_CDP_PORT = 9223;
53
+ /** Default CDP host */
54
+ const DEFAULT_CDP_HOST = '127.0.0.1';
55
+ /** Default connection timeout in ms (30s to handle slow networks and remote browsers) */
56
+ const DEFAULT_CONNECTION_TIMEOUT = 30000;
57
+ /**
58
+ * Get the default Chrome user data directory for the current platform
59
+ */
60
+ function getDefaultChromeUserDataDir() {
61
+ const platform = os.platform();
62
+ const home = os.homedir();
63
+ switch (platform) {
64
+ case 'darwin':
65
+ return path.join(home, 'Library', 'Application Support', 'Google', 'Chrome');
66
+ case 'win32':
67
+ return path.join(home, 'AppData', 'Local', 'Google', 'Chrome', 'User Data');
68
+ default: // linux
69
+ return path.join(home, '.config', 'google-chrome');
70
+ }
71
+ }
72
+ /**
73
+ * Read the DevToolsActivePort file from Chrome's user data directory.
74
+ * Chrome 144+ writes this file when remote debugging is enabled via chrome://inspect/#remote-debugging
75
+ *
76
+ * @param userDataDir - Chrome user data directory
77
+ * @returns WebSocket URL for CDP connection
78
+ * @throws Error if file not found or invalid
79
+ */
80
+ async function readDevToolsActivePort(userDataDir) {
81
+ const portFilePath = path.join(userDataDir, 'DevToolsActivePort');
82
+ try {
83
+ const content = await fs.promises.readFile(portFilePath, 'utf8');
84
+ const lines = content
85
+ .split('\n')
86
+ .map((line) => line.trim())
87
+ .filter(Boolean);
88
+ if (lines.length < 2) {
89
+ throw new Error(`Invalid DevToolsActivePort content: ${content}`);
90
+ }
91
+ const [rawPort, wsPath] = lines;
92
+ const port = parseInt(rawPort, 10);
93
+ if (isNaN(port) || port < 1 || port > 65535) {
94
+ throw new Error(`Invalid port in DevToolsActivePort: ${rawPort}`);
95
+ }
96
+ return `ws://127.0.0.1:${port}${wsPath}`;
97
+ }
98
+ catch (error) {
99
+ if (error.code === 'ENOENT') {
100
+ throw new Error(`DevToolsActivePort file not found at ${portFilePath}. ` +
101
+ 'Make sure Chrome is running and remote debugging is enabled at chrome://inspect/#remote-debugging');
102
+ }
103
+ throw error;
104
+ }
105
+ }
106
+ /**
107
+ * Validates that a URL is a valid HTTP/HTTPS endpoint URL.
108
+ *
109
+ * @param urlString - URL string to validate
110
+ * @returns true if valid http(s) URL
111
+ */
112
+ function isValidHttpUrl(urlString) {
113
+ try {
114
+ const url = new URL(urlString);
115
+ return url.protocol === 'http:' || url.protocol === 'https:';
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ /**
122
+ * Validates that a URL is a valid WebSocket endpoint URL.
123
+ *
124
+ * @param urlString - URL string to validate
125
+ * @returns true if valid ws(s) URL
126
+ */
127
+ function isValidWsUrl(urlString) {
128
+ try {
129
+ const url = new URL(urlString);
130
+ return url.protocol === 'ws:' || url.protocol === 'wss:';
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ }
136
+ /**
137
+ * Manages browser lifecycle and page creation.
138
+ *
139
+ * Supports two modes:
140
+ * - launch(): Start a new browser instance
141
+ * - connect(): Connect to an existing browser via CDP
142
+ */
143
+ export class SessionManager {
144
+ browser = null;
145
+ context = null;
146
+ registry;
147
+ logger = getLogger();
148
+ isExternalBrowser = false;
149
+ /** Connection state machine */
150
+ _connectionState = 'idle';
151
+ /** State change listeners */
152
+ stateChangeListeners = new Set();
153
+ /** Browser disconnect handler reference for cleanup */
154
+ browserDisconnectHandler = null;
155
+ constructor() {
156
+ this.registry = new PageRegistry();
157
+ }
158
+ /**
159
+ * Get current connection state
160
+ */
161
+ get connectionState() {
162
+ return this._connectionState;
163
+ }
164
+ /**
165
+ * Transition to a new connection state
166
+ */
167
+ transitionTo(newState) {
168
+ const previousState = this._connectionState;
169
+ if (previousState === newState)
170
+ return;
171
+ this._connectionState = newState;
172
+ this.logger.debug('Connection state changed', { previousState, currentState: newState });
173
+ // Notify listeners
174
+ const event = {
175
+ previousState,
176
+ currentState: newState,
177
+ timestamp: new Date(),
178
+ };
179
+ for (const listener of this.stateChangeListeners) {
180
+ try {
181
+ listener(event);
182
+ }
183
+ catch (error) {
184
+ this.logger.error('State change listener error', error instanceof Error ? error : undefined);
185
+ }
186
+ }
187
+ }
188
+ /**
189
+ * Subscribe to connection state changes
190
+ */
191
+ onStateChange(listener) {
192
+ this.stateChangeListeners.add(listener);
193
+ return () => this.stateChangeListeners.delete(listener);
194
+ }
195
+ /**
196
+ * Launch a new browser with optional configuration.
197
+ *
198
+ * @param options - Browser launch options
199
+ * @throws BrowserSessionError if browser is already running or connection in progress
200
+ */
201
+ async launch(options = {}) {
202
+ if (this._connectionState !== 'idle' && this._connectionState !== 'failed') {
203
+ throw BrowserSessionError.invalidState(this._connectionState, 'launch');
204
+ }
205
+ this.transitionTo('connecting');
206
+ const { headless = true, viewport, channel = 'chrome', executablePath, isolated = false, userDataDir, args = [], pipe = true, } = options;
207
+ // Determine profile directory
208
+ let profileDir;
209
+ if (!isolated) {
210
+ profileDir = userDataDir ?? DEFAULT_USER_DATA_DIR;
211
+ await fs.promises.mkdir(profileDir, { recursive: true });
212
+ }
213
+ this.logger.info('Launching browser', {
214
+ headless,
215
+ viewport,
216
+ channel,
217
+ isolated,
218
+ hasPersistentProfile: !!profileDir,
219
+ });
220
+ let browser = null;
221
+ try {
222
+ // Build Chrome args
223
+ const chromeArgs = [
224
+ '--hide-crash-restore-bubble',
225
+ '--disable-background-timer-throttling',
226
+ '--disable-backgrounding-occluded-windows',
227
+ ...args,
228
+ ];
229
+ browser = await puppeteer.launch({
230
+ channel: executablePath ? undefined : channel,
231
+ executablePath,
232
+ headless,
233
+ userDataDir: profileDir,
234
+ defaultViewport: viewport ?? null,
235
+ pipe,
236
+ args: chromeArgs,
237
+ });
238
+ // Get the default context (first one)
239
+ this.context = browser.defaultBrowserContext();
240
+ this.browser = browser;
241
+ this.isExternalBrowser = false;
242
+ // Setup disconnect listener
243
+ this.setupBrowserListeners();
244
+ this.transitionTo('connected');
245
+ this.logger.info('Browser launched successfully');
246
+ }
247
+ catch (error) {
248
+ // Cleanup on failure - ignore close errors as browser may be in bad state
249
+ if (browser) {
250
+ await browser.close().catch(() => {
251
+ /* Intentionally empty - cleanup is best-effort */
252
+ });
253
+ }
254
+ this.transitionTo('failed');
255
+ throw BrowserSessionError.connectionFailed(error instanceof Error ? error : new Error(extractErrorMessage(error)), { operation: 'launch' });
256
+ }
257
+ }
258
+ /**
259
+ * Connect to an existing browser via CDP.
260
+ *
261
+ * Use this to connect to any Chromium browser with remote debugging enabled.
262
+ *
263
+ * @param options - Connection options (browserWSEndpoint, browserURL, or autoConnect)
264
+ * @throws BrowserSessionError if browser is already running, connection in progress, or URL is invalid
265
+ *
266
+ * @example
267
+ * ```typescript
268
+ * // Connect to browser on default port
269
+ * await session.connect();
270
+ *
271
+ * // Connect to custom endpoint (HTTP - Puppeteer discovers WebSocket)
272
+ * await session.connect({ browserURL: 'http://localhost:9222' });
273
+ *
274
+ * // Connect via WebSocket directly
275
+ * await session.connect({ browserWSEndpoint: 'ws://localhost:9222/devtools/browser/...' });
276
+ *
277
+ * // Auto-connect to Chrome 144+ with UI-based remote debugging
278
+ * await session.connect({ autoConnect: true });
279
+ * ```
280
+ */
281
+ async connect(options = {}) {
282
+ if (this._connectionState !== 'idle' && this._connectionState !== 'failed') {
283
+ throw BrowserSessionError.invalidState(this._connectionState, 'connect');
284
+ }
285
+ const timeout = options.timeout ?? DEFAULT_CONNECTION_TIMEOUT;
286
+ let connectOptions;
287
+ let endpointForLogging;
288
+ // Determine connection method
289
+ if (options.autoConnect) {
290
+ // Chrome 144+ auto-connect via DevToolsActivePort
291
+ const userDataDir = options.userDataDir ?? getDefaultChromeUserDataDir();
292
+ try {
293
+ const wsEndpoint = await readDevToolsActivePort(userDataDir);
294
+ connectOptions = { browserWSEndpoint: wsEndpoint };
295
+ endpointForLogging = wsEndpoint;
296
+ this.logger.info('Auto-connect: found DevToolsActivePort', { userDataDir, wsEndpoint });
297
+ }
298
+ catch (error) {
299
+ throw BrowserSessionError.connectionFailed(error instanceof Error ? error : new Error(extractErrorMessage(error)), { operation: 'autoConnect', userDataDir });
300
+ }
301
+ }
302
+ else if (options.browserWSEndpoint) {
303
+ // Direct WebSocket connection
304
+ if (!isValidWsUrl(options.browserWSEndpoint)) {
305
+ throw BrowserSessionError.invalidUrl(options.browserWSEndpoint);
306
+ }
307
+ connectOptions = { browserWSEndpoint: options.browserWSEndpoint };
308
+ endpointForLogging = options.browserWSEndpoint;
309
+ }
310
+ else if (options.browserURL) {
311
+ // HTTP endpoint - Puppeteer discovers WebSocket
312
+ if (!isValidHttpUrl(options.browserURL)) {
313
+ throw BrowserSessionError.invalidUrl(options.browserURL);
314
+ }
315
+ connectOptions = { browserURL: options.browserURL };
316
+ endpointForLogging = options.browserURL;
317
+ }
318
+ else if (options.endpointUrl) {
319
+ // Legacy endpointUrl support - convert to appropriate option
320
+ if (isValidWsUrl(options.endpointUrl)) {
321
+ connectOptions = { browserWSEndpoint: options.endpointUrl };
322
+ }
323
+ else if (isValidHttpUrl(options.endpointUrl)) {
324
+ connectOptions = { browserURL: options.endpointUrl };
325
+ }
326
+ else {
327
+ throw BrowserSessionError.invalidUrl(options.endpointUrl);
328
+ }
329
+ endpointForLogging = options.endpointUrl;
330
+ }
331
+ else {
332
+ // Default: construct HTTP URL from host/port
333
+ const host = options.host ?? process.env.CEF_BRIDGE_HOST ?? DEFAULT_CDP_HOST;
334
+ const port = options.port ?? Number(process.env.CEF_BRIDGE_PORT ?? DEFAULT_CDP_PORT);
335
+ const browserURL = `http://${host}:${port}`;
336
+ connectOptions = { browserURL };
337
+ endpointForLogging = browserURL;
338
+ }
339
+ this.transitionTo('connecting');
340
+ this.logger.info('Connecting to browser via CDP', { endpoint: endpointForLogging, timeout });
341
+ let browser = null;
342
+ let timeoutId;
343
+ try {
344
+ // Connect with timeout
345
+ // targetFilter excludes chrome extension targets (service workers, background
346
+ // pages, extension tabs) that cause Puppeteer's ChromeTargetManager to hang
347
+ // during initialization. Chrome 144's UI-based remote debugging exposes
348
+ // extension targets in non-default browser contexts; Puppeteer's
349
+ // Target.setAutoAttach fails for those sessions (-32001), leaving them stuck
350
+ // in #targetIdsForInit so connect() never resolves.
351
+ // See: https://github.com/puppeteer/puppeteer/issues/11627
352
+ const connectionPromise = puppeteer.connect({
353
+ ...connectOptions,
354
+ defaultViewport: null,
355
+ targetFilter: (target) => {
356
+ if (target.url().startsWith('chrome-extension://'))
357
+ return false;
358
+ if (target.type() === TargetType.SERVICE_WORKER)
359
+ return false;
360
+ if (target.type() === TargetType.BACKGROUND_PAGE)
361
+ return false;
362
+ return true;
363
+ },
364
+ });
365
+ const timeoutPromise = new Promise((_, reject) => {
366
+ timeoutId = setTimeout(() => {
367
+ reject(BrowserSessionError.connectionTimeout(endpointForLogging, timeout));
368
+ }, timeout);
369
+ });
370
+ browser = await Promise.race([connectionPromise, timeoutPromise]);
371
+ // Get the default context (existing browser's context)
372
+ const contexts = browser.browserContexts();
373
+ if (contexts.length > 0) {
374
+ this.context = contexts[0];
375
+ }
376
+ else {
377
+ // If no context exists, use default (shouldn't normally happen)
378
+ this.context = browser.defaultBrowserContext();
379
+ }
380
+ this.browser = browser;
381
+ this.isExternalBrowser = true;
382
+ // Setup disconnect listener
383
+ this.setupBrowserListeners();
384
+ // Get page count for logging
385
+ const pages = await this.context.pages();
386
+ this.transitionTo('connected');
387
+ this.logger.info('Connected to browser successfully', {
388
+ contexts: contexts.length,
389
+ pages: pages.length,
390
+ });
391
+ }
392
+ catch (error) {
393
+ // Cleanup on failure - for external browsers, disconnect instead of close
394
+ if (browser) {
395
+ await browser.disconnect().catch(() => {
396
+ /* Intentionally empty - cleanup is best-effort */
397
+ });
398
+ }
399
+ this.transitionTo('failed');
400
+ this.logger.error('Failed to connect', error instanceof Error ? error : undefined, {
401
+ endpoint: endpointForLogging,
402
+ });
403
+ // Re-throw BrowserSessionError as-is, wrap others
404
+ if (BrowserSessionError.isBrowserSessionError(error)) {
405
+ throw error;
406
+ }
407
+ throw BrowserSessionError.connectionFailed(error instanceof Error ? error : new Error(extractErrorMessage(error)), { endpointUrl: endpointForLogging });
408
+ }
409
+ finally {
410
+ if (timeoutId) {
411
+ clearTimeout(timeoutId);
412
+ }
413
+ }
414
+ }
415
+ /**
416
+ * Get the number of pages in the browser context.
417
+ *
418
+ * @returns Number of pages, or 0 if browser not running
419
+ */
420
+ async getPageCount() {
421
+ if (!this.context) {
422
+ return 0;
423
+ }
424
+ const pages = await this.context.pages();
425
+ return pages.length;
426
+ }
427
+ /**
428
+ * Adopt an existing page from the connected browser.
429
+ *
430
+ * When connecting to an external browser, use this to
431
+ * register existing pages instead of creating new ones.
432
+ *
433
+ * This method is idempotent - calling it twice on the same page
434
+ * returns the existing handle without creating a new CDP session.
435
+ *
436
+ * @param index - Page index (default: 0 for first/active page)
437
+ * @returns PageHandle for the adopted page
438
+ * @throws Error if browser not connected or page index invalid
439
+ */
440
+ async adoptPage(index = 0) {
441
+ if (!this.context) {
442
+ throw new Error('Browser not running');
443
+ }
444
+ const pages = await this.context.pages();
445
+ if (index < 0 || index >= pages.length) {
446
+ throw new Error(`Invalid page index: ${index}. Browser has ${pages.length} pages.`);
447
+ }
448
+ const page = pages[index];
449
+ // Check if already adopted (idempotent behavior)
450
+ const existing = this.registry.findByPage(page);
451
+ if (existing) {
452
+ this.logger.debug('Page already adopted', { page_id: existing.page_id });
453
+ return existing;
454
+ }
455
+ const cdpSession = await page.createCDPSession();
456
+ const cdpClient = new PuppeteerCdpClient(cdpSession);
457
+ const handle = this.registry.register(page, cdpClient);
458
+ this.registry.updateMetadata(handle.page_id, { url: page.url() });
459
+ await this.setupPageTracking(page);
460
+ this.logger.debug('Adopted page', { page_id: handle.page_id, url: page.url() });
461
+ return handle;
462
+ }
463
+ /**
464
+ * Create a new page, optionally navigating to a URL
465
+ *
466
+ * @param url - Optional URL to navigate to
467
+ * @returns PageHandle for the new page
468
+ * @throws Error if browser not running
469
+ */
470
+ async createPage(url) {
471
+ if (!this.context) {
472
+ throw new Error('Browser not running');
473
+ }
474
+ const page = await this.context.newPage();
475
+ const cdpSession = await page.createCDPSession();
476
+ const cdpClient = new PuppeteerCdpClient(cdpSession);
477
+ const handle = this.registry.register(page, cdpClient);
478
+ this.logger.debug('Created page', { page_id: handle.page_id });
479
+ if (url) {
480
+ await page.goto(url, { waitUntil: 'domcontentloaded' });
481
+ this.registry.updateMetadata(handle.page_id, { url: page.url() });
482
+ }
483
+ await this.setupPageTracking(page);
484
+ return handle;
485
+ }
486
+ /**
487
+ * Get a page handle by its ID
488
+ *
489
+ * @param page_id - The page identifier
490
+ * @returns PageHandle if found, undefined otherwise
491
+ */
492
+ getPage(page_id) {
493
+ return this.registry.get(page_id);
494
+ }
495
+ /**
496
+ * Touch a page to mark it as most recently used.
497
+ *
498
+ * Call this on page access to update MRU tracking.
499
+ *
500
+ * @param page_id - The page identifier
501
+ */
502
+ touchPage(page_id) {
503
+ this.registry.touch(page_id);
504
+ }
505
+ /**
506
+ * Resolve page_id to a PageHandle.
507
+ *
508
+ * If page_id is provided, returns the specified page.
509
+ * If page_id is omitted, returns the most recently used page.
510
+ * Does NOT auto-create pages.
511
+ *
512
+ * @param page_id - Optional page identifier
513
+ * @returns PageHandle if found, undefined otherwise
514
+ */
515
+ resolvePage(page_id) {
516
+ if (page_id) {
517
+ return this.getPage(page_id);
518
+ }
519
+ return this.registry.getMostRecent();
520
+ }
521
+ /**
522
+ * Resolve page_id to a PageHandle, creating a new page if needed.
523
+ *
524
+ * If page_id is provided, returns the specified page (throws if not found).
525
+ * If page_id is omitted, returns the most recently used page or creates one.
526
+ *
527
+ * @param page_id - Optional page identifier
528
+ * @returns PageHandle for the resolved or created page
529
+ * @throws Error if page_id is provided but not found, or if browser not running
530
+ */
531
+ async resolvePageOrCreate(page_id) {
532
+ if (page_id) {
533
+ const handle = this.getPage(page_id);
534
+ if (!handle) {
535
+ throw new Error(`Page not found: ${page_id}`);
536
+ }
537
+ return handle;
538
+ }
539
+ return this.registry.getMostRecent() ?? (await this.createPage());
540
+ }
541
+ /**
542
+ * Close a page and its CDP session
543
+ *
544
+ * @param page_id - The page identifier
545
+ * @returns true if page was closed, false if not found
546
+ */
547
+ async closePage(page_id) {
548
+ const handle = this.registry.get(page_id);
549
+ if (!handle) {
550
+ return false;
551
+ }
552
+ // Cleanup network tracker before closing
553
+ removeTracker(handle.page);
554
+ try {
555
+ // Close CDP session first
556
+ await handle.cdp.close();
557
+ }
558
+ catch (error) {
559
+ this.logger.debug('Error closing CDP session', {
560
+ page_id,
561
+ error: error instanceof Error ? error.message : String(error),
562
+ });
563
+ }
564
+ try {
565
+ // Close the page
566
+ await handle.page.close();
567
+ }
568
+ catch (error) {
569
+ this.logger.debug('Error closing page', {
570
+ page_id,
571
+ error: error instanceof Error ? error.message : String(error),
572
+ });
573
+ }
574
+ // Remove from registry
575
+ this.registry.remove(page_id);
576
+ this.logger.debug('Closed page', { page_id });
577
+ return true;
578
+ }
579
+ /**
580
+ * Navigate a page to a URL
581
+ *
582
+ * Waits for both DOM ready and network idle to ensure the page is fully loaded.
583
+ * Network idle timeout is generous (5s) but never throws - pages with persistent
584
+ * connections (websockets, long-polling, analytics) may never reach idle.
585
+ *
586
+ * @param page_id - The page identifier
587
+ * @param url - URL to navigate to
588
+ * @throws Error if page not found or navigation fails
589
+ */
590
+ async navigateTo(page_id, url) {
591
+ const handle = this.registry.get(page_id);
592
+ if (!handle) {
593
+ throw new Error('Page not found');
594
+ }
595
+ try {
596
+ // Wait for DOM ready first (fast baseline)
597
+ await handle.page.goto(url, { waitUntil: 'domcontentloaded' });
598
+ // Mark navigation on tracker (bumps generation to ignore stale events)
599
+ const tracker = getOrCreateTracker(handle.page);
600
+ tracker.markNavigation();
601
+ // Then wait for network to settle (catches API calls)
602
+ const networkIdle = await waitForNetworkQuiet(handle.page, NAVIGATION_NETWORK_IDLE_TIMEOUT_MS);
603
+ if (!networkIdle) {
604
+ this.logger.debug('Network did not reach idle state', { page_id, url });
605
+ }
606
+ this.registry.updateMetadata(page_id, {
607
+ url: handle.page.url(),
608
+ });
609
+ // Re-inject observation accumulator (new document context)
610
+ await observationAccumulator.inject(handle.page);
611
+ this.logger.debug('Navigated page', { page_id, url });
612
+ }
613
+ catch (error) {
614
+ this.logger.error('Navigation failed', error instanceof Error ? error : undefined, {
615
+ page_id,
616
+ url,
617
+ });
618
+ throw error;
619
+ }
620
+ }
621
+ /**
622
+ * Shutdown the browser session.
623
+ *
624
+ * For launched browsers: closes all pages, context, and browser.
625
+ * For connected browsers: disconnects but does NOT close the browser.
626
+ */
627
+ async shutdown() {
628
+ // Check if there's anything to shut down
629
+ if (!this.browser || this._connectionState === 'disconnecting') {
630
+ return;
631
+ }
632
+ this.transitionTo('disconnecting');
633
+ this.logger.info('Shutting down browser session', {
634
+ isExternalBrowser: this.isExternalBrowser,
635
+ });
636
+ // Remove browser disconnect listener to prevent duplicate handling
637
+ this.removeBrowserListeners();
638
+ // Close/detach all CDP sessions
639
+ const pages = this.registry.list();
640
+ for (const page of pages) {
641
+ try {
642
+ await page.cdp.close();
643
+ }
644
+ catch (err) {
645
+ // CDP session may already be closed
646
+ this.logger.debug('CDP session close failed during shutdown', {
647
+ page_id: page.page_id,
648
+ error: err instanceof Error ? err.message : String(err),
649
+ });
650
+ }
651
+ }
652
+ if (this.isExternalBrowser) {
653
+ // For external browser: just disconnect, don't close pages or browser
654
+ if (this.browser) {
655
+ // disconnect() is synchronous in Puppeteer
656
+ void this.browser.disconnect();
657
+ }
658
+ this.logger.info('Disconnected from external browser (not closing it)');
659
+ }
660
+ else {
661
+ // For launched browser: close everything
662
+ for (const page of pages) {
663
+ try {
664
+ await page.page.close();
665
+ }
666
+ catch (err) {
667
+ // Page may already be closed
668
+ this.logger.debug('Page close failed during shutdown', {
669
+ page_id: page.page_id,
670
+ error: err instanceof Error ? err.message : String(err),
671
+ });
672
+ }
673
+ }
674
+ // Close browser (this closes all pages and contexts)
675
+ if (this.browser) {
676
+ try {
677
+ await this.browser.close();
678
+ }
679
+ catch (err) {
680
+ // Browser may already be closed
681
+ this.logger.debug('Browser close failed during shutdown', {
682
+ error: err instanceof Error ? err.message : String(err),
683
+ });
684
+ }
685
+ }
686
+ }
687
+ this.browser = null;
688
+ this.context = null;
689
+ this.isExternalBrowser = false;
690
+ this.registry.clear();
691
+ this.transitionTo('idle');
692
+ this.logger.info('Browser session shutdown complete');
693
+ }
694
+ /**
695
+ * Check if browser is running
696
+ *
697
+ * @returns true if browser is active
698
+ */
699
+ isRunning() {
700
+ return this.browser?.connected ?? false;
701
+ }
702
+ /**
703
+ * Get connection health status.
704
+ *
705
+ * Goes beyond binary connected/not-connected to detect degraded CDP sessions:
706
+ * - 'healthy': Browser connected, all CDP sessions operational
707
+ * - 'degraded': Browser connected, but some CDP sessions dead (recoverable)
708
+ * - 'failed': Browser disconnected
709
+ *
710
+ * @returns Connection health status
711
+ */
712
+ async getConnectionHealth() {
713
+ if (this._connectionState !== 'connected' || !this.context) {
714
+ return 'failed';
715
+ }
716
+ const pages = this.registry.list();
717
+ if (pages.length === 0) {
718
+ return 'healthy';
719
+ }
720
+ const results = await Promise.all(pages.map(async (pageHandle) => {
721
+ // Check if page is closed using Puppeteer's isClosed() method
722
+ if (pageHandle.page.isClosed()) {
723
+ return false;
724
+ }
725
+ if (!pageHandle.cdp.isActive()) {
726
+ return false;
727
+ }
728
+ try {
729
+ await pageHandle.cdp.send('Page.getFrameTree', undefined);
730
+ return true;
731
+ }
732
+ catch (err) {
733
+ const message = err instanceof Error ? err.message : String(err);
734
+ this.logger.warning('CDP probe failed', { page_id: pageHandle.page_id, error: message });
735
+ return false;
736
+ }
737
+ }));
738
+ return results.every(Boolean) ? 'healthy' : 'degraded';
739
+ }
740
+ /**
741
+ * Rebind CDP session for a page.
742
+ *
743
+ * Use when CDP session is dead but page is still valid.
744
+ * This creates a new CDP session and updates the registry.
745
+ *
746
+ * @param page_id - Page ID to rebind
747
+ * @returns New PageHandle with fresh CDP session
748
+ * @throws Error if page not found, page is closed, or browser context unavailable
749
+ */
750
+ async rebindCdpSession(page_id) {
751
+ const handle = this.registry.get(page_id);
752
+ if (!handle) {
753
+ throw new Error(`Page not found: ${page_id}`);
754
+ }
755
+ // Check if page is still accessible
756
+ if (handle.page.isClosed()) {
757
+ throw new Error(`Page is closed: ${page_id}`);
758
+ }
759
+ if (!this.context) {
760
+ throw new Error('Browser context not available');
761
+ }
762
+ // Close old CDP session (best effort)
763
+ try {
764
+ await handle.cdp.close();
765
+ }
766
+ catch (err) {
767
+ // May already be closed
768
+ this.logger.debug('Old CDP session close failed during rebind', {
769
+ page_id,
770
+ error: err instanceof Error ? err.message : String(err),
771
+ });
772
+ }
773
+ // Create new CDP session (Puppeteer creates CDP from page, not context)
774
+ const cdpSession = await handle.page.createCDPSession();
775
+ const newCdp = new PuppeteerCdpClient(cdpSession);
776
+ // Update registry with new handle
777
+ const newHandle = {
778
+ ...handle,
779
+ cdp: newCdp,
780
+ };
781
+ this.registry.replace(page_id, newHandle);
782
+ this.logger.info('Rebound CDP session', { page_id });
783
+ return newHandle;
784
+ }
785
+ /**
786
+ * Save the current storage state (cookies, localStorage).
787
+ *
788
+ * Note: Puppeteer doesn't have built-in storageState like Playwright.
789
+ * This method collects cookies and localStorage manually.
790
+ *
791
+ * @param savePath - Optional file path to save state to. If not provided, returns the state object.
792
+ * @returns The storage state object
793
+ * @throws Error if browser not running
794
+ */
795
+ async saveStorageState(savePath) {
796
+ if (!this.context) {
797
+ throw new Error('Browser not running');
798
+ }
799
+ // Get cookies from all pages
800
+ const pages = await this.context.pages();
801
+ const allCookies = await Promise.all(pages.map((page) => page.cookies()));
802
+ const cookieSet = new Map();
803
+ // Deduplicate cookies by name+domain+path
804
+ for (const pageCookies of allCookies) {
805
+ for (const cookie of pageCookies) {
806
+ const key = `${cookie.name}|${cookie.domain}|${cookie.path}`;
807
+ cookieSet.set(key, {
808
+ name: cookie.name,
809
+ value: cookie.value,
810
+ domain: cookie.domain,
811
+ path: cookie.path,
812
+ expires: cookie.expires,
813
+ httpOnly: cookie.httpOnly ?? false,
814
+ secure: cookie.secure ?? false,
815
+ sameSite: (cookie.sameSite ?? undefined),
816
+ });
817
+ }
818
+ }
819
+ // Get localStorage from each origin
820
+ const originsMap = new Map();
821
+ for (const page of pages) {
822
+ try {
823
+ const url = page.url();
824
+ if (!url || url === 'about:blank')
825
+ continue;
826
+ const origin = new URL(url).origin;
827
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment */
828
+ const localStorage = await page.evaluate(() => {
829
+ const storage = globalThis.localStorage;
830
+ const items = [];
831
+ for (let i = 0; i < storage.length; i++) {
832
+ const key = storage.key(i);
833
+ if (key) {
834
+ items.push({ name: key, value: storage.getItem(key) ?? '' });
835
+ }
836
+ }
837
+ return items;
838
+ });
839
+ /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment */
840
+ originsMap.set(origin, localStorage);
841
+ }
842
+ catch (err) {
843
+ // Page may not be accessible
844
+ this.logger.debug('Failed to extract localStorage during storage state save', {
845
+ url: page.url(),
846
+ error: err instanceof Error ? err.message : String(err),
847
+ });
848
+ }
849
+ }
850
+ const state = {
851
+ cookies: Array.from(cookieSet.values()),
852
+ origins: Array.from(originsMap.entries()).map(([origin, localStorage]) => ({
853
+ origin,
854
+ localStorage,
855
+ })),
856
+ };
857
+ if (savePath) {
858
+ await fs.promises.writeFile(savePath, JSON.stringify(state, null, 2));
859
+ }
860
+ return state;
861
+ }
862
+ /**
863
+ * Sync registry with actual browser pages.
864
+ *
865
+ * Adopts any browser pages not yet registered. This ensures the registry
866
+ * reflects the true state of the browser, especially after reconnection
867
+ * or when external tabs are opened.
868
+ *
869
+ * Note: This method does NOT remove stale/closed pages from the registry.
870
+ * Failed adoptions (e.g., CDP session errors) are logged as warnings but do not throw.
871
+ * Successfully synced pages have network tracking set up.
872
+ *
873
+ * @returns Array of all PageHandle objects after sync (includes previously registered pages)
874
+ */
875
+ async syncPages() {
876
+ if (!this.context) {
877
+ return this.registry.list();
878
+ }
879
+ const browserPages = await this.context.pages();
880
+ for (const page of browserPages) {
881
+ // Skip if already registered
882
+ if (this.registry.findByPage(page)) {
883
+ continue;
884
+ }
885
+ // Skip closed pages
886
+ if (page.isClosed()) {
887
+ continue;
888
+ }
889
+ // Adopt the unregistered page
890
+ try {
891
+ const cdpSession = await page.createCDPSession();
892
+ const cdpClient = new PuppeteerCdpClient(cdpSession);
893
+ const handle = this.registry.register(page, cdpClient);
894
+ this.registry.updateMetadata(handle.page_id, { url: page.url() });
895
+ await this.setupPageTracking(page);
896
+ this.logger.debug('Synced unregistered page', { page_id: handle.page_id, url: page.url() });
897
+ }
898
+ catch (err) {
899
+ this.logger.warning('Failed to sync page', {
900
+ url: page.url(),
901
+ error: err instanceof Error ? err.message : String(err),
902
+ });
903
+ }
904
+ }
905
+ return this.registry.list();
906
+ }
907
+ /**
908
+ * List all active pages
909
+ *
910
+ * @returns Array of PageHandle objects
911
+ */
912
+ listPages() {
913
+ return this.registry.list();
914
+ }
915
+ /**
916
+ * Get the page count
917
+ *
918
+ * @returns Number of active pages
919
+ */
920
+ pageCount() {
921
+ return this.registry.size();
922
+ }
923
+ /**
924
+ * Setup browser event listeners for disconnect detection.
925
+ * Called after successful browser launch or connect.
926
+ */
927
+ setupBrowserListeners() {
928
+ if (!this.browser)
929
+ return;
930
+ // Store reference for cleanup
931
+ this.browserDisconnectHandler = () => {
932
+ // Only handle if we're in connected state (not during intentional shutdown)
933
+ if (this._connectionState === 'connected') {
934
+ this.logger.warning('Browser disconnected unexpectedly');
935
+ this.browser = null;
936
+ this.context = null;
937
+ this.registry.clear();
938
+ this.transitionTo('failed');
939
+ }
940
+ };
941
+ this.browser.on('disconnected', this.browserDisconnectHandler);
942
+ }
943
+ /**
944
+ * Remove browser event listeners.
945
+ * Called during shutdown to prevent duplicate handling.
946
+ */
947
+ removeBrowserListeners() {
948
+ if (this.browser && this.browserDisconnectHandler) {
949
+ this.browser.off('disconnected', this.browserDisconnectHandler);
950
+ this.browserDisconnectHandler = null;
951
+ }
952
+ }
953
+ /**
954
+ * Setup tracking infrastructure for a page.
955
+ * Injects observation accumulator and attaches network tracker.
956
+ */
957
+ async setupPageTracking(page) {
958
+ await observationAccumulator.inject(page);
959
+ const tracker = getOrCreateTracker(page);
960
+ tracker.attach(page);
961
+ page.on('close', () => removeTracker(page));
962
+ }
963
+ }
964
+ //# sourceMappingURL=session-manager.js.map