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,504 @@
|
|
|
1
|
+
import { tool } from 'ai';
|
|
2
|
+
import dedent from 'dedent';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { ActionResult } from '../action-result.js';
|
|
5
|
+
import { ExperienceTracker } from '../experience-tracker.js';
|
|
6
|
+
import type { ExplorBot } from '../explorbot.ts';
|
|
7
|
+
import type { WebPageState } from '../state-manager.ts';
|
|
8
|
+
import { Task, Test } from '../test-plan.ts';
|
|
9
|
+
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
10
|
+
import { startLogCapture, stopLogCapture, tag } from '../utils/logger.js';
|
|
11
|
+
import { loop } from '../utils/loop.js';
|
|
12
|
+
import { truncateJson } from '../utils/strings.ts';
|
|
13
|
+
import type { Agent } from './agent.js';
|
|
14
|
+
import { WithIdleMode } from './captain/idle-mode.ts';
|
|
15
|
+
import { type CaptainMode, type ModeContext, debugLog } from './captain/mixin.ts';
|
|
16
|
+
import { WithTestMode } from './captain/test-mode.ts';
|
|
17
|
+
import { WithWebMode } from './captain/web-mode.ts';
|
|
18
|
+
import { type Conversation, toolExecutionLabel } from './conversation.js';
|
|
19
|
+
import type { Navigator } from './navigator.ts';
|
|
20
|
+
import type { Provider } from './provider.ts';
|
|
21
|
+
import { Researcher } from './researcher.ts';
|
|
22
|
+
import { TaskAgent } from './task-agent.ts';
|
|
23
|
+
|
|
24
|
+
const MAX_STEPS = 15;
|
|
25
|
+
|
|
26
|
+
const CaptainBase = WithTestMode(WithWebMode(WithIdleMode(TaskAgent as unknown as new (...args: any[]) => TaskAgent)));
|
|
27
|
+
|
|
28
|
+
export class Captain extends CaptainBase implements Agent {
|
|
29
|
+
protected readonly ACTION_TOOLS = ['click', 'pressKey', 'form', 'navigate'];
|
|
30
|
+
emoji = '🧑✈️';
|
|
31
|
+
private explorBot: ExplorBot;
|
|
32
|
+
private conversation: Conversation | null = null;
|
|
33
|
+
private experienceTracker: ExperienceTracker;
|
|
34
|
+
private hooksRunner: HooksRunner | null = null;
|
|
35
|
+
private commandExecutor: ((cmd: string) => Promise<void>) | null = null;
|
|
36
|
+
private commandDescriptions: { name: string; description: string; options: string }[] = [];
|
|
37
|
+
|
|
38
|
+
constructor(explorBot: ExplorBot) {
|
|
39
|
+
super();
|
|
40
|
+
this.explorBot = explorBot;
|
|
41
|
+
this.experienceTracker = new ExperienceTracker();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setCommandExecutor(fn: (cmd: string) => Promise<void>, descriptions: { name: string; description: string; options: string }[]): void {
|
|
45
|
+
this.commandExecutor = fn;
|
|
46
|
+
this.commandDescriptions = descriptions;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private getHooksRunner(): HooksRunner {
|
|
50
|
+
if (!this.hooksRunner) {
|
|
51
|
+
const explorer = this.explorBot.getExplorer();
|
|
52
|
+
this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
|
|
53
|
+
}
|
|
54
|
+
return this.hooksRunner;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected getNavigator(): Navigator {
|
|
58
|
+
return this.explorBot.agentNavigator();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected getExperienceTracker(): ExperienceTracker {
|
|
62
|
+
return this.experienceTracker;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected getKnowledgeTracker() {
|
|
66
|
+
return this.explorBot.getExplorer().getKnowledgeTracker();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected getProvider(): Provider {
|
|
70
|
+
return this.explorBot.getProvider();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected trackToolExecutions(toolExecutions: any[]): void {
|
|
74
|
+
super.trackToolExecutions(toolExecutions);
|
|
75
|
+
for (const exec of toolExecutions) {
|
|
76
|
+
const label = toolExecutionLabel(exec.input);
|
|
77
|
+
if (!label) continue;
|
|
78
|
+
const icon = exec.wasSuccessful ? '→' : '✗';
|
|
79
|
+
tag('substep').log(`${icon} ${label}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private detectMode(): CaptainMode {
|
|
84
|
+
if (this.explorBot.getExplorer().activeTest) return 'test';
|
|
85
|
+
if (this.explorBot.getExplorer().getStateManager().getCurrentState()) return 'web';
|
|
86
|
+
return 'idle';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private systemPrompt(): string {
|
|
90
|
+
const mode = this.detectMode();
|
|
91
|
+
const currentUrl = this.explorBot.getExplorer().getStateManager().getCurrentState()?.url;
|
|
92
|
+
const customPrompt = this.explorBot.getProvider().getSystemPromptForAgent('captain', currentUrl);
|
|
93
|
+
|
|
94
|
+
return dedent`
|
|
95
|
+
<role>
|
|
96
|
+
You are Captain — a smart assistant for the testing session.
|
|
97
|
+
Current mode: ${mode}. ${mode === 'test' ? 'A test is running.' : ''}
|
|
98
|
+
</role>
|
|
99
|
+
|
|
100
|
+
<modes>
|
|
101
|
+
- idle: plan management, file operations, knowledge. Always available.
|
|
102
|
+
- web: page interaction, navigation, browser diagnostics. When working with a web page.
|
|
103
|
+
- test: test analysis, state inspection. When a test is running or analyzing results.
|
|
104
|
+
</modes>
|
|
105
|
+
|
|
106
|
+
${this.idleModePrompt()}
|
|
107
|
+
${mode === 'web' ? this.webModePrompt() : ''}
|
|
108
|
+
${mode === 'test' ? this.testModePrompt() : ''}
|
|
109
|
+
|
|
110
|
+
<rules>
|
|
111
|
+
- After a successful action, if the pageDiff confirms the goal, call done() immediately — do not verify with see() or context() unless the user explicitly asked for verification
|
|
112
|
+
- Prefer completing in fewer tool calls over thoroughness
|
|
113
|
+
- NEVER run tests unless the user explicitly asks
|
|
114
|
+
${mode === 'web' ? this.webModeRules() : ''}
|
|
115
|
+
${mode === 'test' ? this.testModeRules() : ''}
|
|
116
|
+
</rules>
|
|
117
|
+
|
|
118
|
+
${customPrompt || ''}
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private resetConversation(): Conversation {
|
|
123
|
+
const agenticModel = this.explorBot.getProvider().getAgenticModel('captain');
|
|
124
|
+
this.conversation = this.explorBot.getProvider().startConversation(this.systemPrompt(), 'captain', agenticModel);
|
|
125
|
+
return this.conversation;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private ensureConversation(): Conversation {
|
|
129
|
+
if (!this.conversation) {
|
|
130
|
+
return this.resetConversation();
|
|
131
|
+
}
|
|
132
|
+
return this.conversation;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getConversation(): Conversation | null {
|
|
136
|
+
return this.conversation;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
cleanConversation(): void {
|
|
140
|
+
this.conversation = null;
|
|
141
|
+
tag('info').log('Conversation cleaned');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async getPageContext(): Promise<string> {
|
|
145
|
+
const state = this.explorBot.getExplorer().getStateManager().getCurrentState();
|
|
146
|
+
if (!state) {
|
|
147
|
+
return 'No page loaded';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const actionResult = ActionResult.fromState(state);
|
|
151
|
+
const knowledge = this.getKnowledge(actionResult);
|
|
152
|
+
const experience = this.getExperience(actionResult);
|
|
153
|
+
|
|
154
|
+
const headingLines: string[] = [];
|
|
155
|
+
if (state.h1) headingLines.push(`H1: ${state.h1}`);
|
|
156
|
+
if (state.h2) headingLines.push(`H2: ${state.h2}`);
|
|
157
|
+
if (state.h3) headingLines.push(`H3: ${state.h3}`);
|
|
158
|
+
if (state.h4) headingLines.push(`H4: ${state.h4}`);
|
|
159
|
+
const headingsBlock = headingLines.join('\n');
|
|
160
|
+
|
|
161
|
+
let pageSummary = '';
|
|
162
|
+
const cachedResearch = Researcher.getCachedResearch(state);
|
|
163
|
+
if (cachedResearch) {
|
|
164
|
+
pageSummary = `<page_summary>\n${this.explorBot.agentResearcher().extractBrief(cachedResearch)}\n</page_summary>`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const activeTest = this.explorBot.getExplorer().activeTest;
|
|
168
|
+
let activeTestContext = '';
|
|
169
|
+
if (activeTest) {
|
|
170
|
+
activeTestContext = dedent`
|
|
171
|
+
<active_test>
|
|
172
|
+
Session: ${activeTest.sessionName}
|
|
173
|
+
Scenario: ${activeTest.scenario}
|
|
174
|
+
Status: ${activeTest.status}
|
|
175
|
+
Result: ${activeTest.result || 'pending'}
|
|
176
|
+
Start URL: ${activeTest.startUrl}
|
|
177
|
+
</active_test>
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return dedent`
|
|
182
|
+
<page>
|
|
183
|
+
URL: ${state.url || '/'}
|
|
184
|
+
Title: ${state.title || 'Untitled'}
|
|
185
|
+
${headingsBlock}
|
|
186
|
+
|
|
187
|
+
<page_aria>
|
|
188
|
+
${actionResult.getInteractiveARIA()}
|
|
189
|
+
</page_aria>
|
|
190
|
+
</page>
|
|
191
|
+
|
|
192
|
+
${pageSummary}
|
|
193
|
+
|
|
194
|
+
${activeTestContext}
|
|
195
|
+
|
|
196
|
+
${knowledge}
|
|
197
|
+
|
|
198
|
+
${experience}
|
|
199
|
+
|
|
200
|
+
Use runCommand("/research") if you need deeper page understanding or UI element mapping.
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private planSummary(): string {
|
|
205
|
+
const plan = this.explorBot.getCurrentPlan();
|
|
206
|
+
if (!plan || plan.tests.length === 0) {
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
return dedent`
|
|
210
|
+
<plan>
|
|
211
|
+
${plan.tests
|
|
212
|
+
.map((test) => {
|
|
213
|
+
const parts = [`[${test.priority}] ${test.scenario}`];
|
|
214
|
+
if (test.sessionName) parts.push(`session=${test.sessionName}`);
|
|
215
|
+
if (test.status !== 'pending') parts.push(`status=${test.status}`);
|
|
216
|
+
if (test.result) parts.push(`result=${test.result}`);
|
|
217
|
+
return parts.join(' | ');
|
|
218
|
+
})
|
|
219
|
+
.join('\n')}
|
|
220
|
+
</plan>
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private async reinjectContextIfNeeded(conversation: Conversation, currentState: WebPageState): Promise<void> {
|
|
225
|
+
if (conversation.hasTag('page_html', 5)) return Promise.resolve();
|
|
226
|
+
|
|
227
|
+
const actionResult = ActionResult.fromState(currentState);
|
|
228
|
+
const html = await actionResult.combinedHtml();
|
|
229
|
+
const context = dedent`
|
|
230
|
+
Context:
|
|
231
|
+
|
|
232
|
+
<page>
|
|
233
|
+
CURRENT URL: ${currentState.url}
|
|
234
|
+
CURRENT TITLE: ${currentState.title}
|
|
235
|
+
</page>
|
|
236
|
+
|
|
237
|
+
<page_aria>
|
|
238
|
+
${actionResult.getInteractiveARIA()}
|
|
239
|
+
</page_aria>
|
|
240
|
+
|
|
241
|
+
<page_html>
|
|
242
|
+
${html}
|
|
243
|
+
</page_html>
|
|
244
|
+
`;
|
|
245
|
+
conversation.addUserText(context);
|
|
246
|
+
return Promise.resolve();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private coreTools(task: Task, onDone: (summary: string) => void) {
|
|
250
|
+
return {
|
|
251
|
+
done: tool({
|
|
252
|
+
description: 'Call when the user request is fulfilled.',
|
|
253
|
+
inputSchema: z.object({
|
|
254
|
+
summary: z.string().describe('What was done'),
|
|
255
|
+
}),
|
|
256
|
+
execute: async ({ summary }) => {
|
|
257
|
+
debugLog('done', summary);
|
|
258
|
+
task.addNote(summary);
|
|
259
|
+
onDone(summary);
|
|
260
|
+
return { success: true, summary };
|
|
261
|
+
},
|
|
262
|
+
}),
|
|
263
|
+
runCommand: tool({
|
|
264
|
+
description: dedent`
|
|
265
|
+
Execute a TUI command. Returns log output from command execution.
|
|
266
|
+
${this.commandDescriptions
|
|
267
|
+
.map((c) => {
|
|
268
|
+
const opts = c.options ? ` (${c.options})` : '';
|
|
269
|
+
return `${c.name} — ${c.description}${opts}`;
|
|
270
|
+
})
|
|
271
|
+
.join('\n')}
|
|
272
|
+
`,
|
|
273
|
+
inputSchema: z.object({
|
|
274
|
+
command: z.string().describe('Slash command to execute, e.g. "/research", "/plan authentication", "/test brave-fox123"'),
|
|
275
|
+
}),
|
|
276
|
+
execute: async ({ command }) => {
|
|
277
|
+
if (!this.commandExecutor) return { success: false, message: 'Command executor not available' };
|
|
278
|
+
const cmd = command.startsWith('/') ? command : `/${command}`;
|
|
279
|
+
startLogCapture();
|
|
280
|
+
try {
|
|
281
|
+
await this.commandExecutor(cmd);
|
|
282
|
+
} catch {}
|
|
283
|
+
const logs = stopLogCapture();
|
|
284
|
+
return { success: true, command: cmd, output: logs.join('\n') };
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private async tools(task: Task, onDone: (summary: string) => void) {
|
|
291
|
+
const mode = this.detectMode();
|
|
292
|
+
const ctx: ModeContext = { explorBot: this.explorBot, task };
|
|
293
|
+
const core = this.coreTools(task, onDone);
|
|
294
|
+
const idle = await this.idleModeTools(ctx);
|
|
295
|
+
|
|
296
|
+
if (mode === 'test') return { ...core, ...idle, ...this.testModeTools(ctx) };
|
|
297
|
+
if (mode === 'web') return { ...core, ...idle, ...this.webModeTools(ctx) };
|
|
298
|
+
return { ...core, ...idle };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async processSupervisorInterrupt(userMessage: string, activeTest: Test): Promise<SupervisorAction> {
|
|
302
|
+
const quickMatch = userMessage.trim().toLowerCase();
|
|
303
|
+
if (/^(stop|abort|cancel)$/i.test(quickMatch)) {
|
|
304
|
+
return { action: 'stop', message: 'Stopping test per user request' };
|
|
305
|
+
}
|
|
306
|
+
if (/^(pass|ok|approve)$/i.test(quickMatch)) {
|
|
307
|
+
return { action: 'pass', message: 'Marking test as passed per user request' };
|
|
308
|
+
}
|
|
309
|
+
if (/^(skip|next)$/i.test(quickMatch)) {
|
|
310
|
+
return { action: 'skip', message: 'Skipping test per user request' };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const testerConv = this.explorBot.agentTester().getConversation();
|
|
314
|
+
let recentToolSummary = '';
|
|
315
|
+
if (testerConv) {
|
|
316
|
+
const execs = testerConv.getToolExecutions().slice(-5);
|
|
317
|
+
recentToolSummary = execs.map((e) => `${e.toolName}: ${e.wasSuccessful ? 'ok' : 'fail'} ${truncateJson(e.input)}`).join('\n');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const pilotAnalysis = this.explorBot.agentPilot().getLastAnalysis() || '';
|
|
321
|
+
const currentUrl = this.explorBot.getExplorer().getStateManager().getCurrentState()?.url || '';
|
|
322
|
+
|
|
323
|
+
const schema = z.object({
|
|
324
|
+
action: z.enum(['inject', 'stop', 'pass', 'skip']),
|
|
325
|
+
message: z.string().describe('Message to pass to the tester or display to user'),
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const model = this.explorBot.getProvider().getModelForAgent('captain');
|
|
329
|
+
const result = await this.explorBot.getProvider().generateObject(
|
|
330
|
+
[
|
|
331
|
+
{
|
|
332
|
+
role: 'system',
|
|
333
|
+
content: dedent`
|
|
334
|
+
You are a test supervisor. A test is running and the user has interrupted with a message.
|
|
335
|
+
Decide what action to take based on the user's intent.
|
|
336
|
+
|
|
337
|
+
Actions:
|
|
338
|
+
- inject: Pass refined guidance to the tester (user wants to redirect, suggest, or help)
|
|
339
|
+
- stop: Stop the test (user wants to abort)
|
|
340
|
+
- pass: Mark test as passed (user confirms it's good)
|
|
341
|
+
- skip: Skip this test (user wants to move to next)
|
|
342
|
+
|
|
343
|
+
For "inject", rephrase the user's message as clear instructions for the AI tester.
|
|
344
|
+
`,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
role: 'user',
|
|
348
|
+
content: dedent`
|
|
349
|
+
Test scenario: ${activeTest.scenario}
|
|
350
|
+
Current URL: ${currentUrl}
|
|
351
|
+
Recent tool executions:
|
|
352
|
+
${recentToolSummary || 'None'}
|
|
353
|
+
${pilotAnalysis ? `Pilot analysis: ${pilotAnalysis}` : ''}
|
|
354
|
+
|
|
355
|
+
User message: ${userMessage}
|
|
356
|
+
`,
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
schema,
|
|
360
|
+
model
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
if (!result?.object) {
|
|
364
|
+
return { action: 'inject', message: userMessage };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return result.object;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async handle(input: string, options: { reset?: boolean } = {}): Promise<string | null> {
|
|
371
|
+
const stateManager = this.explorBot.getExplorer().getStateManager();
|
|
372
|
+
const initialState = stateManager.getCurrentState();
|
|
373
|
+
|
|
374
|
+
if (!initialState) {
|
|
375
|
+
tag('warning').log('No page loaded. Use /navigate or I.amOnPage() first.');
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const conversation = options.reset ? this.resetConversation() : this.ensureConversation();
|
|
380
|
+
let isDone = false;
|
|
381
|
+
let finalSummary: string | null = null;
|
|
382
|
+
|
|
383
|
+
const startUrl = initialState.url || '';
|
|
384
|
+
const task = new Task(input, startUrl);
|
|
385
|
+
const onDone = (summary: string) => {
|
|
386
|
+
isDone = true;
|
|
387
|
+
finalSummary = summary;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const tools = await this.tools(task, onDone);
|
|
391
|
+
|
|
392
|
+
await this.getHooksRunner().runBeforeHook('captain', startUrl);
|
|
393
|
+
|
|
394
|
+
const pageContext = await this.getPageContext();
|
|
395
|
+
const planContext = this.planSummary();
|
|
396
|
+
|
|
397
|
+
if (!options.reset && this.conversation) {
|
|
398
|
+
conversation.cleanupTag('page_aria', '...cleaned...', 1);
|
|
399
|
+
conversation.cleanupTag('page_html', '...cleaned...', 1);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const initialPrompt = dedent`
|
|
403
|
+
${pageContext}
|
|
404
|
+
|
|
405
|
+
${planContext}
|
|
406
|
+
|
|
407
|
+
<request>
|
|
408
|
+
${input}
|
|
409
|
+
</request>
|
|
410
|
+
|
|
411
|
+
Execute the request using available tools. After a successful action, check the pageDiff — if it confirms the goal, call done() immediately.
|
|
412
|
+
`;
|
|
413
|
+
|
|
414
|
+
conversation.addUserText(initialPrompt);
|
|
415
|
+
|
|
416
|
+
await loop(
|
|
417
|
+
async ({ stop, iteration, userInput }) => {
|
|
418
|
+
debugLog(`Captain iteration ${iteration}`);
|
|
419
|
+
|
|
420
|
+
if (isDone) {
|
|
421
|
+
stop();
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const currentState = stateManager.getCurrentState();
|
|
426
|
+
if (!currentState) {
|
|
427
|
+
stop();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
await this.reinjectContextIfNeeded(conversation, currentState);
|
|
432
|
+
|
|
433
|
+
if (userInput) {
|
|
434
|
+
const newContext = await this.getPageContext();
|
|
435
|
+
conversation.addUserText(dedent`
|
|
436
|
+
${newContext}
|
|
437
|
+
|
|
438
|
+
<user_redirect>
|
|
439
|
+
${userInput}
|
|
440
|
+
</user_redirect>
|
|
441
|
+
|
|
442
|
+
The user has interrupted and wants to change direction. Follow the new instruction.
|
|
443
|
+
`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const result = await this.explorBot.getProvider().invokeConversation(conversation, tools, {
|
|
447
|
+
maxToolRoundtrips: 5,
|
|
448
|
+
toolChoice: 'auto',
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (!result) {
|
|
452
|
+
stop();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const toolNames = result?.toolExecutions?.map((e: any) => e.toolName) || [];
|
|
457
|
+
debugLog('Tools called:', toolNames.join(', '));
|
|
458
|
+
|
|
459
|
+
this.trackToolExecutions(result?.toolExecutions || []);
|
|
460
|
+
|
|
461
|
+
if (isDone) {
|
|
462
|
+
stop();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (result?.toolExecutions?.length) {
|
|
467
|
+
const lastExec = result.toolExecutions[result.toolExecutions.length - 1];
|
|
468
|
+
if (lastExec.wasSuccessful && this.ACTION_TOOLS.includes(lastExec.toolName)) {
|
|
469
|
+
conversation.addUserText('Action succeeded. If the goal is achieved, call done() now with a brief summary.');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
maxAttempts: MAX_STEPS,
|
|
475
|
+
interruptPrompt: 'Captain interrupted. Enter new instruction (or "stop" to cancel):',
|
|
476
|
+
observability: {
|
|
477
|
+
agent: 'captain',
|
|
478
|
+
},
|
|
479
|
+
catch: async ({ error, stop }) => {
|
|
480
|
+
tag('error').log(`Captain error: ${error}`);
|
|
481
|
+
stop();
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const finalUrl = stateManager.getCurrentState()?.url || startUrl;
|
|
487
|
+
await this.getHooksRunner().runAfterHook('captain', finalUrl);
|
|
488
|
+
|
|
489
|
+
if (finalSummary) {
|
|
490
|
+
tag('info').log(finalSummary);
|
|
491
|
+
} else {
|
|
492
|
+
tag('warning').log('Request may not be fully completed');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export default Captain;
|
|
500
|
+
|
|
501
|
+
interface SupervisorAction {
|
|
502
|
+
action: 'inject' | 'stop' | 'pass' | 'skip';
|
|
503
|
+
message: string;
|
|
504
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { ModelMessage } from 'ai';
|
|
2
|
+
|
|
3
|
+
export interface ToolExecution {
|
|
4
|
+
toolName: string;
|
|
5
|
+
input: any;
|
|
6
|
+
output: any;
|
|
7
|
+
wasSuccessful: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function toolExecutionLabel(input: Record<string, any> | undefined): string {
|
|
11
|
+
return input?.explanation || input?.assertion || input?.reason || input?.request || '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Conversation {
|
|
15
|
+
id: string;
|
|
16
|
+
messages: ModelMessage[];
|
|
17
|
+
model: any;
|
|
18
|
+
telemetryFunctionId?: string;
|
|
19
|
+
private autoTrimRules: Map<string, number>;
|
|
20
|
+
|
|
21
|
+
constructor(messages: ModelMessage[] = [], model?: any, telemetryFunctionId?: string) {
|
|
22
|
+
this.id = this.generateId();
|
|
23
|
+
this.messages = messages;
|
|
24
|
+
this.model = model || '';
|
|
25
|
+
this.telemetryFunctionId = telemetryFunctionId;
|
|
26
|
+
this.autoTrimRules = new Map();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addUserText(text: string): void {
|
|
30
|
+
this.messages.push({
|
|
31
|
+
role: 'user',
|
|
32
|
+
content: this.applyAutoTrim(text),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addUserImage(image: string): void {
|
|
37
|
+
if (!image || image.trim() === '') {
|
|
38
|
+
console.warn('Warning: Attempting to add empty image to conversation');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const imageData = image.startsWith('data:') ? image : `data:image/png;base64,${image}`;
|
|
43
|
+
|
|
44
|
+
this.messages.push({
|
|
45
|
+
role: 'user',
|
|
46
|
+
content: [{ type: 'image', image: imageData }],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
addAssistantText(text: string): void {
|
|
51
|
+
// Skip empty or whitespace-only messages
|
|
52
|
+
if (!text || text.trim() === '') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.messages.push({
|
|
56
|
+
role: 'assistant',
|
|
57
|
+
content: this.applyAutoTrim(text),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getLastMessage(): string {
|
|
62
|
+
const lastMessage = this.messages[this.messages.length - 1];
|
|
63
|
+
if (!lastMessage) return '';
|
|
64
|
+
|
|
65
|
+
if (typeof lastMessage.content === 'string') {
|
|
66
|
+
return lastMessage.content;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(lastMessage.content)) {
|
|
70
|
+
const textPart = lastMessage.content.find((part) => part.type === 'text');
|
|
71
|
+
return textPart ? textPart.text : '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clone(): Conversation {
|
|
78
|
+
return new Conversation([...this.messages], this.model, this.telemetryFunctionId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
cleanupTag(tagName: string, replacement: string, keepLast = 0): void {
|
|
82
|
+
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
83
|
+
const regex = new RegExp(`<${escapedTag}>[\\s\\S]*?<\\/${escapedTag}>`, 'g');
|
|
84
|
+
const replacementText = `<${tagName}>${replacement}</${tagName}>`;
|
|
85
|
+
|
|
86
|
+
if (keepLast === 0) {
|
|
87
|
+
for (const message of this.messages) {
|
|
88
|
+
if (typeof message.content === 'string') {
|
|
89
|
+
message.content = message.content.replace(regex, replacementText);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const allMatches: Array<{ messageIndex: number; startIndex: number; endIndex: number }> = [];
|
|
96
|
+
for (let i = 0; i < this.messages.length; i++) {
|
|
97
|
+
const message = this.messages[i];
|
|
98
|
+
if (typeof message.content === 'string') {
|
|
99
|
+
const matches = [...message.content.matchAll(new RegExp(`<${escapedTag}>[\\s\\S]*?<\\/${escapedTag}>`, 'g'))];
|
|
100
|
+
for (const match of matches) {
|
|
101
|
+
if (match.index !== undefined) {
|
|
102
|
+
allMatches.push({ messageIndex: i, startIndex: match.index, endIndex: match.index + match[0].length });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const keepCount = Math.min(keepLast, allMatches.length);
|
|
109
|
+
const keepMatches = allMatches.slice(-keepCount);
|
|
110
|
+
const keepSet = new Set(keepMatches.map((m) => `${m.messageIndex}:${m.startIndex}`));
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < this.messages.length; i++) {
|
|
113
|
+
const message = this.messages[i];
|
|
114
|
+
if (typeof message.content === 'string') {
|
|
115
|
+
const matches = [...message.content.matchAll(new RegExp(`<${escapedTag}>[\\s\\S]*?<\\/${escapedTag}>`, 'g'))];
|
|
116
|
+
let result = message.content;
|
|
117
|
+
for (let j = matches.length - 1; j >= 0; j--) {
|
|
118
|
+
const match = matches[j];
|
|
119
|
+
if (match.index !== undefined) {
|
|
120
|
+
const key = `${i}:${match.index}`;
|
|
121
|
+
if (!keepSet.has(key)) {
|
|
122
|
+
result = result.substring(0, match.index) + replacementText + result.substring(match.index + match[0].length);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
message.content = result;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
autoTrimTag(tagName: string, maxLength: number): void {
|
|
132
|
+
this.autoTrimRules.set(tagName, maxLength);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
hasTag(tagName: string, lastN?: number): boolean {
|
|
136
|
+
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
137
|
+
const regex = new RegExp(`<${escapedTag}>`, 'g');
|
|
138
|
+
|
|
139
|
+
let messagesToCheck = this.messages;
|
|
140
|
+
if (lastN) {
|
|
141
|
+
messagesToCheck = this.messages.slice(Math.max(0, this.messages.length - lastN));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const message of messagesToCheck) {
|
|
145
|
+
if (typeof message.content === 'string' && regex.test(message.content)) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private applyAutoTrim(text: string): string {
|
|
154
|
+
if (this.autoTrimRules.size === 0) return text;
|
|
155
|
+
|
|
156
|
+
let result = text;
|
|
157
|
+
for (const [tagName, maxLength] of this.autoTrimRules) {
|
|
158
|
+
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
159
|
+
const regex = new RegExp(`<${escapedTag}>([\\s\\S]*?)<\\/${escapedTag}>`, 'g');
|
|
160
|
+
|
|
161
|
+
result = result.replace(regex, (match, content) => {
|
|
162
|
+
if (content.length <= maxLength) return match;
|
|
163
|
+
const trimmed = content.substring(0, maxLength);
|
|
164
|
+
return `<${tagName}>${trimmed}</${tagName}>`;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private generateId(): string {
|
|
172
|
+
return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getToolExecutions(): ToolExecution[] {
|
|
176
|
+
const toolCalls = new Map<string, any>();
|
|
177
|
+
for (const message of this.messages) {
|
|
178
|
+
if (message.role !== 'assistant') continue;
|
|
179
|
+
if (!Array.isArray(message.content)) continue;
|
|
180
|
+
for (const part of message.content) {
|
|
181
|
+
if (part.type !== 'tool-call') continue;
|
|
182
|
+
toolCalls.set(part.toolCallId, part.input);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const executions: ToolExecution[] = [];
|
|
187
|
+
for (const message of this.messages) {
|
|
188
|
+
if (message.role !== 'tool') continue;
|
|
189
|
+
if (!Array.isArray(message.content)) continue;
|
|
190
|
+
for (const part of message.content) {
|
|
191
|
+
if (part.type !== 'tool-result') continue;
|
|
192
|
+
const rawOutput = part.output as Record<string, any>;
|
|
193
|
+
const output = rawOutput?.type === 'json' && rawOutput?.value ? rawOutput.value : rawOutput;
|
|
194
|
+
executions.push({
|
|
195
|
+
toolName: part.toolName,
|
|
196
|
+
input: toolCalls.get(part.toolCallId) || {},
|
|
197
|
+
output,
|
|
198
|
+
wasSuccessful: output?.success !== false,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return executions;
|
|
204
|
+
}
|
|
205
|
+
}
|