explorbot 0.0.1 → 0.0.5
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/README.md +80 -26
- package/bin/explorbot-cli.ts +679 -0
- package/boat/api-tester/src/ai/chief/styles.ts +15 -0
- package/boat/api-tester/src/ai/chief.ts +335 -0
- package/boat/api-tester/src/ai/curler-tools.ts +278 -0
- package/boat/api-tester/src/ai/curler.ts +306 -0
- package/boat/api-tester/src/api-client.ts +28 -0
- package/boat/api-tester/src/apibot.ts +203 -0
- package/boat/api-tester/src/cli.ts +301 -0
- package/boat/api-tester/src/config.ts +190 -0
- package/dist/bin/explorbot-cli.js +19 -98
- package/dist/boat/api-tester/bin/apibot-cli.js +0 -1
- package/dist/boat/api-tester/src/ai/chief/styles.js +0 -1
- package/dist/boat/api-tester/src/ai/chief.js +0 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js +0 -1
- package/dist/boat/api-tester/src/ai/curler.js +0 -1
- package/dist/boat/api-tester/src/api-client.js +0 -1
- package/dist/boat/api-tester/src/apibot.js +0 -1
- package/dist/boat/api-tester/src/cli.js +0 -1
- package/dist/boat/api-tester/src/config.js +0 -1
- package/dist/src/action-result.js +0 -1
- package/dist/src/action.js +0 -1
- package/dist/src/activity.js +0 -1
- package/dist/src/ai/agent.js +0 -1
- package/dist/src/ai/bosun.js +0 -1
- package/dist/src/ai/captain/idle-mode.js +0 -1
- package/dist/src/ai/captain/mixin.js +0 -1
- package/dist/src/ai/captain/test-mode.js +0 -1
- package/dist/src/ai/captain/web-mode.js +0 -1
- package/dist/src/ai/captain.js +0 -1
- package/dist/src/ai/conversation.js +0 -1
- package/dist/src/ai/experience-compactor.js +0 -1
- package/dist/src/ai/fisherman-tools.js +0 -1
- package/dist/src/ai/fisherman.js +0 -1
- package/dist/src/ai/historian.js +0 -1
- package/dist/src/ai/navigator.js +0 -1
- package/dist/src/ai/pilot.js +0 -1
- package/dist/src/ai/planner/session-dedup.js +0 -1
- package/dist/src/ai/planner/styles.js +0 -1
- package/dist/src/ai/planner/subpages.js +0 -1
- package/dist/src/ai/planner.js +0 -1
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.js +0 -1
- package/dist/src/ai/researcher/cache.js +0 -1
- package/dist/src/ai/researcher/coordinates.js +0 -1
- package/dist/src/ai/researcher/deep-analysis.js +0 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +0 -1
- package/dist/src/ai/researcher/focus.js +0 -1
- package/dist/src/ai/researcher/locators.js +0 -1
- package/dist/src/ai/researcher/mixin.js +0 -1
- package/dist/src/ai/researcher/parser.js +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -1
- package/dist/src/ai/researcher.js +0 -1
- package/dist/src/ai/rules.js +0 -1
- package/dist/src/ai/task-agent.js +0 -1
- package/dist/src/ai/tester.js +0 -1
- package/dist/src/ai/tools.js +0 -1
- package/dist/src/api/api-client.js +0 -1
- package/dist/src/api/request-result.js +0 -1
- package/dist/src/api/request-store.js +0 -1
- package/dist/src/api/spec-reader.js +0 -1
- package/dist/src/api/xhr-capture.js +0 -1
- package/dist/src/browser-server.js +0 -1
- package/dist/src/command-handler.js +0 -1
- package/dist/src/commands/add-rule-command.js +0 -1
- package/dist/src/commands/base-command.js +0 -1
- package/dist/src/commands/clean-command.js +0 -1
- package/dist/src/commands/context-aria-command.js +0 -1
- package/dist/src/commands/context-command.js +0 -1
- package/dist/src/commands/context-data-command.js +0 -1
- package/dist/src/commands/context-experience-command.js +0 -1
- package/dist/src/commands/context-html-command.js +0 -1
- package/dist/src/commands/context-knowledge-command.js +0 -1
- package/dist/src/commands/debug-command.js +0 -1
- package/dist/src/commands/drill-command.js +0 -1
- package/dist/src/commands/exit-command.js +0 -1
- package/dist/src/commands/explore-command.js +2 -2
- package/dist/src/commands/freesail-command.js +0 -1
- package/dist/src/commands/help-command.js +0 -1
- package/dist/src/commands/index.js +0 -1
- package/dist/src/commands/init-command.js +115 -0
- package/dist/src/commands/knows-command.js +0 -1
- package/dist/src/commands/learn-command.js +0 -1
- package/dist/src/commands/navigate-command.js +0 -1
- package/dist/src/commands/path-command.js +0 -1
- package/dist/src/commands/plan-clear-command.js +0 -1
- package/dist/src/commands/plan-command.js +0 -1
- package/dist/src/commands/plan-edit-command.js +0 -1
- package/dist/src/commands/plan-load-command.js +0 -1
- package/dist/src/commands/plan-reload-command.js +0 -1
- package/dist/src/commands/plan-save-command.js +0 -1
- package/dist/src/commands/research-command.js +0 -1
- package/dist/src/commands/start-command.js +0 -1
- package/dist/src/commands/status-command.js +0 -1
- package/dist/src/commands/test-command.js +0 -1
- package/dist/src/components/ActivityPane.js +0 -1
- package/dist/src/components/AddKnowledge.js +0 -1
- package/dist/src/components/AddRule.js +0 -1
- package/dist/src/components/App.js +0 -1
- package/dist/src/components/Autocomplete.js +0 -1
- package/dist/src/components/InputPane.js +0 -1
- package/dist/src/components/InputReadline.js +0 -1
- package/dist/src/components/LogPane.js +0 -1
- package/dist/src/components/PlanEditor.js +0 -1
- package/dist/src/components/PlanPane.js +0 -1
- package/dist/src/components/SessionTimer.js +0 -1
- package/dist/src/components/StateTransitionPane.js +0 -1
- package/dist/src/components/StatusPane.js +0 -1
- package/dist/src/components/TaskPane.js +0 -1
- package/dist/src/components/Welcome.js +0 -1
- package/dist/src/components/WelcomeChecklist.js +0 -1
- package/dist/src/components/WelcomeCommands.js +0 -1
- package/dist/src/components/autocomplete-store.js +0 -1
- package/dist/src/components/parse-keypress.js +0 -1
- package/dist/src/config.js +0 -1
- package/dist/src/execution-controller.js +0 -1
- package/dist/src/experience-tracker.js +0 -1
- package/dist/src/explorbot.js +0 -1
- package/dist/src/explorer.js +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/knowledge-tracker.js +2 -2
- package/dist/src/observability.js +0 -1
- package/dist/src/reporter.js +0 -1
- package/dist/src/state-manager.js +0 -1
- package/dist/src/stats.js +0 -1
- package/dist/src/test-plan.js +0 -1
- package/dist/src/utils/aria.js +0 -1
- package/dist/src/utils/cli-name.js +16 -0
- package/dist/src/utils/code-extractor.js +0 -1
- package/dist/src/utils/context-formatter.js +0 -1
- package/dist/src/utils/error-page.js +0 -1
- package/dist/src/utils/expandable.js +0 -1
- package/dist/src/utils/hooks-runner.js +0 -1
- package/dist/src/utils/html-diff.js +0 -1
- package/dist/src/utils/html.js +0 -1
- package/dist/src/utils/logger.js +0 -1
- package/dist/src/utils/loop.js +0 -1
- package/dist/src/utils/markdown-parser.js +0 -1
- package/dist/src/utils/markdown-query.js +0 -1
- package/dist/src/utils/markdown-terminal.js +0 -1
- package/dist/src/utils/research-parser.js +0 -1
- package/dist/src/utils/retry.js +0 -1
- package/dist/src/utils/rules-loader.js +0 -1
- package/dist/src/utils/strings.js +0 -1
- package/dist/src/utils/test-plan-markdown.js +0 -1
- package/dist/src/utils/throttle.js +0 -1
- package/dist/src/utils/unique-names.js +0 -1
- package/dist/src/utils/url-matcher.js +0 -1
- package/dist/src/utils/web-element.js +0 -1
- package/dist/src/utils/xpath.js +0 -1
- package/package.json +27 -3
- package/src/action-result.ts +694 -0
- package/src/action.ts +445 -0
- package/src/activity.ts +111 -0
- package/src/ai/agent.ts +3 -0
- package/src/ai/bosun.ts +557 -0
- package/src/ai/captain/idle-mode.ts +116 -0
- package/src/ai/captain/mixin.ts +22 -0
- package/src/ai/captain/test-mode.ts +262 -0
- package/src/ai/captain/web-mode.ts +136 -0
- package/src/ai/captain.ts +504 -0
- package/src/ai/conversation.ts +205 -0
- package/src/ai/experience-compactor.ts +284 -0
- package/src/ai/fisherman-tools.ts +181 -0
- package/src/ai/fisherman.ts +223 -0
- package/src/ai/historian.ts +457 -0
- package/src/ai/navigator.ts +572 -0
- package/src/ai/pilot.ts +776 -0
- package/src/ai/planner/session-dedup.ts +35 -0
- package/src/ai/planner/styles.ts +17 -0
- package/src/ai/planner/subpages.ts +141 -0
- package/src/ai/planner.ts +536 -0
- package/src/ai/provider.ts +613 -0
- package/src/ai/quartermaster.ts +286 -0
- package/src/ai/researcher/cache.ts +103 -0
- package/src/ai/researcher/coordinates.ts +238 -0
- package/src/ai/researcher/deep-analysis.ts +415 -0
- package/src/ai/researcher/fingerprint-worker.ts +59 -0
- package/src/ai/researcher/focus.ts +42 -0
- package/src/ai/researcher/locators.ts +282 -0
- package/src/ai/researcher/mixin.ts +4 -0
- package/src/ai/researcher/parser.ts +186 -0
- package/src/ai/researcher/research-result.ts +115 -0
- package/src/ai/researcher.ts +857 -0
- package/src/ai/rules.ts +376 -0
- package/src/ai/task-agent.ts +141 -0
- package/src/ai/tester.ts +939 -0
- package/src/ai/tools.ts +1117 -0
- package/src/api/api-client.ts +109 -0
- package/src/api/request-result.ts +212 -0
- package/src/api/request-store.ts +130 -0
- package/src/api/spec-reader.ts +174 -0
- package/src/api/xhr-capture.ts +100 -0
- package/src/browser-server.ts +74 -0
- package/src/command-handler.ts +454 -0
- package/src/commands/add-rule-command.ts +63 -0
- package/src/commands/base-command.ts +27 -0
- package/src/commands/clean-command.ts +73 -0
- package/src/commands/context-aria-command.ts +22 -0
- package/src/commands/context-command.ts +67 -0
- package/src/commands/context-data-command.ts +30 -0
- package/src/commands/context-experience-command.ts +48 -0
- package/src/commands/context-html-command.ts +33 -0
- package/src/commands/context-knowledge-command.ts +43 -0
- package/src/commands/debug-command.ts +13 -0
- package/src/commands/drill-command.ts +34 -0
- package/src/commands/exit-command.ts +32 -0
- package/src/commands/explore-command.ts +129 -0
- package/src/commands/freesail-command.ts +95 -0
- package/src/commands/help-command.ts +8 -0
- package/src/commands/index.ts +69 -0
- package/src/commands/init-command.ts +128 -0
- package/src/commands/knows-command.ts +68 -0
- package/src/commands/learn-command.ts +44 -0
- package/src/commands/navigate-command.ts +18 -0
- package/src/commands/path-command.ts +83 -0
- package/src/commands/plan-clear-command.ts +14 -0
- package/src/commands/plan-command.ts +41 -0
- package/src/commands/plan-edit-command.ts +9 -0
- package/src/commands/plan-load-command.ts +18 -0
- package/src/commands/plan-reload-command.ts +28 -0
- package/src/commands/plan-save-command.ts +25 -0
- package/src/commands/research-command.ts +45 -0
- package/src/commands/start-command.ts +13 -0
- package/src/commands/status-command.tsx +23 -0
- package/src/commands/test-command.ts +84 -0
- package/src/components/ActivityPane.tsx +80 -0
- package/src/components/AddKnowledge.tsx +169 -0
- package/src/components/AddRule.tsx +174 -0
- package/src/components/App.tsx +377 -0
- package/src/components/Autocomplete.tsx +63 -0
- package/src/components/InputPane.tsx +259 -0
- package/src/components/InputReadline.tsx +704 -0
- package/src/components/LogPane.tsx +187 -0
- package/src/components/PlanEditor.tsx +150 -0
- package/src/components/PlanPane.tsx +71 -0
- package/src/components/SessionTimer.tsx +35 -0
- package/src/components/StateTransitionPane.tsx +149 -0
- package/src/components/StatusPane.tsx +62 -0
- package/src/components/TaskPane.tsx +119 -0
- package/src/components/Welcome.tsx +83 -0
- package/src/components/WelcomeChecklist.tsx +118 -0
- package/src/components/WelcomeCommands.tsx +102 -0
- package/src/components/autocomplete-store.ts +35 -0
- package/src/components/parse-keypress.ts +170 -0
- package/src/config.ts +490 -0
- package/src/execution-controller.ts +109 -0
- package/src/experience-tracker.ts +350 -0
- package/src/explorbot.ts +405 -0
- package/src/explorer.ts +713 -0
- package/src/index.tsx +62 -0
- package/src/knowledge-tracker.ts +230 -0
- package/src/observability.ts +150 -0
- package/src/reporter.ts +224 -0
- package/src/state-manager.ts +556 -0
- package/src/stats.ts +53 -0
- package/src/test-plan.ts +432 -0
- package/src/utils/aria.ts +629 -0
- package/src/utils/cli-name.ts +13 -0
- package/src/utils/code-extractor.ts +22 -0
- package/src/utils/context-formatter.ts +239 -0
- package/src/utils/error-page.ts +23 -0
- package/src/utils/expandable.ts +38 -0
- package/src/utils/hooks-runner.ts +79 -0
- package/src/utils/html-diff.ts +918 -0
- package/src/utils/html.ts +1316 -0
- package/src/utils/logger.ts +534 -0
- package/src/utils/loop.ts +176 -0
- package/src/utils/markdown-parser.ts +127 -0
- package/src/utils/markdown-query.ts +466 -0
- package/src/utils/markdown-terminal.ts +43 -0
- package/src/utils/research-parser.ts +11 -0
- package/src/utils/retry.ts +73 -0
- package/src/utils/rules-loader.ts +118 -0
- package/src/utils/strings.ts +13 -0
- package/src/utils/test-plan-markdown.ts +332 -0
- package/src/utils/throttle.ts +18 -0
- package/src/utils/unique-names.ts +14 -0
- package/src/utils/url-matcher.ts +45 -0
- package/src/utils/web-element.ts +145 -0
- package/src/utils/xpath.ts +129 -0
- package/dist/bin/explorbot-cli.js.map +0 -1
- package/dist/boat/api-tester/bin/apibot-cli.js.map +0 -1
- package/dist/boat/api-tester/example/apibot.config.js +0 -31
- package/dist/boat/api-tester/example/apibot.config.js.map +0 -1
- package/dist/boat/api-tester/src/ai/chief/styles.js.map +0 -1
- package/dist/boat/api-tester/src/ai/chief.js.map +0 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js.map +0 -1
- package/dist/boat/api-tester/src/ai/curler.js.map +0 -1
- package/dist/boat/api-tester/src/api-client.js.map +0 -1
- package/dist/boat/api-tester/src/apibot.js.map +0 -1
- package/dist/boat/api-tester/src/cli.js.map +0 -1
- package/dist/boat/api-tester/src/config.js.map +0 -1
- package/dist/prompts/audit-rules.md +0 -124
- package/dist/src/action-result.js.map +0 -1
- package/dist/src/action.js.map +0 -1
- package/dist/src/activity.js.map +0 -1
- package/dist/src/ai/agent.js.map +0 -1
- package/dist/src/ai/bosun.js.map +0 -1
- package/dist/src/ai/captain/idle-mode.js.map +0 -1
- package/dist/src/ai/captain/mixin.js.map +0 -1
- package/dist/src/ai/captain/test-mode.js.map +0 -1
- package/dist/src/ai/captain/web-mode.js.map +0 -1
- package/dist/src/ai/captain.js.map +0 -1
- package/dist/src/ai/conversation.js.map +0 -1
- package/dist/src/ai/experience-compactor.js.map +0 -1
- package/dist/src/ai/fisherman-tools.js.map +0 -1
- package/dist/src/ai/fisherman.js.map +0 -1
- package/dist/src/ai/historian.js.map +0 -1
- package/dist/src/ai/navigator.js.map +0 -1
- package/dist/src/ai/pilot.js.map +0 -1
- package/dist/src/ai/planner/session-dedup.js.map +0 -1
- package/dist/src/ai/planner/styles.js.map +0 -1
- package/dist/src/ai/planner/subpages.js.map +0 -1
- package/dist/src/ai/planner.js.map +0 -1
- package/dist/src/ai/provider.js.map +0 -1
- package/dist/src/ai/quartermaster.js.map +0 -1
- package/dist/src/ai/researcher/cache.js.map +0 -1
- package/dist/src/ai/researcher/coordinates.js.map +0 -1
- package/dist/src/ai/researcher/deep-analysis.js.map +0 -1
- package/dist/src/ai/researcher/fingerprint-worker.js.map +0 -1
- package/dist/src/ai/researcher/focus.js.map +0 -1
- package/dist/src/ai/researcher/locators.js.map +0 -1
- package/dist/src/ai/researcher/mixin.js.map +0 -1
- package/dist/src/ai/researcher/parser.js.map +0 -1
- package/dist/src/ai/researcher/research-result.js.map +0 -1
- package/dist/src/ai/researcher.js.map +0 -1
- package/dist/src/ai/rules.js.map +0 -1
- package/dist/src/ai/task-agent.js.map +0 -1
- package/dist/src/ai/tester.js.map +0 -1
- package/dist/src/ai/tools.js.map +0 -1
- package/dist/src/api/api-client.js.map +0 -1
- package/dist/src/api/request-result.js.map +0 -1
- package/dist/src/api/request-store.js.map +0 -1
- package/dist/src/api/spec-reader.js.map +0 -1
- package/dist/src/api/xhr-capture.js.map +0 -1
- package/dist/src/browser-server.js.map +0 -1
- package/dist/src/command-handler.js.map +0 -1
- package/dist/src/commands/add-rule-command.js.map +0 -1
- package/dist/src/commands/base-command.js.map +0 -1
- package/dist/src/commands/clean-command.js.map +0 -1
- package/dist/src/commands/context-aria-command.js.map +0 -1
- package/dist/src/commands/context-command.js.map +0 -1
- package/dist/src/commands/context-data-command.js.map +0 -1
- package/dist/src/commands/context-experience-command.js.map +0 -1
- package/dist/src/commands/context-html-command.js.map +0 -1
- package/dist/src/commands/context-knowledge-command.js.map +0 -1
- package/dist/src/commands/debug-command.js.map +0 -1
- package/dist/src/commands/drill-command.js.map +0 -1
- package/dist/src/commands/exit-command.js.map +0 -1
- package/dist/src/commands/explore-command.js.map +0 -1
- package/dist/src/commands/freesail-command.js.map +0 -1
- package/dist/src/commands/help-command.js.map +0 -1
- package/dist/src/commands/index.js.map +0 -1
- package/dist/src/commands/knows-command.js.map +0 -1
- package/dist/src/commands/learn-command.js.map +0 -1
- package/dist/src/commands/navigate-command.js.map +0 -1
- package/dist/src/commands/path-command.js.map +0 -1
- package/dist/src/commands/plan-clear-command.js.map +0 -1
- package/dist/src/commands/plan-command.js.map +0 -1
- package/dist/src/commands/plan-edit-command.js.map +0 -1
- package/dist/src/commands/plan-load-command.js.map +0 -1
- package/dist/src/commands/plan-reload-command.js.map +0 -1
- package/dist/src/commands/plan-save-command.js.map +0 -1
- package/dist/src/commands/research-command.js.map +0 -1
- package/dist/src/commands/start-command.js.map +0 -1
- package/dist/src/commands/status-command.js.map +0 -1
- package/dist/src/commands/test-command.js.map +0 -1
- package/dist/src/components/ActivityPane.js.map +0 -1
- package/dist/src/components/AddKnowledge.js.map +0 -1
- package/dist/src/components/AddRule.js.map +0 -1
- package/dist/src/components/App.js.map +0 -1
- package/dist/src/components/Autocomplete.js.map +0 -1
- package/dist/src/components/InputPane.js.map +0 -1
- package/dist/src/components/InputReadline.js.map +0 -1
- package/dist/src/components/LogPane.js.map +0 -1
- package/dist/src/components/PlanEditor.js.map +0 -1
- package/dist/src/components/PlanPane.js.map +0 -1
- package/dist/src/components/SessionTimer.js.map +0 -1
- package/dist/src/components/StateTransitionPane.js.map +0 -1
- package/dist/src/components/StatusPane.js.map +0 -1
- package/dist/src/components/TaskPane.js.map +0 -1
- package/dist/src/components/Welcome.js.map +0 -1
- package/dist/src/components/WelcomeChecklist.js.map +0 -1
- package/dist/src/components/WelcomeCommands.js.map +0 -1
- package/dist/src/components/autocomplete-store.js.map +0 -1
- package/dist/src/components/parse-keypress.js.map +0 -1
- package/dist/src/config.js.map +0 -1
- package/dist/src/execution-controller.js.map +0 -1
- package/dist/src/experience-tracker.js.map +0 -1
- package/dist/src/explorbot.js.map +0 -1
- package/dist/src/explorer.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/knowledge-tracker.js.map +0 -1
- package/dist/src/observability.js.map +0 -1
- package/dist/src/reporter.js.map +0 -1
- package/dist/src/state-manager.js.map +0 -1
- package/dist/src/stats.js.map +0 -1
- package/dist/src/test-plan.js.map +0 -1
- package/dist/src/utils/aria.js.map +0 -1
- package/dist/src/utils/code-extractor.js.map +0 -1
- package/dist/src/utils/context-formatter.js.map +0 -1
- package/dist/src/utils/error-page.js.map +0 -1
- package/dist/src/utils/expandable.js.map +0 -1
- package/dist/src/utils/hooks-runner.js.map +0 -1
- package/dist/src/utils/html-diff.js.map +0 -1
- package/dist/src/utils/html.js.map +0 -1
- package/dist/src/utils/logger.js.map +0 -1
- package/dist/src/utils/loop.js.map +0 -1
- package/dist/src/utils/markdown-parser.js.map +0 -1
- package/dist/src/utils/markdown-query.js.map +0 -1
- package/dist/src/utils/markdown-terminal.js.map +0 -1
- package/dist/src/utils/research-parser.js.map +0 -1
- package/dist/src/utils/retry.js.map +0 -1
- package/dist/src/utils/rules-loader.js.map +0 -1
- package/dist/src/utils/strings.js.map +0 -1
- package/dist/src/utils/test-plan-markdown.js.map +0 -1
- package/dist/src/utils/throttle.js.map +0 -1
- package/dist/src/utils/unique-names.js.map +0 -1
- package/dist/src/utils/url-matcher.js.map +0 -1
- package/dist/src/utils/web-element.js.map +0 -1
- package/dist/src/utils/xpath.js.map +0 -1
- package/prompts/audit-rules.md +0 -124
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import dedent from 'dedent';
|
|
2
|
+
import { ActionResult, type Diff } from '../../action-result.js';
|
|
3
|
+
import type Explorer from '../../explorer.ts';
|
|
4
|
+
import type { StateManager } from '../../state-manager.js';
|
|
5
|
+
import { WebPageState } from '../../state-manager.js';
|
|
6
|
+
import { diffAriaSnapshots } from '../../utils/aria.ts';
|
|
7
|
+
import { executionController } from '../../execution-controller.ts';
|
|
8
|
+
import { tag } from '../../utils/logger.js';
|
|
9
|
+
import type { Provider } from '../provider.js';
|
|
10
|
+
import { type Constructor, debugLog } from './mixin.ts';
|
|
11
|
+
import { type ResearchElement, parseResearchSections } from './parser.ts';
|
|
12
|
+
import type { ResearchResult } from './research-result.ts';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_MAX_EXPANDABLE_CLICKS = 10;
|
|
15
|
+
|
|
16
|
+
export function WithDeepAnalysis<T extends Constructor>(Base: T) {
|
|
17
|
+
return class extends Base {
|
|
18
|
+
declare explorer: Explorer;
|
|
19
|
+
declare provider: Provider;
|
|
20
|
+
declare stateManager: StateManager;
|
|
21
|
+
declare actionResult: ActionResult | undefined;
|
|
22
|
+
|
|
23
|
+
async performDeepAnalysis(state: WebPageState, result: ResearchResult): Promise<void> {
|
|
24
|
+
tag('info').log('Starting deep analysis of expandable elements');
|
|
25
|
+
await (this as any).navigateTo(state.url);
|
|
26
|
+
|
|
27
|
+
let expandables = await this._discoverExpandables(result.text);
|
|
28
|
+
if (expandables.length === 0) {
|
|
29
|
+
tag('info').log('No expandable elements identified by AI');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
tag('substep').log(`Identified ${expandables.length} expandable elements`);
|
|
33
|
+
|
|
34
|
+
const maxClicks = (this.explorer.getConfig().ai?.agents?.researcher as any)?.maxExpandableClicks ?? DEFAULT_MAX_EXPANDABLE_CLICKS;
|
|
35
|
+
if (expandables.length > maxClicks) {
|
|
36
|
+
expandables = await this._selectExpandables(expandables, state.url, maxClicks);
|
|
37
|
+
tag('substep').log(`Selected ${expandables.length} expandables to click (max: ${maxClicks})`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const elements = expandables
|
|
41
|
+
.map((el) => ({
|
|
42
|
+
commands: this._buildClickCommands(el),
|
|
43
|
+
description: el.name,
|
|
44
|
+
}))
|
|
45
|
+
.filter((el) => el.commands.length > 0);
|
|
46
|
+
|
|
47
|
+
if (elements.length === 0) {
|
|
48
|
+
tag('info').log('No expandables with valid locators');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const expandableRows = elements.map((el) => `| ${el.description} | \`${el.commands[0]}\` |`).join('\n');
|
|
53
|
+
result.text += `\n\n# Expandables\n\n| Element | Action |\n|---------|--------|\n${expandableRows}`;
|
|
54
|
+
|
|
55
|
+
for (const el of elements) debugLog(`Expandable: ${el.description} → ${el.commands[0]}`);
|
|
56
|
+
tag('substep').log(`Clicking ${elements.length} expandable elements`);
|
|
57
|
+
|
|
58
|
+
const expandedSections: string[] = [];
|
|
59
|
+
const navigationLinks: Array<{ code: string; url: string }> = [];
|
|
60
|
+
|
|
61
|
+
await this._clickExpandableElements(elements, state, expandedSections, navigationLinks);
|
|
62
|
+
|
|
63
|
+
tag('info').log(`Deep analysis complete. Sections: ${expandedSections.length}, navigation links: ${navigationLinks.length}`);
|
|
64
|
+
|
|
65
|
+
const dedupedSections = this._deduplicateExpandedSections(expandedSections);
|
|
66
|
+
if (dedupedSections.length !== expandedSections.length) {
|
|
67
|
+
tag('substep').log(`Deduplicated ${expandedSections.length} → ${dedupedSections.length} extended sections`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (dedupedSections.length > 0) {
|
|
71
|
+
result.text += `\n\n# Extended Research\n\n${dedupedSections.join('\n\n---\n\n')}`;
|
|
72
|
+
}
|
|
73
|
+
if (navigationLinks.length > 0) {
|
|
74
|
+
const links = navigationLinks.map((l) => `- \`${l.code}\` opens ${l.url}`).join('\n');
|
|
75
|
+
result.text += `\n\n## Navigation Links\n\n${links}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async _discoverExpandables(researchText: string): Promise<ExpandableElement[]> {
|
|
80
|
+
const allElements = new Map<number, ExpandableElement>();
|
|
81
|
+
for (const section of parseResearchSections(researchText)) {
|
|
82
|
+
for (const el of section.elements) {
|
|
83
|
+
if (el.eidx != null) allElements.set(el.eidx, { ...el, container: section.containerCss });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (allElements.size === 0) return [];
|
|
87
|
+
|
|
88
|
+
const eidxList = [...allElements.keys()].join(', ');
|
|
89
|
+
|
|
90
|
+
const textPrompt = dedent`
|
|
91
|
+
From this UI research, identify elements that could reveal hidden UI when clicked
|
|
92
|
+
(dropdown menus, popups, expandable panels, accordion sections, overflow menus, tab switches).
|
|
93
|
+
|
|
94
|
+
Available eidx numbers: ${eidxList}
|
|
95
|
+
|
|
96
|
+
${researchText}
|
|
97
|
+
|
|
98
|
+
Rules:
|
|
99
|
+
- Only pick elements that HIDE content until clicked (menus, dropdowns, accordions, tabs)
|
|
100
|
+
- Skip regular links, data items, and navigation
|
|
101
|
+
- For repeated elements (same expand button on every row), pick only the FIRST one
|
|
102
|
+
- Respond with comma-separated eidx numbers only, e.g.: 3, 7, 15
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const model = this.provider.getModelForAgent('researcher');
|
|
106
|
+
const textCall = this.provider.chat([{ role: 'user', content: textPrompt }], model, {
|
|
107
|
+
agentName: 'researcher',
|
|
108
|
+
telemetryFunctionId: 'researcher.discoverExpandables.text',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let visionCall: Promise<{ text?: string } | null> = Promise.resolve(null);
|
|
112
|
+
const screenshot = this.actionResult?.screenshot;
|
|
113
|
+
if (screenshot && this.provider.hasVision()) {
|
|
114
|
+
const visionPrompt = dedent`
|
|
115
|
+
This screenshot has interactive elements labeled with eidx numbers (solid bordered boxes with numbers).
|
|
116
|
+
Identify elements that could reveal hidden UI when clicked.
|
|
117
|
+
|
|
118
|
+
Look for: overflow/ellipsis menus, chevron dropdowns, hamburger menus,
|
|
119
|
+
gear/settings buttons, accordion toggles, tab switches, filter buttons.
|
|
120
|
+
|
|
121
|
+
Rules:
|
|
122
|
+
- For repeated icons (same icon on every list row), pick only the FIRST one
|
|
123
|
+
- Skip regular text buttons, links, and navigation items
|
|
124
|
+
- Respond with comma-separated eidx numbers only, e.g.: 3, 7, 15
|
|
125
|
+
`;
|
|
126
|
+
visionCall = this.provider.processImage(visionPrompt, screenshot.toString('base64'));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const [textRes, visionRes] = await Promise.all([textCall, visionCall]);
|
|
130
|
+
|
|
131
|
+
const eidxSet = new Set<number>();
|
|
132
|
+
for (const res of [textRes, visionRes]) {
|
|
133
|
+
if (!res?.text) continue;
|
|
134
|
+
const nums = res.text.match(/\d+/g)?.map(Number) || [];
|
|
135
|
+
for (const n of nums) {
|
|
136
|
+
if (allElements.has(n)) eidxSet.add(n);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const textNums =
|
|
141
|
+
textRes?.text
|
|
142
|
+
?.match(/\d+/g)
|
|
143
|
+
?.map(Number)
|
|
144
|
+
.filter((n) => allElements.has(n)) || [];
|
|
145
|
+
const visionNums =
|
|
146
|
+
visionRes?.text
|
|
147
|
+
?.match(/\d+/g)
|
|
148
|
+
?.map(Number)
|
|
149
|
+
.filter((n) => allElements.has(n)) || [];
|
|
150
|
+
debugLog(`Text model picked eidx: [${textNums.join(', ')}], Vision model picked eidx: [${visionNums.join(', ')}]`);
|
|
151
|
+
|
|
152
|
+
return [...eidxSet].map((eidx) => allElements.get(eidx)!);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _buildClickCommands(el: ExpandableElement): string[] {
|
|
156
|
+
const commands: string[] = [];
|
|
157
|
+
const escapeQuote = (s: string) => s.replace(/'/g, "\\'");
|
|
158
|
+
const hasAriaText = el.aria && /\w/.test(el.aria.text) && el.aria.text !== '-';
|
|
159
|
+
if (hasAriaText && el.container) {
|
|
160
|
+
commands.push(`I.click(${JSON.stringify(el.aria)}, '${escapeQuote(el.container)}')`);
|
|
161
|
+
}
|
|
162
|
+
if (hasAriaText) {
|
|
163
|
+
commands.push(`I.click(${JSON.stringify(el.aria)})`);
|
|
164
|
+
}
|
|
165
|
+
if (el.css && el.container) {
|
|
166
|
+
commands.push(`I.click('${escapeQuote(el.css)}', '${escapeQuote(el.container)}')`);
|
|
167
|
+
}
|
|
168
|
+
if (el.css) {
|
|
169
|
+
commands.push(`I.click('${escapeQuote(el.css)}')`);
|
|
170
|
+
}
|
|
171
|
+
if (el.coordinates) {
|
|
172
|
+
const match = el.coordinates.match(/\((\d+),\s*(\d+)\)/);
|
|
173
|
+
if (match && Number(match[1]) > 1 && Number(match[2]) > 1) {
|
|
174
|
+
commands.push(`I.clickXY(${match[1]}, ${match[2]})`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return commands;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async _selectExpandables(expandables: ExpandableElement[], url: string, maxClicks: number): Promise<ExpandableElement[]> {
|
|
181
|
+
const list = expandables.map((el, i) => `${i + 1}. ${el.name} ${el.aria ? JSON.stringify(el.aria) : el.css || ''}`).join('\n');
|
|
182
|
+
|
|
183
|
+
const prompt = dedent`
|
|
184
|
+
Page: ${url}
|
|
185
|
+
|
|
186
|
+
These expandable elements were found on the page.
|
|
187
|
+
Select up to ${maxClicks} elements worth clicking to discover hidden UI.
|
|
188
|
+
|
|
189
|
+
${list}
|
|
190
|
+
|
|
191
|
+
Rules:
|
|
192
|
+
- Prioritize overflow/ellipsis menus, settings dropdowns, and toolbar buttons
|
|
193
|
+
- Skip repeated expand icons on list rows — keep only the first
|
|
194
|
+
- Skip global navigation, sidebar menus, user profile menus
|
|
195
|
+
- Respond with comma-separated numbers to keep, e.g.: 1, 3, 5
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
const model = this.provider.getModelForAgent('researcher');
|
|
199
|
+
const r = await this.provider.chat([{ role: 'user', content: prompt }], model, {
|
|
200
|
+
agentName: 'researcher',
|
|
201
|
+
telemetryFunctionId: 'researcher.selectExpandables',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const nums = (r.text || '').match(/\d+/g)?.map(Number) || [];
|
|
205
|
+
const selected = expandables.filter((_, i) => nums.includes(i + 1));
|
|
206
|
+
return selected.length > 0 ? selected.slice(0, maxClicks) : expandables.slice(0, maxClicks);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private async _clickExpandableElements(elements: Array<{ commands: string[]; description: string }>, state: WebPageState, expandedSections: string[], navigationLinks: Array<{ code: string; url: string }>): Promise<void> {
|
|
210
|
+
const originalAria = state.ariaSnapshot || '';
|
|
211
|
+
|
|
212
|
+
for (const el of elements) {
|
|
213
|
+
if (executionController.isInterrupted()) break;
|
|
214
|
+
try {
|
|
215
|
+
debugLog(`Clicking: ${el.description.slice(0, 100)}`);
|
|
216
|
+
const previousState = ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
217
|
+
|
|
218
|
+
const isCoordinateClick = el.commands[0].startsWith('I.clickXY(');
|
|
219
|
+
if (!isCoordinateClick) {
|
|
220
|
+
const hoverCmd = el.commands[0].replace('I.click(', 'I.moveCursorTo(');
|
|
221
|
+
const hoverAction = this.explorer.createAction();
|
|
222
|
+
await hoverAction.attempt(hoverCmd, undefined, false);
|
|
223
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
224
|
+
|
|
225
|
+
await this.explorer.createAction().capturePageState();
|
|
226
|
+
const hoverAR = ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
227
|
+
const hoverDiff = await hoverAR.diff(previousState);
|
|
228
|
+
await hoverDiff.calculate();
|
|
229
|
+
const hoverHtmlSize = hoverDiff.htmlParts.reduce((sum, p) => sum + p.subtree.length, 0);
|
|
230
|
+
const hoverRevealed = hoverDiff.ariaChanged && hoverHtmlSize > 500;
|
|
231
|
+
|
|
232
|
+
if (hoverRevealed) {
|
|
233
|
+
const sectionMarkdown = await this._analyzeExpandedAction(hoverCmd, el.description, hoverDiff, this._summarizeExpanded(expandedSections));
|
|
234
|
+
if (sectionMarkdown) {
|
|
235
|
+
expandedSections.push(sectionMarkdown);
|
|
236
|
+
debugLog(`Captured section from hover: ${el.description.slice(0, 80)}`);
|
|
237
|
+
await this._restorePageState(state.url, originalAria);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
await this._restorePageState(state.url, originalAria);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let clickCode: string | null = null;
|
|
245
|
+
const action = this.explorer.createAction();
|
|
246
|
+
for (const cmd of el.commands) {
|
|
247
|
+
if (await action.attempt(cmd, undefined, false)) {
|
|
248
|
+
clickCode = cmd;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!clickCode) {
|
|
253
|
+
debugLog(`Click failed: ${el.description.slice(0, 80)}`);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
258
|
+
|
|
259
|
+
let diff: Diff;
|
|
260
|
+
try {
|
|
261
|
+
await this.explorer.createAction().capturePageState();
|
|
262
|
+
const currAR = ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
263
|
+
diff = await currAR.diff(previousState);
|
|
264
|
+
await diff.calculate();
|
|
265
|
+
} catch (err) {
|
|
266
|
+
tag('warning').log(`State capture failed after click: ${err instanceof Error ? err.message : err}`);
|
|
267
|
+
await this._restorePageState(state.url, originalAria);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (diff.urlHasChanged()) {
|
|
272
|
+
debugLog(`Click navigated to ${this.stateManager.getCurrentState()?.url}`);
|
|
273
|
+
navigationLinks.push({ code: clickCode, url: this.stateManager.getCurrentState()?.url || '' });
|
|
274
|
+
await (this as any).navigateTo(state.url);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const clickHtmlSize = diff.htmlParts.reduce((sum, p) => sum + p.subtree.length, 0);
|
|
279
|
+
if (!diff.ariaChanged && clickHtmlSize <= 150) {
|
|
280
|
+
debugLog(`No changes from: ${el.description.slice(0, 80)}`);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const sectionMarkdown = await this._analyzeExpandedAction(clickCode, el.description, diff, this._summarizeExpanded(expandedSections));
|
|
285
|
+
if (sectionMarkdown) {
|
|
286
|
+
expandedSections.push(sectionMarkdown);
|
|
287
|
+
debugLog(`Captured section from: ${el.description.slice(0, 80)}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await this._restorePageState(state.url, originalAria);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
tag('warning').log(`Expandable click failed for "${el.description.slice(0, 80)}": ${err instanceof Error ? err.message : err}`);
|
|
293
|
+
try {
|
|
294
|
+
await this._restorePageState(state.url, originalAria);
|
|
295
|
+
} catch {}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async _restorePageState(url: string, originalAria: string): Promise<void> {
|
|
301
|
+
try {
|
|
302
|
+
await (this as any).cancelInUi();
|
|
303
|
+
await this.explorer.createAction().capturePageState();
|
|
304
|
+
const currentAria = this.stateManager.getCurrentState()?.ariaSnapshot || '';
|
|
305
|
+
if (!diffAriaSnapshots(originalAria, currentAria)) return;
|
|
306
|
+
} catch (err) {
|
|
307
|
+
tag('warning').log(`State capture failed after cancelInUi: ${err instanceof Error ? err.message : err}`);
|
|
308
|
+
}
|
|
309
|
+
debugLog('ARIA not restored after cancelInUi, reloading page');
|
|
310
|
+
try {
|
|
311
|
+
await (this as any).navigateTo(url);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
tag('warning').log(`navigateTo failed during restore: ${err instanceof Error ? err.message : err}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private async _analyzeExpandedAction(code: string, description: string, diff: Diff, alreadyExpanded: string[]): Promise<string | null> {
|
|
318
|
+
const alreadyHint = alreadyExpanded.length > 0 ? `\nAlready expanded sections:\n${alreadyExpanded.join('\n')}` : '';
|
|
319
|
+
|
|
320
|
+
const prompt = dedent`
|
|
321
|
+
An action on "${description}" (\`${code}\`) revealed new UI content.
|
|
322
|
+
Analyze the changes and produce a UI map section.
|
|
323
|
+
|
|
324
|
+
ARIA changes:
|
|
325
|
+
${diff.ariaChanged || 'none'}
|
|
326
|
+
|
|
327
|
+
HTML changes:
|
|
328
|
+
${diff.htmlParts.map((p) => `[Container: ${p.container}]\n${p.subtree}`).join('\n\n') || 'none'}
|
|
329
|
+
${alreadyHint}
|
|
330
|
+
|
|
331
|
+
Respond with a SINGLE section in this format:
|
|
332
|
+
|
|
333
|
+
### <Short descriptive name>
|
|
334
|
+
|
|
335
|
+
Action:
|
|
336
|
+
|
|
337
|
+
\`\`\`js
|
|
338
|
+
${code}
|
|
339
|
+
\`\`\`
|
|
340
|
+
|
|
341
|
+
<One sentence: what appeared — dropdown menu, modal, tab content, expanded panel, etc.>
|
|
342
|
+
|
|
343
|
+
| Element | ARIA | CSS |
|
|
344
|
+
|---------|------|-----|
|
|
345
|
+
| 'Save' | { role: 'button', text: 'Save' } | 'button.save' |
|
|
346
|
+
|
|
347
|
+
Rules:
|
|
348
|
+
- Only include interactive elements (buttons, links, inputs, selects, toggles)
|
|
349
|
+
- Exclude non-interactive elements (paragraphs, headings, static text, decorative icons) — describe them in the sentence above the table
|
|
350
|
+
- Provide CSS selectors derived from HTML classes, or ARIA locators — both are acceptable
|
|
351
|
+
- Do NOT expand similar list items — if the revealed content has the same structure as an already expanded section (same type of elements on a different list row), respond with "No meaningful expansion."
|
|
352
|
+
- If changes are minor (no new interactive elements), respond with "No meaningful expansion."
|
|
353
|
+
- If the revealed content is purely data items (list of records, entries, rows) with no new UI controls, respond with "No meaningful expansion."
|
|
354
|
+
- If you cannot clearly name what appeared, respond with "No meaningful expansion."
|
|
355
|
+
`;
|
|
356
|
+
|
|
357
|
+
const model = this.provider.getModelForAgent('researcher');
|
|
358
|
+
const r = await this.provider.chat([{ role: 'user', content: prompt }], model, { agentName: 'researcher', telemetryFunctionId: 'researcher.analyzeExpandedAction' });
|
|
359
|
+
const text = r.text || '';
|
|
360
|
+
|
|
361
|
+
if (text.toLowerCase().includes('no meaningful expansion')) return null;
|
|
362
|
+
|
|
363
|
+
const sections = parseResearchSections(text);
|
|
364
|
+
if (sections.length === 0) return null;
|
|
365
|
+
|
|
366
|
+
return sections[0].rawMarkdown;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private _deduplicateExpandedSections(sections: string[]): string[] {
|
|
370
|
+
const seen = new Set<string>();
|
|
371
|
+
const result: string[] = [];
|
|
372
|
+
for (const section of sections) {
|
|
373
|
+
const fingerprint = this._sectionFingerprint(section);
|
|
374
|
+
if (fingerprint && seen.has(fingerprint)) {
|
|
375
|
+
debugLog('Dedup: skipping duplicate extended section');
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (fingerprint) seen.add(fingerprint);
|
|
379
|
+
result.push(section);
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _summarizeExpanded(expandedSections: string[]): string[] {
|
|
385
|
+
return expandedSections
|
|
386
|
+
.map((s) => {
|
|
387
|
+
const info = this._parseSectionKeys(s);
|
|
388
|
+
if (!info) return null;
|
|
389
|
+
return `- ${info.name}: ${info.keys.join(', ')}`;
|
|
390
|
+
})
|
|
391
|
+
.filter(Boolean) as string[];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private _sectionFingerprint(sectionMarkdown: string): string | null {
|
|
395
|
+
const info = this._parseSectionKeys(sectionMarkdown);
|
|
396
|
+
if (!info) return null;
|
|
397
|
+
return [...info.keys].sort().join('|');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private _parseSectionKeys(sectionMarkdown: string): { name: string; keys: string[] } | null {
|
|
401
|
+
const parsed = parseResearchSections(sectionMarkdown);
|
|
402
|
+
if (parsed.length === 0 || parsed[0].elements.length === 0) return null;
|
|
403
|
+
const section = parsed[0];
|
|
404
|
+
return { name: section.name, keys: section.elements.map((el) => el.css || (el.aria ? `${el.aria.role}:${el.aria.text}` : null) || el.name) };
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
interface ExpandableElement extends ResearchElement {
|
|
410
|
+
container: string | null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export interface DeepAnalysisMethods {
|
|
414
|
+
performDeepAnalysis(state: WebPageState, result: ResearchResult): Promise<void>;
|
|
415
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { computeHtmlFingerprint } from '../../utils/html-diff.ts';
|
|
4
|
+
|
|
5
|
+
declare const self: Worker;
|
|
6
|
+
|
|
7
|
+
function diceSimilarity(a: Set<string>, b: Set<string>): number {
|
|
8
|
+
let intersection = 0;
|
|
9
|
+
for (const item of a) {
|
|
10
|
+
if (b.has(item)) intersection++;
|
|
11
|
+
}
|
|
12
|
+
const total = a.size + b.size;
|
|
13
|
+
if (total === 0) return 100;
|
|
14
|
+
return Math.round(((2 * intersection) / total) * 100);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
self.onmessage = (event: MessageEvent) => {
|
|
18
|
+
const { html, statesDir, maxAgeMs, threshold } = event.data as {
|
|
19
|
+
html: string;
|
|
20
|
+
statesDir: string;
|
|
21
|
+
maxAgeMs: number;
|
|
22
|
+
threshold: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (!existsSync(statesDir)) {
|
|
26
|
+
self.postMessage({ matchHash: null, similarity: 0 });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const currentFingerprint = new Set(computeHtmlFingerprint(html));
|
|
31
|
+
if (currentFingerprint.size === 0) {
|
|
32
|
+
self.postMessage({ matchHash: null, similarity: 0 });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const files = readdirSync(statesDir).filter((f) => f.endsWith('.fingerprint'));
|
|
38
|
+
|
|
39
|
+
let bestHash: string | null = null;
|
|
40
|
+
let bestSimilarity = 0;
|
|
41
|
+
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const filePath = join(statesDir, file);
|
|
44
|
+
const mtime = statSync(filePath).mtimeMs;
|
|
45
|
+
if (now - mtime > maxAgeMs) continue;
|
|
46
|
+
|
|
47
|
+
const lines = readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
|
|
48
|
+
const storedFingerprint = new Set(lines);
|
|
49
|
+
const similarity = diceSimilarity(currentFingerprint, storedFingerprint);
|
|
50
|
+
|
|
51
|
+
if (similarity > bestSimilarity) {
|
|
52
|
+
bestSimilarity = similarity;
|
|
53
|
+
bestHash = file.replace('.fingerprint', '');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const matched = bestSimilarity >= threshold;
|
|
58
|
+
self.postMessage({ matchHash: matched ? bestHash : null, similarity: bestSimilarity });
|
|
59
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { detectFocusArea } from '../../utils/aria.ts';
|
|
2
|
+
import { mdq } from '../../utils/markdown-query.ts';
|
|
3
|
+
import type { ResearchSection } from './parser.ts';
|
|
4
|
+
import type { ResearchResult } from './research-result.ts';
|
|
5
|
+
|
|
6
|
+
export const FOCUSED_MARKER = '> **Focused**';
|
|
7
|
+
const FOCUS_SKIP_SECTIONS = new Set(['navigation', 'menu']);
|
|
8
|
+
|
|
9
|
+
export function hasFocusedSection(text: string): boolean {
|
|
10
|
+
return text.includes(FOCUSED_MARKER);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function detectFocusFromAria(ariaSnapshot: string | null, sections: ResearchSection[]): string | null {
|
|
14
|
+
const focusArea = detectFocusArea(ariaSnapshot);
|
|
15
|
+
if (!focusArea.detected) return null;
|
|
16
|
+
|
|
17
|
+
if (focusArea.type === 'dialog' || focusArea.type === 'modal') {
|
|
18
|
+
const dialogSection = sections.find((s) => s.containerCss && (s.containerCss.includes('[role="dialog"]') || s.containerCss.includes('[role="alertdialog"]') || s.containerCss.includes('[aria-modal')));
|
|
19
|
+
if (dialogSection) return dialogSection.name;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function markSectionAsFocused(result: ResearchResult, sectionName: string): void {
|
|
26
|
+
if (hasFocusedSection(result.text)) return;
|
|
27
|
+
|
|
28
|
+
const escaped = sectionName.replace(/"/g, '\\"');
|
|
29
|
+
let sectionQuery = mdq(result.text).query(`section2(~"${escaped}")`);
|
|
30
|
+
if (sectionQuery.count() === 0) sectionQuery = mdq(result.text).query(`section3(~"${escaped}")`);
|
|
31
|
+
if (sectionQuery.count() === 0) return;
|
|
32
|
+
|
|
33
|
+
const containerBq = sectionQuery.query('blockquote[0]').text();
|
|
34
|
+
if (!containerBq) return;
|
|
35
|
+
|
|
36
|
+
result.text = result.text.replace(containerBq, `${containerBq}\n${FOCUSED_MARKER}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function pickDefaultFocusedSection(sections: ResearchSection[]): string | null {
|
|
40
|
+
const candidate = sections.find((s) => s.containerCss && !FOCUS_SKIP_SECTIONS.has(s.name.toLowerCase().replace(/^section:\s*/, '')));
|
|
41
|
+
return candidate?.name || null;
|
|
42
|
+
}
|