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,704 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects logical form boundaries in a BaseSnapshot, regardless of
|
|
5
|
+
* HTML <form> tag presence. Uses signal scoring to identify form
|
|
6
|
+
* regions with confidence scores.
|
|
7
|
+
*
|
|
8
|
+
* Detection approaches:
|
|
9
|
+
* 1. Semantic: <form> tags, role="form", role="search", <fieldset>
|
|
10
|
+
* 2. Structural: Input clusters, label-input pairs, submit buttons
|
|
11
|
+
* 3. Naming: Form keywords in labels, field name patterns
|
|
12
|
+
*
|
|
13
|
+
* @module form/form-detector
|
|
14
|
+
*/
|
|
15
|
+
import { DEFAULT_FORM_DETECTION_CONFIG } from './types.js';
|
|
16
|
+
import { extractFields } from './field-extractor.js';
|
|
17
|
+
import { computeFormState } from './form-state.js';
|
|
18
|
+
import { createHash } from 'crypto';
|
|
19
|
+
/**
|
|
20
|
+
* Interactive input kinds
|
|
21
|
+
*/
|
|
22
|
+
const INPUT_KINDS = new Set([
|
|
23
|
+
'input',
|
|
24
|
+
'textarea',
|
|
25
|
+
'select',
|
|
26
|
+
'combobox',
|
|
27
|
+
'checkbox',
|
|
28
|
+
'radio',
|
|
29
|
+
'switch',
|
|
30
|
+
'slider',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* Button kinds that could be submit buttons
|
|
34
|
+
*/
|
|
35
|
+
const BUTTON_KINDS = new Set(['button']);
|
|
36
|
+
/**
|
|
37
|
+
* Signal weights for form detection
|
|
38
|
+
*/
|
|
39
|
+
const SIGNAL_WEIGHTS = {
|
|
40
|
+
form_tag: 0.5,
|
|
41
|
+
role_form: 0.45,
|
|
42
|
+
role_search: 0.4,
|
|
43
|
+
fieldset: 0.3,
|
|
44
|
+
input_cluster: 0.25,
|
|
45
|
+
label_input_pairs: 0.2,
|
|
46
|
+
submit_button: 0.3,
|
|
47
|
+
form_keywords: 0.15,
|
|
48
|
+
naming_pattern: 0.1,
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Weighted keywords that indicate form intent.
|
|
52
|
+
* Higher weight = more explicit signal (e.g., "create account" is more explicitly signup than "email")
|
|
53
|
+
* Lower weight = ambiguous signal that appears in multiple form types
|
|
54
|
+
*/
|
|
55
|
+
const INTENT_KEYWORDS = {
|
|
56
|
+
login: [
|
|
57
|
+
{ keyword: 'log in', weight: 3 },
|
|
58
|
+
{ keyword: 'login', weight: 3 },
|
|
59
|
+
{ keyword: 'sign in', weight: 3 },
|
|
60
|
+
{ keyword: 'signin', weight: 3 },
|
|
61
|
+
// These are ambiguous - they appear in both login and signup forms
|
|
62
|
+
{ keyword: 'email', weight: 0.5 },
|
|
63
|
+
{ keyword: 'password', weight: 0.5 },
|
|
64
|
+
{ keyword: 'username', weight: 0.5 },
|
|
65
|
+
],
|
|
66
|
+
signup: [
|
|
67
|
+
{ keyword: 'sign up', weight: 3 },
|
|
68
|
+
{ keyword: 'signup', weight: 3 },
|
|
69
|
+
{ keyword: 'register', weight: 3 },
|
|
70
|
+
{ keyword: 'create account', weight: 3 },
|
|
71
|
+
{ keyword: 'join', weight: 2 },
|
|
72
|
+
],
|
|
73
|
+
search: [
|
|
74
|
+
{ keyword: 'search', weight: 3 },
|
|
75
|
+
{ keyword: 'find', weight: 2 },
|
|
76
|
+
{ keyword: 'lookup', weight: 2 },
|
|
77
|
+
{ keyword: 'query', weight: 2 },
|
|
78
|
+
],
|
|
79
|
+
checkout: [
|
|
80
|
+
{ keyword: 'checkout', weight: 3 },
|
|
81
|
+
{ keyword: 'payment', weight: 2 },
|
|
82
|
+
{ keyword: 'order', weight: 2 },
|
|
83
|
+
{ keyword: 'purchase', weight: 2 },
|
|
84
|
+
{ keyword: 'buy now', weight: 3 },
|
|
85
|
+
],
|
|
86
|
+
filter: [
|
|
87
|
+
{ keyword: 'filter', weight: 3 },
|
|
88
|
+
{ keyword: 'sort', weight: 2 },
|
|
89
|
+
{ keyword: 'refine', weight: 2 },
|
|
90
|
+
{ keyword: 'narrow', weight: 2 },
|
|
91
|
+
],
|
|
92
|
+
settings: [
|
|
93
|
+
{ keyword: 'settings', weight: 3 },
|
|
94
|
+
{ keyword: 'preferences', weight: 2 },
|
|
95
|
+
{ keyword: 'configuration', weight: 2 },
|
|
96
|
+
{ keyword: 'options', weight: 1 },
|
|
97
|
+
],
|
|
98
|
+
contact: [
|
|
99
|
+
{ keyword: 'contact', weight: 3 },
|
|
100
|
+
{ keyword: 'message', weight: 1 },
|
|
101
|
+
{ keyword: 'feedback', weight: 2 },
|
|
102
|
+
{ keyword: 'inquiry', weight: 2 },
|
|
103
|
+
],
|
|
104
|
+
subscribe: [
|
|
105
|
+
{ keyword: 'subscribe', weight: 3 },
|
|
106
|
+
{ keyword: 'newsletter', weight: 3 },
|
|
107
|
+
{ keyword: 'email updates', weight: 2 },
|
|
108
|
+
],
|
|
109
|
+
shipping: [
|
|
110
|
+
{ keyword: 'shipping', weight: 3 },
|
|
111
|
+
{ keyword: 'delivery', weight: 2 },
|
|
112
|
+
{ keyword: 'address', weight: 1 },
|
|
113
|
+
],
|
|
114
|
+
payment: [
|
|
115
|
+
{ keyword: 'payment', weight: 3 },
|
|
116
|
+
{ keyword: 'credit card', weight: 3 },
|
|
117
|
+
{ keyword: 'billing', weight: 2 },
|
|
118
|
+
{ keyword: 'card number', weight: 3 },
|
|
119
|
+
],
|
|
120
|
+
profile: [
|
|
121
|
+
{ keyword: 'profile', weight: 3 },
|
|
122
|
+
{ keyword: 'account', weight: 1 },
|
|
123
|
+
{ keyword: 'personal info', weight: 2 },
|
|
124
|
+
],
|
|
125
|
+
unknown: [],
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Submit button keywords
|
|
129
|
+
*/
|
|
130
|
+
const SUBMIT_KEYWORDS = [
|
|
131
|
+
'submit',
|
|
132
|
+
'send',
|
|
133
|
+
'continue',
|
|
134
|
+
'next',
|
|
135
|
+
'save',
|
|
136
|
+
'apply',
|
|
137
|
+
'confirm',
|
|
138
|
+
'add to',
|
|
139
|
+
'sign in',
|
|
140
|
+
'log in',
|
|
141
|
+
'sign up',
|
|
142
|
+
'register',
|
|
143
|
+
'search',
|
|
144
|
+
'buy',
|
|
145
|
+
'checkout',
|
|
146
|
+
'purchase',
|
|
147
|
+
'subscribe',
|
|
148
|
+
];
|
|
149
|
+
/**
|
|
150
|
+
* Form Detector class
|
|
151
|
+
*/
|
|
152
|
+
export class FormDetector {
|
|
153
|
+
config;
|
|
154
|
+
constructor(config) {
|
|
155
|
+
this.config = { ...DEFAULT_FORM_DETECTION_CONFIG, ...config };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Detect all form regions in a snapshot.
|
|
159
|
+
*
|
|
160
|
+
* @param snapshot - BaseSnapshot to analyze
|
|
161
|
+
* @returns Array of detected FormRegions
|
|
162
|
+
*/
|
|
163
|
+
detect(snapshot) {
|
|
164
|
+
const candidates = [];
|
|
165
|
+
// Phase 1: Detect explicit form elements (semantic signals)
|
|
166
|
+
const explicitForms = this.detectExplicitForms(snapshot);
|
|
167
|
+
candidates.push(...explicitForms);
|
|
168
|
+
// Track which fields are already claimed by explicit forms
|
|
169
|
+
const claimedFields = new Set();
|
|
170
|
+
for (const candidate of explicitForms) {
|
|
171
|
+
for (const eid of candidate.field_eids) {
|
|
172
|
+
claimedFields.add(eid);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Phase 2: Detect implicit forms (formless input clusters)
|
|
176
|
+
if (this.config.detect_formless) {
|
|
177
|
+
const implicitForms = this.detectImplicitForms(snapshot, claimedFields);
|
|
178
|
+
candidates.push(...implicitForms);
|
|
179
|
+
}
|
|
180
|
+
// Phase 3: Filter by minimum confidence
|
|
181
|
+
const validCandidates = candidates.filter((c) => c.confidence >= this.config.min_confidence);
|
|
182
|
+
// Phase 4: Transform candidates to FormRegions
|
|
183
|
+
return validCandidates.map((candidate, index) => this.buildFormRegion(candidate, snapshot, index));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Detect explicit form elements (form tags, role=form, etc.)
|
|
187
|
+
*/
|
|
188
|
+
detectExplicitForms(snapshot) {
|
|
189
|
+
const candidates = [];
|
|
190
|
+
const inputNodes = snapshot.nodes.filter((n) => INPUT_KINDS.has(n.kind));
|
|
191
|
+
// Find form structural nodes
|
|
192
|
+
const formNodes = snapshot.nodes.filter((n) => n.kind === 'form' || n.attributes?.role === 'form' || n.attributes?.role === 'search');
|
|
193
|
+
for (const formNode of formNodes) {
|
|
194
|
+
const signals = [];
|
|
195
|
+
// Determine signal type
|
|
196
|
+
if (formNode.kind === 'form') {
|
|
197
|
+
signals.push({
|
|
198
|
+
type: 'form_tag',
|
|
199
|
+
strength: 1.0,
|
|
200
|
+
evidence: `<form> element at ${formNode.node_id}`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else if (formNode.attributes?.role === 'search') {
|
|
204
|
+
signals.push({
|
|
205
|
+
type: 'role_search',
|
|
206
|
+
strength: 1.0,
|
|
207
|
+
evidence: `role="search" at ${formNode.node_id}`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else if (formNode.attributes?.role === 'form') {
|
|
211
|
+
signals.push({
|
|
212
|
+
type: 'role_form',
|
|
213
|
+
strength: 1.0,
|
|
214
|
+
evidence: `role="form" at ${formNode.node_id}`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// Find fields within this form's region/group
|
|
218
|
+
const fieldEids = [];
|
|
219
|
+
for (const input of inputNodes) {
|
|
220
|
+
// Check if input is in the same group or under the same heading context
|
|
221
|
+
const isInForm = this.isNodeWithinForm(input, formNode, snapshot);
|
|
222
|
+
if (isInForm) {
|
|
223
|
+
fieldEids.push(input.node_id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Add input cluster signal if we found fields
|
|
227
|
+
if (fieldEids.length > 0) {
|
|
228
|
+
signals.push({
|
|
229
|
+
type: 'input_cluster',
|
|
230
|
+
strength: Math.min(1.0, fieldEids.length / 5),
|
|
231
|
+
evidence: `${fieldEids.length} input fields`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// Check for submit button
|
|
235
|
+
const submitButton = this.findSubmitButton(snapshot, formNode, fieldEids);
|
|
236
|
+
if (submitButton) {
|
|
237
|
+
signals.push({
|
|
238
|
+
type: 'submit_button',
|
|
239
|
+
strength: 1.0,
|
|
240
|
+
evidence: `Submit button: "${submitButton.label}"`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// Compute confidence
|
|
244
|
+
const confidence = this.computeConfidence(signals);
|
|
245
|
+
// Infer intent
|
|
246
|
+
const intent = this.inferIntent(snapshot, fieldEids, formNode);
|
|
247
|
+
candidates.push({
|
|
248
|
+
root_node_id: formNode.node_id,
|
|
249
|
+
root_backend_node_id: formNode.backend_node_id,
|
|
250
|
+
signals,
|
|
251
|
+
field_eids: fieldEids,
|
|
252
|
+
confidence,
|
|
253
|
+
intent,
|
|
254
|
+
bbox: formNode.layout?.bbox
|
|
255
|
+
? {
|
|
256
|
+
x: formNode.layout.bbox.x,
|
|
257
|
+
y: formNode.layout.bbox.y,
|
|
258
|
+
width: formNode.layout.bbox.w,
|
|
259
|
+
height: formNode.layout.bbox.h,
|
|
260
|
+
}
|
|
261
|
+
: undefined,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return candidates;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Detect implicit forms (input clusters without form tag)
|
|
268
|
+
*/
|
|
269
|
+
detectImplicitForms(snapshot, claimedFields) {
|
|
270
|
+
const candidates = [];
|
|
271
|
+
// Find unclaimed input nodes
|
|
272
|
+
const unclaimedInputs = snapshot.nodes.filter((n) => INPUT_KINDS.has(n.kind) && !claimedFields.has(n.node_id));
|
|
273
|
+
if (unclaimedInputs.length === 0) {
|
|
274
|
+
return candidates;
|
|
275
|
+
}
|
|
276
|
+
// Group inputs by proximity and structural context
|
|
277
|
+
const clusters = this.clusterInputs(unclaimedInputs, snapshot);
|
|
278
|
+
for (const cluster of clusters) {
|
|
279
|
+
if (cluster.length < 1)
|
|
280
|
+
continue;
|
|
281
|
+
const signals = [];
|
|
282
|
+
// Input cluster signal
|
|
283
|
+
signals.push({
|
|
284
|
+
type: 'input_cluster',
|
|
285
|
+
strength: Math.min(1.0, cluster.length / 3),
|
|
286
|
+
evidence: `${cluster.length} input fields clustered`,
|
|
287
|
+
});
|
|
288
|
+
// Check for label-input pairs
|
|
289
|
+
const labeledCount = cluster.filter((n) => n.label && n.label.trim().length > 0).length;
|
|
290
|
+
if (labeledCount > 0) {
|
|
291
|
+
signals.push({
|
|
292
|
+
type: 'label_input_pairs',
|
|
293
|
+
strength: labeledCount / cluster.length,
|
|
294
|
+
evidence: `${labeledCount}/${cluster.length} fields have labels`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// Check for form keywords in labels
|
|
298
|
+
const allKeywords = Object.values(INTENT_KEYWORDS)
|
|
299
|
+
.flat()
|
|
300
|
+
.map((entry) => entry.keyword);
|
|
301
|
+
const hasFormKeywords = cluster.some((n) => this.hasIntentKeywords(n.label, allKeywords));
|
|
302
|
+
if (hasFormKeywords) {
|
|
303
|
+
signals.push({
|
|
304
|
+
type: 'form_keywords',
|
|
305
|
+
strength: 0.8,
|
|
306
|
+
evidence: 'Form-related keywords in labels',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// Check for submit button near cluster
|
|
310
|
+
const fieldEids = cluster.map((n) => n.node_id);
|
|
311
|
+
const submitButton = this.findSubmitButtonNearCluster(snapshot, cluster);
|
|
312
|
+
if (submitButton) {
|
|
313
|
+
signals.push({
|
|
314
|
+
type: 'submit_button',
|
|
315
|
+
strength: 0.9,
|
|
316
|
+
evidence: `Nearby submit button: "${submitButton.label}"`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
// Compute confidence
|
|
320
|
+
const confidence = this.computeConfidence(signals);
|
|
321
|
+
// Infer intent
|
|
322
|
+
const intent = this.inferIntent(snapshot, fieldEids, undefined);
|
|
323
|
+
// Compute bounding box from cluster
|
|
324
|
+
const bbox = this.computeClusterBbox(cluster);
|
|
325
|
+
candidates.push({
|
|
326
|
+
signals,
|
|
327
|
+
field_eids: fieldEids,
|
|
328
|
+
confidence,
|
|
329
|
+
intent,
|
|
330
|
+
bbox,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return candidates;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Cluster input nodes by proximity and structural context.
|
|
337
|
+
*/
|
|
338
|
+
clusterInputs(inputs, _snapshot) {
|
|
339
|
+
if (inputs.length === 0)
|
|
340
|
+
return [];
|
|
341
|
+
if (inputs.length === 1)
|
|
342
|
+
return [[inputs[0]]];
|
|
343
|
+
// Group by region first
|
|
344
|
+
const byRegion = new Map();
|
|
345
|
+
for (const input of inputs) {
|
|
346
|
+
const key = input.where.region ?? 'unknown';
|
|
347
|
+
const group = byRegion.get(key) ?? [];
|
|
348
|
+
group.push(input);
|
|
349
|
+
byRegion.set(key, group);
|
|
350
|
+
}
|
|
351
|
+
const clusters = [];
|
|
352
|
+
// Within each region, cluster by proximity
|
|
353
|
+
for (const regionInputs of byRegion.values()) {
|
|
354
|
+
if (regionInputs.length === 1) {
|
|
355
|
+
clusters.push(regionInputs);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
// Simple clustering by vertical proximity
|
|
359
|
+
const sorted = [...regionInputs].sort((a, b) => {
|
|
360
|
+
const yA = a.layout?.bbox?.y ?? 0;
|
|
361
|
+
const yB = b.layout?.bbox?.y ?? 0;
|
|
362
|
+
return yA - yB;
|
|
363
|
+
});
|
|
364
|
+
let currentCluster = [sorted[0]];
|
|
365
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
366
|
+
const prev = sorted[i - 1];
|
|
367
|
+
const curr = sorted[i];
|
|
368
|
+
const prevY = (prev.layout?.bbox?.y ?? 0) + (prev.layout?.bbox?.h ?? 0);
|
|
369
|
+
const currY = curr.layout?.bbox?.y ?? 0;
|
|
370
|
+
const distance = currY - prevY;
|
|
371
|
+
if (distance <= this.config.cluster_distance) {
|
|
372
|
+
currentCluster.push(curr);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
if (currentCluster.length > 0) {
|
|
376
|
+
clusters.push(currentCluster);
|
|
377
|
+
}
|
|
378
|
+
currentCluster = [curr];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (currentCluster.length > 0) {
|
|
382
|
+
clusters.push(currentCluster);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return clusters;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Check if a node is likely within a form's scope.
|
|
389
|
+
*/
|
|
390
|
+
isNodeWithinForm(node, formNode, _snapshot) {
|
|
391
|
+
// Check region match
|
|
392
|
+
if (node.where.region !== formNode.where.region) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
// Check group_id if available
|
|
396
|
+
if (formNode.where.group_id && node.where.group_id) {
|
|
397
|
+
if (node.where.group_id === formNode.where.group_id) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Check heading context
|
|
402
|
+
if (formNode.where.heading_context && node.where.heading_context) {
|
|
403
|
+
if (node.where.heading_context === formNode.where.heading_context) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Check spatial proximity using bounding boxes
|
|
408
|
+
if (formNode.layout?.bbox && node.layout?.bbox) {
|
|
409
|
+
const formBbox = formNode.layout.bbox;
|
|
410
|
+
const nodeBbox = node.layout.bbox;
|
|
411
|
+
// Check if node is within or near form's bounding box
|
|
412
|
+
const isWithinX = nodeBbox.x >= formBbox.x - 50 && nodeBbox.x <= formBbox.x + formBbox.w + 50;
|
|
413
|
+
const isWithinY = nodeBbox.y >= formBbox.y - 50 && nodeBbox.y <= formBbox.y + formBbox.h + 50;
|
|
414
|
+
if (isWithinX && isWithinY) {
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Find a submit button associated with a form.
|
|
422
|
+
*/
|
|
423
|
+
findSubmitButton(snapshot, formNode, fieldEids) {
|
|
424
|
+
const buttons = snapshot.nodes.filter((n) => BUTTON_KINDS.has(n.kind));
|
|
425
|
+
for (const button of buttons) {
|
|
426
|
+
// Check if button is within form's scope
|
|
427
|
+
if (!this.isNodeWithinForm(button, formNode, snapshot)) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
// Check if button label suggests submission
|
|
431
|
+
if (this.isSubmitButton(button)) {
|
|
432
|
+
return button;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Also check buttons near the fields
|
|
436
|
+
if (fieldEids.length > 0) {
|
|
437
|
+
const fieldNodes = fieldEids
|
|
438
|
+
.map((eid) => snapshot.nodes.find((n) => n.node_id === eid))
|
|
439
|
+
.filter((n) => n !== undefined);
|
|
440
|
+
return this.findSubmitButtonNearCluster(snapshot, fieldNodes);
|
|
441
|
+
}
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Find a submit button near a cluster of inputs.
|
|
446
|
+
*/
|
|
447
|
+
findSubmitButtonNearCluster(snapshot, cluster) {
|
|
448
|
+
if (cluster.length === 0)
|
|
449
|
+
return undefined;
|
|
450
|
+
const buttons = snapshot.nodes.filter((n) => BUTTON_KINDS.has(n.kind));
|
|
451
|
+
const clusterBbox = this.computeClusterBbox(cluster);
|
|
452
|
+
if (!clusterBbox)
|
|
453
|
+
return undefined;
|
|
454
|
+
// Find buttons near the cluster
|
|
455
|
+
const nearbyButtons = buttons.filter((button) => {
|
|
456
|
+
if (!button.layout?.bbox)
|
|
457
|
+
return false;
|
|
458
|
+
const btnBbox = button.layout.bbox;
|
|
459
|
+
// Check if button is below or to the right of the cluster
|
|
460
|
+
const isNearX = btnBbox.x >= clusterBbox.x - 100 && btnBbox.x <= clusterBbox.x + clusterBbox.width + 100;
|
|
461
|
+
const isNearY = btnBbox.y >= clusterBbox.y - 50 && btnBbox.y <= clusterBbox.y + clusterBbox.height + 150;
|
|
462
|
+
return isNearX && isNearY;
|
|
463
|
+
});
|
|
464
|
+
// Find the best submit button candidate
|
|
465
|
+
for (const button of nearbyButtons) {
|
|
466
|
+
if (this.isSubmitButton(button)) {
|
|
467
|
+
return button;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return undefined;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Check if a button looks like a submit button.
|
|
474
|
+
*/
|
|
475
|
+
isSubmitButton(button) {
|
|
476
|
+
const label = button.label.toLowerCase();
|
|
477
|
+
// Check for submit keywords
|
|
478
|
+
for (const keyword of SUBMIT_KEYWORDS) {
|
|
479
|
+
if (label.includes(keyword)) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Check for type="submit" attribute
|
|
484
|
+
if (button.attributes?.input_type === 'submit') {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Compute confidence score from signals.
|
|
491
|
+
*/
|
|
492
|
+
computeConfidence(signals) {
|
|
493
|
+
let score = 0;
|
|
494
|
+
for (const signal of signals) {
|
|
495
|
+
const weight = SIGNAL_WEIGHTS[signal.type] ?? 0;
|
|
496
|
+
score += weight * signal.strength;
|
|
497
|
+
}
|
|
498
|
+
// Normalize to 0-1
|
|
499
|
+
return Math.min(1.0, score);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Infer the intent of a form.
|
|
503
|
+
*/
|
|
504
|
+
inferIntent(snapshot, fieldEids, formNode) {
|
|
505
|
+
// Collect all relevant text to analyze
|
|
506
|
+
const textToAnalyze = [];
|
|
507
|
+
// Add form node label if available
|
|
508
|
+
if (formNode?.label) {
|
|
509
|
+
textToAnalyze.push(formNode.label);
|
|
510
|
+
}
|
|
511
|
+
// Add form heading context
|
|
512
|
+
if (formNode?.where.heading_context) {
|
|
513
|
+
textToAnalyze.push(formNode.where.heading_context);
|
|
514
|
+
}
|
|
515
|
+
// Add field labels
|
|
516
|
+
for (const eid of fieldEids) {
|
|
517
|
+
const node = snapshot.nodes.find((n) => n.node_id === eid);
|
|
518
|
+
if (node?.label) {
|
|
519
|
+
textToAnalyze.push(node.label);
|
|
520
|
+
}
|
|
521
|
+
if (node?.attributes?.placeholder) {
|
|
522
|
+
textToAnalyze.push(node.attributes.placeholder);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const combinedText = textToAnalyze.join(' ').toLowerCase();
|
|
526
|
+
// Score each intent using weighted keywords
|
|
527
|
+
let bestIntent = 'unknown';
|
|
528
|
+
let bestScore = 0;
|
|
529
|
+
for (const [intent, keywordEntries] of Object.entries(INTENT_KEYWORDS)) {
|
|
530
|
+
if (intent === 'unknown')
|
|
531
|
+
continue;
|
|
532
|
+
let score = 0;
|
|
533
|
+
for (const entry of keywordEntries) {
|
|
534
|
+
if (combinedText.includes(entry.keyword)) {
|
|
535
|
+
score += entry.weight;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (score > bestScore) {
|
|
539
|
+
bestScore = score;
|
|
540
|
+
bestIntent = intent;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return bestIntent;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Check if text contains any of the given keywords.
|
|
547
|
+
*/
|
|
548
|
+
hasIntentKeywords(text, keywords) {
|
|
549
|
+
const lower = text.toLowerCase();
|
|
550
|
+
return keywords.some((k) => lower.includes(k));
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Compute bounding box for a cluster of nodes.
|
|
554
|
+
*/
|
|
555
|
+
computeClusterBbox(nodes) {
|
|
556
|
+
const bboxes = nodes
|
|
557
|
+
.map((n) => n.layout?.bbox)
|
|
558
|
+
.filter((b) => b !== undefined);
|
|
559
|
+
if (bboxes.length === 0)
|
|
560
|
+
return undefined;
|
|
561
|
+
let minX = Infinity;
|
|
562
|
+
let minY = Infinity;
|
|
563
|
+
let maxX = -Infinity;
|
|
564
|
+
let maxY = -Infinity;
|
|
565
|
+
for (const bbox of bboxes) {
|
|
566
|
+
minX = Math.min(minX, bbox.x);
|
|
567
|
+
minY = Math.min(minY, bbox.y);
|
|
568
|
+
maxX = Math.max(maxX, bbox.x + bbox.w);
|
|
569
|
+
maxY = Math.max(maxY, bbox.y + bbox.h);
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
x: minX,
|
|
573
|
+
y: minY,
|
|
574
|
+
width: maxX - minX,
|
|
575
|
+
height: maxY - minY,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Build a FormRegion from a candidate.
|
|
580
|
+
*/
|
|
581
|
+
buildFormRegion(candidate, snapshot, index) {
|
|
582
|
+
// Generate form ID
|
|
583
|
+
const formId = this.generateFormId(candidate, index);
|
|
584
|
+
// Extract fields
|
|
585
|
+
const fields = extractFields(snapshot, candidate.field_eids, this.config);
|
|
586
|
+
// Find action buttons
|
|
587
|
+
const actions = this.extractFormActions(snapshot, candidate);
|
|
588
|
+
// Compute form state
|
|
589
|
+
const state = computeFormState(fields);
|
|
590
|
+
// Determine form pattern
|
|
591
|
+
const pattern = this.inferPattern(fields, snapshot);
|
|
592
|
+
// Build detection info
|
|
593
|
+
const detection = {
|
|
594
|
+
method: candidate.root_node_id ? 'semantic' : 'structural',
|
|
595
|
+
confidence: candidate.confidence,
|
|
596
|
+
signals: candidate.signals,
|
|
597
|
+
};
|
|
598
|
+
return {
|
|
599
|
+
form_id: formId,
|
|
600
|
+
detection,
|
|
601
|
+
intent: candidate.intent,
|
|
602
|
+
pattern,
|
|
603
|
+
fields,
|
|
604
|
+
actions,
|
|
605
|
+
state,
|
|
606
|
+
bbox: candidate.bbox,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Generate a unique form ID.
|
|
611
|
+
*/
|
|
612
|
+
generateFormId(candidate, index) {
|
|
613
|
+
const components = [
|
|
614
|
+
candidate.intent ?? 'form',
|
|
615
|
+
candidate.root_node_id ?? `cluster-${index}`,
|
|
616
|
+
String(candidate.field_eids.length),
|
|
617
|
+
];
|
|
618
|
+
const hash = createHash('sha256').update(components.join('::')).digest('hex');
|
|
619
|
+
return `form-${hash.substring(0, 8)}`;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Extract form action buttons.
|
|
623
|
+
*/
|
|
624
|
+
extractFormActions(snapshot, candidate) {
|
|
625
|
+
const actions = [];
|
|
626
|
+
const buttons = snapshot.nodes.filter((n) => BUTTON_KINDS.has(n.kind));
|
|
627
|
+
for (const button of buttons) {
|
|
628
|
+
// Skip disabled buttons for now but still include them
|
|
629
|
+
const isSubmit = this.isSubmitButton(button);
|
|
630
|
+
const isNearForm = candidate.bbox
|
|
631
|
+
? this.isButtonNearBbox(button, candidate.bbox)
|
|
632
|
+
: candidate.field_eids.length === 0 ||
|
|
633
|
+
this.isButtonNearFields(button, snapshot, candidate.field_eids);
|
|
634
|
+
if (!isNearForm)
|
|
635
|
+
continue;
|
|
636
|
+
// Determine action type
|
|
637
|
+
let type = 'action';
|
|
638
|
+
const label = button.label.toLowerCase();
|
|
639
|
+
if (isSubmit) {
|
|
640
|
+
type = 'submit';
|
|
641
|
+
}
|
|
642
|
+
else if (label.includes('cancel') || label.includes('close')) {
|
|
643
|
+
type = 'cancel';
|
|
644
|
+
}
|
|
645
|
+
else if (label.includes('back') || label.includes('previous')) {
|
|
646
|
+
type = 'back';
|
|
647
|
+
}
|
|
648
|
+
else if (label.includes('next') || label.includes('continue')) {
|
|
649
|
+
type = 'next';
|
|
650
|
+
}
|
|
651
|
+
else if (label.includes('reset') || label.includes('clear')) {
|
|
652
|
+
type = 'reset';
|
|
653
|
+
}
|
|
654
|
+
actions.push({
|
|
655
|
+
eid: button.node_id,
|
|
656
|
+
backend_node_id: button.backend_node_id,
|
|
657
|
+
label: button.label,
|
|
658
|
+
type,
|
|
659
|
+
enabled: button.state?.enabled ?? true,
|
|
660
|
+
is_primary: isSubmit,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
return actions;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Check if a button is near a bounding box.
|
|
667
|
+
*/
|
|
668
|
+
isButtonNearBbox(button, bbox) {
|
|
669
|
+
if (!button.layout?.bbox)
|
|
670
|
+
return false;
|
|
671
|
+
const btnBbox = button.layout.bbox;
|
|
672
|
+
const isNearX = btnBbox.x >= bbox.x - 100 && btnBbox.x <= bbox.x + bbox.width + 100;
|
|
673
|
+
const isNearY = btnBbox.y >= bbox.y - 50 && btnBbox.y <= bbox.y + bbox.height + 150;
|
|
674
|
+
return isNearX && isNearY;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Check if a button is near a set of fields.
|
|
678
|
+
*/
|
|
679
|
+
isButtonNearFields(button, snapshot, fieldEids) {
|
|
680
|
+
const fieldNodes = fieldEids
|
|
681
|
+
.map((eid) => snapshot.nodes.find((n) => n.node_id === eid))
|
|
682
|
+
.filter((n) => n !== undefined);
|
|
683
|
+
const clusterBbox = this.computeClusterBbox(fieldNodes);
|
|
684
|
+
if (!clusterBbox)
|
|
685
|
+
return false;
|
|
686
|
+
return this.isButtonNearBbox(button, clusterBbox);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Infer form pattern (single page, multi-step, etc.)
|
|
690
|
+
*/
|
|
691
|
+
inferPattern(_fields, _snapshot) {
|
|
692
|
+
// For now, default to single_page
|
|
693
|
+
// Future: detect multi-step wizards, accordions, tabs
|
|
694
|
+
return 'single_page';
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Convenience function for detecting forms in a snapshot.
|
|
699
|
+
*/
|
|
700
|
+
export function detectForms(snapshot, config) {
|
|
701
|
+
const detector = new FormDetector(config);
|
|
702
|
+
return detector.detect(snapshot);
|
|
703
|
+
}
|
|
704
|
+
//# sourceMappingURL=form-detector.js.map
|