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.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/src/browser/ensure-browser.d.ts +39 -0
- package/dist/src/browser/ensure-browser.d.ts.map +1 -0
- package/dist/src/browser/ensure-browser.js +65 -0
- package/dist/src/browser/ensure-browser.js.map +1 -0
- package/dist/src/browser/index.d.ts +8 -0
- package/dist/src/browser/index.d.ts.map +1 -0
- package/dist/src/browser/index.js +8 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/browser/page-network-tracker.d.ts +96 -0
- package/dist/src/browser/page-network-tracker.d.ts.map +1 -0
- package/dist/src/browser/page-network-tracker.js +235 -0
- package/dist/src/browser/page-network-tracker.js.map +1 -0
- package/dist/src/browser/page-registry.d.ts +137 -0
- package/dist/src/browser/page-registry.d.ts.map +1 -0
- package/dist/src/browser/page-registry.js +194 -0
- package/dist/src/browser/page-registry.js.map +1 -0
- package/dist/src/browser/page-stabilization.d.ts +35 -0
- package/dist/src/browser/page-stabilization.d.ts.map +1 -0
- package/dist/src/browser/page-stabilization.js +42 -0
- package/dist/src/browser/page-stabilization.js.map +1 -0
- package/dist/src/browser/session-manager.d.ts +336 -0
- package/dist/src/browser/session-manager.d.ts.map +1 -0
- package/dist/src/browser/session-manager.js +964 -0
- package/dist/src/browser/session-manager.js.map +1 -0
- package/dist/src/cdp/cdp-client.interface.d.ts +193 -0
- package/dist/src/cdp/cdp-client.interface.d.ts.map +1 -0
- package/dist/src/cdp/cdp-client.interface.js +9 -0
- package/dist/src/cdp/cdp-client.interface.js.map +1 -0
- package/dist/src/cdp/index.d.ts +9 -0
- package/dist/src/cdp/index.d.ts.map +1 -0
- package/dist/src/cdp/index.js +8 -0
- package/dist/src/cdp/index.js.map +1 -0
- package/dist/src/cdp/puppeteer-cdp-client.d.ts +97 -0
- package/dist/src/cdp/puppeteer-cdp-client.d.ts.map +1 -0
- package/dist/src/cdp/puppeteer-cdp-client.js +273 -0
- package/dist/src/cdp/puppeteer-cdp-client.js.map +1 -0
- package/dist/src/cli/args.d.ts +35 -0
- package/dist/src/cli/args.d.ts.map +1 -0
- package/dist/src/cli/args.js +76 -0
- package/dist/src/cli/args.js.map +1 -0
- package/dist/src/delta/dom-stabilizer.d.ts +46 -0
- package/dist/src/delta/dom-stabilizer.d.ts.map +1 -0
- package/dist/src/delta/dom-stabilizer.js +121 -0
- package/dist/src/delta/dom-stabilizer.js.map +1 -0
- package/dist/src/delta/index.d.ts +8 -0
- package/dist/src/delta/index.d.ts.map +1 -0
- package/dist/src/delta/index.js +7 -0
- package/dist/src/delta/index.js.map +1 -0
- package/dist/src/factpack/action-selector.d.ts +36 -0
- package/dist/src/factpack/action-selector.d.ts.map +1 -0
- package/dist/src/factpack/action-selector.js +367 -0
- package/dist/src/factpack/action-selector.js.map +1 -0
- package/dist/src/factpack/dialog-detector.d.ts +19 -0
- package/dist/src/factpack/dialog-detector.d.ts.map +1 -0
- package/dist/src/factpack/dialog-detector.js +354 -0
- package/dist/src/factpack/dialog-detector.js.map +1 -0
- package/dist/src/factpack/form-detector.d.ts +28 -0
- package/dist/src/factpack/form-detector.d.ts.map +1 -0
- package/dist/src/factpack/form-detector.js +555 -0
- package/dist/src/factpack/form-detector.js.map +1 -0
- package/dist/src/factpack/index.d.ts +32 -0
- package/dist/src/factpack/index.d.ts.map +1 -0
- package/dist/src/factpack/index.js +73 -0
- package/dist/src/factpack/index.js.map +1 -0
- package/dist/src/factpack/page-classifier.d.ts +22 -0
- package/dist/src/factpack/page-classifier.d.ts.map +1 -0
- package/dist/src/factpack/page-classifier.js +526 -0
- package/dist/src/factpack/page-classifier.js.map +1 -0
- package/dist/src/factpack/types.d.ts +307 -0
- package/dist/src/factpack/types.d.ts.map +1 -0
- package/dist/src/factpack/types.js +12 -0
- package/dist/src/factpack/types.js.map +1 -0
- package/dist/src/form/dependency-tracker.d.ts +108 -0
- package/dist/src/form/dependency-tracker.d.ts.map +1 -0
- package/dist/src/form/dependency-tracker.js +298 -0
- package/dist/src/form/dependency-tracker.js.map +1 -0
- package/dist/src/form/field-extractor.d.ts +32 -0
- package/dist/src/form/field-extractor.d.ts.map +1 -0
- package/dist/src/form/field-extractor.js +544 -0
- package/dist/src/form/field-extractor.js.map +1 -0
- package/dist/src/form/form-detector.d.ts +103 -0
- package/dist/src/form/form-detector.d.ts.map +1 -0
- package/dist/src/form/form-detector.js +704 -0
- package/dist/src/form/form-detector.js.map +1 -0
- package/dist/src/form/form-state.d.ts +23 -0
- package/dist/src/form/form-state.d.ts.map +1 -0
- package/dist/src/form/form-state.js +39 -0
- package/dist/src/form/form-state.js.map +1 -0
- package/dist/src/form/index.d.ts +23 -0
- package/dist/src/form/index.d.ts.map +1 -0
- package/dist/src/form/index.js +27 -0
- package/dist/src/form/index.js.map +1 -0
- package/dist/src/form/runtime-value-reader.d.ts +72 -0
- package/dist/src/form/runtime-value-reader.d.ts.map +1 -0
- package/dist/src/form/runtime-value-reader.js +232 -0
- package/dist/src/form/runtime-value-reader.js.map +1 -0
- package/dist/src/form/types.d.ts +384 -0
- package/dist/src/form/types.d.ts.map +1 -0
- package/dist/src/form/types.js +17 -0
- package/dist/src/form/types.js.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +212 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/constants.d.ts +27 -0
- package/dist/src/lib/constants.d.ts.map +1 -0
- package/dist/src/lib/constants.js +63 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/index.d.ts +12 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js +17 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/lib/regions.d.ts +29 -0
- package/dist/src/lib/regions.d.ts.map +1 -0
- package/dist/src/lib/regions.js +93 -0
- package/dist/src/lib/regions.js.map +1 -0
- package/dist/src/lib/scoring.d.ts +47 -0
- package/dist/src/lib/scoring.d.ts.map +1 -0
- package/dist/src/lib/scoring.js +79 -0
- package/dist/src/lib/scoring.js.map +1 -0
- package/dist/src/lib/selectors.d.ts +42 -0
- package/dist/src/lib/selectors.d.ts.map +1 -0
- package/dist/src/lib/selectors.js +138 -0
- package/dist/src/lib/selectors.js.map +1 -0
- package/dist/src/lib/text-utils.d.ts +155 -0
- package/dist/src/lib/text-utils.d.ts.map +1 -0
- package/dist/src/lib/text-utils.js +391 -0
- package/dist/src/lib/text-utils.js.map +1 -0
- package/dist/src/observation/eid-linker.d.ts +104 -0
- package/dist/src/observation/eid-linker.d.ts.map +1 -0
- package/dist/src/observation/eid-linker.js +403 -0
- package/dist/src/observation/eid-linker.js.map +1 -0
- package/dist/src/observation/index.d.ts +12 -0
- package/dist/src/observation/index.d.ts.map +1 -0
- package/dist/src/observation/index.js +15 -0
- package/dist/src/observation/index.js.map +1 -0
- package/dist/src/observation/observation-accumulator.d.ts +58 -0
- package/dist/src/observation/observation-accumulator.d.ts.map +1 -0
- package/dist/src/observation/observation-accumulator.js +213 -0
- package/dist/src/observation/observation-accumulator.js.map +1 -0
- package/dist/src/observation/observation.types.d.ts +139 -0
- package/dist/src/observation/observation.types.d.ts.map +1 -0
- package/dist/src/observation/observation.types.js +59 -0
- package/dist/src/observation/observation.types.js.map +1 -0
- package/dist/src/observation/observer-script.d.ts +19 -0
- package/dist/src/observation/observer-script.d.ts.map +1 -0
- package/dist/src/observation/observer-script.js +569 -0
- package/dist/src/observation/observer-script.js.map +1 -0
- package/dist/src/query/index.d.ts +9 -0
- package/dist/src/query/index.d.ts.map +1 -0
- package/dist/src/query/index.js +10 -0
- package/dist/src/query/index.js.map +1 -0
- package/dist/src/query/query-engine.d.ts +111 -0
- package/dist/src/query/query-engine.d.ts.map +1 -0
- package/dist/src/query/query-engine.js +509 -0
- package/dist/src/query/query-engine.js.map +1 -0
- package/dist/src/query/types/index.d.ts +5 -0
- package/dist/src/query/types/index.d.ts.map +1 -0
- package/dist/src/query/types/index.js +5 -0
- package/dist/src/query/types/index.js.map +1 -0
- package/dist/src/query/types/query.types.d.ts +141 -0
- package/dist/src/query/types/query.types.d.ts.map +1 -0
- package/dist/src/query/types/query.types.js +19 -0
- package/dist/src/query/types/query.types.js.map +1 -0
- package/dist/src/renderer/budget-manager.d.ts +46 -0
- package/dist/src/renderer/budget-manager.d.ts.map +1 -0
- package/dist/src/renderer/budget-manager.js +133 -0
- package/dist/src/renderer/budget-manager.js.map +1 -0
- package/dist/src/renderer/constants.d.ts +38 -0
- package/dist/src/renderer/constants.d.ts.map +1 -0
- package/dist/src/renderer/constants.js +29 -0
- package/dist/src/renderer/constants.js.map +1 -0
- package/dist/src/renderer/index.d.ts +12 -0
- package/dist/src/renderer/index.d.ts.map +1 -0
- package/dist/src/renderer/index.js +16 -0
- package/dist/src/renderer/index.js.map +1 -0
- package/dist/src/renderer/section-renderers.d.ts +42 -0
- package/dist/src/renderer/section-renderers.d.ts.map +1 -0
- package/dist/src/renderer/section-renderers.js +252 -0
- package/dist/src/renderer/section-renderers.js.map +1 -0
- package/dist/src/renderer/token-counter.d.ts +45 -0
- package/dist/src/renderer/token-counter.d.ts.map +1 -0
- package/dist/src/renderer/token-counter.js +65 -0
- package/dist/src/renderer/token-counter.js.map +1 -0
- package/dist/src/renderer/types.d.ts +71 -0
- package/dist/src/renderer/types.d.ts.map +1 -0
- package/dist/src/renderer/types.js +7 -0
- package/dist/src/renderer/types.js.map +1 -0
- package/dist/src/renderer/xml-renderer.d.ts +42 -0
- package/dist/src/renderer/xml-renderer.d.ts.map +1 -0
- package/dist/src/renderer/xml-renderer.js +103 -0
- package/dist/src/renderer/xml-renderer.js.map +1 -0
- package/dist/src/server/index.d.ts +8 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +8 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/mcp-server.d.ts +59 -0
- package/dist/src/server/mcp-server.d.ts.map +1 -0
- package/dist/src/server/mcp-server.js +140 -0
- package/dist/src/server/mcp-server.js.map +1 -0
- package/dist/src/server/server-config.d.ts +41 -0
- package/dist/src/server/server-config.d.ts.map +1 -0
- package/dist/src/server/server-config.js +80 -0
- package/dist/src/server/server-config.js.map +1 -0
- package/dist/src/server/session-store.d.ts +148 -0
- package/dist/src/server/session-store.d.ts.map +1 -0
- package/dist/src/server/session-store.js +224 -0
- package/dist/src/server/session-store.js.map +1 -0
- package/dist/src/shared/errors/browser-session.error.d.ts +102 -0
- package/dist/src/shared/errors/browser-session.error.d.ts.map +1 -0
- package/dist/src/shared/errors/browser-session.error.js +153 -0
- package/dist/src/shared/errors/browser-session.error.js.map +1 -0
- package/dist/src/shared/errors/index.d.ts +5 -0
- package/dist/src/shared/errors/index.d.ts.map +1 -0
- package/dist/src/shared/errors/index.js +5 -0
- package/dist/src/shared/errors/index.js.map +1 -0
- package/dist/src/shared/services/dom-transformer.service.d.ts +71 -0
- package/dist/src/shared/services/dom-transformer.service.d.ts.map +1 -0
- package/dist/src/shared/services/dom-transformer.service.js +190 -0
- package/dist/src/shared/services/dom-transformer.service.js.map +1 -0
- package/dist/src/shared/services/index.d.ts +7 -0
- package/dist/src/shared/services/index.d.ts.map +1 -0
- package/dist/src/shared/services/index.js +7 -0
- package/dist/src/shared/services/index.js.map +1 -0
- package/dist/src/shared/services/logging.service.d.ts +154 -0
- package/dist/src/shared/services/logging.service.d.ts.map +1 -0
- package/dist/src/shared/services/logging.service.js +267 -0
- package/dist/src/shared/services/logging.service.js.map +1 -0
- package/dist/src/shared/services/selector-builder.service.d.ts +53 -0
- package/dist/src/shared/services/selector-builder.service.d.ts.map +1 -0
- package/dist/src/shared/services/selector-builder.service.js +240 -0
- package/dist/src/shared/services/selector-builder.service.js.map +1 -0
- package/dist/src/shared/types/base.types.d.ts +45 -0
- package/dist/src/shared/types/base.types.d.ts.map +1 -0
- package/dist/src/shared/types/base.types.js +8 -0
- package/dist/src/shared/types/base.types.js.map +1 -0
- package/dist/src/shared/types/index.d.ts +5 -0
- package/dist/src/shared/types/index.d.ts.map +1 -0
- package/dist/src/shared/types/index.js +5 -0
- package/dist/src/shared/types/index.js.map +1 -0
- package/dist/src/snapshot/element-resolver.d.ts +102 -0
- package/dist/src/snapshot/element-resolver.d.ts.map +1 -0
- package/dist/src/snapshot/element-resolver.js +379 -0
- package/dist/src/snapshot/element-resolver.js.map +1 -0
- package/dist/src/snapshot/extractors/attribute-extractor.d.ts +40 -0
- package/dist/src/snapshot/extractors/attribute-extractor.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/attribute-extractor.js +237 -0
- package/dist/src/snapshot/extractors/attribute-extractor.js.map +1 -0
- package/dist/src/snapshot/extractors/ax-extractor.d.ts +36 -0
- package/dist/src/snapshot/extractors/ax-extractor.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/ax-extractor.js +144 -0
- package/dist/src/snapshot/extractors/ax-extractor.js.map +1 -0
- package/dist/src/snapshot/extractors/dom-extractor.d.ts +21 -0
- package/dist/src/snapshot/extractors/dom-extractor.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/dom-extractor.js +137 -0
- package/dist/src/snapshot/extractors/dom-extractor.js.map +1 -0
- package/dist/src/snapshot/extractors/grouping-resolver.d.ts +39 -0
- package/dist/src/snapshot/extractors/grouping-resolver.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/grouping-resolver.js +260 -0
- package/dist/src/snapshot/extractors/grouping-resolver.js.map +1 -0
- package/dist/src/snapshot/extractors/index.d.ts +19 -0
- package/dist/src/snapshot/extractors/index.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/index.js +27 -0
- package/dist/src/snapshot/extractors/index.js.map +1 -0
- package/dist/src/snapshot/extractors/label-resolver.d.ts +44 -0
- package/dist/src/snapshot/extractors/label-resolver.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/label-resolver.js +173 -0
- package/dist/src/snapshot/extractors/label-resolver.js.map +1 -0
- package/dist/src/snapshot/extractors/layout-extractor.d.ts +52 -0
- package/dist/src/snapshot/extractors/layout-extractor.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/layout-extractor.js +382 -0
- package/dist/src/snapshot/extractors/layout-extractor.js.map +1 -0
- package/dist/src/snapshot/extractors/locator-builder.d.ts +27 -0
- package/dist/src/snapshot/extractors/locator-builder.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/locator-builder.js +223 -0
- package/dist/src/snapshot/extractors/locator-builder.js.map +1 -0
- package/dist/src/snapshot/extractors/region-resolver.d.ts +31 -0
- package/dist/src/snapshot/extractors/region-resolver.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/region-resolver.js +168 -0
- package/dist/src/snapshot/extractors/region-resolver.js.map +1 -0
- package/dist/src/snapshot/extractors/state-extractor.d.ts +30 -0
- package/dist/src/snapshot/extractors/state-extractor.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/state-extractor.js +181 -0
- package/dist/src/snapshot/extractors/state-extractor.js.map +1 -0
- package/dist/src/snapshot/extractors/types.d.ts +213 -0
- package/dist/src/snapshot/extractors/types.d.ts.map +1 -0
- package/dist/src/snapshot/extractors/types.js +145 -0
- package/dist/src/snapshot/extractors/types.js.map +1 -0
- package/dist/src/snapshot/index.d.ts +14 -0
- package/dist/src/snapshot/index.d.ts.map +1 -0
- package/dist/src/snapshot/index.js +18 -0
- package/dist/src/snapshot/index.js.map +1 -0
- package/dist/src/snapshot/snapshot-compiler.d.ts +73 -0
- package/dist/src/snapshot/snapshot-compiler.d.ts.map +1 -0
- package/dist/src/snapshot/snapshot-compiler.js +763 -0
- package/dist/src/snapshot/snapshot-compiler.js.map +1 -0
- package/dist/src/snapshot/snapshot-health.d.ts +97 -0
- package/dist/src/snapshot/snapshot-health.d.ts.map +1 -0
- package/dist/src/snapshot/snapshot-health.js +214 -0
- package/dist/src/snapshot/snapshot-health.js.map +1 -0
- package/dist/src/snapshot/snapshot-store.d.ts +137 -0
- package/dist/src/snapshot/snapshot-store.d.ts.map +1 -0
- package/dist/src/snapshot/snapshot-store.js +202 -0
- package/dist/src/snapshot/snapshot-store.js.map +1 -0
- package/dist/src/snapshot/snapshot.types.d.ts +250 -0
- package/dist/src/snapshot/snapshot.types.d.ts.map +1 -0
- package/dist/src/snapshot/snapshot.types.js +54 -0
- package/dist/src/snapshot/snapshot.types.js.map +1 -0
- package/dist/src/state/actionables-filter.d.ts +47 -0
- package/dist/src/state/actionables-filter.d.ts.map +1 -0
- package/dist/src/state/actionables-filter.js +173 -0
- package/dist/src/state/actionables-filter.js.map +1 -0
- package/dist/src/state/atoms-extractor.d.ts +23 -0
- package/dist/src/state/atoms-extractor.d.ts.map +1 -0
- package/dist/src/state/atoms-extractor.js +160 -0
- package/dist/src/state/atoms-extractor.js.map +1 -0
- package/dist/src/state/constants.d.ts +125 -0
- package/dist/src/state/constants.d.ts.map +1 -0
- package/dist/src/state/constants.js +131 -0
- package/dist/src/state/constants.js.map +1 -0
- package/dist/src/state/diff-engine.d.ts +23 -0
- package/dist/src/state/diff-engine.d.ts.map +1 -0
- package/dist/src/state/diff-engine.js +475 -0
- package/dist/src/state/diff-engine.js.map +1 -0
- package/dist/src/state/element-identity.d.ts +75 -0
- package/dist/src/state/element-identity.d.ts.map +1 -0
- package/dist/src/state/element-identity.js +129 -0
- package/dist/src/state/element-identity.js.map +1 -0
- package/dist/src/state/element-ref.types.d.ts +135 -0
- package/dist/src/state/element-ref.types.d.ts.map +1 -0
- package/dist/src/state/element-ref.types.js +13 -0
- package/dist/src/state/element-ref.types.js.map +1 -0
- package/dist/src/state/element-registry.d.ts +118 -0
- package/dist/src/state/element-registry.d.ts.map +1 -0
- package/dist/src/state/element-registry.js +222 -0
- package/dist/src/state/element-registry.js.map +1 -0
- package/dist/src/state/health.types.d.ts +93 -0
- package/dist/src/state/health.types.d.ts.map +1 -0
- package/dist/src/state/health.types.js +56 -0
- package/dist/src/state/health.types.js.map +1 -0
- package/dist/src/state/layer-detector.d.ts +23 -0
- package/dist/src/state/layer-detector.d.ts.map +1 -0
- package/dist/src/state/layer-detector.js +368 -0
- package/dist/src/state/layer-detector.js.map +1 -0
- package/dist/src/state/locator-generator.d.ts +21 -0
- package/dist/src/state/locator-generator.d.ts.map +1 -0
- package/dist/src/state/locator-generator.js +137 -0
- package/dist/src/state/locator-generator.js.map +1 -0
- package/dist/src/state/state-manager.d.ts +104 -0
- package/dist/src/state/state-manager.d.ts.map +1 -0
- package/dist/src/state/state-manager.js +618 -0
- package/dist/src/state/state-manager.js.map +1 -0
- package/dist/src/state/state-renderer.d.ts +63 -0
- package/dist/src/state/state-renderer.d.ts.map +1 -0
- package/dist/src/state/state-renderer.js +340 -0
- package/dist/src/state/state-renderer.js.map +1 -0
- package/dist/src/state/types.d.ts +353 -0
- package/dist/src/state/types.d.ts.map +1 -0
- package/dist/src/state/types.js +8 -0
- package/dist/src/state/types.js.map +1 -0
- package/dist/src/tools/browser-tools.d.ts +140 -0
- package/dist/src/tools/browser-tools.d.ts.map +1 -0
- package/dist/src/tools/browser-tools.js +657 -0
- package/dist/src/tools/browser-tools.js.map +1 -0
- package/dist/src/tools/errors.d.ts +63 -0
- package/dist/src/tools/errors.d.ts.map +1 -0
- package/dist/src/tools/errors.js +86 -0
- package/dist/src/tools/errors.js.map +1 -0
- package/dist/src/tools/execute-action.d.ts +135 -0
- package/dist/src/tools/execute-action.d.ts.map +1 -0
- package/dist/src/tools/execute-action.js +579 -0
- package/dist/src/tools/execute-action.js.map +1 -0
- package/dist/src/tools/form-tools.d.ts +109 -0
- package/dist/src/tools/form-tools.d.ts.map +1 -0
- package/dist/src/tools/form-tools.js +434 -0
- package/dist/src/tools/form-tools.js.map +1 -0
- package/dist/src/tools/index.d.ts +11 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +49 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/response-builder.d.ts +98 -0
- package/dist/src/tools/response-builder.d.ts.map +1 -0
- package/dist/src/tools/response-builder.js +219 -0
- package/dist/src/tools/response-builder.js.map +1 -0
- package/dist/src/tools/tool-schemas.d.ts +2482 -0
- package/dist/src/tools/tool-schemas.d.ts.map +1 -0
- package/dist/src/tools/tool-schemas.js +606 -0
- package/dist/src/tools/tool-schemas.js.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +16 -0
- package/dist/vitest.config.js.map +1 -0
- 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
|