explorbot 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -26
- package/bin/explorbot-cli.ts +680 -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 +23 -101
- 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 +14 -12
- 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 +42 -7
- package/dist/src/ai/planner.js +15 -4
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.js +0 -1
- package/dist/src/ai/researcher/cache.js +13 -9
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.js +16 -20
- 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 +1 -2
- package/dist/src/ai/researcher/mixin.js +0 -1
- package/dist/src/ai/researcher/parser.js +4 -4
- package/dist/src/ai/researcher/research-result.js +2 -1
- package/dist/src/ai/researcher.js +6 -6
- 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 +4 -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 +2 -3
- 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 +3 -3
- 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 +117 -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 +6 -2
- 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 +1 -2
- package/dist/src/explorer.js +58 -17
- 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 +6 -5
- package/dist/src/utils/xpath.js +0 -1
- package/package.json +28 -4
- package/src/action-result.ts +694 -0
- package/src/action.ts +449 -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 +171 -0
- package/src/ai/planner.ts +549 -0
- package/src/ai/provider.ts +613 -0
- package/src/ai/quartermaster.ts +286 -0
- package/src/ai/researcher/cache.ts +109 -0
- package/src/ai/researcher/coordinates.ts +239 -0
- package/src/ai/researcher/deep-analysis.ts +412 -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 +116 -0
- package/src/ai/researcher.ts +858 -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 +1122 -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 +131 -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 +46 -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 +491 -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 +760 -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 +147 -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,534 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { type Span, context, trace } from '@opentelemetry/api';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import dedent from 'dedent';
|
|
7
|
+
import { marked } from 'marked';
|
|
8
|
+
import { ConfigParser } from '../config.js';
|
|
9
|
+
import { Observability } from '../observability.ts';
|
|
10
|
+
import stripAnsi from 'strip-ansi';
|
|
11
|
+
import { parseMarkdownToTerminal } from './markdown-terminal.ts';
|
|
12
|
+
|
|
13
|
+
export type LogType = 'info' | 'success' | 'error' | 'warning' | 'debug' | 'substep' | 'step' | 'multiline' | 'html' | 'input';
|
|
14
|
+
|
|
15
|
+
export interface TaggedLogEntry {
|
|
16
|
+
type: LogType;
|
|
17
|
+
content: string;
|
|
18
|
+
timestamp?: Date;
|
|
19
|
+
originalArgs?: any[];
|
|
20
|
+
namespace?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type LogEntry = TaggedLogEntry;
|
|
24
|
+
|
|
25
|
+
interface LogDestination {
|
|
26
|
+
isEnabled(): boolean;
|
|
27
|
+
write(entry: TaggedLogEntry): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class DebugFilter {
|
|
31
|
+
private patterns: { regex: RegExp; exclude: boolean }[] = [];
|
|
32
|
+
private parsed = false;
|
|
33
|
+
|
|
34
|
+
private parse(): void {
|
|
35
|
+
if (this.parsed) return;
|
|
36
|
+
this.parsed = true;
|
|
37
|
+
|
|
38
|
+
const debugEnv = process.env.DEBUG || '';
|
|
39
|
+
if (!debugEnv) return;
|
|
40
|
+
|
|
41
|
+
const parts = debugEnv.split(/[\s,]+/).filter(Boolean);
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
const exclude = part.startsWith('-');
|
|
44
|
+
const pattern = exclude ? part.slice(1) : part;
|
|
45
|
+
const regex = this.patternToRegex(pattern);
|
|
46
|
+
this.patterns.push({ regex, exclude });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private patternToRegex(pattern: string): RegExp {
|
|
51
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
52
|
+
return new RegExp(`^${escaped}$`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isEnabled(namespace: string): boolean {
|
|
56
|
+
this.parse();
|
|
57
|
+
|
|
58
|
+
if (this.patterns.length === 0) return false;
|
|
59
|
+
|
|
60
|
+
let enabled = false;
|
|
61
|
+
for (const { regex, exclude } of this.patterns) {
|
|
62
|
+
if (regex.test(namespace)) {
|
|
63
|
+
enabled = !exclude;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return enabled;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
hasAnyPatterns(): boolean {
|
|
70
|
+
this.parse();
|
|
71
|
+
return this.patterns.length > 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const debugFilter = new DebugFilter();
|
|
76
|
+
|
|
77
|
+
class ConsoleDestination implements LogDestination {
|
|
78
|
+
private verboseMode = false;
|
|
79
|
+
private forceEnabled = false;
|
|
80
|
+
|
|
81
|
+
isEnabled(): boolean {
|
|
82
|
+
return this.forceEnabled || !process.env.INK_RUNNING;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
forceEnable(enabled: boolean): void {
|
|
86
|
+
this.forceEnabled = enabled;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setVerboseMode(enabled: boolean): void {
|
|
90
|
+
this.verboseMode = enabled;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
write(entry: TaggedLogEntry): void {
|
|
94
|
+
if (entry.type === 'debug') return;
|
|
95
|
+
if (entry.type === 'html') return;
|
|
96
|
+
let content = entry.content;
|
|
97
|
+
if (entry.type === 'multiline') {
|
|
98
|
+
const cleaned = stripAnsi(dedent(entry.content));
|
|
99
|
+
const parsed = parseMarkdownToTerminal(cleaned);
|
|
100
|
+
content = parsed;
|
|
101
|
+
content = `${chalk.gray(content)}\n`;
|
|
102
|
+
} else if (entry.type === 'success') {
|
|
103
|
+
content = `${chalk.green(content)}\n`;
|
|
104
|
+
} else if (entry.type === 'error') {
|
|
105
|
+
content = `${chalk.red(content)}\n`;
|
|
106
|
+
} else if (entry.type === 'warning') {
|
|
107
|
+
content = chalk.yellow(content);
|
|
108
|
+
} else if (entry.type === 'step') {
|
|
109
|
+
content = chalk.gray(` ${content}`);
|
|
110
|
+
} else if (entry.type === 'substep') {
|
|
111
|
+
content = chalk.gray(` > ${content}`);
|
|
112
|
+
}
|
|
113
|
+
console.log(content);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class DebugDestination implements LogDestination {
|
|
118
|
+
private verboseMode = false;
|
|
119
|
+
|
|
120
|
+
isEnabled(): boolean {
|
|
121
|
+
if (process.env.INK_RUNNING) return false;
|
|
122
|
+
return this.verboseMode || debugFilter.hasAnyPatterns();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
isNamespaceEnabled(namespace: string): boolean {
|
|
126
|
+
if (this.verboseMode) return true;
|
|
127
|
+
return debugFilter.isEnabled(namespace);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
setVerboseMode(enabled: boolean): void {
|
|
131
|
+
this.verboseMode = enabled;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
write(namespace: string, ...args: any[]): void {
|
|
135
|
+
if (!this.isNamespaceEnabled(namespace)) return;
|
|
136
|
+
debug(namespace).apply(null, args);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class FileDestination implements LogDestination {
|
|
141
|
+
private initialized = false;
|
|
142
|
+
private logFilePath: string | null = null;
|
|
143
|
+
private verboseMode = true;
|
|
144
|
+
|
|
145
|
+
isEnabled(): boolean {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setVerboseMode(enabled: boolean): void {
|
|
150
|
+
this.verboseMode = enabled;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
write(entry: TaggedLogEntry): void {
|
|
154
|
+
if (entry.type === 'html') return;
|
|
155
|
+
if (entry.type === 'multiline') return;
|
|
156
|
+
|
|
157
|
+
this.ensureInitialized();
|
|
158
|
+
if (this.logFilePath) {
|
|
159
|
+
try {
|
|
160
|
+
const timestamp = entry.timestamp?.toISOString() || new Date().toISOString();
|
|
161
|
+
fs.appendFileSync(this.logFilePath, `[${timestamp}] [${entry.type.toUpperCase()}] ${entry.content}\n`);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn('Failed to write to log file:', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private ensureInitialized(): void {
|
|
169
|
+
if (this.initialized) return;
|
|
170
|
+
|
|
171
|
+
this.initialized = true;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const outputDir = ConfigParser.getInstance().getOutputDir();
|
|
175
|
+
|
|
176
|
+
if (!fs.existsSync(outputDir)) {
|
|
177
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
this.logFilePath = path.join(outputDir, 'explorbot.log');
|
|
180
|
+
const timestamp = new Date().toISOString();
|
|
181
|
+
fs.appendFileSync(this.logFilePath, `\n\n=== ExplorBot Session Started at ${timestamp} ===\n\n`);
|
|
182
|
+
} catch {
|
|
183
|
+
this.logFilePath = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class SpanDestination implements LogDestination {
|
|
189
|
+
isEnabled(): boolean {
|
|
190
|
+
return Observability.isTracing();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
write(entry: TaggedLogEntry): void {
|
|
194
|
+
if (entry.type !== 'step') return;
|
|
195
|
+
const activeSpan = stepSpanParent;
|
|
196
|
+
if (!activeSpan) return;
|
|
197
|
+
const tracer = trace.getTracer('ai');
|
|
198
|
+
const step = entry.originalArgs?.[0];
|
|
199
|
+
const stepError = entry.originalArgs?.[1];
|
|
200
|
+
if (!step?.toCode) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const stepName = step?.name ? `I.${step.name}` : 'I.step';
|
|
204
|
+
const stepInput = typeof step?.toCode === 'function' ? step.toCode() : entry.content;
|
|
205
|
+
const errorFromStep = step?.error;
|
|
206
|
+
const errorMessage =
|
|
207
|
+
stepError && typeof stepError === 'object' && 'message' in stepError && typeof stepError.message === 'string'
|
|
208
|
+
? stepError.message
|
|
209
|
+
: errorFromStep && typeof errorFromStep === 'object' && 'message' in errorFromStep && typeof errorFromStep.message === 'string'
|
|
210
|
+
? errorFromStep.message
|
|
211
|
+
: stepError && typeof stepError === 'string'
|
|
212
|
+
? stepError
|
|
213
|
+
: undefined;
|
|
214
|
+
const stepOutput = errorMessage ? `failed: ${errorMessage}` : step?.status || (step?.failed ? 'failed' : step?.success ? 'success' : 'passed');
|
|
215
|
+
const span = tracer.startSpan(stepName, undefined, trace.setSpan(context.active(), activeSpan));
|
|
216
|
+
span.setAttribute('ai.toolCall.name', stepName);
|
|
217
|
+
span.setAttribute('ai.toolCall.args', JSON.stringify({ command: stepInput }));
|
|
218
|
+
span.setAttribute('ai.toolCall.result', JSON.stringify({ status: stepOutput }));
|
|
219
|
+
span.setAttribute('log.timestamp', entry.timestamp?.toISOString() || new Date().toISOString());
|
|
220
|
+
if (step) {
|
|
221
|
+
try {
|
|
222
|
+
const parsedStep = typeof step === 'string' ? JSON.parse(step) : step.simplify();
|
|
223
|
+
span.setAttribute('ai.telemetry.metadata.step', JSON.stringify(parsedStep));
|
|
224
|
+
} catch {
|
|
225
|
+
span.setAttribute('ai.telemetry.metadata.step', '[unserializable step]');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
span.end();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
class ReactDestination implements LogDestination {
|
|
233
|
+
private logPane: ((entry: LogEntry) => void) | null = null;
|
|
234
|
+
private pendingLogs: TaggedLogEntry[] = [];
|
|
235
|
+
private debugMode = false;
|
|
236
|
+
|
|
237
|
+
isEnabled(): boolean {
|
|
238
|
+
return this.logPane !== null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
setDebugMode(enabled: boolean): void {
|
|
242
|
+
this.debugMode = enabled;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
isDebugMode(): boolean {
|
|
246
|
+
return this.debugMode;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private shouldWrite(entry: TaggedLogEntry): boolean {
|
|
250
|
+
if (entry.type !== 'debug') return true;
|
|
251
|
+
if (this.debugMode) return true;
|
|
252
|
+
if (!entry.namespace) return true;
|
|
253
|
+
return debugFilter.isEnabled(entry.namespace);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
write(entry: TaggedLogEntry): void {
|
|
257
|
+
if (!this.shouldWrite(entry)) return;
|
|
258
|
+
|
|
259
|
+
if (!this.isEnabled()) {
|
|
260
|
+
if (process.env.INK_RUNNING) {
|
|
261
|
+
this.pendingLogs.push(entry);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (this.pendingLogs.length > 0) {
|
|
266
|
+
for (const pending of this.pendingLogs) {
|
|
267
|
+
if (this.shouldWrite(pending)) {
|
|
268
|
+
this.logPane!(pending);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
this.pendingLogs = [];
|
|
272
|
+
}
|
|
273
|
+
this.logPane!(entry);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
registerLogPane(addLog: (entry: LogEntry) => void): void {
|
|
277
|
+
this.logPane = addLog;
|
|
278
|
+
if (this.pendingLogs.length > 0) {
|
|
279
|
+
for (const pending of this.pendingLogs) {
|
|
280
|
+
if (this.shouldWrite(pending)) {
|
|
281
|
+
this.logPane(pending);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
this.pendingLogs = [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
unregisterLogPane(addLog: (entry: LogEntry) => void): void {
|
|
289
|
+
if (this.logPane !== addLog) return;
|
|
290
|
+
this.logPane = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
class CaptainDestination implements LogDestination {
|
|
295
|
+
private entries: TaggedLogEntry[] = [];
|
|
296
|
+
private capturing = false;
|
|
297
|
+
|
|
298
|
+
isEnabled(): boolean {
|
|
299
|
+
return this.capturing;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
startCapture(): void {
|
|
303
|
+
this.entries = [];
|
|
304
|
+
this.capturing = true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
stopCapture(): string[] {
|
|
308
|
+
this.capturing = false;
|
|
309
|
+
const logs = this.entries.filter((e) => e.type !== 'debug' && e.type !== 'html' && e.type !== 'multiline').map((e) => `[${e.type}] ${e.content}`);
|
|
310
|
+
this.entries = [];
|
|
311
|
+
return logs;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
write(entry: TaggedLogEntry): void {
|
|
315
|
+
this.entries.push(entry);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
class Logger {
|
|
320
|
+
private static instance: Logger;
|
|
321
|
+
private console = new ConsoleDestination();
|
|
322
|
+
private debugDestination = new DebugDestination();
|
|
323
|
+
private file = new FileDestination();
|
|
324
|
+
private span = new SpanDestination();
|
|
325
|
+
public react = new ReactDestination();
|
|
326
|
+
public captain = new CaptainDestination();
|
|
327
|
+
private truncateTags: string[] = ['page_html'];
|
|
328
|
+
|
|
329
|
+
private constructor() {}
|
|
330
|
+
|
|
331
|
+
static getInstance(): Logger {
|
|
332
|
+
if (!Logger.instance) {
|
|
333
|
+
Logger.instance = new Logger();
|
|
334
|
+
}
|
|
335
|
+
return Logger.instance;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
setVerboseMode(enabled: boolean): void {
|
|
339
|
+
this.debugDestination.setVerboseMode(enabled);
|
|
340
|
+
this.console.setVerboseMode(enabled);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
setDebugMode(enabled: boolean): void {
|
|
344
|
+
this.react.setDebugMode(enabled);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
isDebugMode(): boolean {
|
|
348
|
+
return this.react.isDebugMode();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
setPreserveConsoleLogs(enabled: boolean): void {
|
|
352
|
+
this.console.forceEnable(enabled);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
isVerboseMode(): boolean {
|
|
356
|
+
return this.debugDestination.isEnabled();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
registerLogPane(addLog: (entry: LogEntry) => void): void {
|
|
360
|
+
this.react.registerLogPane(addLog);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
unregisterLogPane(addLog: (entry: LogEntry) => void): void {
|
|
364
|
+
this.react.unregisterLogPane(addLog);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
addTruncateTag(tagName: string): void {
|
|
368
|
+
if (!this.truncateTags.includes(tagName)) {
|
|
369
|
+
this.truncateTags.push(tagName);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private truncateTagContent(content: string, tagName: string): string {
|
|
374
|
+
const escapedTag = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
375
|
+
const tagRegex = new RegExp(`<${escapedTag}>([\\s\\S]*?)<\\/${escapedTag}>`, 'g');
|
|
376
|
+
return content.replace(tagRegex, (match, innerContent) => {
|
|
377
|
+
const trimmed = innerContent.trim();
|
|
378
|
+
const totalLength = trimmed.length;
|
|
379
|
+
if (totalLength <= 100) {
|
|
380
|
+
return `<${tagName}>\n ${trimmed}\n</${tagName}>`;
|
|
381
|
+
}
|
|
382
|
+
const truncated = trimmed.substring(0, 100);
|
|
383
|
+
return `<${tagName}>\n ${truncated}.... (${totalLength}chars total)\n</${tagName}>`;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private processArgs(args: any[]): string {
|
|
388
|
+
const processed = args
|
|
389
|
+
.map((arg) => {
|
|
390
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
391
|
+
try {
|
|
392
|
+
return JSON.stringify(arg, null, 2);
|
|
393
|
+
} catch {
|
|
394
|
+
return '[Object]';
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return String(arg);
|
|
398
|
+
})
|
|
399
|
+
.join(' ');
|
|
400
|
+
|
|
401
|
+
let result = processed;
|
|
402
|
+
for (const tag of this.truncateTags) {
|
|
403
|
+
result = this.truncateTagContent(result, tag);
|
|
404
|
+
}
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
log(type: LogType, ...args: any[]): void {
|
|
409
|
+
if (type === 'debug') {
|
|
410
|
+
let namespace = 'explorbot';
|
|
411
|
+
let contentArgs = args;
|
|
412
|
+
if (args.length > 1) {
|
|
413
|
+
namespace = String(args[0]);
|
|
414
|
+
contentArgs = args.slice(1);
|
|
415
|
+
}
|
|
416
|
+
const processedContent = this.processArgs(contentArgs);
|
|
417
|
+
|
|
418
|
+
if (!process.env.INK_RUNNING && this.debugDestination.isEnabled()) {
|
|
419
|
+
this.debugDestination.write(namespace, processedContent);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (process.env.INK_RUNNING) {
|
|
423
|
+
const entry: TaggedLogEntry = {
|
|
424
|
+
type: 'debug',
|
|
425
|
+
content: `${namespace}: ${processedContent}`,
|
|
426
|
+
timestamp: new Date(),
|
|
427
|
+
namespace,
|
|
428
|
+
originalArgs: contentArgs,
|
|
429
|
+
};
|
|
430
|
+
this.react.write(entry);
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let content = this.processArgs(args);
|
|
436
|
+
if (type === 'step' && args[0]?.toCode) {
|
|
437
|
+
content = args[0].toCode();
|
|
438
|
+
}
|
|
439
|
+
const entry: TaggedLogEntry = {
|
|
440
|
+
type,
|
|
441
|
+
content,
|
|
442
|
+
timestamp: new Date(),
|
|
443
|
+
originalArgs: args,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
if (this.file.isEnabled()) this.file.write(entry);
|
|
447
|
+
if (this.span.isEnabled()) this.span.write(entry);
|
|
448
|
+
if (this.captain.isEnabled()) this.captain.write(entry);
|
|
449
|
+
if (process.env.INK_RUNNING) {
|
|
450
|
+
this.react.write(entry);
|
|
451
|
+
} else if (this.console.isEnabled()) {
|
|
452
|
+
this.console.write(entry);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
info(...args: any[]): void {
|
|
457
|
+
this.log('info', ...args);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
success(...args: any[]): void {
|
|
461
|
+
this.log('success', ...args);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
error(...args: any[]): void {
|
|
465
|
+
this.log('error', ...args);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
warning(...args: any[]): void {
|
|
469
|
+
this.log('warning', ...args);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
debug(namespace: string, ...args: any[]): void {
|
|
473
|
+
this.log('debug', namespace, ...args);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
substep(...args: any[]): void {
|
|
477
|
+
this.log('substep', ...args);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
multiline(...args: any[]): void {
|
|
481
|
+
this.log('multiline', ...args);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const logger = Logger.getInstance();
|
|
486
|
+
let stepSpanParent: Span | null = null;
|
|
487
|
+
|
|
488
|
+
export const tag = (type: LogType) => ({
|
|
489
|
+
log: (...args: any[]) => logger.log(type, ...args),
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
export const log = (...args: any[]) => logger.info(...args);
|
|
493
|
+
export const logSuccess = (...args: any[]) => logger.success(...args);
|
|
494
|
+
export const logError = (...args: any[]) => logger.error(...args);
|
|
495
|
+
export const logWarning = (...args: any[]) => logger.warning(...args);
|
|
496
|
+
export const logSubstep = (...args: any[]) => logger.substep(...args);
|
|
497
|
+
|
|
498
|
+
export const createDebug = (namespace: string) => {
|
|
499
|
+
return (...args: any[]) => logger.debug(namespace, ...args);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
export const getMethodsOfObject = (obj: any): string[] => {
|
|
503
|
+
const methods: string[] = [];
|
|
504
|
+
|
|
505
|
+
for (const key in obj) {
|
|
506
|
+
if (typeof obj[key] === 'function' && key !== 'constructor') {
|
|
507
|
+
methods.push(key);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return methods.sort();
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
export const startLogCapture = () => logger.captain.startCapture();
|
|
515
|
+
export const stopLogCapture = () => logger.captain.stopCapture();
|
|
516
|
+
export const setVerboseMode = (enabled: boolean) => logger.setVerboseMode(enabled);
|
|
517
|
+
export const setPreserveConsoleLogs = (enabled: boolean) => logger.setPreserveConsoleLogs(enabled);
|
|
518
|
+
export const isVerboseMode = () => logger.isVerboseMode();
|
|
519
|
+
export const setDebugMode = (enabled: boolean) => logger.setDebugMode(enabled);
|
|
520
|
+
export const isDebugMode = () => logger.isDebugMode();
|
|
521
|
+
|
|
522
|
+
export const registerLogPane = (addLog: (entry: LogEntry) => void) => logger.registerLogPane(addLog);
|
|
523
|
+
export const unregisterLogPane = (addLog: (entry: LogEntry) => void) => logger.unregisterLogPane(addLog);
|
|
524
|
+
export const addTruncateTag = (tagName: string) => logger.addTruncateTag(tagName);
|
|
525
|
+
export const setStepSpanParent = (span: Span | null) => {
|
|
526
|
+
stepSpanParent = span;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// Legacy alias for backward compatibility
|
|
530
|
+
export const setLogCallback = registerLogPane;
|
|
531
|
+
|
|
532
|
+
export const pluralize = (count: number, singular: string, plural?: string): string => {
|
|
533
|
+
return count === 1 ? singular : plural || `${singular}s`;
|
|
534
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { executionController } from '../execution-controller.ts';
|
|
2
|
+
import { Observability } from '../observability.ts';
|
|
3
|
+
import { createDebug } from './logger.js';
|
|
4
|
+
|
|
5
|
+
const debugLog = createDebug('explorbot:loop');
|
|
6
|
+
|
|
7
|
+
export class StopError extends Error {
|
|
8
|
+
constructor(message = 'Loop stopped') {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'StopError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class InterruptError extends Error {
|
|
15
|
+
userInput: string | null;
|
|
16
|
+
constructor(userInput: string | null) {
|
|
17
|
+
super('Loop interrupted by user');
|
|
18
|
+
this.name = 'InterruptError';
|
|
19
|
+
this.userInput = userInput;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LoopContext {
|
|
24
|
+
stop: () => void;
|
|
25
|
+
iteration: number;
|
|
26
|
+
pause: (prompt?: string) => Promise<string | null>;
|
|
27
|
+
userInput: string | null;
|
|
28
|
+
setUserInput: (input: string) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CatchContext {
|
|
32
|
+
error: Error;
|
|
33
|
+
stop: () => void;
|
|
34
|
+
iteration: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LoopOptions {
|
|
38
|
+
maxAttempts?: number;
|
|
39
|
+
interruptible?: boolean;
|
|
40
|
+
interruptPrompt?: string;
|
|
41
|
+
onInterrupt?: (userInput: string | null, context: LoopContext) => Promise<void> | void;
|
|
42
|
+
catch?: (context: CatchContext) => Promise<void> | void;
|
|
43
|
+
observability?: {
|
|
44
|
+
name?: string;
|
|
45
|
+
agent?: string;
|
|
46
|
+
sessionId?: string;
|
|
47
|
+
tags?: string[];
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function pause(prompt?: string): Promise<string | null> {
|
|
53
|
+
const message = prompt || 'Paused. Enter new instruction or press Enter to continue:';
|
|
54
|
+
return await executionController.requestInput(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function raceWithInterrupt<T>(promise: Promise<T>): Promise<{ result: T; interrupted: false } | { interrupted: true }> {
|
|
58
|
+
const interruptPromise = executionController.waitForInterrupt().then(() => ({ interrupted: true as const }));
|
|
59
|
+
const resultPromise = promise.then((result) => ({ result, interrupted: false as const }));
|
|
60
|
+
|
|
61
|
+
return Promise.race([resultPromise, interruptPromise]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function loop<T>(handler: (context: LoopContext) => Promise<T>, options?: LoopOptions): Promise<any> {
|
|
65
|
+
const run = async () => {
|
|
66
|
+
const maxAttempts = options?.maxAttempts ?? 5;
|
|
67
|
+
const catchHandler = options?.catch;
|
|
68
|
+
const interruptible = options?.interruptible ?? true;
|
|
69
|
+
const interruptPrompt = options?.interruptPrompt;
|
|
70
|
+
const onInterrupt = options?.onInterrupt;
|
|
71
|
+
|
|
72
|
+
let result: any;
|
|
73
|
+
let shouldStop = false;
|
|
74
|
+
let pendingUserInput: string | null = null;
|
|
75
|
+
|
|
76
|
+
const createStopFunction = () => () => {
|
|
77
|
+
shouldStop = true;
|
|
78
|
+
throw new StopError();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for (let iteration = 0; iteration < maxAttempts; iteration++) {
|
|
82
|
+
try {
|
|
83
|
+
if (iteration > 0) debugLog(`Loop iteration ${iteration + 1}/${maxAttempts}`);
|
|
84
|
+
|
|
85
|
+
const context: LoopContext = {
|
|
86
|
+
stop: createStopFunction(),
|
|
87
|
+
iteration: iteration + 1,
|
|
88
|
+
pause: (prompt?: string) => pause(prompt),
|
|
89
|
+
userInput: pendingUserInput,
|
|
90
|
+
setUserInput: (input: string) => {
|
|
91
|
+
pendingUserInput = input;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
pendingUserInput = null;
|
|
95
|
+
|
|
96
|
+
if (!interruptible) {
|
|
97
|
+
result = await handler(context);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const raceResult = await raceWithInterrupt(handler(context));
|
|
102
|
+
|
|
103
|
+
if (raceResult.interrupted) {
|
|
104
|
+
debugLog(`Loop interrupted at iteration ${iteration + 1}`);
|
|
105
|
+
const userInput = await executionController.handleInterrupt(interruptPrompt);
|
|
106
|
+
|
|
107
|
+
if (userInput === null || userInput.toLowerCase() === 'stop' || userInput.toLowerCase() === 'exit') {
|
|
108
|
+
debugLog('User requested stop');
|
|
109
|
+
shouldStop = true;
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (onInterrupt) {
|
|
114
|
+
await onInterrupt(userInput, context);
|
|
115
|
+
} else {
|
|
116
|
+
pendingUserInput = userInput;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
result = raceResult.result;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof StopError && shouldStop) {
|
|
125
|
+
debugLog(`Loop stopped successfully at iteration ${iteration + 1}`);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (catchHandler) {
|
|
130
|
+
try {
|
|
131
|
+
const catchContext: CatchContext = {
|
|
132
|
+
error: error as Error,
|
|
133
|
+
stop: createStopFunction(),
|
|
134
|
+
iteration: iteration + 1,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
await catchHandler(catchContext);
|
|
138
|
+
|
|
139
|
+
if (shouldStop) {
|
|
140
|
+
debugLog(`Loop stopped via catch handler at iteration ${iteration + 1}`);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
} catch (catchError) {
|
|
145
|
+
if (catchError instanceof StopError && shouldStop) {
|
|
146
|
+
debugLog(`Loop stopped via catch handler at iteration ${iteration + 1}`);
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
throw catchError;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
debugLog(`Loop error at iteration ${iteration + 1}:`, error);
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const observability = options?.observability;
|
|
162
|
+
if (!observability) {
|
|
163
|
+
return run();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const name = observability.name || (observability.agent ? `${observability.agent}.loop` : 'loop');
|
|
167
|
+
const tags = observability.tags ?? (observability.agent ? [observability.agent] : undefined);
|
|
168
|
+
const baseMetadata = observability.metadata || {};
|
|
169
|
+
const metadata = {
|
|
170
|
+
...baseMetadata,
|
|
171
|
+
...(observability.sessionId ? { sessionId: observability.sessionId } : {}),
|
|
172
|
+
...(tags ? { tags } : {}),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return Observability.run(name, metadata, run);
|
|
176
|
+
}
|