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,556 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import { ActionResult } from './action-result.js';
|
|
5
|
+
import { ConfigParser, outputPath } from './config.js';
|
|
6
|
+
import { ExperienceTracker } from './experience-tracker.js';
|
|
7
|
+
import { detectFocusArea } from './utils/aria.js';
|
|
8
|
+
import { htmlTextSnapshot } from './utils/html.js';
|
|
9
|
+
import { createDebug, tag } from './utils/logger.js';
|
|
10
|
+
import { extractStatePath } from './utils/url-matcher.js';
|
|
11
|
+
|
|
12
|
+
const debugLog = createDebug('explorbot:state');
|
|
13
|
+
|
|
14
|
+
export interface Link {
|
|
15
|
+
title: string;
|
|
16
|
+
url: string;
|
|
17
|
+
visible?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WebPageState {
|
|
21
|
+
/** Unique incremental state identifier */
|
|
22
|
+
id?: number;
|
|
23
|
+
/** URL path without domain, including hash: /path/to/page#section */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Page title */
|
|
26
|
+
title?: string;
|
|
27
|
+
/** Full URL for reference */
|
|
28
|
+
fullUrl?: string;
|
|
29
|
+
/** Timestamp when state was captured */
|
|
30
|
+
timestamp?: Date;
|
|
31
|
+
/** Hash of the state for unique identification */
|
|
32
|
+
hash?: string;
|
|
33
|
+
/** HTML file path */
|
|
34
|
+
htmlFile?: string;
|
|
35
|
+
/** Screenshot file path */
|
|
36
|
+
screenshotFile?: string;
|
|
37
|
+
/** Log file path */
|
|
38
|
+
logFile?: string;
|
|
39
|
+
/** HTML content */
|
|
40
|
+
html?: string;
|
|
41
|
+
|
|
42
|
+
notes?: string[];
|
|
43
|
+
/** Page headings */
|
|
44
|
+
h1?: string;
|
|
45
|
+
h2?: string;
|
|
46
|
+
h3?: string;
|
|
47
|
+
h4?: string;
|
|
48
|
+
ariaSnapshot?: string | null;
|
|
49
|
+
ariaSnapshotFile?: string;
|
|
50
|
+
links?: Link[];
|
|
51
|
+
verifications?: Record<string, boolean>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface StateTransition {
|
|
55
|
+
/** Previous state (null if this is the first state) */
|
|
56
|
+
fromState: WebPageState | null;
|
|
57
|
+
/** Current state */
|
|
58
|
+
toState: WebPageState;
|
|
59
|
+
/** Code block that caused the transition */
|
|
60
|
+
codeBlock: string;
|
|
61
|
+
/** Timestamp of the transition */
|
|
62
|
+
timestamp: Date;
|
|
63
|
+
/** Any error that occurred during transition */
|
|
64
|
+
error?: string;
|
|
65
|
+
/** What triggered the state change */
|
|
66
|
+
trigger: 'manual' | 'navigation' | 'automatic';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type StateChangeListener = (event: StateTransition) => void;
|
|
70
|
+
|
|
71
|
+
export interface Knowledge extends WebPageState {
|
|
72
|
+
/** File path */
|
|
73
|
+
filePath: string;
|
|
74
|
+
/** Markdown content */
|
|
75
|
+
content: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class StateManager {
|
|
79
|
+
private currentState: WebPageState | null = null;
|
|
80
|
+
private stateHistory: StateTransition[] = [];
|
|
81
|
+
private allVisitedUrls: Set<string> = new Set();
|
|
82
|
+
private knowledgeCache: Knowledge[] = [];
|
|
83
|
+
private lastKnowledgeScan: Date | null = null;
|
|
84
|
+
private stateChangeListeners: StateChangeListener[] = [];
|
|
85
|
+
private experienceTracker!: ExperienceTracker;
|
|
86
|
+
private knowledgeDir: string;
|
|
87
|
+
private nextStateId = 1;
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
this.experienceTracker = new ExperienceTracker();
|
|
91
|
+
const configParser = ConfigParser.getInstance();
|
|
92
|
+
const config = configParser.getConfig();
|
|
93
|
+
const configPath = configParser.getConfigPath();
|
|
94
|
+
|
|
95
|
+
// Resolve knowledge directory relative to the config file location (project root)
|
|
96
|
+
if (configPath) {
|
|
97
|
+
const projectRoot = dirname(configPath);
|
|
98
|
+
this.knowledgeDir = join(projectRoot, config.dirs?.knowledge || 'knowledge');
|
|
99
|
+
} else {
|
|
100
|
+
this.knowledgeDir = config.dirs?.knowledge || 'knowledge';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getExperienceTracker(): ExperienceTracker {
|
|
105
|
+
return this.experienceTracker;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Subscribe to state change events
|
|
110
|
+
*/
|
|
111
|
+
onStateChange(listener: StateChangeListener): () => void {
|
|
112
|
+
this.stateChangeListeners.push(listener);
|
|
113
|
+
|
|
114
|
+
// Return unsubscribe function
|
|
115
|
+
return () => {
|
|
116
|
+
const index = this.stateChangeListeners.indexOf(listener);
|
|
117
|
+
if (index > -1) {
|
|
118
|
+
this.stateChangeListeners.splice(index, 1);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Emit state change event to all listeners
|
|
125
|
+
*/
|
|
126
|
+
private emitStateChange(event: StateTransition): void {
|
|
127
|
+
// Log HTML content when state changes
|
|
128
|
+
if (event.toState.html && event.toState.html !== event.fromState?.html) {
|
|
129
|
+
let htmlContent = event?.toState?.html ?? '';
|
|
130
|
+
htmlContent = htmlTextSnapshot(htmlContent);
|
|
131
|
+
// tag('html').log(`Page HTML for ${event.toState.url}:\n${htmlContent}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.stateChangeListeners.forEach((listener) => {
|
|
135
|
+
try {
|
|
136
|
+
listener(event);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
debugLog('Error in state change listener:', error);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extract state path from full URL
|
|
145
|
+
* Removes domain, port, protocol, and query params
|
|
146
|
+
* Keeps path and hash: /path/to/page#section
|
|
147
|
+
*/
|
|
148
|
+
/**
|
|
149
|
+
* Update current state from ActionResult and record transition if state changed
|
|
150
|
+
*/
|
|
151
|
+
updateState(actionResult: ActionResult, codeBlock?: string, trigger: 'manual' | 'navigation' | 'automatic' = 'manual'): WebPageState {
|
|
152
|
+
const previousState = this.currentState;
|
|
153
|
+
const previousHash = previousState?.hash;
|
|
154
|
+
|
|
155
|
+
const newState = actionResult;
|
|
156
|
+
this.currentState = newState;
|
|
157
|
+
this.currentState.id = this.nextStateId++;
|
|
158
|
+
if (newState.url) this.allVisitedUrls.add(normalizeUrl(newState.url));
|
|
159
|
+
|
|
160
|
+
const hashChanged = actionResult.hash !== previousHash;
|
|
161
|
+
const dialogOpened = !hashChanged && this.hasDialogAppeared(previousState, newState);
|
|
162
|
+
|
|
163
|
+
if (hashChanged || dialogOpened) {
|
|
164
|
+
const transition: StateTransition = {
|
|
165
|
+
fromState: previousState,
|
|
166
|
+
toState: newState,
|
|
167
|
+
codeBlock: codeBlock || '',
|
|
168
|
+
timestamp: new Date(),
|
|
169
|
+
trigger,
|
|
170
|
+
};
|
|
171
|
+
this.stateHistory.push(transition);
|
|
172
|
+
this.emitStateChange(transition);
|
|
173
|
+
|
|
174
|
+
if (dialogOpened) {
|
|
175
|
+
debugLog('State change detected: modal dialog appeared');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
debugLog(`State updated: ${this.currentState.url} (${this.currentState.hash})`);
|
|
180
|
+
|
|
181
|
+
return newState;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Update state from basic data (for navigation events)
|
|
186
|
+
*/
|
|
187
|
+
updateStateFromBasic(url: string, title?: string, trigger: 'manual' | 'navigation' | 'automatic' = 'navigation'): WebPageState {
|
|
188
|
+
const path = extractStatePath(url) || '/';
|
|
189
|
+
|
|
190
|
+
// no extra navigation happened
|
|
191
|
+
if (normalizeUrl(this.currentState?.url || '') === normalizeUrl(path)) {
|
|
192
|
+
return this.currentState!;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const newState: WebPageState = {
|
|
196
|
+
id: this.nextStateId++,
|
|
197
|
+
url: path,
|
|
198
|
+
title: title || 'Unknown Page',
|
|
199
|
+
fullUrl: url,
|
|
200
|
+
timestamp: new Date(),
|
|
201
|
+
hash: this.generateBasicHash(path, title),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Create transition record
|
|
205
|
+
const transition: StateTransition = {
|
|
206
|
+
fromState: this.currentState,
|
|
207
|
+
toState: newState,
|
|
208
|
+
codeBlock: '',
|
|
209
|
+
timestamp: new Date(),
|
|
210
|
+
trigger,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
this.stateHistory.push(transition);
|
|
214
|
+
this.currentState = newState;
|
|
215
|
+
this.allVisitedUrls.add(normalizeUrl(newState.url));
|
|
216
|
+
|
|
217
|
+
this.emitStateChange(transition);
|
|
218
|
+
|
|
219
|
+
debugLog(`State updated from navigation: ${this.currentState.url} (${this.currentState.hash})`);
|
|
220
|
+
|
|
221
|
+
return newState;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private hasDialogAppeared(previousState: WebPageState | null, newState: WebPageState): boolean {
|
|
225
|
+
const prevFocus = detectFocusArea(previousState?.ariaSnapshot ?? null);
|
|
226
|
+
const newFocus = detectFocusArea(newState.ariaSnapshot ?? null);
|
|
227
|
+
return !prevFocus.detected && newFocus.detected;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate a basic hash for state comparison
|
|
232
|
+
*/
|
|
233
|
+
private generateBasicHash(url: string, title?: string): string {
|
|
234
|
+
const parts = [url];
|
|
235
|
+
if (title) {
|
|
236
|
+
parts.push(`title_${title}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return parts
|
|
240
|
+
.join('_')
|
|
241
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
242
|
+
.replace(/_+/g, '_')
|
|
243
|
+
.replace(/^_|_$/g, '')
|
|
244
|
+
.toLowerCase()
|
|
245
|
+
.substring(0, 200);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get current state
|
|
250
|
+
*/
|
|
251
|
+
getCurrentState(): WebPageState | null {
|
|
252
|
+
return this.currentState;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check if state has changed since a given state
|
|
257
|
+
*/
|
|
258
|
+
hasStateChanged(previousState: WebPageState | null): boolean {
|
|
259
|
+
if (!previousState && !this.currentState) return false;
|
|
260
|
+
if (!previousState || !this.currentState) return true;
|
|
261
|
+
return previousState.hash !== this.currentState.hash;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Compare two states by their hash
|
|
266
|
+
*/
|
|
267
|
+
statesEqual(state1: WebPageState | null, state2: WebPageState | null): boolean {
|
|
268
|
+
if (!state1 && !state2) return true;
|
|
269
|
+
if (!state1 || !state2) return false;
|
|
270
|
+
return state1.hash === state2.hash;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get state history
|
|
275
|
+
*/
|
|
276
|
+
getStateHistory(): StateTransition[] {
|
|
277
|
+
return [...this.stateHistory];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
isInDeadLoop(): boolean {
|
|
281
|
+
const minWindow = 6;
|
|
282
|
+
const increment = 2;
|
|
283
|
+
const stateHashes = this.stateHistory.map((transition) => {
|
|
284
|
+
const state = transition.toState;
|
|
285
|
+
return state.hash || this.generateBasicHash(state.url || '/', state.title);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
debugLog(`Current state hash: ${this.currentState?.hash}`);
|
|
289
|
+
debugLog(`State hashes: ${stateHashes.join(', ')}`);
|
|
290
|
+
|
|
291
|
+
if (stateHashes.length < minWindow) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const currentHash = this.currentState?.hash || stateHashes[stateHashes.length - 1];
|
|
296
|
+
if (!currentHash) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let windowSize = minWindow;
|
|
301
|
+
let uniqueLimit = 1;
|
|
302
|
+
|
|
303
|
+
while (windowSize <= stateHashes.length) {
|
|
304
|
+
const window = stateHashes.slice(-windowSize);
|
|
305
|
+
if (!window.includes(currentHash)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const unique = new Map<string, number>();
|
|
310
|
+
for (const hash of window) {
|
|
311
|
+
unique.set(hash, (unique.get(hash) || 0) + 1);
|
|
312
|
+
if (unique.size > uniqueLimit) {
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (unique.size <= uniqueLimit) {
|
|
318
|
+
debugLog(`DEAD LOOP DETECTED: ${window.join(', ')}`);
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
windowSize += increment;
|
|
323
|
+
uniqueLimit += 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get the last N transitions
|
|
331
|
+
*/
|
|
332
|
+
getRecentTransitions(count = 5): StateTransition[] {
|
|
333
|
+
return this.stateHistory.slice(-count);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Scan knowledge directory for .md files and cache them
|
|
338
|
+
*/
|
|
339
|
+
private scanKnowledgeFiles(): void {
|
|
340
|
+
const now = new Date();
|
|
341
|
+
|
|
342
|
+
// Only rescan every 30 seconds to avoid excessive file I/O
|
|
343
|
+
if (this.lastKnowledgeScan && now.getTime() - this.lastKnowledgeScan.getTime() < 30000) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this.knowledgeCache = [];
|
|
348
|
+
|
|
349
|
+
if (!existsSync(this.knowledgeDir)) {
|
|
350
|
+
debugLog(`Knowledge directory not found: ${this.knowledgeDir}`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
const files = readdirSync(this.knowledgeDir, { recursive: true })
|
|
356
|
+
.filter((file) => typeof file === 'string' && file.endsWith('.md'))
|
|
357
|
+
.map((file) => join(this.knowledgeDir, file as string));
|
|
358
|
+
|
|
359
|
+
for (const filePath of files) {
|
|
360
|
+
try {
|
|
361
|
+
const fileContent = readFileSync(filePath, 'utf8');
|
|
362
|
+
const parsed = matter(fileContent);
|
|
363
|
+
|
|
364
|
+
const urlPattern = parsed.data.url || parsed.data.path || '*';
|
|
365
|
+
|
|
366
|
+
this.knowledgeCache.push({
|
|
367
|
+
filePath,
|
|
368
|
+
url: urlPattern,
|
|
369
|
+
...parsed.data,
|
|
370
|
+
content: parsed.content,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
debugLog(`Loaded knowledge file: ${filePath} (pattern: ${urlPattern})`);
|
|
374
|
+
} catch (error) {
|
|
375
|
+
debugLog(`Failed to load knowledge file ${filePath}:`, error);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this.lastKnowledgeScan = now;
|
|
380
|
+
debugLog(`Scanned ${this.knowledgeCache.length} knowledge files`);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
debugLog('Failed to scan knowledge directory:', error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get relevant knowledge files for current state
|
|
387
|
+
*/
|
|
388
|
+
getRelevantKnowledge(): Knowledge[] {
|
|
389
|
+
if (!this.currentState) return [];
|
|
390
|
+
|
|
391
|
+
this.scanKnowledgeFiles();
|
|
392
|
+
|
|
393
|
+
const actionResult = ActionResult.fromState(this.currentState);
|
|
394
|
+
return this.knowledgeCache.filter((knowledge) => actionResult.isMatchedBy(knowledge));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get relevant experience files for current state
|
|
399
|
+
*/
|
|
400
|
+
getRelevantExperience(): string[] {
|
|
401
|
+
if (!this.currentState) {
|
|
402
|
+
return [];
|
|
403
|
+
}
|
|
404
|
+
const actionResult = ActionResult.fromState(this.currentState);
|
|
405
|
+
return this.experienceTracker.getRelevantExperience(actionResult).map((experience) => experience.content);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Get all context for current state (knowledge + experience)
|
|
410
|
+
*/
|
|
411
|
+
getCurrentContext(): {
|
|
412
|
+
state: WebPageState;
|
|
413
|
+
knowledge: Knowledge[];
|
|
414
|
+
experience: string[];
|
|
415
|
+
recentTransitions: StateTransition[];
|
|
416
|
+
} {
|
|
417
|
+
if (!this.currentState) {
|
|
418
|
+
throw new Error('No current state available');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
state: this.currentState,
|
|
423
|
+
knowledge: this.getRelevantKnowledge(),
|
|
424
|
+
experience: this.getRelevantExperience(),
|
|
425
|
+
recentTransitions: this.getRecentTransitions(),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Check if we've been in this state before
|
|
431
|
+
*/
|
|
432
|
+
hasVisitedState(path: string): boolean {
|
|
433
|
+
return this.allVisitedUrls.has(normalizeUrl(path));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
getAllVisitedUrls(): Set<string> {
|
|
437
|
+
return this.allVisitedUrls;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Get how many times we've visited a specific path
|
|
442
|
+
*/
|
|
443
|
+
getVisitCount(path: string): number {
|
|
444
|
+
return this.stateHistory.filter((transition) => normalizeUrl(transition.toState.url) === normalizeUrl(path)).length;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Find the most recent transition to a specific path
|
|
449
|
+
*/
|
|
450
|
+
getLastVisitToPath(path: string): StateTransition | null {
|
|
451
|
+
for (let i = this.stateHistory.length - 1; i >= 0; i--) {
|
|
452
|
+
if (normalizeUrl(this.stateHistory[i].toState.url) === normalizeUrl(path)) {
|
|
453
|
+
return this.stateHistory[i];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get previous state from history for comparison.
|
|
461
|
+
* If the last transition changed URL, returns the fromState (for URL change detection).
|
|
462
|
+
* Otherwise returns the most recent toState with content (for diffing).
|
|
463
|
+
*/
|
|
464
|
+
getPreviousState(): WebPageState | null {
|
|
465
|
+
if (this.stateHistory.length === 0) return null;
|
|
466
|
+
|
|
467
|
+
const lastTransition = this.stateHistory[this.stateHistory.length - 1];
|
|
468
|
+
|
|
469
|
+
if (lastTransition.fromState?.url !== lastTransition.toState?.url) {
|
|
470
|
+
return lastTransition.fromState;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
for (let i = this.stateHistory.length - 1; i >= 0; i--) {
|
|
474
|
+
const toState = this.stateHistory[i].toState;
|
|
475
|
+
|
|
476
|
+
if (!toState) continue;
|
|
477
|
+
if (toState.id === this.currentState?.id) continue;
|
|
478
|
+
|
|
479
|
+
if (toState.html || toState.ariaSnapshot) {
|
|
480
|
+
return toState;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Load HTML content from file
|
|
489
|
+
*/
|
|
490
|
+
loadHtmlFromFile(htmlFile: string): string | null {
|
|
491
|
+
try {
|
|
492
|
+
const filePath = outputPath('states', htmlFile);
|
|
493
|
+
if (existsSync(filePath)) {
|
|
494
|
+
return readFileSync(filePath, 'utf8');
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
debugLog('Failed to load HTML from file:', error);
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Clear state history (useful for testing or reset)
|
|
505
|
+
* Note: This preserves the current state, only clears navigation history
|
|
506
|
+
*/
|
|
507
|
+
clearHistory(): void {
|
|
508
|
+
this.stateHistory = [];
|
|
509
|
+
debugLog('State history cleared');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get the number of active state change listeners
|
|
514
|
+
*/
|
|
515
|
+
getListenerCount(): number {
|
|
516
|
+
return this.stateChangeListeners.length;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Clear all state change listeners
|
|
521
|
+
*/
|
|
522
|
+
clearListeners(): void {
|
|
523
|
+
this.stateChangeListeners = [];
|
|
524
|
+
debugLog('All state change listeners cleared');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Complete cleanup of the StateManager instance
|
|
529
|
+
* Clears all state, history, listeners, and caches
|
|
530
|
+
*/
|
|
531
|
+
cleanup(): void {
|
|
532
|
+
this.currentState = null;
|
|
533
|
+
this.stateHistory = [];
|
|
534
|
+
this.allVisitedUrls.clear();
|
|
535
|
+
this.stateChangeListeners = [];
|
|
536
|
+
this.knowledgeCache = [];
|
|
537
|
+
this.lastKnowledgeScan = null;
|
|
538
|
+
this.nextStateId = 1;
|
|
539
|
+
|
|
540
|
+
// Clean up experience tracker if it has cleanup method
|
|
541
|
+
if (this.experienceTracker && typeof this.experienceTracker.cleanup === 'function') {
|
|
542
|
+
this.experienceTracker.cleanup();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
debugLog('StateManager cleanup completed');
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export function normalizeUrl(url: string): string {
|
|
550
|
+
try {
|
|
551
|
+
const parsed = new URL(url, 'http://localhost');
|
|
552
|
+
return parsed.pathname.replace(/^\/+|\/+$/g, '');
|
|
553
|
+
} catch {
|
|
554
|
+
return url.replace(/^\/+|\/+$/g, '');
|
|
555
|
+
}
|
|
556
|
+
}
|
package/src/stats.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface TokenUsage {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
total: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class Stats {
|
|
8
|
+
static startTime = Date.now();
|
|
9
|
+
static researches = 0;
|
|
10
|
+
static tests = 0;
|
|
11
|
+
static plans = 0;
|
|
12
|
+
static models: Record<string, TokenUsage> = {};
|
|
13
|
+
|
|
14
|
+
static recordTokens(_agent: string, model: string, usage: TokenUsage): void {
|
|
15
|
+
if (!Stats.models[model]) {
|
|
16
|
+
Stats.models[model] = { input: 0, output: 0, total: 0 };
|
|
17
|
+
}
|
|
18
|
+
Stats.models[model].input += usage.input;
|
|
19
|
+
Stats.models[model].output += usage.output;
|
|
20
|
+
Stats.models[model].total += usage.total;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static getElapsedTime(): string {
|
|
24
|
+
const elapsed = Date.now() - Stats.startTime;
|
|
25
|
+
const seconds = Math.floor(elapsed / 1000) % 60;
|
|
26
|
+
const minutes = Math.floor(elapsed / 60000) % 60;
|
|
27
|
+
const hours = Math.floor(elapsed / 3600000);
|
|
28
|
+
|
|
29
|
+
if (hours > 0) {
|
|
30
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
31
|
+
}
|
|
32
|
+
if (minutes > 0) {
|
|
33
|
+
return `${minutes}m ${seconds}s`;
|
|
34
|
+
}
|
|
35
|
+
return `${seconds}s`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static humanizeTokens(num: number): string {
|
|
39
|
+
if (num >= 1_000_000) {
|
|
40
|
+
return `${(num / 1_000_000).toFixed(1)}M`;
|
|
41
|
+
}
|
|
42
|
+
if (num >= 1_000) {
|
|
43
|
+
return `${(num / 1_000).toFixed(0)}K`;
|
|
44
|
+
}
|
|
45
|
+
return String(num);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static hasActivity(): boolean {
|
|
49
|
+
if (Stats.tests > 0 || Stats.plans > 0 || Stats.researches > 0) return true;
|
|
50
|
+
const totalTokens = Object.values(Stats.models).reduce((sum, m) => sum + m.total, 0);
|
|
51
|
+
return totalTokens > 0;
|
|
52
|
+
}
|
|
53
|
+
}
|