@xagent-ai/cli 1.0.1
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/.eslintrc.js +25 -0
- package/.gitmodules +3 -0
- package/.prettierrc.json +8 -0
- package/CONTRIBUTING.md +167 -0
- package/LICENSE +21 -0
- package/README.md +280 -0
- package/README_CN.md +280 -0
- package/dist/agents.d.ts +21 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +463 -0
- package/dist/agents.js.map +1 -0
- package/dist/ai-client.d.ts +83 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +1280 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +573 -0
- package/dist/auth.js.map +1 -0
- package/dist/cancellation.d.ts +46 -0
- package/dist/cancellation.d.ts.map +1 -0
- package/dist/cancellation.js +154 -0
- package/dist/cancellation.js.map +1 -0
- package/dist/checkpoint.d.ts +28 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +186 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +364 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +205 -0
- package/dist/config.js.map +1 -0
- package/dist/context-compressor.d.ts +51 -0
- package/dist/context-compressor.d.ts.map +1 -0
- package/dist/context-compressor.js +231 -0
- package/dist/context-compressor.js.map +1 -0
- package/dist/conversation.d.ts +34 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +221 -0
- package/dist/conversation.js.map +1 -0
- package/dist/gui-subagent/action-parser/actionParser.d.ts +19 -0
- package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -0
- package/dist/gui-subagent/action-parser/actionParser.js +203 -0
- package/dist/gui-subagent/action-parser/actionParser.js.map +1 -0
- package/dist/gui-subagent/action-parser/constants.d.ts +8 -0
- package/dist/gui-subagent/action-parser/constants.d.ts.map +1 -0
- package/dist/gui-subagent/action-parser/constants.js +12 -0
- package/dist/gui-subagent/action-parser/constants.js.map +1 -0
- package/dist/gui-subagent/action-parser/index.d.ts +3 -0
- package/dist/gui-subagent/action-parser/index.d.ts.map +1 -0
- package/dist/gui-subagent/action-parser/index.js +6 -0
- package/dist/gui-subagent/action-parser/index.js.map +1 -0
- package/dist/gui-subagent/action-parser/types.d.ts +24 -0
- package/dist/gui-subagent/action-parser/types.d.ts.map +1 -0
- package/dist/gui-subagent/action-parser/types.js +12 -0
- package/dist/gui-subagent/action-parser/types.js.map +1 -0
- package/dist/gui-subagent/agent/gui-agent.d.ts +126 -0
- package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -0
- package/dist/gui-subagent/agent/gui-agent.js +820 -0
- package/dist/gui-subagent/agent/gui-agent.js.map +1 -0
- package/dist/gui-subagent/agent/index.d.ts +5 -0
- package/dist/gui-subagent/agent/index.d.ts.map +1 -0
- package/dist/gui-subagent/agent/index.js +5 -0
- package/dist/gui-subagent/agent/index.js.map +1 -0
- package/dist/gui-subagent/index.d.ts +43 -0
- package/dist/gui-subagent/index.d.ts.map +1 -0
- package/dist/gui-subagent/index.js +96 -0
- package/dist/gui-subagent/index.js.map +1 -0
- package/dist/gui-subagent/operator/base-operator.d.ts +108 -0
- package/dist/gui-subagent/operator/base-operator.d.ts.map +1 -0
- package/dist/gui-subagent/operator/base-operator.js +172 -0
- package/dist/gui-subagent/operator/base-operator.js.map +1 -0
- package/dist/gui-subagent/operator/browser-operator.d.ts +36 -0
- package/dist/gui-subagent/operator/browser-operator.d.ts.map +1 -0
- package/dist/gui-subagent/operator/browser-operator.js +306 -0
- package/dist/gui-subagent/operator/browser-operator.js.map +1 -0
- package/dist/gui-subagent/operator/computer-operator.d.ts +31 -0
- package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -0
- package/dist/gui-subagent/operator/computer-operator.js +441 -0
- package/dist/gui-subagent/operator/computer-operator.js.map +1 -0
- package/dist/gui-subagent/operator/desktop-operator.d.ts +55 -0
- package/dist/gui-subagent/operator/desktop-operator.d.ts.map +1 -0
- package/dist/gui-subagent/operator/desktop-operator.js +527 -0
- package/dist/gui-subagent/operator/desktop-operator.js.map +1 -0
- package/dist/gui-subagent/operator/index.d.ts +7 -0
- package/dist/gui-subagent/operator/index.d.ts.map +1 -0
- package/dist/gui-subagent/operator/index.js +6 -0
- package/dist/gui-subagent/operator/index.js.map +1 -0
- package/dist/gui-subagent/types/actions.d.ts +108 -0
- package/dist/gui-subagent/types/actions.d.ts.map +1 -0
- package/dist/gui-subagent/types/actions.js +39 -0
- package/dist/gui-subagent/types/actions.js.map +1 -0
- package/dist/gui-subagent/types/index.d.ts +6 -0
- package/dist/gui-subagent/types/index.d.ts.map +1 -0
- package/dist/gui-subagent/types/index.js +6 -0
- package/dist/gui-subagent/types/index.js.map +1 -0
- package/dist/gui-subagent/types/operator.d.ts +95 -0
- package/dist/gui-subagent/types/operator.d.ts.map +1 -0
- package/dist/gui-subagent/types/operator.js +16 -0
- package/dist/gui-subagent/types/operator.js.map +1 -0
- package/dist/gui-subagent/utils.d.ts +19 -0
- package/dist/gui-subagent/utils.d.ts.map +1 -0
- package/dist/gui-subagent/utils.js +42 -0
- package/dist/gui-subagent/utils.js.map +1 -0
- package/dist/hook.d.ts +73 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +156 -0
- package/dist/hook.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/input-history.d.ts +24 -0
- package/dist/input-history.d.ts.map +1 -0
- package/dist/input-history.js +94 -0
- package/dist/input-history.js.map +1 -0
- package/dist/input-processor.d.ts +31 -0
- package/dist/input-processor.d.ts.map +1 -0
- package/dist/input-processor.js +233 -0
- package/dist/input-processor.js.map +1 -0
- package/dist/keyboard-manager.d.ts +151 -0
- package/dist/keyboard-manager.d.ts.map +1 -0
- package/dist/keyboard-manager.js +396 -0
- package/dist/keyboard-manager.js.map +1 -0
- package/dist/logger.d.ts +75 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +339 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp.d.ts +57 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +483 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory.d.ts +25 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +250 -0
- package/dist/memory.js.map +1 -0
- package/dist/print-system-prompt.d.ts +2 -0
- package/dist/print-system-prompt.d.ts.map +1 -0
- package/dist/print-system-prompt.js +40 -0
- package/dist/print-system-prompt.js.map +1 -0
- package/dist/session-manager.d.ts +41 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +234 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +1081 -0
- package/dist/session.js.map +1 -0
- package/dist/skill-invoker.d.ts +177 -0
- package/dist/skill-invoker.d.ts.map +1 -0
- package/dist/skill-invoker.js +1643 -0
- package/dist/skill-invoker.js.map +1 -0
- package/dist/skill-loader.d.ts +76 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +407 -0
- package/dist/skill-loader.js.map +1 -0
- package/dist/slash-commands.d.ts +60 -0
- package/dist/slash-commands.d.ts.map +1 -0
- package/dist/slash-commands.js +1021 -0
- package/dist/slash-commands.js.map +1 -0
- package/dist/smart-approval.d.ts +137 -0
- package/dist/smart-approval.d.ts.map +1 -0
- package/dist/smart-approval.js +512 -0
- package/dist/smart-approval.js.map +1 -0
- package/dist/system-prompt-generator.d.ts +35 -0
- package/dist/system-prompt-generator.d.ts.map +1 -0
- package/dist/system-prompt-generator.js +729 -0
- package/dist/system-prompt-generator.js.map +1 -0
- package/dist/test-boundary-conditions.d.ts.map +1 -0
- package/dist/test-boundary-conditions.js.map +1 -0
- package/dist/test-cancellation-fix.d.ts.map +1 -0
- package/dist/test-cancellation-fix.js.map +1 -0
- package/dist/test-input-history.d.ts.map +1 -0
- package/dist/test-input-history.js.map +1 -0
- package/dist/test-interaction-flow.d.ts.map +1 -0
- package/dist/test-interaction-flow.js.map +1 -0
- package/dist/test-quick.d.ts.map +1 -0
- package/dist/test-quick.js.map +1 -0
- package/dist/test-user-interaction.d.ts.map +1 -0
- package/dist/test-user-interaction.js.map +1 -0
- package/dist/theme.d.ts +353 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +383 -0
- package/dist/theme.js.map +1 -0
- package/dist/tools.d.ts +373 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +2906 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +180 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/unified-session.d.ts +42 -0
- package/dist/unified-session.d.ts.map +1 -0
- package/dist/unified-session.js +271 -0
- package/dist/unified-session.js.map +1 -0
- package/dist/update.d.ts +30 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +211 -0
- package/dist/update.js.map +1 -0
- package/dist/workflow.d.ts +53 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +405 -0
- package/dist/workflow.js.map +1 -0
- package/docs/architecture/mcp-integration-guide.md +131 -0
- package/docs/architecture/overview.md +93 -0
- package/docs/architecture/tool-system-design.md +89 -0
- package/docs/cli/commands.md +189 -0
- package/docs/smart-mode.md +257 -0
- package/docs/third-party-models.md +449 -0
- package/package.json +85 -0
- package/scripts/init-skills-path.js +58 -0
- package/skills/.claude-plugin/marketplace.json +45 -0
- package/skills/README.md +94 -0
- package/skills/THIRD_PARTY_NOTICES.md +405 -0
- package/skills/skills/algorithmic-art/LICENSE.txt +202 -0
- package/skills/skills/algorithmic-art/SKILL.md +405 -0
- package/skills/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/skills/skills/algorithmic-art/templates/viewer.html +599 -0
- package/skills/skills/brand-guidelines/LICENSE.txt +202 -0
- package/skills/skills/brand-guidelines/SKILL.md +73 -0
- package/skills/skills/canvas-design/LICENSE.txt +202 -0
- package/skills/skills/canvas-design/SKILL.md +130 -0
- package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/skills/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/skills/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/skills/skills/doc-coauthoring/SKILL.md +375 -0
- package/skills/skills/docx/LICENSE.txt +30 -0
- package/skills/skills/docx/SKILL.md +197 -0
- package/skills/skills/docx/docx-js.md +350 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/skills/docx/ooxml/scripts/pack.py +159 -0
- package/skills/skills/docx/ooxml/scripts/unpack.py +29 -0
- package/skills/skills/docx/ooxml/scripts/validate.py +69 -0
- package/skills/skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/skills/skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/skills/skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/skills/skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/skills/skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/skills/skills/docx/ooxml.md +610 -0
- package/skills/skills/docx/scripts/__init__.py +1 -0
- package/skills/skills/docx/scripts/document.py +1276 -0
- package/skills/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/skills/docx/scripts/utilities.py +374 -0
- package/skills/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/skills/frontend-design/SKILL.md +42 -0
- package/skills/skills/internal-comms/LICENSE.txt +202 -0
- package/skills/skills/internal-comms/SKILL.md +32 -0
- package/skills/skills/internal-comms/examples/3p-updates.md +47 -0
- package/skills/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/skills/skills/internal-comms/examples/faq-answers.md +30 -0
- package/skills/skills/internal-comms/examples/general-comms.md +16 -0
- package/skills/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/skills/mcp-builder/SKILL.md +236 -0
- package/skills/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/skills/pdf/LICENSE.txt +30 -0
- package/skills/skills/pdf/SKILL.md +294 -0
- package/skills/skills/pdf/forms.md +205 -0
- package/skills/skills/pdf/reference.md +612 -0
- package/skills/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/skills/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/skills/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/skills/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/skills/skills/pdf/scripts/create_validation_image.py +41 -0
- package/skills/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/skills/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/skills/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/skills/skills/pptx/LICENSE.txt +30 -0
- package/skills/skills/pptx/SKILL.md +484 -0
- package/skills/skills/pptx/html2pptx.md +625 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/skills/pptx/ooxml/scripts/pack.py +159 -0
- package/skills/skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/skills/skills/pptx/ooxml/scripts/validate.py +69 -0
- package/skills/skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/skills/skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/skills/skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/skills/skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/skills/skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/skills/skills/pptx/ooxml.md +427 -0
- package/skills/skills/pptx/scripts/html2pptx.js +979 -0
- package/skills/skills/pptx/scripts/inventory.py +1020 -0
- package/skills/skills/pptx/scripts/rearrange.py +231 -0
- package/skills/skills/pptx/scripts/replace.py +385 -0
- package/skills/skills/pptx/scripts/thumbnail.py +450 -0
- package/skills/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skills/skill-creator/SKILL.md +356 -0
- package/skills/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/skills/skills/slack-gif-creator/LICENSE.txt +202 -0
- package/skills/skills/slack-gif-creator/SKILL.md +254 -0
- package/skills/skills/slack-gif-creator/core/easing.py +234 -0
- package/skills/skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/skills/skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/skills/skills/slack-gif-creator/core/validators.py +136 -0
- package/skills/skills/slack-gif-creator/requirements.txt +4 -0
- package/skills/skills/theme-factory/LICENSE.txt +202 -0
- package/skills/skills/theme-factory/SKILL.md +59 -0
- package/skills/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/skills/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/skills/skills/theme-factory/themes/desert-rose.md +19 -0
- package/skills/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/skills/skills/theme-factory/themes/golden-hour.md +19 -0
- package/skills/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/skills/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/skills/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/skills/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/skills/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/skills/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/skills/skills/web-artifacts-builder/SKILL.md +74 -0
- package/skills/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/skills/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/skills/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/skills/webapp-testing/SKILL.md +96 -0
- package/skills/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/skills/xlsx/LICENSE.txt +30 -0
- package/skills/skills/xlsx/SKILL.md +289 -0
- package/skills/skills/xlsx/recalc.py +178 -0
- package/skills/spec/agent-skills-spec.md +3 -0
- package/skills/template/SKILL.md +6 -0
- package/src/agents.ts +504 -0
- package/src/ai-client.ts +1456 -0
- package/src/auth.ts +648 -0
- package/src/cancellation.ts +176 -0
- package/src/checkpoint.ts +219 -0
- package/src/cli.ts +384 -0
- package/src/config.ts +248 -0
- package/src/context-compressor.ts +290 -0
- package/src/conversation.ts +288 -0
- package/src/gui-subagent/action-parser/actionParser.ts +312 -0
- package/src/gui-subagent/action-parser/constants.ts +12 -0
- package/src/gui-subagent/action-parser/index.ts +6 -0
- package/src/gui-subagent/action-parser/types.ts +31 -0
- package/src/gui-subagent/agent/gui-agent.ts +982 -0
- package/src/gui-subagent/agent/index.ts +5 -0
- package/src/gui-subagent/index.ts +139 -0
- package/src/gui-subagent/operator/base-operator.ts +246 -0
- package/src/gui-subagent/operator/computer-operator.ts +520 -0
- package/src/gui-subagent/operator/index.ts +7 -0
- package/src/gui-subagent/types/actions.ts +263 -0
- package/src/gui-subagent/types/index.ts +6 -0
- package/src/gui-subagent/types/operator.ts +106 -0
- package/src/gui-subagent/utils.ts +51 -0
- package/src/index.ts +18 -0
- package/src/input-processor.ts +282 -0
- package/src/logger.ts +438 -0
- package/src/mcp.ts +563 -0
- package/src/memory.ts +303 -0
- package/src/session-manager.ts +308 -0
- package/src/session.ts +1280 -0
- package/src/skill-invoker.ts +1888 -0
- package/src/skill-loader.ts +476 -0
- package/src/slash-commands.ts +1150 -0
- package/src/smart-approval.ts +595 -0
- package/src/system-prompt-generator.ts +786 -0
- package/src/theme.ts +455 -0
- package/src/tools.ts +3398 -0
- package/src/types.ts +198 -0
- package/src/update.ts +270 -0
- package/src/workflow.ts +508 -0
- package/tsconfig.json +22 -0
- package/vitest.config.ts +19 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,2906 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import { exec, spawn } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import { ExecutionMode, AuthType } from './types.js';
|
|
10
|
+
import { colors, icons } from './theme.js';
|
|
11
|
+
import { getCancellationManager } from './cancellation.js';
|
|
12
|
+
import { getLogger } from './logger.js';
|
|
13
|
+
import { SystemPromptGenerator } from './system-prompt-generator.js';
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
export class ReadTool {
|
|
16
|
+
name = 'Read';
|
|
17
|
+
description = `Read the contents of a file. This is your PRIMARY tool for understanding existing code, configuration, and documentation.
|
|
18
|
+
|
|
19
|
+
# When to Use
|
|
20
|
+
- When you need to understand existing code before making changes
|
|
21
|
+
- When user asks you to "read", "show", "view", or "check" a file
|
|
22
|
+
- When debugging and need to inspect source files
|
|
23
|
+
- When analyzing project structure by reading key files
|
|
24
|
+
- When examining configuration files (package.json, tsconfig.json, etc.)
|
|
25
|
+
- When checking documentation or README files
|
|
26
|
+
|
|
27
|
+
# When NOT to Use
|
|
28
|
+
- For files you've already read in the same conversation (use memory instead)
|
|
29
|
+
- When you only need file metadata (use ListDirectory or Bash with ls instead)
|
|
30
|
+
- For binary files that cannot be read as text
|
|
31
|
+
|
|
32
|
+
# Parameters
|
|
33
|
+
- \`filePath\`: Absolute path or path relative to project root
|
|
34
|
+
- \`offset\`: (Optional) Line number to start reading from (0-based)
|
|
35
|
+
- \`limit\`: (Optional) Maximum number of lines to read
|
|
36
|
+
|
|
37
|
+
# Examples
|
|
38
|
+
- Read specific file: Read(filePath="/path/to/file.ts")
|
|
39
|
+
- Read with pagination: Read(filePath="src/app.ts", offset=0, limit=100)
|
|
40
|
+
|
|
41
|
+
# Best Practices
|
|
42
|
+
- Use absolute paths or paths relative to the project root
|
|
43
|
+
- Use offset and limit for large files to avoid loading entire content
|
|
44
|
+
- Combine with ListDirectory to explore project structure first
|
|
45
|
+
- Don't re-read files unnecessarily`;
|
|
46
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
47
|
+
async execute(params) {
|
|
48
|
+
const { filePath, offset = 0, limit } = params;
|
|
49
|
+
try {
|
|
50
|
+
// Handle ~ (user home directory) in file paths
|
|
51
|
+
let resolvedPath = filePath;
|
|
52
|
+
if (filePath.startsWith('~')) {
|
|
53
|
+
// On Windows, prefer USERPROFILE over HOME to avoid POSIX path issues
|
|
54
|
+
// Some tools like Git Bash may set HOME to a POSIX path on Windows
|
|
55
|
+
let homeDir = process.env.USERPROFILE || '';
|
|
56
|
+
if (!homeDir || homeDir.startsWith('/')) {
|
|
57
|
+
homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
58
|
+
}
|
|
59
|
+
resolvedPath = path.join(homeDir, filePath.slice(1));
|
|
60
|
+
}
|
|
61
|
+
const absolutePath = path.resolve(resolvedPath);
|
|
62
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
63
|
+
const lines = content.split('\n');
|
|
64
|
+
const startLine = Math.max(0, offset);
|
|
65
|
+
const endLine = limit !== undefined ? Math.min(lines.length, startLine + limit) : lines.length;
|
|
66
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
67
|
+
return selectedLines.join('\n');
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
// Show user-friendly path in error message
|
|
71
|
+
let displayPath = filePath;
|
|
72
|
+
if (filePath.startsWith('~')) {
|
|
73
|
+
// On Windows, prefer USERPROFILE over HOME to avoid POSIX path issues
|
|
74
|
+
let homeDir = process.env.USERPROFILE || '';
|
|
75
|
+
if (!homeDir || homeDir.startsWith('/')) {
|
|
76
|
+
homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
77
|
+
}
|
|
78
|
+
displayPath = path.join(homeDir, filePath.slice(1));
|
|
79
|
+
}
|
|
80
|
+
throw new Error(`Failed to read file ${displayPath}: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class WriteTool {
|
|
85
|
+
name = 'Write';
|
|
86
|
+
description = `Create a new file or completely overwrite an existing file with new content.
|
|
87
|
+
|
|
88
|
+
# When to Use
|
|
89
|
+
- Creating new files (source code, configuration, documentation)
|
|
90
|
+
- Completely replacing file content (not partial edits)
|
|
91
|
+
- Generating files from templates or scratch
|
|
92
|
+
- When user explicitly asks to "create", "write", or "generate" a file
|
|
93
|
+
|
|
94
|
+
# When NOT to Use
|
|
95
|
+
- For making small edits to existing files (use Replace instead)
|
|
96
|
+
- When you only need to append content (read file first, then write)
|
|
97
|
+
- For creating directories (use CreateDirectory instead)
|
|
98
|
+
|
|
99
|
+
# Parameters
|
|
100
|
+
- \`filePath\`: Absolute path or path relative to project root
|
|
101
|
+
- \`content\`: The complete content to write to the file
|
|
102
|
+
|
|
103
|
+
# Examples
|
|
104
|
+
- Create new file: Write(filePath="src/utils.ts", content="...")
|
|
105
|
+
- Create config file: Write(filePath=".env.example", content="API_KEY=...")
|
|
106
|
+
|
|
107
|
+
# Best Practices
|
|
108
|
+
- Parent directories are created automatically
|
|
109
|
+
- Use appropriate file extensions
|
|
110
|
+
- Ensure content is complete and syntactically correct
|
|
111
|
+
- For partial edits, use Replace tool instead`;
|
|
112
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
113
|
+
async execute(params) {
|
|
114
|
+
const { filePath, content } = params;
|
|
115
|
+
try {
|
|
116
|
+
const absolutePath = path.resolve(filePath);
|
|
117
|
+
const dir = path.dirname(absolutePath);
|
|
118
|
+
await fs.mkdir(dir, { recursive: true });
|
|
119
|
+
await fs.writeFile(absolutePath, content, 'utf-8');
|
|
120
|
+
return {
|
|
121
|
+
success: true,
|
|
122
|
+
message: `Successfully wrote to ${filePath}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
throw new Error(`Failed to write file ${filePath}: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export class GrepTool {
|
|
131
|
+
name = 'Grep';
|
|
132
|
+
description = `Search for text patterns within files using regex or literal string matching. This is your PRIMARY tool for finding specific code, functions, or content.
|
|
133
|
+
|
|
134
|
+
# When to Use
|
|
135
|
+
- Finding specific function definitions or calls
|
|
136
|
+
- Searching for variable usages or imports
|
|
137
|
+
- Locating error messages or log statements
|
|
138
|
+
- Finding all occurrences of a pattern across the codebase
|
|
139
|
+
- When you need line-by-line results with context
|
|
140
|
+
|
|
141
|
+
# When NOT to Use
|
|
142
|
+
- When you only need to find files containing text (use SearchCodebase instead)
|
|
143
|
+
- When searching by file pattern rather than content (use SearchCodebase)
|
|
144
|
+
- For very large codebases where you only need file names (SearchCodebase is faster)
|
|
145
|
+
|
|
146
|
+
# Parameters
|
|
147
|
+
- \`pattern\`: Regex or literal string to search for
|
|
148
|
+
- \`path\`: (Optional) Directory to search in, default: "."
|
|
149
|
+
- \`include\`: (Optional) File glob pattern to include
|
|
150
|
+
- \`exclude\`: (Optional) File glob pattern to exclude
|
|
151
|
+
- \`case_sensitive\`: (Optional) Case-sensitive search, default: false
|
|
152
|
+
- \`fixed_strings\`: (Optional) Treat pattern as literal string, default: false
|
|
153
|
+
- \`context\`: (Optional) Lines of context before/after matches
|
|
154
|
+
- \`no_ignore\`: (Optional) Don't ignore node_modules/.git, default: false
|
|
155
|
+
|
|
156
|
+
# Examples
|
|
157
|
+
- Find function: Grep(pattern="function myFunction")
|
|
158
|
+
- Find with context: Grep(pattern="TODO", context=3)
|
|
159
|
+
- TypeScript only: Grep(pattern="interface", include="*.ts")
|
|
160
|
+
|
|
161
|
+
# Best Practices
|
|
162
|
+
- Use case_sensitive=true for short patterns to reduce false positives
|
|
163
|
+
- Use fixed_strings=true if your pattern has special regex characters
|
|
164
|
+
- Use context to see the surrounding code for each match
|
|
165
|
+
- Combine with include/exclude to narrow down file types`;
|
|
166
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
167
|
+
async execute(params) {
|
|
168
|
+
const { pattern, path: searchPath = '.', include, exclude, case_sensitive = false, fixed_strings = false, context, after, before, no_ignore = false } = params;
|
|
169
|
+
try {
|
|
170
|
+
const ignorePatterns = no_ignore ? [] : ['node_modules/**', '.git/**', 'dist/**', 'build/**'];
|
|
171
|
+
if (exclude) {
|
|
172
|
+
ignorePatterns.push(exclude);
|
|
173
|
+
}
|
|
174
|
+
const absolutePath = path.resolve(searchPath);
|
|
175
|
+
const files = await glob('**/*', {
|
|
176
|
+
cwd: absolutePath,
|
|
177
|
+
nodir: true,
|
|
178
|
+
ignore: ignorePatterns
|
|
179
|
+
});
|
|
180
|
+
const results = [];
|
|
181
|
+
for (const file of files) {
|
|
182
|
+
const fullPath = path.join(absolutePath, file);
|
|
183
|
+
if (include && !file.match(include)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
188
|
+
const lines = content.split('\n');
|
|
189
|
+
lines.forEach((line, index) => {
|
|
190
|
+
let matches = false;
|
|
191
|
+
if (fixed_strings) {
|
|
192
|
+
matches = case_sensitive
|
|
193
|
+
? line.includes(pattern)
|
|
194
|
+
: line.toLowerCase().includes(pattern.toLowerCase());
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
try {
|
|
198
|
+
const flags = case_sensitive ? 'g' : 'gi';
|
|
199
|
+
const regex = new RegExp(pattern, flags);
|
|
200
|
+
matches = regex.test(line);
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
matches = case_sensitive
|
|
204
|
+
? line.includes(pattern)
|
|
205
|
+
: line.toLowerCase().includes(pattern.toLowerCase());
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (matches) {
|
|
209
|
+
const contextLines = [];
|
|
210
|
+
if (before || context) {
|
|
211
|
+
const beforeCount = before || context || 0;
|
|
212
|
+
for (let i = Math.max(0, index - beforeCount); i < index; i++) {
|
|
213
|
+
contextLines.push(`${fullPath}:${i + 1}:${lines[i].trim()}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
contextLines.push(`${fullPath}:${index + 1}:${line.trim()}`);
|
|
217
|
+
if (after || context) {
|
|
218
|
+
const afterCount = after || context || 0;
|
|
219
|
+
for (let i = index + 1; i < Math.min(lines.length, index + 1 + afterCount); i++) {
|
|
220
|
+
contextLines.push(`${fullPath}:${i + 1}:${lines[i].trim()}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
results.push(...contextLines);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw new Error(`Grep failed: ${error.message}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export class BashTool {
|
|
239
|
+
name = 'Bash';
|
|
240
|
+
description = `Execute shell commands in the terminal. This is your PRIMARY tool for running commands, scripts, and system operations.
|
|
241
|
+
|
|
242
|
+
# When to Use
|
|
243
|
+
- Running build commands (npm run build, tsc, etc.)
|
|
244
|
+
- Installing dependencies (npm install, pip install, etc.)
|
|
245
|
+
- Running tests (npm test, pytest, etc.)
|
|
246
|
+
- Git operations (git commit, git push, etc.)
|
|
247
|
+
- Running linters or formatters
|
|
248
|
+
- Any command-line operations
|
|
249
|
+
|
|
250
|
+
# When NOT to Use
|
|
251
|
+
- For file operations (use Read/Write/Replace/CreateDirectory instead)
|
|
252
|
+
- For searching file content (use Grep instead)
|
|
253
|
+
- For finding files (use SearchCodebase or ListDirectory instead)
|
|
254
|
+
- For commands that require user interaction (non-interactive only)
|
|
255
|
+
- For dangerous commands without understanding the impact
|
|
256
|
+
|
|
257
|
+
# Parameters
|
|
258
|
+
- \`command\`: The shell command to execute
|
|
259
|
+
- \`cwd\`: (Optional) Working directory for the command
|
|
260
|
+
- \`description\`: (Optional) Description of what the command does
|
|
261
|
+
- \`timeout\`: (Optional) Timeout in seconds, default: 120
|
|
262
|
+
- \`run_in_bg\`: (Optional) Run in background, default: false
|
|
263
|
+
|
|
264
|
+
# Examples
|
|
265
|
+
- Install dependencies: Bash(command="npm install", description="Install npm dependencies")
|
|
266
|
+
- Run tests: Bash(command="npm test", description="Run unit tests")
|
|
267
|
+
- Build project: Bash(command="npm run build", description="Build the project")
|
|
268
|
+
|
|
269
|
+
# Best Practices
|
|
270
|
+
- Always provide a description for context
|
|
271
|
+
- Set appropriate timeout for long-running commands
|
|
272
|
+
- Use run_in_bg=true for commands that take a long time
|
|
273
|
+
- Check the command is safe before executing
|
|
274
|
+
- Use absolute paths or paths relative to project root`;
|
|
275
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
276
|
+
async execute(params) {
|
|
277
|
+
const { command, cwd, description, timeout = 120, run_in_bg = false } = params;
|
|
278
|
+
// Determine effective working directory
|
|
279
|
+
// Only use cwd if the command doesn't contain 'cd' (let LLM control directory)
|
|
280
|
+
let effectiveCwd;
|
|
281
|
+
const hasCdCommand = /cd\s+["']?[^"&|;]+["']?/.test(command);
|
|
282
|
+
if (cwd && !hasCdCommand) {
|
|
283
|
+
// Command doesn't control its own directory, use provided cwd
|
|
284
|
+
effectiveCwd = cwd;
|
|
285
|
+
}
|
|
286
|
+
else if (cwd && hasCdCommand) {
|
|
287
|
+
// Command uses cd, ignore cwd to let cd take effect
|
|
288
|
+
effectiveCwd = undefined;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// No cwd provided, use default
|
|
292
|
+
effectiveCwd = undefined;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
if (run_in_bg) {
|
|
296
|
+
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
297
|
+
const childProcess = spawn(command, {
|
|
298
|
+
cwd: effectiveCwd || process.cwd(),
|
|
299
|
+
shell: true,
|
|
300
|
+
detached: true
|
|
301
|
+
});
|
|
302
|
+
const output = [];
|
|
303
|
+
childProcess.stdout?.on('data', (data) => {
|
|
304
|
+
const text = data.toString();
|
|
305
|
+
output.push(text);
|
|
306
|
+
});
|
|
307
|
+
childProcess.stderr?.on('data', (data) => {
|
|
308
|
+
const text = data.toString();
|
|
309
|
+
output.push(text);
|
|
310
|
+
});
|
|
311
|
+
childProcess.on('close', (code) => {
|
|
312
|
+
console.log(`Background task ${taskId} exited with code ${code}`);
|
|
313
|
+
});
|
|
314
|
+
const toolRegistry = getToolRegistry();
|
|
315
|
+
toolRegistry.addBackgroundTask(taskId, {
|
|
316
|
+
process: childProcess,
|
|
317
|
+
startTime: Date.now(),
|
|
318
|
+
output
|
|
319
|
+
});
|
|
320
|
+
return {
|
|
321
|
+
stdout: '',
|
|
322
|
+
stderr: '',
|
|
323
|
+
exitCode: 0,
|
|
324
|
+
taskId
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
329
|
+
cwd: effectiveCwd || process.cwd(),
|
|
330
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
331
|
+
timeout: timeout * 1000
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
stdout,
|
|
335
|
+
stderr,
|
|
336
|
+
exitCode: 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
return {
|
|
342
|
+
stdout: error.stdout || '',
|
|
343
|
+
stderr: error.stderr || error.message,
|
|
344
|
+
exitCode: error.code || 1
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
export class ListDirectoryTool {
|
|
350
|
+
name = 'ListDirectory';
|
|
351
|
+
description = `List files and directories in a path. This is your PRIMARY tool for exploring project structure.
|
|
352
|
+
|
|
353
|
+
# When to Use
|
|
354
|
+
- Exploring project structure and organization
|
|
355
|
+
- Finding what files exist in a directory
|
|
356
|
+
- Getting an overview of the codebase layout
|
|
357
|
+
- When user asks to "list files" or "show directory contents"
|
|
358
|
+
- Navigating through project directories
|
|
359
|
+
|
|
360
|
+
# When NOT to Use
|
|
361
|
+
- When you need to read file contents (use Read instead)
|
|
362
|
+
- For recursive exploration of entire codebase (use recursive=true)
|
|
363
|
+
- When you need to search for specific files (use SearchCodebase instead)
|
|
364
|
+
|
|
365
|
+
# Parameters
|
|
366
|
+
- \`path\`: (Optional) Directory path, default: "."
|
|
367
|
+
- \`recursive\`: (Optional) List recursively, default: false
|
|
368
|
+
|
|
369
|
+
# Examples
|
|
370
|
+
- List current directory: ListDirectory(path=".")
|
|
371
|
+
- List src directory: ListDirectory(path="src")
|
|
372
|
+
- List all files recursively: ListDirectory(path=".", recursive=true)
|
|
373
|
+
|
|
374
|
+
# Best Practices
|
|
375
|
+
- Use recursive=true to see entire subtree
|
|
376
|
+
- Results are absolute paths
|
|
377
|
+
- Ignores node_modules and .git by default
|
|
378
|
+
- Combine with Read to examine file contents`;
|
|
379
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
380
|
+
async execute(params) {
|
|
381
|
+
const { path: dirPath = '.', recursive = false } = params;
|
|
382
|
+
try {
|
|
383
|
+
const absolutePath = path.resolve(dirPath);
|
|
384
|
+
const stats = await fs.stat(absolutePath).catch(() => null);
|
|
385
|
+
if (!stats || !stats.isDirectory()) {
|
|
386
|
+
throw new Error(`Directory does not exist: ${dirPath}`);
|
|
387
|
+
}
|
|
388
|
+
const pattern = recursive ? '**/*' : '*';
|
|
389
|
+
const files = await glob(pattern, {
|
|
390
|
+
cwd: absolutePath,
|
|
391
|
+
nodir: false,
|
|
392
|
+
ignore: ['node_modules/**', '.git/**']
|
|
393
|
+
});
|
|
394
|
+
return files.map(file => path.join(absolutePath, file));
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
throw new Error(`Failed to list directory: ${error.message}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
export class SearchCodebaseTool {
|
|
402
|
+
name = 'SearchCodebase';
|
|
403
|
+
description = `Search for files matching a glob pattern. This is your PRIMARY tool for finding files by name or extension.
|
|
404
|
+
|
|
405
|
+
# When to Use
|
|
406
|
+
- Finding all files of a certain type (*.ts, *.json, *.md)
|
|
407
|
+
- Locating files in specific directories or subdirectories
|
|
408
|
+
- Finding configuration files, test files, or source files
|
|
409
|
+
- When you need a list of file paths, not content
|
|
410
|
+
|
|
411
|
+
# When NOT to Use
|
|
412
|
+
- When you need to search file contents (use Grep instead)
|
|
413
|
+
- When you need to find specific text within files (use Grep instead)
|
|
414
|
+
- For searching non-file patterns (use Grep or Bash)
|
|
415
|
+
|
|
416
|
+
# Parameters
|
|
417
|
+
- \`pattern\`: Glob pattern (e.g., "**/*.ts", "src/**/*.test.ts")
|
|
418
|
+
- \`path\`: (Optional) Directory to search in, default: "."
|
|
419
|
+
|
|
420
|
+
# Examples
|
|
421
|
+
- Find all TypeScript files: SearchCodebase(pattern="**/*.ts")
|
|
422
|
+
- Find test files: SearchCodebase(pattern="**/*.test.ts")
|
|
423
|
+
- Find config files: SearchCodebase(pattern="**/config.*")
|
|
424
|
+
|
|
425
|
+
# Glob Patterns
|
|
426
|
+
- \`*\` matches any characters except /
|
|
427
|
+
- \`**\` matches any characters including /
|
|
428
|
+
- \`?\` matches single character
|
|
429
|
+
- Use brackets for character classes: [abc]
|
|
430
|
+
|
|
431
|
+
# Best Practices
|
|
432
|
+
- Use **/*.ts for recursive search in all directories
|
|
433
|
+
- Combine with path parameter to search specific directories
|
|
434
|
+
- Results are file paths, not content (use Grep on results if needed)`;
|
|
435
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
436
|
+
async execute(params) {
|
|
437
|
+
const { pattern, path: searchPath = '.' } = params;
|
|
438
|
+
try {
|
|
439
|
+
const files = await glob(pattern, {
|
|
440
|
+
cwd: searchPath,
|
|
441
|
+
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
|
|
442
|
+
});
|
|
443
|
+
return files;
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
throw new Error(`Search failed: ${error.message}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
export class DeleteFileTool {
|
|
451
|
+
name = 'DeleteFile';
|
|
452
|
+
description = `Delete a file from the filesystem.
|
|
453
|
+
|
|
454
|
+
# When to Use
|
|
455
|
+
- Removing temporary or debug files
|
|
456
|
+
- Cleaning up generated files
|
|
457
|
+
- Removing files as part of a refactoring task
|
|
458
|
+
- When user explicitly requests file deletion
|
|
459
|
+
|
|
460
|
+
# When NOT to Use
|
|
461
|
+
- For removing directories (use Bash with rm -rf instead)
|
|
462
|
+
- When uncertain if a file should be deleted (confirm with user first)
|
|
463
|
+
- For removing important source files without explicit user request
|
|
464
|
+
|
|
465
|
+
# Parameters
|
|
466
|
+
- \`filePath\`: Absolute path to the file to delete
|
|
467
|
+
|
|
468
|
+
# Examples
|
|
469
|
+
- Delete temporary file: DeleteFile(filePath="debug.log")
|
|
470
|
+
- Remove unused file: DeleteFile(filePath="src/old-component.tsx")
|
|
471
|
+
|
|
472
|
+
# Best Practices
|
|
473
|
+
- Ensure you have the correct file path
|
|
474
|
+
- Consider if the file might be needed later
|
|
475
|
+
- This action is irreversible - be certain before executing`;
|
|
476
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
477
|
+
async execute(params) {
|
|
478
|
+
const { filePath } = params;
|
|
479
|
+
try {
|
|
480
|
+
const absolutePath = path.resolve(filePath);
|
|
481
|
+
await fs.unlink(absolutePath);
|
|
482
|
+
return {
|
|
483
|
+
success: true,
|
|
484
|
+
message: `Successfully deleted ${filePath}`
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
throw new Error(`Failed to delete file ${filePath}: ${error.message}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
export class CreateDirectoryTool {
|
|
493
|
+
name = 'CreateDirectory';
|
|
494
|
+
description = `Create a new directory (folder) in the filesystem.
|
|
495
|
+
|
|
496
|
+
# When to Use
|
|
497
|
+
- Creating project structure (src/components, tests/unit, etc.)
|
|
498
|
+
- Setting up directories for new features or modules
|
|
499
|
+
- Organizing files into appropriate folders
|
|
500
|
+
- When user requests to create a folder structure
|
|
501
|
+
|
|
502
|
+
# When NOT to Use
|
|
503
|
+
- For creating parent directories while writing files (Write tool does this automatically)
|
|
504
|
+
- For creating multiple nested directories at once (create step by step or use Bash)
|
|
505
|
+
|
|
506
|
+
# Parameters
|
|
507
|
+
- \`dirPath\`: Path of the directory to create
|
|
508
|
+
- \`recursive\`: (Optional, default: true) Create parent directories if they don't exist
|
|
509
|
+
|
|
510
|
+
# Examples
|
|
511
|
+
- Create single directory: CreateDirectory(dirPath="src/utils")
|
|
512
|
+
- Create nested structure: CreateDirectory(dirPath="src/components/buttons", recursive=true)
|
|
513
|
+
|
|
514
|
+
# Best Practices
|
|
515
|
+
- recursive=true (default) creates all intermediate parent directories
|
|
516
|
+
- Use appropriate naming conventions (kebab-case for directories)
|
|
517
|
+
- Consider the overall project structure before creating`;
|
|
518
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
519
|
+
async execute(params) {
|
|
520
|
+
const { dirPath, recursive = true } = params;
|
|
521
|
+
try {
|
|
522
|
+
const absolutePath = path.resolve(dirPath);
|
|
523
|
+
await fs.mkdir(absolutePath, { recursive });
|
|
524
|
+
return {
|
|
525
|
+
success: true,
|
|
526
|
+
message: `Successfully created directory ${dirPath}`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
export class ReplaceTool {
|
|
535
|
+
name = 'replace';
|
|
536
|
+
description = `Replace specific text within an existing file. This is your PRIMARY tool for making targeted edits to code.
|
|
537
|
+
|
|
538
|
+
# When to Use
|
|
539
|
+
- Modifying specific code sections without rewriting entire files
|
|
540
|
+
- Changing function implementations, variable values, or configurations
|
|
541
|
+
- Fixing bugs by editing specific lines
|
|
542
|
+
- Updating imports, exports, or references
|
|
543
|
+
|
|
544
|
+
# When NOT to Use
|
|
545
|
+
- When you need to create a completely new file (use Write instead)
|
|
546
|
+
- When you want to append content to a file (read first, then Write)
|
|
547
|
+
- When making changes across multiple files (use Grep to find, then Replace individually)
|
|
548
|
+
|
|
549
|
+
# Parameters
|
|
550
|
+
- \`file_path\`: Path to the file to edit
|
|
551
|
+
- \`instruction\`: Description of what to change (for your own tracking)
|
|
552
|
+
- \`old_string\`: The exact text to find and replace (must match exactly)
|
|
553
|
+
- \`new_string\`: The new text to replace with
|
|
554
|
+
|
|
555
|
+
# Critical Requirements
|
|
556
|
+
- \`old_string\` MUST be an EXACT match, including whitespace and indentation
|
|
557
|
+
- Include at least 3 lines of context before and after the target text
|
|
558
|
+
- Ensure unique matching to avoid unintended replacements
|
|
559
|
+
|
|
560
|
+
# Examples
|
|
561
|
+
replace(
|
|
562
|
+
file_path="src/app.ts",
|
|
563
|
+
instruction="Update API endpoint",
|
|
564
|
+
old_string="const API_URL = 'https://api.old.com';",
|
|
565
|
+
new_string="const API_URL = 'https://api.new.com';"
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
# Best Practices
|
|
569
|
+
- Read the file first to understand the exact content
|
|
570
|
+
- Include sufficient context in old_string to ensure unique match
|
|
571
|
+
- Be careful with special regex characters in old_string (they're escaped automatically)
|
|
572
|
+
- If multiple occurrences exist, all will be replaced`;
|
|
573
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
574
|
+
async execute(params) {
|
|
575
|
+
const { file_path, instruction, old_string, new_string } = params;
|
|
576
|
+
try {
|
|
577
|
+
const absolutePath = path.resolve(file_path);
|
|
578
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
579
|
+
const occurrences = (content.match(new RegExp(this.escapeRegExp(old_string), 'g')) || []).length;
|
|
580
|
+
if (occurrences === 0) {
|
|
581
|
+
return {
|
|
582
|
+
success: false,
|
|
583
|
+
message: `No occurrences found to replace in ${file_path}`,
|
|
584
|
+
changes: 0
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const newContent = content.replace(new RegExp(this.escapeRegExp(old_string), 'g'), new_string);
|
|
588
|
+
await fs.writeFile(absolutePath, newContent, 'utf-8');
|
|
589
|
+
return {
|
|
590
|
+
success: true,
|
|
591
|
+
message: `Successfully replaced ${occurrences} occurrence(s) in ${file_path}`,
|
|
592
|
+
changes: occurrences
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
throw new Error(`Failed to replace in file ${file_path}: ${error.message}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
escapeRegExp(string) {
|
|
600
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
export class WebSearchTool {
|
|
604
|
+
name = 'web_search';
|
|
605
|
+
description = `Search the web for information. This tool queries a search API to find relevant results.
|
|
606
|
+
|
|
607
|
+
# When to Use
|
|
608
|
+
- When you need current information not in your training data
|
|
609
|
+
- Finding documentation, tutorials, or guides
|
|
610
|
+
- Researching APIs, libraries, or tools
|
|
611
|
+
- Getting up-to-date information on technical topics
|
|
612
|
+
- When user asks for "latest", "recent", or "current" information
|
|
613
|
+
|
|
614
|
+
# When NOT to Use
|
|
615
|
+
- When information is likely in the codebase or project files
|
|
616
|
+
- For information that doesn't change frequently (check docs first)
|
|
617
|
+
- When you can use web_fetch with a known URL instead
|
|
618
|
+
- For purely conversational queries
|
|
619
|
+
|
|
620
|
+
# Parameters
|
|
621
|
+
- \`query\`: Search query string
|
|
622
|
+
|
|
623
|
+
# Examples
|
|
624
|
+
- Find React documentation: web_search(query="React useEffect documentation")
|
|
625
|
+
- Get latest Node.js version: web_search(query="Node.js latest LTS version 2024")
|
|
626
|
+
|
|
627
|
+
# Best Practices
|
|
628
|
+
- Be specific in your query for better results
|
|
629
|
+
- Combine with web_fetch to get full content from relevant URLs
|
|
630
|
+
- Use quotes for exact phrase matching
|
|
631
|
+
- Consider adding context like year or version in query`;
|
|
632
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
633
|
+
async execute(params) {
|
|
634
|
+
const { query } = params;
|
|
635
|
+
try {
|
|
636
|
+
const configManager = await import('./config.js');
|
|
637
|
+
const { getConfigManager } = configManager;
|
|
638
|
+
const config = getConfigManager();
|
|
639
|
+
const searchApiKey = config.get('searchApiKey');
|
|
640
|
+
const baseUrl = config.get('baseUrl') || 'https://apis.xagent.cn/v1';
|
|
641
|
+
if (!searchApiKey) {
|
|
642
|
+
throw new Error('Search API key not configured. Please set searchApiKey in settings.');
|
|
643
|
+
}
|
|
644
|
+
const response = await axios.post(`${baseUrl}/search`, { query }, {
|
|
645
|
+
headers: {
|
|
646
|
+
'Authorization': `Bearer ${searchApiKey}`,
|
|
647
|
+
'Content-Type': 'application/json'
|
|
648
|
+
},
|
|
649
|
+
timeout: 30000
|
|
650
|
+
});
|
|
651
|
+
return {
|
|
652
|
+
results: response.data.results || [],
|
|
653
|
+
message: `Found ${response.data.results?.length || 0} results for "${query}"`
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
throw new Error(`Web search failed: ${error.message}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
export class TodoWriteTool {
|
|
662
|
+
name = 'todo_write';
|
|
663
|
+
description = `Create and manage structured task todo lists. Use this tool VERY frequently to track your progress and give users visibility into what needs to be done.
|
|
664
|
+
|
|
665
|
+
# When to Use
|
|
666
|
+
- Complex, multi-step tasks (3+ steps)
|
|
667
|
+
- User explicitly requests a todo list
|
|
668
|
+
- User provides multiple tasks to accomplish
|
|
669
|
+
- Immediately when starting work on a new feature
|
|
670
|
+
- After completing a task (update status immediately)
|
|
671
|
+
- Breaking down large features into smaller steps
|
|
672
|
+
- Tracking independent subtasks that can be worked on
|
|
673
|
+
|
|
674
|
+
# When NOT to Use
|
|
675
|
+
- Single, straightforward task
|
|
676
|
+
- Trivial operations in less than 3 steps
|
|
677
|
+
- Purely conversational or informational responses
|
|
678
|
+
- When you already have an up-to-date todo list
|
|
679
|
+
|
|
680
|
+
# Task States
|
|
681
|
+
- **pending** - Not started, waiting to be worked on
|
|
682
|
+
- **in_progress** - Currently working on (limit ONE at a time)
|
|
683
|
+
- **completed** - Finished successfully
|
|
684
|
+
- **failed** - Could not complete due to errors
|
|
685
|
+
|
|
686
|
+
# Task Descriptions
|
|
687
|
+
Each task needs:
|
|
688
|
+
- \`id\`: Unique identifier
|
|
689
|
+
- \`task\`: Clear, actionable description in imperative form (e.g., "Run tests")
|
|
690
|
+
- \`status\`: Current state
|
|
691
|
+
- \`priority\`: high/medium/low
|
|
692
|
+
|
|
693
|
+
# Examples
|
|
694
|
+
\`\`\`json
|
|
695
|
+
{
|
|
696
|
+
"todos": [
|
|
697
|
+
{ "id": "1", "task": "Run the build and check for errors", "status": "in_progress", "priority": "high" },
|
|
698
|
+
{ "id": "2", "task": "Fix any type errors found", "status": "pending", "priority": "high" },
|
|
699
|
+
{ "id": "3", "task": "Write unit tests for new feature", "status": "pending", "priority": "medium" }
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
\`\`\`
|
|
703
|
+
|
|
704
|
+
# Best Practices
|
|
705
|
+
- Mark tasks as completed IMMEDIATELY after finishing
|
|
706
|
+
- Don't batch multiple completions - update as you go
|
|
707
|
+
- Keep task descriptions clear and actionable
|
|
708
|
+
- Use appropriate priority levels to indicate urgency`;
|
|
709
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
710
|
+
todoList = [];
|
|
711
|
+
async execute(params) {
|
|
712
|
+
const { todos } = params;
|
|
713
|
+
try {
|
|
714
|
+
this.todoList = todos;
|
|
715
|
+
const summary = {
|
|
716
|
+
pending: todos.filter(t => t.status === 'pending').length,
|
|
717
|
+
in_progress: todos.filter(t => t.status === 'in_progress').length,
|
|
718
|
+
completed: todos.filter(t => t.status === 'completed').length,
|
|
719
|
+
failed: todos.filter(t => t.status === 'failed').length
|
|
720
|
+
};
|
|
721
|
+
return {
|
|
722
|
+
success: true,
|
|
723
|
+
message: `Updated todo list: ${summary.pending} pending, ${summary.in_progress} in progress, ${summary.completed} completed, ${summary.failed} failed`,
|
|
724
|
+
todos: this.todoList
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
throw new Error(`Failed to update todo list: ${error.message}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
getTodos() {
|
|
732
|
+
return this.todoList;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
export class TodoReadTool {
|
|
736
|
+
name = 'todo_read';
|
|
737
|
+
description = `Read the current session's todo list and get a summary of all tasks. Use this to check what tasks remain and their current status.
|
|
738
|
+
|
|
739
|
+
# When to Use
|
|
740
|
+
- Before starting work to understand what needs to be done
|
|
741
|
+
- After completing a task to verify the todo list is updated
|
|
742
|
+
- When user asks about progress or remaining tasks
|
|
743
|
+
- To get an overview of task distribution (pending, in_progress, completed)
|
|
744
|
+
|
|
745
|
+
# What It Returns
|
|
746
|
+
- Full list of all todos with their IDs, tasks, statuses, and priorities
|
|
747
|
+
- Summary counts: total, pending, in_progress, completed, failed
|
|
748
|
+
|
|
749
|
+
# Examples
|
|
750
|
+
- User asks: "What are we working on right now?" → Use todo_read to show current state
|
|
751
|
+
- After a task completes → Check todo_read to confirm the list is accurate
|
|
752
|
+
|
|
753
|
+
# Best Practices
|
|
754
|
+
- Use todo_write to modify the list, not todo_read
|
|
755
|
+
- Check todo_read after todo_write to verify updates`;
|
|
756
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
757
|
+
todoWriteTool;
|
|
758
|
+
constructor(todoWriteTool) {
|
|
759
|
+
this.todoWriteTool = todoWriteTool;
|
|
760
|
+
}
|
|
761
|
+
async execute() {
|
|
762
|
+
try {
|
|
763
|
+
const todos = this.todoWriteTool.getTodos();
|
|
764
|
+
const summary = {
|
|
765
|
+
total: todos.length,
|
|
766
|
+
pending: todos.filter(t => t.status === 'pending').length,
|
|
767
|
+
in_progress: todos.filter(t => t.status === 'in_progress').length,
|
|
768
|
+
completed: todos.filter(t => t.status === 'completed').length,
|
|
769
|
+
failed: todos.filter(t => t.status === 'failed').length
|
|
770
|
+
};
|
|
771
|
+
return {
|
|
772
|
+
todos,
|
|
773
|
+
summary
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
throw new Error(`Failed to read todo list: ${error.message}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
export class TaskTool {
|
|
782
|
+
name = 'task';
|
|
783
|
+
description = `Launch specialized AI subagents to handle complex, multi-step tasks. Subagents are expert agents designed for specific domains like planning, code exploration, frontend testing, and more.
|
|
784
|
+
|
|
785
|
+
# When to Use
|
|
786
|
+
- Complex tasks requiring specialized expertise (planning, analysis, testing)
|
|
787
|
+
- Multi-step workflows that benefit from dedicated focus
|
|
788
|
+
- When you need to delegate work to avoid context overload
|
|
789
|
+
- Parallel execution of independent tasks across different domains
|
|
790
|
+
- User explicitly requests a specific type of agent (e.g., "use the frontend tester")
|
|
791
|
+
|
|
792
|
+
# Available SubAgents
|
|
793
|
+
1. **plan-agent** - Task planning and breakdown, risk analysis, implementation roadmaps
|
|
794
|
+
2. **explore-agent** - Codebase exploration, architecture analysis, finding specific code
|
|
795
|
+
3. **frontend-tester** - Writing and running frontend tests, UI validation
|
|
796
|
+
4. **code-reviewer** - Code review, security checks, bug detection
|
|
797
|
+
5. **frontend-developer** - Frontend development (React, TypeScript, modern web)
|
|
798
|
+
6. **backend-developer** - Backend development (Node.js, APIs, databases)
|
|
799
|
+
7. **gui-subagent** - Browser automation, visual web interactions, desktop application automation
|
|
800
|
+
|
|
801
|
+
# When NOT to Use
|
|
802
|
+
- Simple, straightforward tasks you can handle directly
|
|
803
|
+
- Tasks that don't require specialized expertise
|
|
804
|
+
- Single-step operations (use other tools instead)
|
|
805
|
+
|
|
806
|
+
# Examples
|
|
807
|
+
- "Analyze the authentication module and create a security report" → explore-agent
|
|
808
|
+
- "Create a detailed implementation plan for feature X" → plan-agent
|
|
809
|
+
- "Write unit tests for this React component" → frontend-tester
|
|
810
|
+
- "Review my changes for potential bugs" → code-reviewer
|
|
811
|
+
- "Automatically fill out this form and navigate the website" → gui-subagent
|
|
812
|
+
- "Test the login process on the desktop application" → gui-subagent
|
|
813
|
+
- "send a message to the my mom on the desktop application wechat" → gui-subagent
|
|
814
|
+
|
|
815
|
+
# Best Practices
|
|
816
|
+
- Provide clear, specific prompts to subagents
|
|
817
|
+
- Include relevant context (file paths, requirements, constraints)
|
|
818
|
+
- Set appropriate executionMode if needed
|
|
819
|
+
- For parallel execution, ensure tasks are truly independent`;
|
|
820
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
821
|
+
async execute(params, _executionMode) {
|
|
822
|
+
const mode = params.executionMode || _executionMode || ExecutionMode.YOLO;
|
|
823
|
+
try {
|
|
824
|
+
const { getAgentManager } = await import('./agents.js');
|
|
825
|
+
const agentManager = getAgentManager(process.cwd());
|
|
826
|
+
const { getConfigManager } = await import('./config.js');
|
|
827
|
+
const config = getConfigManager();
|
|
828
|
+
const { AIClient } = await import('./ai-client.js');
|
|
829
|
+
const aiClient = new AIClient({
|
|
830
|
+
type: AuthType.API_KEY,
|
|
831
|
+
apiKey: config.get('apiKey'),
|
|
832
|
+
baseUrl: config.get('baseUrl'),
|
|
833
|
+
modelName: config.get('modelName') || 'Qwen3-Coder'
|
|
834
|
+
});
|
|
835
|
+
const toolRegistry = getToolRegistry();
|
|
836
|
+
if (params.agents && params.agents.length > 0) {
|
|
837
|
+
return await this.executeParallelAgents(params.agents, params.description, mode, agentManager, toolRegistry, aiClient);
|
|
838
|
+
}
|
|
839
|
+
if (!params.subagent_type || !params.prompt) {
|
|
840
|
+
throw new Error('Either subagent_type and prompt, or agents array must be provided');
|
|
841
|
+
}
|
|
842
|
+
const result = await this.executeSingleAgent(params.subagent_type, params.prompt, params.description, params.useContext ?? true, params.constraints || [], mode, agentManager, toolRegistry, aiClient, config);
|
|
843
|
+
return result;
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
throw new Error(`Task execution failed: ${error.message}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Execute GUI subagent by directly calling GUIAgent.run()
|
|
851
|
+
* This bypasses the normal subagent message loop for better GUI control
|
|
852
|
+
*/
|
|
853
|
+
async executeGUIAgent(prompt, description, agent, mode, config, indentLevel = 1) {
|
|
854
|
+
const indent = ' '.repeat(indentLevel);
|
|
855
|
+
const cancellationManager = getCancellationManager();
|
|
856
|
+
const logger = getLogger();
|
|
857
|
+
console.log(`${indent}${colors.primaryBright(`${icons.robot} GUI Agent`)}: ${description}`);
|
|
858
|
+
console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
|
|
859
|
+
console.log('');
|
|
860
|
+
// Get model config for GUI agent
|
|
861
|
+
// Priority: guiSubagentBaseUrl (test first) -> baseUrl (fallback)
|
|
862
|
+
// When falling back to baseUrl, also use the corresponding modelName and apiKey
|
|
863
|
+
const primaryBaseUrl = config.get('guiSubagentBaseUrl') || '';
|
|
864
|
+
const fallbackBaseUrl = config.get('baseUrl') || '';
|
|
865
|
+
const primaryApiKey = config.get('guiSubagentApiKey') || '';
|
|
866
|
+
const fallbackApiKey = config.get('apiKey') || '';
|
|
867
|
+
const primaryModelName = config.get('guiSubagentModel') || '';
|
|
868
|
+
const fallbackModelName = config.get('modelName') || '';
|
|
869
|
+
let baseUrl = primaryBaseUrl;
|
|
870
|
+
let modelName = primaryModelName;
|
|
871
|
+
let apiKey = primaryApiKey;
|
|
872
|
+
// Test API availability (like curl) and choose the right baseUrl
|
|
873
|
+
if (primaryBaseUrl) {
|
|
874
|
+
try {
|
|
875
|
+
const controller = new AbortController();
|
|
876
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
877
|
+
const response = await fetch(`${primaryBaseUrl.replace(/\/v1\/?$/, '')}/models`, {
|
|
878
|
+
method: 'GET',
|
|
879
|
+
headers: primaryApiKey ? { 'Authorization': `Bearer ${primaryApiKey}` } : {},
|
|
880
|
+
signal: controller.signal
|
|
881
|
+
});
|
|
882
|
+
clearTimeout(timeoutId);
|
|
883
|
+
if (!response.ok) {
|
|
884
|
+
// Fallback to baseUrl with its corresponding model and API key
|
|
885
|
+
baseUrl = fallbackBaseUrl;
|
|
886
|
+
modelName = fallbackModelName;
|
|
887
|
+
apiKey = fallbackApiKey;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
// Fallback to baseUrl with its corresponding model and API key
|
|
892
|
+
baseUrl = fallbackBaseUrl;
|
|
893
|
+
modelName = fallbackModelName;
|
|
894
|
+
apiKey = fallbackApiKey;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
baseUrl = fallbackBaseUrl;
|
|
899
|
+
modelName = fallbackModelName;
|
|
900
|
+
apiKey = fallbackApiKey;
|
|
901
|
+
}
|
|
902
|
+
if (!baseUrl) {
|
|
903
|
+
return {
|
|
904
|
+
success: false,
|
|
905
|
+
message: `GUI task "${description}" failed: No valid API URL configured`
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
// Set up stdin polling for ESC cancellation
|
|
909
|
+
let rawModeEnabled = false;
|
|
910
|
+
let stdinPollingInterval = null;
|
|
911
|
+
const setupStdinPolling = () => {
|
|
912
|
+
if (process.stdin.isTTY) {
|
|
913
|
+
try {
|
|
914
|
+
process.stdin.setRawMode(true);
|
|
915
|
+
rawModeEnabled = true;
|
|
916
|
+
process.stdin.resume();
|
|
917
|
+
readline.emitKeypressEvents(process.stdin);
|
|
918
|
+
}
|
|
919
|
+
catch (e) {
|
|
920
|
+
logger.debug(`[GUIAgent] Could not set raw mode: ${e}`);
|
|
921
|
+
}
|
|
922
|
+
stdinPollingInterval = setInterval(() => {
|
|
923
|
+
try {
|
|
924
|
+
if (rawModeEnabled) {
|
|
925
|
+
const chunk = process.stdin.read(1);
|
|
926
|
+
if (chunk && chunk.length > 0) {
|
|
927
|
+
const code = chunk[0];
|
|
928
|
+
if (code === 0x1B) { // ESC
|
|
929
|
+
logger.debug('[GUIAgent] ESC detected!');
|
|
930
|
+
cancellationManager.cancel();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
catch (e) {
|
|
936
|
+
// Ignore polling errors
|
|
937
|
+
}
|
|
938
|
+
}, 10);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
const cleanupStdinPolling = () => {
|
|
942
|
+
if (stdinPollingInterval) {
|
|
943
|
+
clearInterval(stdinPollingInterval);
|
|
944
|
+
stdinPollingInterval = null;
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
// Set up cancellation
|
|
948
|
+
let cancelled = false;
|
|
949
|
+
const cancelHandler = () => {
|
|
950
|
+
cancelled = true;
|
|
951
|
+
};
|
|
952
|
+
cancellationManager.on('cancelled', cancelHandler);
|
|
953
|
+
// Start polling for ESC
|
|
954
|
+
setupStdinPolling();
|
|
955
|
+
try {
|
|
956
|
+
// Import and create GUIAgent
|
|
957
|
+
const { createGUISubAgent } = await import('./gui-subagent/index.js');
|
|
958
|
+
const guiAgent = await createGUISubAgent({
|
|
959
|
+
model: modelName,
|
|
960
|
+
modelBaseUrl: baseUrl || undefined,
|
|
961
|
+
modelApiKey: apiKey || undefined,
|
|
962
|
+
maxLoopCount: 30,
|
|
963
|
+
loopIntervalInMs: 500,
|
|
964
|
+
showAIDebugInfo: config.get('showAIDebugInfo') || false,
|
|
965
|
+
});
|
|
966
|
+
// Add constraints to prompt if any
|
|
967
|
+
const fullPrompt = prompt;
|
|
968
|
+
// Execute GUI task - this will run autonomously until completion
|
|
969
|
+
const result = await guiAgent.run(fullPrompt);
|
|
970
|
+
// Cleanup
|
|
971
|
+
await guiAgent.cleanup();
|
|
972
|
+
// Check cancellation
|
|
973
|
+
if (cancelled || cancellationManager.isOperationCancelled()) {
|
|
974
|
+
cleanupStdinPolling();
|
|
975
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
976
|
+
// Flush stdout to prevent residual output after prompt
|
|
977
|
+
process.stdout.write('\n');
|
|
978
|
+
return {
|
|
979
|
+
success: true,
|
|
980
|
+
cancelled: true, // Mark as cancelled so main agent won't continue
|
|
981
|
+
message: `GUI task "${description}" cancelled by user`,
|
|
982
|
+
result: 'Task cancelled'
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
cleanupStdinPolling();
|
|
986
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
987
|
+
// Flush stdout to ensure all output is displayed before returning
|
|
988
|
+
process.stdout.write('\n');
|
|
989
|
+
// Return result based on GUIAgent status
|
|
990
|
+
if (result.status === 'end') {
|
|
991
|
+
const iterations = result.conversations.filter(c => c.from === 'human' && c.screenshotBase64).length;
|
|
992
|
+
console.log(`${indent}${colors.success(`${icons.check} GUI task completed in ${iterations} iterations`)}`);
|
|
993
|
+
return {
|
|
994
|
+
success: true,
|
|
995
|
+
message: `GUI task "${description}" completed`,
|
|
996
|
+
result: `Completed in ${iterations} iterations`
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
else if (result.status === 'user_stopped') {
|
|
1000
|
+
return {
|
|
1001
|
+
success: true,
|
|
1002
|
+
message: `GUI task "${description}" stopped by user`,
|
|
1003
|
+
result: 'User stopped'
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
// status is 'error' or other non-success status
|
|
1008
|
+
const errorMsg = result.error || 'Unknown error';
|
|
1009
|
+
return {
|
|
1010
|
+
success: false,
|
|
1011
|
+
message: `GUI task "${description}" failed: ${errorMsg}`
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
catch (error) {
|
|
1016
|
+
cleanupStdinPolling();
|
|
1017
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
1018
|
+
// Flush stdout to prevent residual output
|
|
1019
|
+
process.stdout.write('\n');
|
|
1020
|
+
// If the user cancelled the task, ignore any API errors (like 429)
|
|
1021
|
+
// and return cancelled status instead
|
|
1022
|
+
if (cancelled || cancellationManager.isOperationCancelled()) {
|
|
1023
|
+
return {
|
|
1024
|
+
success: true,
|
|
1025
|
+
cancelled: true, // Mark as cancelled so main agent won't continue
|
|
1026
|
+
message: `GUI task "${description}" cancelled by user`,
|
|
1027
|
+
result: 'Task cancelled'
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
if (error.message === 'Operation cancelled by user') {
|
|
1031
|
+
return {
|
|
1032
|
+
success: true,
|
|
1033
|
+
message: `GUI task "${description}" cancelled by user`,
|
|
1034
|
+
result: 'Task cancelled'
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
// Return failure without throwing - let the main agent handle it
|
|
1038
|
+
return {
|
|
1039
|
+
success: false,
|
|
1040
|
+
message: `GUI task "${description}" failed: ${error.message}`
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
async executeSingleAgent(subagent_type, prompt, description, useContext, constraints, mode, agentManager, toolRegistry, aiClient, config, indentLevel = 1) {
|
|
1045
|
+
const agent = agentManager.getAgent(subagent_type);
|
|
1046
|
+
if (!agent) {
|
|
1047
|
+
throw new Error(`Agent ${subagent_type} not found`);
|
|
1048
|
+
}
|
|
1049
|
+
// Special handling for gui-subagent: directly call GUIAgent.run() instead of subagent message loop
|
|
1050
|
+
if (subagent_type === 'gui-subagent') {
|
|
1051
|
+
return this.executeGUIAgent(prompt, description, agent, mode, config, indentLevel);
|
|
1052
|
+
}
|
|
1053
|
+
// Determine the model to use for this subagent
|
|
1054
|
+
let modelName = config.get('modelName') || 'Qwen3-Coder';
|
|
1055
|
+
let baseUrl = config.get('baseUrl') || 'https://apis.xagent.cn/v1';
|
|
1056
|
+
let apiKey = config.get('apiKey') || '';
|
|
1057
|
+
if (agent.model) {
|
|
1058
|
+
// If agent has a model field, it can be a model name or a config reference like 'guiSubagentModel'
|
|
1059
|
+
if (typeof agent.model === 'string' && agent.model.endsWith('Model')) {
|
|
1060
|
+
// It's a config reference, use corresponding config values
|
|
1061
|
+
modelName = config.get(agent.model) || modelName;
|
|
1062
|
+
const baseUrlKey = agent.model.replace('Model', 'BaseUrl');
|
|
1063
|
+
const apiKeyKey = agent.model.replace('Model', 'ApiKey');
|
|
1064
|
+
if (config.get(baseUrlKey)) {
|
|
1065
|
+
baseUrl = config.get(baseUrlKey);
|
|
1066
|
+
}
|
|
1067
|
+
if (config.get(apiKeyKey)) {
|
|
1068
|
+
apiKey = config.get(apiKeyKey);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
else if (typeof agent.model === 'string') {
|
|
1072
|
+
// It's an explicit model name
|
|
1073
|
+
modelName = agent.model;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
// Create a new AIClient for this subagent with its specific model
|
|
1077
|
+
const { AIClient: SubAgentAIClient } = await import('./ai-client.js');
|
|
1078
|
+
const subAgentClient = new SubAgentAIClient({
|
|
1079
|
+
type: AuthType.API_KEY,
|
|
1080
|
+
apiKey: apiKey,
|
|
1081
|
+
baseUrl: baseUrl,
|
|
1082
|
+
modelName: modelName,
|
|
1083
|
+
showAIDebugInfo: config.get('showAIDebugInfo') || false
|
|
1084
|
+
});
|
|
1085
|
+
const indent = ' '.repeat(indentLevel);
|
|
1086
|
+
const indentNext = ' '.repeat(indentLevel + 1);
|
|
1087
|
+
const agentName = agent.name || subagent_type;
|
|
1088
|
+
const cancellationManager = getCancellationManager();
|
|
1089
|
+
const logger = getLogger();
|
|
1090
|
+
let cancelled = false;
|
|
1091
|
+
// Set up raw mode and stdin polling for ESC detection
|
|
1092
|
+
let rawModeEnabled = false;
|
|
1093
|
+
let stdinPollingInterval = null;
|
|
1094
|
+
const setupStdinPolling = () => {
|
|
1095
|
+
if (process.stdin.isTTY) {
|
|
1096
|
+
try {
|
|
1097
|
+
process.stdin.setRawMode(true);
|
|
1098
|
+
rawModeEnabled = true;
|
|
1099
|
+
process.stdin.resume();
|
|
1100
|
+
readline.emitKeypressEvents(process.stdin);
|
|
1101
|
+
}
|
|
1102
|
+
catch (e) {
|
|
1103
|
+
logger.debug(`[TaskTool] Could not set raw mode: ${e}`);
|
|
1104
|
+
}
|
|
1105
|
+
// Start polling for ESC key (10ms interval for faster response)
|
|
1106
|
+
stdinPollingInterval = setInterval(() => {
|
|
1107
|
+
try {
|
|
1108
|
+
if (rawModeEnabled) {
|
|
1109
|
+
const chunk = process.stdin.read(1);
|
|
1110
|
+
if (chunk && chunk.length > 0) {
|
|
1111
|
+
const code = chunk[0];
|
|
1112
|
+
if (code === 0x1B) { // ESC
|
|
1113
|
+
logger.debug('[TaskTool] ESC detected via polling!');
|
|
1114
|
+
cancellationManager.cancel();
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
catch (e) {
|
|
1120
|
+
// Ignore polling errors
|
|
1121
|
+
}
|
|
1122
|
+
}, 10);
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
const cleanupStdinPolling = () => {
|
|
1126
|
+
if (stdinPollingInterval) {
|
|
1127
|
+
clearInterval(stdinPollingInterval);
|
|
1128
|
+
stdinPollingInterval = null;
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
// Start polling for ESC
|
|
1132
|
+
setupStdinPolling();
|
|
1133
|
+
// Listen for cancellation
|
|
1134
|
+
const cancelHandler = () => {
|
|
1135
|
+
cancelled = true;
|
|
1136
|
+
};
|
|
1137
|
+
cancellationManager.on('cancelled', cancelHandler);
|
|
1138
|
+
// Helper function to indent multi-line content
|
|
1139
|
+
const indentMultiline = (content, baseIndent) => {
|
|
1140
|
+
return content.split('\n').map(line => `${baseIndent} ${line}`).join('\n');
|
|
1141
|
+
};
|
|
1142
|
+
// Check if operation is cancelled
|
|
1143
|
+
const checkCancellation = () => {
|
|
1144
|
+
if (cancelled || cancellationManager.isOperationCancelled()) {
|
|
1145
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
1146
|
+
cleanupStdinPolling();
|
|
1147
|
+
throw new Error('Operation cancelled by user');
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, mode, agent);
|
|
1151
|
+
const enhancedSystemPrompt = await systemPromptGenerator.generateEnhancedSystemPrompt(agent.systemPrompt);
|
|
1152
|
+
const fullPrompt = constraints.length > 0
|
|
1153
|
+
? `${prompt}\n\nConstraints:\n${constraints.map(c => `- ${c}`).join('\n')}`
|
|
1154
|
+
: prompt;
|
|
1155
|
+
let messages = [
|
|
1156
|
+
{ role: 'system', content: enhancedSystemPrompt },
|
|
1157
|
+
{ role: 'user', content: fullPrompt }
|
|
1158
|
+
];
|
|
1159
|
+
const availableTools = agentManager.getAvailableToolsForAgent(agent, mode);
|
|
1160
|
+
const allToolDefinitions = toolRegistry.getToolDefinitions();
|
|
1161
|
+
const toolDefinitions = availableTools.map((toolName) => {
|
|
1162
|
+
const fullDef = allToolDefinitions.find((def) => def.function.name === toolName);
|
|
1163
|
+
if (fullDef) {
|
|
1164
|
+
return fullDef;
|
|
1165
|
+
}
|
|
1166
|
+
return {
|
|
1167
|
+
type: 'function',
|
|
1168
|
+
function: {
|
|
1169
|
+
name: toolName,
|
|
1170
|
+
description: `Tool: ${toolName}`,
|
|
1171
|
+
parameters: { type: 'object', properties: {}, required: [] }
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
});
|
|
1175
|
+
let iteration = 0;
|
|
1176
|
+
const maxIterations = 10;
|
|
1177
|
+
while (iteration < maxIterations) {
|
|
1178
|
+
iteration++;
|
|
1179
|
+
// Check for cancellation before each iteration
|
|
1180
|
+
checkCancellation();
|
|
1181
|
+
// Use withCancellation to make API call cancellable
|
|
1182
|
+
const result = await cancellationManager.withCancellation(subAgentClient.chatCompletion(messages, {
|
|
1183
|
+
tools: toolDefinitions,
|
|
1184
|
+
temperature: 0.7
|
|
1185
|
+
}), `api-${subagent_type}-${iteration}`);
|
|
1186
|
+
// Check for cancellation after API call
|
|
1187
|
+
checkCancellation();
|
|
1188
|
+
if (!result || !result.choices || result.choices.length === 0) {
|
|
1189
|
+
throw new Error(`Sub-agent ${subagent_type} returned empty response`);
|
|
1190
|
+
}
|
|
1191
|
+
const choice = result.choices[0];
|
|
1192
|
+
const messageContent = choice.message?.content;
|
|
1193
|
+
const toolCalls = choice.message.tool_calls;
|
|
1194
|
+
let contentStr;
|
|
1195
|
+
let hasValidContent = false;
|
|
1196
|
+
if (typeof messageContent === 'string') {
|
|
1197
|
+
contentStr = messageContent;
|
|
1198
|
+
hasValidContent = messageContent.trim() !== '';
|
|
1199
|
+
}
|
|
1200
|
+
else if (Array.isArray(messageContent)) {
|
|
1201
|
+
const textParts = messageContent
|
|
1202
|
+
.filter(item => typeof item?.text === 'string' && item.text.trim() !== '')
|
|
1203
|
+
.map(item => item.text);
|
|
1204
|
+
contentStr = textParts.join('');
|
|
1205
|
+
hasValidContent = textParts.length > 0;
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
contentStr = '';
|
|
1209
|
+
hasValidContent = false;
|
|
1210
|
+
}
|
|
1211
|
+
// Only throw empty content error if there's no text content AND no tool calls
|
|
1212
|
+
// When AI model returns tool_calls, message.content can be null/empty, which is valid
|
|
1213
|
+
if (!hasValidContent && (!toolCalls || toolCalls.length === 0)) {
|
|
1214
|
+
throw new Error(`Sub-agent ${subagent_type} returned empty content`);
|
|
1215
|
+
}
|
|
1216
|
+
if (choice.finish_reason === 'length') {
|
|
1217
|
+
throw new Error(`Sub-agent ${subagent_type} response truncated due to length limits`);
|
|
1218
|
+
}
|
|
1219
|
+
// Add assistant message to conversation
|
|
1220
|
+
messages.push({ role: 'assistant', content: contentStr });
|
|
1221
|
+
// Display assistant response (if there's any text content) with proper indentation
|
|
1222
|
+
if (contentStr) {
|
|
1223
|
+
console.log(`\n${indent}${colors.primaryBright(agentName)}: ${description}`);
|
|
1224
|
+
const truncatedContent = contentStr.length > 500 ? contentStr.substring(0, 500) + '...' : contentStr;
|
|
1225
|
+
const indentedContent = indentMultiline(truncatedContent, indent);
|
|
1226
|
+
console.log(`${indentedContent}\n`);
|
|
1227
|
+
}
|
|
1228
|
+
// Process tool calls with proper indentation
|
|
1229
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
1230
|
+
for (const toolCall of toolCalls) {
|
|
1231
|
+
const { name, arguments: params } = toolCall.function;
|
|
1232
|
+
let parsedParams;
|
|
1233
|
+
try {
|
|
1234
|
+
parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
|
|
1235
|
+
}
|
|
1236
|
+
catch (e) {
|
|
1237
|
+
parsedParams = params;
|
|
1238
|
+
}
|
|
1239
|
+
console.log(`${indent}${colors.textMuted(`${icons.loading} Tool: ${name}`)}`);
|
|
1240
|
+
try {
|
|
1241
|
+
// Check cancellation before tool execution
|
|
1242
|
+
checkCancellation();
|
|
1243
|
+
const toolResult = await cancellationManager.withCancellation(toolRegistry.execute(name, parsedParams, mode, indent), `subagent-${subagent_type}-${name}-${iteration}`);
|
|
1244
|
+
// Display tool result with proper indentation for multi-line content
|
|
1245
|
+
const resultPreview = typeof toolResult === 'string' ? toolResult : JSON.stringify(toolResult, null, 2);
|
|
1246
|
+
const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
|
|
1247
|
+
const indentedPreview = indentMultiline(truncatedPreview, indent);
|
|
1248
|
+
console.log(`${indent}${colors.success(`${icons.check} Completed`)}\n${indentedPreview}\n`);
|
|
1249
|
+
messages.push({
|
|
1250
|
+
role: 'tool',
|
|
1251
|
+
content: JSON.stringify(toolResult),
|
|
1252
|
+
tool_call_id: toolCall.id
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
catch (error) {
|
|
1256
|
+
if (error.message === 'Operation cancelled by user') {
|
|
1257
|
+
console.log(`${indent}${colors.warning(`⚠️ Operation cancelled`)}\n`);
|
|
1258
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
1259
|
+
cleanupStdinPolling();
|
|
1260
|
+
return {
|
|
1261
|
+
success: false,
|
|
1262
|
+
message: `Task "${description}" cancelled by user`,
|
|
1263
|
+
result: contentStr
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
console.log(`${indent}${colors.error(`${icons.cross} Error:`)} ${error.message}\n`);
|
|
1267
|
+
messages.push({
|
|
1268
|
+
role: 'tool',
|
|
1269
|
+
content: JSON.stringify({ error: error.message }),
|
|
1270
|
+
tool_call_id: toolCall.id
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
console.log('');
|
|
1275
|
+
continue; // Continue to next iteration to get final response
|
|
1276
|
+
}
|
|
1277
|
+
// No more tool calls, return the result
|
|
1278
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
1279
|
+
cleanupStdinPolling();
|
|
1280
|
+
return {
|
|
1281
|
+
success: true,
|
|
1282
|
+
message: `Task "${description}" completed by ${subagent_type}`,
|
|
1283
|
+
result: contentStr
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
// Max iterations reached - return accumulated results instead of throwing error
|
|
1287
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
1288
|
+
cleanupStdinPolling();
|
|
1289
|
+
// Get the last assistant message content
|
|
1290
|
+
const lastAssistantMsg = messages.filter(m => m.role === 'assistant').pop();
|
|
1291
|
+
const lastContent = lastAssistantMsg?.content || '';
|
|
1292
|
+
return {
|
|
1293
|
+
success: true,
|
|
1294
|
+
message: `Task "${description}" completed (max iterations reached) by ${subagent_type}`,
|
|
1295
|
+
result: lastContent
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
async executeParallelAgents(agents, description, mode, agentManager, toolRegistry, aiClient, indentLevel = 1) {
|
|
1299
|
+
const indent = ' '.repeat(indentLevel);
|
|
1300
|
+
const indentNext = ' '.repeat(indentLevel + 1);
|
|
1301
|
+
const cancellationManager = getCancellationManager();
|
|
1302
|
+
const logger = getLogger();
|
|
1303
|
+
// Set up raw mode and stdin polling for ESC detection
|
|
1304
|
+
let rawModeEnabled = false;
|
|
1305
|
+
let stdinPollingInterval = null;
|
|
1306
|
+
const setupStdinPolling = () => {
|
|
1307
|
+
if (process.stdin.isTTY) {
|
|
1308
|
+
try {
|
|
1309
|
+
process.stdin.setRawMode(true);
|
|
1310
|
+
rawModeEnabled = true;
|
|
1311
|
+
process.stdin.resume();
|
|
1312
|
+
readline.emitKeypressEvents(process.stdin);
|
|
1313
|
+
}
|
|
1314
|
+
catch (e) {
|
|
1315
|
+
logger.debug(`[ParallelAgents] Could not set raw mode: ${e}`);
|
|
1316
|
+
}
|
|
1317
|
+
stdinPollingInterval = setInterval(() => {
|
|
1318
|
+
try {
|
|
1319
|
+
if (rawModeEnabled) {
|
|
1320
|
+
const chunk = process.stdin.read(1);
|
|
1321
|
+
if (chunk && chunk.length > 0) {
|
|
1322
|
+
const code = chunk[0];
|
|
1323
|
+
if (code === 0x1B) { // ESC
|
|
1324
|
+
logger.debug('[ParallelAgents] ESC detected via polling!');
|
|
1325
|
+
cancellationManager.cancel();
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
catch (e) {
|
|
1331
|
+
// Ignore polling errors
|
|
1332
|
+
}
|
|
1333
|
+
}, 10);
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
const cleanupStdinPolling = () => {
|
|
1337
|
+
if (stdinPollingInterval) {
|
|
1338
|
+
clearInterval(stdinPollingInterval);
|
|
1339
|
+
stdinPollingInterval = null;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
// Start polling for ESC
|
|
1343
|
+
setupStdinPolling();
|
|
1344
|
+
// Listen for cancellation to stop parallel execution
|
|
1345
|
+
let cancelled = false;
|
|
1346
|
+
const cancelHandler = () => {
|
|
1347
|
+
cancelled = true;
|
|
1348
|
+
};
|
|
1349
|
+
cancellationManager.on('cancelled', cancelHandler);
|
|
1350
|
+
console.log(`\n${indent}${colors.accent('◆')} ${colors.primaryBright('Parallel Agents')}: ${agents.length} running...`);
|
|
1351
|
+
const startTime = Date.now();
|
|
1352
|
+
const agentPromises = agents.map(async (agentTask, index) => {
|
|
1353
|
+
// Check if cancelled
|
|
1354
|
+
if (cancelled || cancellationManager.isOperationCancelled()) {
|
|
1355
|
+
return {
|
|
1356
|
+
success: false,
|
|
1357
|
+
agent: agentTask.subagent_type,
|
|
1358
|
+
description: agentTask.description,
|
|
1359
|
+
error: 'Operation cancelled by user'
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
try {
|
|
1363
|
+
const result = await this.executeSingleAgent(agentTask.subagent_type, agentTask.prompt, agentTask.description, agentTask.useContext ?? true, agentTask.constraints || [], mode, agentManager, toolRegistry, aiClient, indentLevel + 1);
|
|
1364
|
+
return {
|
|
1365
|
+
success: true,
|
|
1366
|
+
agent: agentTask.subagent_type,
|
|
1367
|
+
description: agentTask.description,
|
|
1368
|
+
result: result.result
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
catch (error) {
|
|
1372
|
+
return {
|
|
1373
|
+
success: false,
|
|
1374
|
+
agent: agentTask.subagent_type,
|
|
1375
|
+
description: agentTask.description,
|
|
1376
|
+
error: error.message
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
const results = await Promise.all(agentPromises);
|
|
1381
|
+
const duration = Date.now() - startTime;
|
|
1382
|
+
const successfulAgents = results.filter(r => r.success);
|
|
1383
|
+
const failedAgents = results.filter(r => !r.success);
|
|
1384
|
+
console.log(`${indent}${colors.success('✔')} Parallel task completed in ${colors.textMuted(duration + 'ms')}`);
|
|
1385
|
+
console.log(`${indent}${colors.info('ℹ')} Success: ${successfulAgents.length}/${agents.length} agents\n`);
|
|
1386
|
+
if (failedAgents.length > 0) {
|
|
1387
|
+
console.log(`${indent}${colors.error('✖')} Failed agents:`);
|
|
1388
|
+
for (const failed of failedAgents) {
|
|
1389
|
+
console.log(`${indentNext} ${colors.error('•')} ${failed.agent}: ${failed.error}`);
|
|
1390
|
+
}
|
|
1391
|
+
console.log('');
|
|
1392
|
+
}
|
|
1393
|
+
// Cleanup
|
|
1394
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
1395
|
+
cleanupStdinPolling();
|
|
1396
|
+
return {
|
|
1397
|
+
success: failedAgents.length === 0,
|
|
1398
|
+
message: `Parallel task "${description}" completed: ${successfulAgents.length}/${agents.length} successful`,
|
|
1399
|
+
results: successfulAgents.map(r => ({
|
|
1400
|
+
agent: r.agent,
|
|
1401
|
+
description: r.description,
|
|
1402
|
+
result: r.result
|
|
1403
|
+
})),
|
|
1404
|
+
errors: failedAgents.map(r => ({
|
|
1405
|
+
agent: r.agent,
|
|
1406
|
+
description: r.description,
|
|
1407
|
+
error: r.error
|
|
1408
|
+
}))
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
export class ReadBashOutputTool {
|
|
1413
|
+
name = 'ReadBashOutput';
|
|
1414
|
+
description = `Retrieve output from a background task that was started with Bash(run_in_bg=true).
|
|
1415
|
+
|
|
1416
|
+
# When to Use
|
|
1417
|
+
- Checking the output of a long-running background process
|
|
1418
|
+
- Monitoring progress of builds, tests, or servers
|
|
1419
|
+
- Retrieving logs from background tasks
|
|
1420
|
+
- When you started a task with run_in_bg=true and need results
|
|
1421
|
+
|
|
1422
|
+
# When NOT to Use
|
|
1423
|
+
- For synchronous commands (they return output directly)
|
|
1424
|
+
- When the background task hasn't been started yet
|
|
1425
|
+
- For tasks that have already completed (use Bash directly)
|
|
1426
|
+
|
|
1427
|
+
# Parameters
|
|
1428
|
+
- \`task_id\`: The ID returned from the background Bash command
|
|
1429
|
+
- \`poll_interval\`: (Optional) Seconds to wait before checking, default: 10
|
|
1430
|
+
|
|
1431
|
+
# Examples
|
|
1432
|
+
- Check build output: ReadBashOutput(task_id="task_1234567890")
|
|
1433
|
+
- Wait and check: ReadBashOutput(task_id="task_123", poll_interval=5)
|
|
1434
|
+
|
|
1435
|
+
# Best Practices
|
|
1436
|
+
- Save the task_id from Bash response for later use
|
|
1437
|
+
- Use appropriate poll_interval based on expected task duration
|
|
1438
|
+
- Check status to see if task is still running or completed
|
|
1439
|
+
- Combine with todo_write to track background task progress`;
|
|
1440
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
1441
|
+
async execute(params) {
|
|
1442
|
+
const { task_id, poll_interval = 10 } = params;
|
|
1443
|
+
try {
|
|
1444
|
+
const toolRegistry = getToolRegistry();
|
|
1445
|
+
const task = toolRegistry.getBackgroundTask(task_id);
|
|
1446
|
+
if (!task) {
|
|
1447
|
+
throw new Error(`Task ${task_id} not found`);
|
|
1448
|
+
}
|
|
1449
|
+
const interval = Math.min(Math.max(poll_interval, 1), 120);
|
|
1450
|
+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
|
|
1451
|
+
const duration = Date.now() - task.startTime;
|
|
1452
|
+
const output = task.output.join('');
|
|
1453
|
+
const status = task.process.exitCode === null ? 'running' : 'completed';
|
|
1454
|
+
return {
|
|
1455
|
+
taskId: task_id,
|
|
1456
|
+
output,
|
|
1457
|
+
status,
|
|
1458
|
+
duration: Math.floor(duration / 1000)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
catch (error) {
|
|
1462
|
+
throw new Error(`Failed to read bash output: ${error.message}`);
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
export class WebFetchTool {
|
|
1467
|
+
name = 'web_fetch';
|
|
1468
|
+
description = `Fetch and extract content from a specific URL. This tool retrieves the full content of a webpage.
|
|
1469
|
+
|
|
1470
|
+
# When to Use
|
|
1471
|
+
- When you have a specific URL and need its content
|
|
1472
|
+
- Extracting documentation from web pages
|
|
1473
|
+
- Fetching API documentation or guides
|
|
1474
|
+
- Getting content from known URLs (not for searching)
|
|
1475
|
+
|
|
1476
|
+
# When NOT to Use
|
|
1477
|
+
- When you need to search but don't have a specific URL (use web_search first)
|
|
1478
|
+
- For pages requiring authentication or login
|
|
1479
|
+
- For very large files or pages (may timeout)
|
|
1480
|
+
- When the URL format is unknown (use web_search first)
|
|
1481
|
+
|
|
1482
|
+
# Parameters
|
|
1483
|
+
- \`prompt\`: A prompt containing the URL to fetch (e.g., "Summarize https://example.com/docs")
|
|
1484
|
+
|
|
1485
|
+
# Examples
|
|
1486
|
+
- Fetch documentation: web_fetch(prompt="Extract key points from https://react.dev/docs")
|
|
1487
|
+
- Get API spec: web_fetch(prompt="Fetch the OpenAPI spec from https://api.example.com/openapi.json")
|
|
1488
|
+
|
|
1489
|
+
# Best Practices
|
|
1490
|
+
- Ensure the URL is accessible and doesn't require authentication
|
|
1491
|
+
- Use specific prompts to extract relevant information
|
|
1492
|
+
- Check if the page is accessible if you get errors
|
|
1493
|
+
- Large pages may be truncated due to size limits`;
|
|
1494
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
1495
|
+
async execute(params) {
|
|
1496
|
+
const { prompt } = params;
|
|
1497
|
+
try {
|
|
1498
|
+
const urlMatch = prompt.match(/https?:\/\/[^\s]+/i);
|
|
1499
|
+
if (!urlMatch) {
|
|
1500
|
+
throw new Error('No URL found in prompt');
|
|
1501
|
+
}
|
|
1502
|
+
const url = urlMatch[0];
|
|
1503
|
+
const response = await axios.get(url, {
|
|
1504
|
+
timeout: 30000,
|
|
1505
|
+
maxContentLength: 10 * 1024 * 1024,
|
|
1506
|
+
validateStatus: () => true
|
|
1507
|
+
});
|
|
1508
|
+
let content = response.data;
|
|
1509
|
+
if (typeof content === 'object') {
|
|
1510
|
+
content = JSON.stringify(content, null, 2);
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
content,
|
|
1514
|
+
url,
|
|
1515
|
+
status: response.status
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
catch (error) {
|
|
1519
|
+
throw new Error(`Failed to fetch URL: ${error.message}`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
export class AskUserQuestionTool {
|
|
1524
|
+
name = 'ask_user_question';
|
|
1525
|
+
description = `Ask the user questions during execution to gather input, preferences, or clarifications.
|
|
1526
|
+
|
|
1527
|
+
# When to Use
|
|
1528
|
+
- When you need user input or preferences to proceed
|
|
1529
|
+
- When a task has multiple options and user should choose
|
|
1530
|
+
- When clarification is needed for ambiguous requests
|
|
1531
|
+
- When user explicitly asks to be prompted
|
|
1532
|
+
|
|
1533
|
+
# When NOT to Use
|
|
1534
|
+
- When you can make reasonable assumptions
|
|
1535
|
+
- For simple confirmations (just proceed with reasonable default)
|
|
1536
|
+
- When the information is already available in context
|
|
1537
|
+
- For information you should know or can infer
|
|
1538
|
+
|
|
1539
|
+
# Parameters
|
|
1540
|
+
- \`questions\`: Array of questions with:
|
|
1541
|
+
- \`question\`: The question text
|
|
1542
|
+
- \`header\`: (Optional) Short label for the question
|
|
1543
|
+
- \`options\`: (Optional) Multiple choice options
|
|
1544
|
+
- \`multiSelect\`: (Optional) Allow multiple selections
|
|
1545
|
+
|
|
1546
|
+
# Examples
|
|
1547
|
+
- Simple input: Ask user their preferred name
|
|
1548
|
+
- Multiple choice: Ask which framework to use (React, Vue, Angular)
|
|
1549
|
+
- Multi-select: Ask which features to include (with checkboxes)
|
|
1550
|
+
|
|
1551
|
+
# Best Practices
|
|
1552
|
+
- Limit to 1-4 questions at a time
|
|
1553
|
+
- Provide options when possible for faster response
|
|
1554
|
+
- Use multiSelect=true when multiple answers are valid
|
|
1555
|
+
- Be clear and concise in question wording`;
|
|
1556
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
1557
|
+
async execute(params) {
|
|
1558
|
+
const { questions } = params;
|
|
1559
|
+
try {
|
|
1560
|
+
if (questions.length === 0 || questions.length > 4) {
|
|
1561
|
+
throw new Error('Must provide 1-4 questions');
|
|
1562
|
+
}
|
|
1563
|
+
const answers = [];
|
|
1564
|
+
for (const q of questions) {
|
|
1565
|
+
if (q.options && q.options.length > 0) {
|
|
1566
|
+
const result = await inquirer.prompt([
|
|
1567
|
+
{
|
|
1568
|
+
type: q.multiSelect ? 'checkbox' : 'list',
|
|
1569
|
+
name: 'answer',
|
|
1570
|
+
message: q.question,
|
|
1571
|
+
choices: q.options,
|
|
1572
|
+
default: q.multiSelect ? [] : q.options[0]
|
|
1573
|
+
}
|
|
1574
|
+
]);
|
|
1575
|
+
answers.push(Array.isArray(result.answer) ? result.answer.join(', ') : result.answer);
|
|
1576
|
+
}
|
|
1577
|
+
else {
|
|
1578
|
+
const result = await inquirer.prompt([
|
|
1579
|
+
{
|
|
1580
|
+
type: 'input',
|
|
1581
|
+
name: 'answer',
|
|
1582
|
+
message: q.question
|
|
1583
|
+
}
|
|
1584
|
+
]);
|
|
1585
|
+
answers.push(result.answer);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return { answers };
|
|
1589
|
+
}
|
|
1590
|
+
catch (error) {
|
|
1591
|
+
throw new Error(`Failed to ask user questions: ${error.message}`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
export class SaveMemoryTool {
|
|
1596
|
+
name = 'save_memory';
|
|
1597
|
+
description = `Save specific information to long-term memory for future sessions. Useful for remembering user preferences, project conventions, or important facts.
|
|
1598
|
+
|
|
1599
|
+
# When to Use
|
|
1600
|
+
- User explicitly asks to "remember" something
|
|
1601
|
+
- User provides preferences or configuration details
|
|
1602
|
+
- Important project conventions or patterns to remember
|
|
1603
|
+
- Information that should persist across sessions
|
|
1604
|
+
|
|
1605
|
+
# When NOT to Use
|
|
1606
|
+
- For temporary information only needed in current session
|
|
1607
|
+
- For information already in project files or configuration
|
|
1608
|
+
- For obvious or trivial facts
|
|
1609
|
+
- When user doesn't explicitly want information saved
|
|
1610
|
+
|
|
1611
|
+
# Parameters
|
|
1612
|
+
- \`fact\`: The specific fact or information to remember
|
|
1613
|
+
|
|
1614
|
+
# Examples
|
|
1615
|
+
- Remember user preference: save_memory(fact="User prefers TypeScript over JavaScript")
|
|
1616
|
+
- Remember project convention: save_memory(fact="Project uses kebab-case for component files")
|
|
1617
|
+
- Remember important context: save_memory(fact="API endpoint is https://api.example.com/v2")
|
|
1618
|
+
|
|
1619
|
+
# Best Practices
|
|
1620
|
+
- Save only when user explicitly requests or provides clear preference
|
|
1621
|
+
- Keep facts concise and specific
|
|
1622
|
+
- Remember project-specific conventions for consistency
|
|
1623
|
+
- This persists across sessions (global memory)`;
|
|
1624
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
1625
|
+
async execute(params) {
|
|
1626
|
+
const { fact } = params;
|
|
1627
|
+
try {
|
|
1628
|
+
const { getMemoryManager } = await import('./memory.js');
|
|
1629
|
+
const memoryManager = getMemoryManager(process.cwd());
|
|
1630
|
+
await memoryManager.saveMemory(fact, 'global');
|
|
1631
|
+
return {
|
|
1632
|
+
success: true,
|
|
1633
|
+
message: `Successfully saved fact to memory`
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
catch (error) {
|
|
1637
|
+
throw new Error(`Failed to save memory: ${error.message}`);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
export class ExitPlanModeTool {
|
|
1642
|
+
name = 'exit_plan_mode';
|
|
1643
|
+
description = `Complete plan presentation in plan mode and transition to execution. This tool is used when you have finished planning and are ready to implement.
|
|
1644
|
+
|
|
1645
|
+
# When to Use
|
|
1646
|
+
- When you have completed creating a plan or design document
|
|
1647
|
+
- When the plan is ready for review and execution
|
|
1648
|
+
- After presenting the full implementation plan to the user
|
|
1649
|
+
- When ready to transition from planning to coding
|
|
1650
|
+
|
|
1651
|
+
# When NOT to Use
|
|
1652
|
+
- When still in the middle of planning (continue planning first)
|
|
1653
|
+
- When the plan needs revision based on feedback
|
|
1654
|
+
- When user hasn't reviewed the plan yet
|
|
1655
|
+
- In non-plan execution modes
|
|
1656
|
+
|
|
1657
|
+
# Parameters
|
|
1658
|
+
- \`plan\`: The complete plan text to be saved and executed
|
|
1659
|
+
|
|
1660
|
+
# Examples
|
|
1661
|
+
- Exit after creating implementation plan
|
|
1662
|
+
- Present final design and exit to implementation
|
|
1663
|
+
|
|
1664
|
+
# Best Practices
|
|
1665
|
+
- Ensure the plan is complete and comprehensive
|
|
1666
|
+
- Include all necessary steps and considerations
|
|
1667
|
+
- The plan will be saved for reference during execution
|
|
1668
|
+
- Use this only when truly ready to start coding`;
|
|
1669
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
1670
|
+
async execute(params) {
|
|
1671
|
+
const { plan } = params;
|
|
1672
|
+
try {
|
|
1673
|
+
return {
|
|
1674
|
+
success: true,
|
|
1675
|
+
message: 'Plan completed and ready for execution',
|
|
1676
|
+
plan
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
catch (error) {
|
|
1680
|
+
throw new Error(`Failed to exit plan mode: ${error.message}`);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
export class XmlEscapeTool {
|
|
1685
|
+
name = 'xml_escape';
|
|
1686
|
+
description = `Automatically escape special characters in XML/HTML files to make them valid.
|
|
1687
|
+
|
|
1688
|
+
# When to Use
|
|
1689
|
+
- When content contains special XML characters (<, >, &, ", ')
|
|
1690
|
+
- When generating XML/HTML from raw content
|
|
1691
|
+
- When fixing encoding issues in markup files
|
|
1692
|
+
|
|
1693
|
+
# When NOT to Use
|
|
1694
|
+
- For files that should contain raw XML/HTML
|
|
1695
|
+
- For JavaScript, CSS, or other non-XML files
|
|
1696
|
+
- When escaping should be done manually
|
|
1697
|
+
|
|
1698
|
+
# Parameters
|
|
1699
|
+
- \`file_path\`: Path to the file to escape
|
|
1700
|
+
- \`escape_all\`: (Optional) Also escape additional entities (©, ®, €)
|
|
1701
|
+
|
|
1702
|
+
# Examples
|
|
1703
|
+
- Escape XML content in HTML file
|
|
1704
|
+
- Fix special characters in generated markup
|
|
1705
|
+
|
|
1706
|
+
# Best Practices
|
|
1707
|
+
- Backup files before escaping if unsure
|
|
1708
|
+
- escape_all=true adds common HTML entities`;
|
|
1709
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
1710
|
+
async execute(params) {
|
|
1711
|
+
const { file_path, escape_all = false } = params;
|
|
1712
|
+
try {
|
|
1713
|
+
const absolutePath = path.resolve(file_path);
|
|
1714
|
+
let content = await fs.readFile(absolutePath, 'utf-8');
|
|
1715
|
+
const specialChars = [
|
|
1716
|
+
{ char: '&', replacement: '&' },
|
|
1717
|
+
{ char: '<', replacement: '<' },
|
|
1718
|
+
{ char: '>', replacement: '>' },
|
|
1719
|
+
{ char: '"', replacement: '"' },
|
|
1720
|
+
{ char: "'", replacement: ''' }
|
|
1721
|
+
];
|
|
1722
|
+
let changes = 0;
|
|
1723
|
+
for (const { char, replacement } of specialChars) {
|
|
1724
|
+
const regex = new RegExp(this.escapeRegExp(char), 'g');
|
|
1725
|
+
const matches = content.match(regex);
|
|
1726
|
+
if (matches) {
|
|
1727
|
+
changes += matches.length;
|
|
1728
|
+
content = content.replace(regex, replacement);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (escape_all) {
|
|
1732
|
+
const additionalChars = [
|
|
1733
|
+
{ char: '©', replacement: '©' },
|
|
1734
|
+
{ char: '®', replacement: '®' },
|
|
1735
|
+
{ char: '€', replacement: '€' }
|
|
1736
|
+
];
|
|
1737
|
+
for (const { char, replacement } of additionalChars) {
|
|
1738
|
+
const regex = new RegExp(this.escapeRegExp(char), 'g');
|
|
1739
|
+
const matches = content.match(regex);
|
|
1740
|
+
if (matches) {
|
|
1741
|
+
changes += matches.length;
|
|
1742
|
+
content = content.replace(regex, replacement);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
await fs.writeFile(absolutePath, content, 'utf-8');
|
|
1747
|
+
return {
|
|
1748
|
+
success: true,
|
|
1749
|
+
message: `Successfully escaped ${changes} character(s) in ${file_path}`,
|
|
1750
|
+
changes
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
catch (error) {
|
|
1754
|
+
throw new Error(`Failed to escape XML/HTML in file ${file_path}: ${error.message}`);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
escapeRegExp(string) {
|
|
1758
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
export class ImageReadTool {
|
|
1762
|
+
name = 'image_read';
|
|
1763
|
+
description = `Read image files and generate detailed analysis using a vision-language model.
|
|
1764
|
+
|
|
1765
|
+
# When to Use
|
|
1766
|
+
- Analyzing UI designs or mockups
|
|
1767
|
+
- Examining screenshots or diagrams
|
|
1768
|
+
- Extracting information from images
|
|
1769
|
+
- Validating visual content or assets
|
|
1770
|
+
|
|
1771
|
+
# When NOT to Use
|
|
1772
|
+
- For text-based file analysis (use Read instead)
|
|
1773
|
+
- When the image is not relevant to the task
|
|
1774
|
+
- For very large images (may have size limits)
|
|
1775
|
+
|
|
1776
|
+
# Parameters
|
|
1777
|
+
- \`image_input\`: Path to image or base64 data
|
|
1778
|
+
- \`prompt\`: Instructions for what to analyze
|
|
1779
|
+
- \`input_type\`: (Optional) 'file_path' or 'base64'
|
|
1780
|
+
- \`task_brief\`: (Optional) Brief task description
|
|
1781
|
+
|
|
1782
|
+
# Examples
|
|
1783
|
+
- Analyze UI mockup: image_read(image_input="design.png", prompt="Describe the UI components")
|
|
1784
|
+
- Validate screenshot: image_read(image_input="screenshot.jpg", prompt="Check if login form is visible")
|
|
1785
|
+
|
|
1786
|
+
# Best Practices
|
|
1787
|
+
- Provide clear prompts for what to look for
|
|
1788
|
+
- Use task_brief for context
|
|
1789
|
+
- Supports PNG, JPG, GIF, WEBP, SVG, BMP`;
|
|
1790
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
1791
|
+
async execute(params) {
|
|
1792
|
+
const { image_input, prompt, task_brief, input_type = 'file_path', mime_type } = params;
|
|
1793
|
+
try {
|
|
1794
|
+
let imageData;
|
|
1795
|
+
if (input_type === 'file_path') {
|
|
1796
|
+
const absolutePath = path.resolve(image_input);
|
|
1797
|
+
const imageBuffer = await fs.readFile(absolutePath);
|
|
1798
|
+
imageData = imageBuffer.toString('base64');
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1801
|
+
imageData = image_input;
|
|
1802
|
+
}
|
|
1803
|
+
const { AIClient } = await import('./ai-client.js');
|
|
1804
|
+
const configManager = await import('./config.js');
|
|
1805
|
+
const { getConfigManager } = configManager;
|
|
1806
|
+
const config = getConfigManager();
|
|
1807
|
+
const aiClient = new AIClient({
|
|
1808
|
+
type: AuthType.API_KEY,
|
|
1809
|
+
apiKey: config.get('apiKey'),
|
|
1810
|
+
baseUrl: config.get('baseUrl'),
|
|
1811
|
+
modelName: config.get('modelName') || 'Qwen3-Coder'
|
|
1812
|
+
});
|
|
1813
|
+
const textContent = task_brief ? `${task_brief}\n\n${prompt}` : prompt;
|
|
1814
|
+
const messages = [
|
|
1815
|
+
{
|
|
1816
|
+
role: 'user',
|
|
1817
|
+
content: [
|
|
1818
|
+
{
|
|
1819
|
+
type: 'text',
|
|
1820
|
+
text: textContent
|
|
1821
|
+
},
|
|
1822
|
+
{
|
|
1823
|
+
type: 'image_url',
|
|
1824
|
+
image_url: {
|
|
1825
|
+
url: `data:${mime_type || 'image/jpeg'};base64,${imageData}`
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
]
|
|
1829
|
+
}
|
|
1830
|
+
];
|
|
1831
|
+
const result = await aiClient.chatCompletion(messages, {
|
|
1832
|
+
temperature: 0.7
|
|
1833
|
+
});
|
|
1834
|
+
const messageContent = result.choices[0]?.message?.content;
|
|
1835
|
+
const analysis = typeof messageContent === 'string' ? messageContent : '';
|
|
1836
|
+
return {
|
|
1837
|
+
analysis,
|
|
1838
|
+
image_info: {
|
|
1839
|
+
input_type,
|
|
1840
|
+
prompt,
|
|
1841
|
+
task_brief
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
catch (error) {
|
|
1846
|
+
throw new Error(`Failed to read image: ${error.message}`);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
// export class SkillTool implements Tool {
|
|
1851
|
+
// name = 'Skill';
|
|
1852
|
+
// description = `Execute pre-defined workflows (skills) from the xAgent marketplace. Skills are reusable workflows that automate common tasks.
|
|
1853
|
+
// # When to Use
|
|
1854
|
+
// - When a skill exists for the requested task
|
|
1855
|
+
// - When you need to run a multi-step workflow
|
|
1856
|
+
// - When the task matches a marketplace workflow
|
|
1857
|
+
// # When NOT to Use
|
|
1858
|
+
// - When a simple tool can accomplish the task
|
|
1859
|
+
// - When creating new functionality from scratch
|
|
1860
|
+
// - When skill doesn't exist for the specific task
|
|
1861
|
+
// # Parameters
|
|
1862
|
+
// - \`skill\`: The skill/workflow name to execute
|
|
1863
|
+
// # Examples
|
|
1864
|
+
// - Execute a PDF processing skill
|
|
1865
|
+
// - Run a data analysis workflow
|
|
1866
|
+
// # Best Practices
|
|
1867
|
+
// - Skills are pre-configured workflows from the marketplace
|
|
1868
|
+
// - Check if a relevant skill exists first`;
|
|
1869
|
+
// allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
1870
|
+
// async execute(params: { skill: string }): Promise<{ success: boolean; message: string; result?: any }> {
|
|
1871
|
+
// const { skill } = params;
|
|
1872
|
+
// try {
|
|
1873
|
+
// const { getWorkflowManager } = await import('./workflow.js');
|
|
1874
|
+
// const workflowManager = getWorkflowManager(process.cwd());
|
|
1875
|
+
// const workflow = workflowManager.getWorkflow(skill);
|
|
1876
|
+
// if (!workflow) {
|
|
1877
|
+
// throw new Error(`Skill ${skill} not found`);
|
|
1878
|
+
// }
|
|
1879
|
+
// await workflowManager.executeWorkflow(skill, 'Execute skill');
|
|
1880
|
+
// return {
|
|
1881
|
+
// success: true,
|
|
1882
|
+
// message: `Successfully executed skill: ${skill}`,
|
|
1883
|
+
// result: workflow
|
|
1884
|
+
// };
|
|
1885
|
+
// } catch (error: any) {
|
|
1886
|
+
// throw new Error(`Failed to execute skill: ${error.message}`);
|
|
1887
|
+
// }
|
|
1888
|
+
// }
|
|
1889
|
+
// }
|
|
1890
|
+
export class InvokeSkillTool {
|
|
1891
|
+
name = 'InvokeSkill';
|
|
1892
|
+
description = `Invoke a specialized skill to handle domain-specific tasks. Skills are AI-powered capabilities that understand complex requirements and generate high-quality outputs.
|
|
1893
|
+
|
|
1894
|
+
# When to Use
|
|
1895
|
+
- When user requests involve document processing (Word, PDF, PowerPoint)
|
|
1896
|
+
- When user wants to create frontend interfaces or web applications
|
|
1897
|
+
- When user needs visual design, posters, or generative art
|
|
1898
|
+
- When user asks for documentation or internal communications
|
|
1899
|
+
- When the task matches a specific skill domain
|
|
1900
|
+
|
|
1901
|
+
# When NOT to Use
|
|
1902
|
+
- For simple file operations (use Read/Write instead)
|
|
1903
|
+
- For basic code changes (use Replace/Write instead)
|
|
1904
|
+
- When a regular tool can accomplish the task
|
|
1905
|
+
|
|
1906
|
+
# Parameters
|
|
1907
|
+
- \`skillId\`: The skill identifier (e.g., "docx", "frontend-design", "canvas-design")
|
|
1908
|
+
- \`taskDescription\`: Detailed description of what to accomplish
|
|
1909
|
+
- \`inputFile\`: (Optional) Path to input file if applicable
|
|
1910
|
+
- \`outputFile\`: (Optional) Desired output file path
|
|
1911
|
+
- \`options\`: (Optional) Additional options for the skill
|
|
1912
|
+
|
|
1913
|
+
# Examples
|
|
1914
|
+
- "Create a Word document with contract terms" → InvokeSkill(skillId="docx", taskDescription="Create a professional Word document with contract terms, including numbered sections, signature blocks, and professional formatting")
|
|
1915
|
+
- "Build a landing page for a product" → InvokeSkill(skillId="frontend-design", taskDescription="Create a visually striking landing page with hero section, features, pricing, and footer. Use bold typography and animations.")
|
|
1916
|
+
- "Create a poster for a music festival" → InvokeSkill(skillId="canvas-design", taskDescription="Create a poster for an electronic music festival. The topic is subtle reference to techno culture and underground rave scene.")
|
|
1917
|
+
- "Write API documentation" → InvokeSkill(skillId="doc-coauthoring", taskDescription="Write comprehensive API documentation for a REST API including endpoints, request/response examples, and error handling")
|
|
1918
|
+
|
|
1919
|
+
# Best Practices
|
|
1920
|
+
- Provide detailed task descriptions for better results
|
|
1921
|
+
- Include relevant file paths when working with existing files
|
|
1922
|
+
- Match the skill to the domain (e.g., don't use frontend-design for Word docs)
|
|
1923
|
+
- Skills will guide you through their specific workflows`;
|
|
1924
|
+
allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
1925
|
+
async execute(params, _executionMode) {
|
|
1926
|
+
const { skillId, taskDescription, inputFile, outputFile, options } = params;
|
|
1927
|
+
try {
|
|
1928
|
+
const { getSkillInvoker } = await import('./skill-invoker.js');
|
|
1929
|
+
const { SkillExecutionParams } = await import('./skill-invoker.js');
|
|
1930
|
+
const skillInvoker = getSkillInvoker();
|
|
1931
|
+
await skillInvoker.initialize();
|
|
1932
|
+
// Verify skill exists
|
|
1933
|
+
const skillDetails = await skillInvoker.getSkillDetails(skillId);
|
|
1934
|
+
if (!skillDetails) {
|
|
1935
|
+
// Try to auto-match the skill
|
|
1936
|
+
const match = await skillInvoker.matchSkill(taskDescription);
|
|
1937
|
+
if (match) {
|
|
1938
|
+
return {
|
|
1939
|
+
success: true,
|
|
1940
|
+
message: `Auto-matched skill: ${match.skill.name} (${match.category})`,
|
|
1941
|
+
skill: match.skill.id,
|
|
1942
|
+
task: taskDescription,
|
|
1943
|
+
result: {
|
|
1944
|
+
category: match.category,
|
|
1945
|
+
confidence: match.confidence,
|
|
1946
|
+
matchedKeywords: match.matchedKeywords
|
|
1947
|
+
},
|
|
1948
|
+
guidance: '请按照匹配到的技能继续执行任务。'
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
throw new Error(`Skill not found: ${skillId}`);
|
|
1952
|
+
}
|
|
1953
|
+
const result = await skillInvoker.executeSkill({
|
|
1954
|
+
skillId,
|
|
1955
|
+
taskDescription,
|
|
1956
|
+
inputFile,
|
|
1957
|
+
outputFile,
|
|
1958
|
+
options
|
|
1959
|
+
});
|
|
1960
|
+
if (result.success) {
|
|
1961
|
+
// 生成指导信息,告诉 Agent 接下来要做什么
|
|
1962
|
+
let guidance = '';
|
|
1963
|
+
if (result.nextSteps && result.nextSteps.length > 0) {
|
|
1964
|
+
guidance = `\n## 🎯 下一步操作\n\n请按照以下步骤继续执行任务:\n\n`;
|
|
1965
|
+
for (const step of result.nextSteps) {
|
|
1966
|
+
guidance += `### 步骤 ${step.step}: ${step.action}\n`;
|
|
1967
|
+
guidance += `- **描述**: ${step.description}\n`;
|
|
1968
|
+
guidance += `- **原因**: ${step.reason}\n`;
|
|
1969
|
+
if (step.command) {
|
|
1970
|
+
guidance += `- **命令**: \`${step.command}\`\n`;
|
|
1971
|
+
}
|
|
1972
|
+
if (step.file) {
|
|
1973
|
+
guidance += `- **文件**: ${step.file}\n`;
|
|
1974
|
+
}
|
|
1975
|
+
guidance += '\n';
|
|
1976
|
+
}
|
|
1977
|
+
guidance += `---\n**重要**: 上述步骤是根据 SKILL.md 自动生成的执行指南。请按照这些步骤继续完成任务,而不是结束对话。\n`;
|
|
1978
|
+
}
|
|
1979
|
+
return {
|
|
1980
|
+
success: true,
|
|
1981
|
+
message: `技能已激活: ${skillDetails.name}`,
|
|
1982
|
+
skill: skillId,
|
|
1983
|
+
task: taskDescription,
|
|
1984
|
+
result: result.output + (guidance ? guidance : ''),
|
|
1985
|
+
files: result.files,
|
|
1986
|
+
nextSteps: result.nextSteps,
|
|
1987
|
+
guidance: guidance
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
else {
|
|
1991
|
+
throw new Error(result.error);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
catch (error) {
|
|
1995
|
+
throw new Error(`Failed to invoke skill: ${error.message}`);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
// export class ListSkillsTool implements Tool {
|
|
2000
|
+
// name = 'ListSkills';
|
|
2001
|
+
// description = `List all available skills from the xAgent skills library. Use this tool when you need to:
|
|
2002
|
+
// - See what skills are available
|
|
2003
|
+
// - Find skills that match a user's request
|
|
2004
|
+
// - Get an overview of capabilities
|
|
2005
|
+
// This returns a list of all skills with their names, descriptions, and categories.`;
|
|
2006
|
+
// allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.SMART];
|
|
2007
|
+
// async execute(): Promise<{ success: boolean; skills: any[] }> {
|
|
2008
|
+
// try {
|
|
2009
|
+
// const { getWorkflowManager } = await import('./workflow.js');
|
|
2010
|
+
// const workflowManager = getWorkflowManager(process.cwd());
|
|
2011
|
+
// const skills = await workflowManager.listSkills();
|
|
2012
|
+
// return {
|
|
2013
|
+
// success: true,
|
|
2014
|
+
// skills: skills.map(s => ({
|
|
2015
|
+
// id: s.id,
|
|
2016
|
+
// name: s.name,
|
|
2017
|
+
// description: s.description,
|
|
2018
|
+
// category: s.category
|
|
2019
|
+
// }))
|
|
2020
|
+
// };
|
|
2021
|
+
// } catch (error: any) {
|
|
2022
|
+
// throw new Error(`Failed to list skills: ${error.message}`);
|
|
2023
|
+
// }
|
|
2024
|
+
// }
|
|
2025
|
+
// }
|
|
2026
|
+
// export class GetSkillDetailsTool implements Tool {
|
|
2027
|
+
// name = 'GetSkillDetails';
|
|
2028
|
+
// description = `Get detailed information about a specific skill. Use this tool when:
|
|
2029
|
+
// - You want to understand what a skill does before executing it
|
|
2030
|
+
// - You need the full skill documentation to help the user
|
|
2031
|
+
// - You need to verify a skill exists before using it
|
|
2032
|
+
// # Parameters
|
|
2033
|
+
// - \`skill\`: The skill name/id to get details for
|
|
2034
|
+
// # Returns
|
|
2035
|
+
// The full skill documentation including instructions, examples, and guidelines.`;
|
|
2036
|
+
// allowedModes = [ExecutionMode.YOLO, ExecutionMode.ACCEPT_EDITS, ExecutionMode.PLAN, ExecutionMode.SMART];
|
|
2037
|
+
// async execute(params: { skill: string }): Promise<{ success: boolean; details: any }> {
|
|
2038
|
+
// const { skill } = params;
|
|
2039
|
+
// if (!skill) {
|
|
2040
|
+
// throw new Error('Skill parameter is required');
|
|
2041
|
+
// }
|
|
2042
|
+
// try {
|
|
2043
|
+
// const { getWorkflowManager } = await import('./workflow.js');
|
|
2044
|
+
// const workflowManager = getWorkflowManager(process.cwd());
|
|
2045
|
+
// const details = await workflowManager.getSkillDetails(skill);
|
|
2046
|
+
// if (!details) {
|
|
2047
|
+
// throw new Error(`Skill '${skill}' not found`);
|
|
2048
|
+
// }
|
|
2049
|
+
// return {
|
|
2050
|
+
// success: true,
|
|
2051
|
+
// details: {
|
|
2052
|
+
// id: details.id,
|
|
2053
|
+
// name: details.name,
|
|
2054
|
+
// description: details.description,
|
|
2055
|
+
// category: details.category,
|
|
2056
|
+
// content: details.content
|
|
2057
|
+
// }
|
|
2058
|
+
// };
|
|
2059
|
+
// } catch (error: any) {
|
|
2060
|
+
// throw new Error(`Failed to get skill details: ${error.message}`);
|
|
2061
|
+
// }
|
|
2062
|
+
// }
|
|
2063
|
+
// }
|
|
2064
|
+
export class ToolRegistry {
|
|
2065
|
+
tools = new Map();
|
|
2066
|
+
todoWriteTool;
|
|
2067
|
+
backgroundTasks = new Map();
|
|
2068
|
+
constructor() {
|
|
2069
|
+
this.todoWriteTool = new TodoWriteTool();
|
|
2070
|
+
this.registerDefaultTools();
|
|
2071
|
+
}
|
|
2072
|
+
registerDefaultTools() {
|
|
2073
|
+
this.register(new ReadTool());
|
|
2074
|
+
this.register(new WriteTool());
|
|
2075
|
+
this.register(new GrepTool());
|
|
2076
|
+
this.register(new BashTool());
|
|
2077
|
+
this.register(new ListDirectoryTool());
|
|
2078
|
+
this.register(new SearchCodebaseTool());
|
|
2079
|
+
this.register(new DeleteFileTool());
|
|
2080
|
+
this.register(new CreateDirectoryTool());
|
|
2081
|
+
this.register(new ReplaceTool());
|
|
2082
|
+
this.register(new WebSearchTool());
|
|
2083
|
+
this.register(this.todoWriteTool);
|
|
2084
|
+
this.register(new TodoReadTool(this.todoWriteTool));
|
|
2085
|
+
this.register(new TaskTool());
|
|
2086
|
+
this.register(new ReadBashOutputTool());
|
|
2087
|
+
this.register(new WebFetchTool());
|
|
2088
|
+
this.register(new AskUserQuestionTool());
|
|
2089
|
+
this.register(new SaveMemoryTool());
|
|
2090
|
+
this.register(new ExitPlanModeTool());
|
|
2091
|
+
this.register(new XmlEscapeTool());
|
|
2092
|
+
this.register(new ImageReadTool());
|
|
2093
|
+
// Deprecated: Use InvokeSkillTool instead (2026-01-17)
|
|
2094
|
+
// this.register(new SkillTool());
|
|
2095
|
+
// this.register(new ListSkillsTool());
|
|
2096
|
+
// this.register(new GetSkillDetailsTool());
|
|
2097
|
+
this.register(new InvokeSkillTool());
|
|
2098
|
+
// GUI Subagent Tools
|
|
2099
|
+
// this.register(new GUIOperateTool());
|
|
2100
|
+
// this.register(new GUIScreenshotTool());
|
|
2101
|
+
// this.register(new GUICleanupTool());
|
|
2102
|
+
}
|
|
2103
|
+
register(tool) {
|
|
2104
|
+
this.tools.set(tool.name, tool);
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Register MCP tools with their simple names (without server prefix)
|
|
2108
|
+
* This allows the LLM to call MCP tools using simple names like "create_issue"
|
|
2109
|
+
* instead of "github__create_issue"
|
|
2110
|
+
*/
|
|
2111
|
+
registerMCPTools(mcpTools) {
|
|
2112
|
+
let registeredCount = 0;
|
|
2113
|
+
for (const [fullName, tool] of mcpTools) {
|
|
2114
|
+
const [serverName, originalName] = fullName.split('__');
|
|
2115
|
+
if (!originalName) {
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
// Auto-rename if conflict, ensure unique name
|
|
2119
|
+
let toolName = originalName;
|
|
2120
|
+
let suffix = 1;
|
|
2121
|
+
while (this.tools.has(toolName)) {
|
|
2122
|
+
const existingTool = this.tools.get(toolName);
|
|
2123
|
+
const existingIsMcp = existingTool._isMcpTool;
|
|
2124
|
+
if (existingIsMcp && existingTool._mcpFullName === fullName) {
|
|
2125
|
+
// Same MCP tool already registered, skip silently
|
|
2126
|
+
break;
|
|
2127
|
+
}
|
|
2128
|
+
// Conflict - auto-rename with suffix
|
|
2129
|
+
toolName = `${originalName}_mcp${suffix}`;
|
|
2130
|
+
suffix++;
|
|
2131
|
+
}
|
|
2132
|
+
if (!this.tools.has(toolName)) {
|
|
2133
|
+
// Create a wrapper tool for the MCP tool - hide MCP origin from LLM
|
|
2134
|
+
const mcpTool = {
|
|
2135
|
+
name: toolName,
|
|
2136
|
+
description: tool.description || 'MCP tool',
|
|
2137
|
+
allowedModes: [ExecutionMode.YOLO, ExecutionMode.SMART, ExecutionMode.ACCEPT_EDITS],
|
|
2138
|
+
inputSchema: tool.inputSchema,
|
|
2139
|
+
_isMcpTool: true,
|
|
2140
|
+
_mcpServerName: serverName,
|
|
2141
|
+
_mcpFullName: fullName,
|
|
2142
|
+
execute: async (params) => {
|
|
2143
|
+
const { getMCPManager } = await import('./mcp.js');
|
|
2144
|
+
const mcpManager = getMCPManager();
|
|
2145
|
+
return await mcpManager.callTool(fullName, params);
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
this.tools.set(toolName, mcpTool);
|
|
2149
|
+
registeredCount++;
|
|
2150
|
+
if (toolName !== originalName) {
|
|
2151
|
+
console.log(`[MCP] Tool '${originalName}' renamed to '${toolName}' to avoid conflict`);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
if (registeredCount > 0) {
|
|
2156
|
+
console.log(`[MCP] Registered ${registeredCount} tool(s)`);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Remove all MCP tool wrappers (useful when MCP servers are removed)
|
|
2161
|
+
*/
|
|
2162
|
+
unregisterMCPTools(serverName) {
|
|
2163
|
+
for (const [name, tool] of this.tools) {
|
|
2164
|
+
// Remove MCP tool wrappers by checking marker
|
|
2165
|
+
const mcpTool = tool;
|
|
2166
|
+
if (mcpTool._isMcpTool && (!serverName || mcpTool._mcpServerName === serverName)) {
|
|
2167
|
+
this.tools.delete(name);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
unregister(toolName) {
|
|
2172
|
+
this.tools.delete(toolName);
|
|
2173
|
+
}
|
|
2174
|
+
get(toolName) {
|
|
2175
|
+
return this.tools.get(toolName);
|
|
2176
|
+
}
|
|
2177
|
+
getAll() {
|
|
2178
|
+
return Array.from(this.tools.values());
|
|
2179
|
+
}
|
|
2180
|
+
addBackgroundTask(taskId, task) {
|
|
2181
|
+
this.backgroundTasks.set(taskId, task);
|
|
2182
|
+
}
|
|
2183
|
+
getBackgroundTask(taskId) {
|
|
2184
|
+
return this.backgroundTasks.get(taskId);
|
|
2185
|
+
}
|
|
2186
|
+
removeBackgroundTask(taskId) {
|
|
2187
|
+
this.backgroundTasks.delete(taskId);
|
|
2188
|
+
}
|
|
2189
|
+
getToolDefinitions() {
|
|
2190
|
+
return Array.from(this.tools.values()).map(tool => {
|
|
2191
|
+
let parameters = {
|
|
2192
|
+
type: 'object',
|
|
2193
|
+
properties: {},
|
|
2194
|
+
required: []
|
|
2195
|
+
};
|
|
2196
|
+
// Define specific parameters for each tool
|
|
2197
|
+
switch (tool.name) {
|
|
2198
|
+
case 'Read':
|
|
2199
|
+
parameters = {
|
|
2200
|
+
type: 'object',
|
|
2201
|
+
properties: {
|
|
2202
|
+
filePath: {
|
|
2203
|
+
type: 'string',
|
|
2204
|
+
description: 'The absolute path to the file to read'
|
|
2205
|
+
},
|
|
2206
|
+
offset: {
|
|
2207
|
+
type: 'number',
|
|
2208
|
+
description: 'Optional: Line number to start reading from (0-based)'
|
|
2209
|
+
},
|
|
2210
|
+
limit: {
|
|
2211
|
+
type: 'number',
|
|
2212
|
+
description: 'Optional: Maximum number of lines to read'
|
|
2213
|
+
}
|
|
2214
|
+
},
|
|
2215
|
+
required: ['filePath']
|
|
2216
|
+
};
|
|
2217
|
+
break;
|
|
2218
|
+
case 'Write':
|
|
2219
|
+
parameters = {
|
|
2220
|
+
type: 'object',
|
|
2221
|
+
properties: {
|
|
2222
|
+
filePath: {
|
|
2223
|
+
type: 'string',
|
|
2224
|
+
description: 'The absolute path to the file to write'
|
|
2225
|
+
},
|
|
2226
|
+
content: {
|
|
2227
|
+
type: 'string',
|
|
2228
|
+
description: 'The content to write to the file'
|
|
2229
|
+
}
|
|
2230
|
+
},
|
|
2231
|
+
required: ['filePath', 'content']
|
|
2232
|
+
};
|
|
2233
|
+
break;
|
|
2234
|
+
case 'Grep':
|
|
2235
|
+
parameters = {
|
|
2236
|
+
type: 'object',
|
|
2237
|
+
properties: {
|
|
2238
|
+
pattern: {
|
|
2239
|
+
type: 'string',
|
|
2240
|
+
description: 'The regex pattern to search for'
|
|
2241
|
+
},
|
|
2242
|
+
path: {
|
|
2243
|
+
type: 'string',
|
|
2244
|
+
description: 'Optional: The path to search in (default: current directory)'
|
|
2245
|
+
},
|
|
2246
|
+
include: {
|
|
2247
|
+
type: 'string',
|
|
2248
|
+
description: 'Optional: Glob pattern to filter files'
|
|
2249
|
+
},
|
|
2250
|
+
case_sensitive: {
|
|
2251
|
+
type: 'boolean',
|
|
2252
|
+
description: 'Optional: Case-sensitive search (default: false)'
|
|
2253
|
+
},
|
|
2254
|
+
context: {
|
|
2255
|
+
type: 'number',
|
|
2256
|
+
description: 'Optional: Number of context lines to show'
|
|
2257
|
+
}
|
|
2258
|
+
},
|
|
2259
|
+
required: ['pattern']
|
|
2260
|
+
};
|
|
2261
|
+
break;
|
|
2262
|
+
case 'Bash':
|
|
2263
|
+
parameters = {
|
|
2264
|
+
type: 'object',
|
|
2265
|
+
properties: {
|
|
2266
|
+
command: {
|
|
2267
|
+
type: 'string',
|
|
2268
|
+
description: 'The shell command to execute'
|
|
2269
|
+
},
|
|
2270
|
+
cwd: {
|
|
2271
|
+
type: 'string',
|
|
2272
|
+
description: 'Optional: Working directory'
|
|
2273
|
+
},
|
|
2274
|
+
description: {
|
|
2275
|
+
type: 'string',
|
|
2276
|
+
description: 'Optional: Brief description of the command'
|
|
2277
|
+
},
|
|
2278
|
+
timeout: {
|
|
2279
|
+
type: 'number',
|
|
2280
|
+
description: 'Optional: Timeout in seconds (default: 120)'
|
|
2281
|
+
},
|
|
2282
|
+
run_in_bg: {
|
|
2283
|
+
type: 'boolean',
|
|
2284
|
+
description: 'Optional: Run in background (default: false)'
|
|
2285
|
+
}
|
|
2286
|
+
},
|
|
2287
|
+
required: ['command']
|
|
2288
|
+
};
|
|
2289
|
+
break;
|
|
2290
|
+
case 'ListDirectory':
|
|
2291
|
+
parameters = {
|
|
2292
|
+
type: 'object',
|
|
2293
|
+
properties: {
|
|
2294
|
+
path: {
|
|
2295
|
+
type: 'string',
|
|
2296
|
+
description: 'Optional: The directory path to list (default: current directory)'
|
|
2297
|
+
},
|
|
2298
|
+
recursive: {
|
|
2299
|
+
type: 'boolean',
|
|
2300
|
+
description: 'Optional: List recursively (default: false)'
|
|
2301
|
+
}
|
|
2302
|
+
},
|
|
2303
|
+
required: []
|
|
2304
|
+
};
|
|
2305
|
+
break;
|
|
2306
|
+
case 'SearchCodebase':
|
|
2307
|
+
parameters = {
|
|
2308
|
+
type: 'object',
|
|
2309
|
+
properties: {
|
|
2310
|
+
pattern: {
|
|
2311
|
+
type: 'string',
|
|
2312
|
+
description: 'The glob pattern to match files'
|
|
2313
|
+
},
|
|
2314
|
+
path: {
|
|
2315
|
+
type: 'string',
|
|
2316
|
+
description: 'Optional: The path to search in (default: current directory)'
|
|
2317
|
+
}
|
|
2318
|
+
},
|
|
2319
|
+
required: ['pattern']
|
|
2320
|
+
};
|
|
2321
|
+
break;
|
|
2322
|
+
case 'DeleteFile':
|
|
2323
|
+
parameters = {
|
|
2324
|
+
type: 'object',
|
|
2325
|
+
properties: {
|
|
2326
|
+
filePath: {
|
|
2327
|
+
type: 'string',
|
|
2328
|
+
description: 'The path to the file to delete'
|
|
2329
|
+
}
|
|
2330
|
+
},
|
|
2331
|
+
required: ['filePath']
|
|
2332
|
+
};
|
|
2333
|
+
break;
|
|
2334
|
+
case 'CreateDirectory':
|
|
2335
|
+
parameters = {
|
|
2336
|
+
type: 'object',
|
|
2337
|
+
properties: {
|
|
2338
|
+
dirPath: {
|
|
2339
|
+
type: 'string',
|
|
2340
|
+
description: 'The directory path to create'
|
|
2341
|
+
},
|
|
2342
|
+
recursive: {
|
|
2343
|
+
type: 'boolean',
|
|
2344
|
+
description: 'Optional: Create parent directories (default: true)'
|
|
2345
|
+
}
|
|
2346
|
+
},
|
|
2347
|
+
required: ['dirPath']
|
|
2348
|
+
};
|
|
2349
|
+
break;
|
|
2350
|
+
case 'replace':
|
|
2351
|
+
parameters = {
|
|
2352
|
+
type: 'object',
|
|
2353
|
+
properties: {
|
|
2354
|
+
file_path: {
|
|
2355
|
+
type: 'string',
|
|
2356
|
+
description: 'The absolute path to the file'
|
|
2357
|
+
},
|
|
2358
|
+
instruction: {
|
|
2359
|
+
type: 'string',
|
|
2360
|
+
description: 'Description of what needs to be changed'
|
|
2361
|
+
},
|
|
2362
|
+
old_string: {
|
|
2363
|
+
type: 'string',
|
|
2364
|
+
description: 'The exact text to replace'
|
|
2365
|
+
},
|
|
2366
|
+
new_string: {
|
|
2367
|
+
type: 'string',
|
|
2368
|
+
description: 'The exact text to replace with'
|
|
2369
|
+
}
|
|
2370
|
+
},
|
|
2371
|
+
required: ['file_path', 'instruction', 'old_string', 'new_string']
|
|
2372
|
+
};
|
|
2373
|
+
break;
|
|
2374
|
+
case 'web_search':
|
|
2375
|
+
parameters = {
|
|
2376
|
+
type: 'object',
|
|
2377
|
+
properties: {
|
|
2378
|
+
query: {
|
|
2379
|
+
type: 'string',
|
|
2380
|
+
description: 'The search query'
|
|
2381
|
+
}
|
|
2382
|
+
},
|
|
2383
|
+
required: ['query']
|
|
2384
|
+
};
|
|
2385
|
+
break;
|
|
2386
|
+
case 'todo_write':
|
|
2387
|
+
parameters = {
|
|
2388
|
+
type: 'object',
|
|
2389
|
+
properties: {
|
|
2390
|
+
todos: {
|
|
2391
|
+
type: 'array',
|
|
2392
|
+
description: 'Array of todo items',
|
|
2393
|
+
items: {
|
|
2394
|
+
type: 'object',
|
|
2395
|
+
properties: {
|
|
2396
|
+
id: { type: 'string' },
|
|
2397
|
+
task: { type: 'string' },
|
|
2398
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'failed'] },
|
|
2399
|
+
priority: { type: 'string', enum: ['high', 'medium', 'low'] }
|
|
2400
|
+
},
|
|
2401
|
+
required: ['id', 'task', 'status']
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
},
|
|
2405
|
+
required: ['todos']
|
|
2406
|
+
};
|
|
2407
|
+
break;
|
|
2408
|
+
case 'todo_read':
|
|
2409
|
+
parameters = {
|
|
2410
|
+
type: 'object',
|
|
2411
|
+
properties: {},
|
|
2412
|
+
required: []
|
|
2413
|
+
};
|
|
2414
|
+
break;
|
|
2415
|
+
case 'task':
|
|
2416
|
+
parameters = {
|
|
2417
|
+
type: 'object',
|
|
2418
|
+
properties: {
|
|
2419
|
+
description: {
|
|
2420
|
+
type: 'string',
|
|
2421
|
+
description: 'Brief description of the task (3-5 words)'
|
|
2422
|
+
},
|
|
2423
|
+
agents: {
|
|
2424
|
+
type: 'array',
|
|
2425
|
+
description: 'Optional: Array of agents to run in parallel for comprehensive analysis',
|
|
2426
|
+
items: {
|
|
2427
|
+
type: 'object',
|
|
2428
|
+
properties: {
|
|
2429
|
+
description: {
|
|
2430
|
+
type: 'string',
|
|
2431
|
+
description: 'Brief description of the sub-agent task'
|
|
2432
|
+
},
|
|
2433
|
+
prompt: {
|
|
2434
|
+
type: 'string',
|
|
2435
|
+
description: 'The task for the sub-agent to perform'
|
|
2436
|
+
},
|
|
2437
|
+
subagent_type: {
|
|
2438
|
+
type: 'string',
|
|
2439
|
+
enum: ['general-purpose', 'plan-agent', 'explore-agent', 'frontend-tester', 'code-reviewer', 'frontend-developer', 'backend-developer'],
|
|
2440
|
+
description: 'The type of specialized agent'
|
|
2441
|
+
},
|
|
2442
|
+
constraints: {
|
|
2443
|
+
type: 'array',
|
|
2444
|
+
items: { type: 'string' },
|
|
2445
|
+
description: 'Optional: Constraints or limitations'
|
|
2446
|
+
}
|
|
2447
|
+
},
|
|
2448
|
+
required: ['description', 'prompt', 'subagent_type']
|
|
2449
|
+
}
|
|
2450
|
+
},
|
|
2451
|
+
prompt: {
|
|
2452
|
+
type: 'string',
|
|
2453
|
+
description: 'Optional: The task for the agent to perform (use agents for parallel execution)'
|
|
2454
|
+
},
|
|
2455
|
+
subagent_type: {
|
|
2456
|
+
type: 'string',
|
|
2457
|
+
enum: ['general-purpose', 'plan-agent', 'explore-agent', 'frontend-tester', 'code-reviewer', 'frontend-developer', 'backend-developer'],
|
|
2458
|
+
description: 'Optional: The type of specialized agent (use agents for parallel execution)'
|
|
2459
|
+
},
|
|
2460
|
+
useContext: {
|
|
2461
|
+
type: 'boolean',
|
|
2462
|
+
description: 'Optional: Include main agent context'
|
|
2463
|
+
},
|
|
2464
|
+
outputFormat: {
|
|
2465
|
+
type: 'string',
|
|
2466
|
+
description: 'Optional: Output format template'
|
|
2467
|
+
},
|
|
2468
|
+
constraints: {
|
|
2469
|
+
type: 'array',
|
|
2470
|
+
items: { type: 'string' },
|
|
2471
|
+
description: 'Optional: Constraints or limitations'
|
|
2472
|
+
}
|
|
2473
|
+
},
|
|
2474
|
+
required: ['description']
|
|
2475
|
+
};
|
|
2476
|
+
break;
|
|
2477
|
+
case 'ReadBashOutput':
|
|
2478
|
+
parameters = {
|
|
2479
|
+
type: 'object',
|
|
2480
|
+
properties: {
|
|
2481
|
+
task_id: {
|
|
2482
|
+
type: 'string',
|
|
2483
|
+
description: 'The ID of the task'
|
|
2484
|
+
},
|
|
2485
|
+
poll_interval: {
|
|
2486
|
+
type: 'number',
|
|
2487
|
+
description: 'Optional: Polling interval in seconds (default: 10)'
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
required: ['task_id']
|
|
2491
|
+
};
|
|
2492
|
+
break;
|
|
2493
|
+
case 'web_fetch':
|
|
2494
|
+
parameters = {
|
|
2495
|
+
type: 'object',
|
|
2496
|
+
properties: {
|
|
2497
|
+
prompt: {
|
|
2498
|
+
type: 'string',
|
|
2499
|
+
description: 'Prompt containing URL(s) and processing instructions'
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
required: ['prompt']
|
|
2503
|
+
};
|
|
2504
|
+
break;
|
|
2505
|
+
case 'ask_user_question':
|
|
2506
|
+
parameters = {
|
|
2507
|
+
type: 'object',
|
|
2508
|
+
properties: {
|
|
2509
|
+
questions: {
|
|
2510
|
+
type: 'array',
|
|
2511
|
+
description: 'Array of questions to ask',
|
|
2512
|
+
items: {
|
|
2513
|
+
type: 'object',
|
|
2514
|
+
properties: {
|
|
2515
|
+
question: { type: 'string' },
|
|
2516
|
+
header: { type: 'string', description: 'Short label (max 12 chars)' },
|
|
2517
|
+
options: {
|
|
2518
|
+
type: 'array',
|
|
2519
|
+
items: { type: 'string' },
|
|
2520
|
+
description: 'Available choices (2-4 options)'
|
|
2521
|
+
},
|
|
2522
|
+
multiSelect: { type: 'boolean' }
|
|
2523
|
+
},
|
|
2524
|
+
required: ['question', 'header', 'options', 'multiSelect']
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
},
|
|
2528
|
+
required: ['questions']
|
|
2529
|
+
};
|
|
2530
|
+
break;
|
|
2531
|
+
case 'save_memory':
|
|
2532
|
+
parameters = {
|
|
2533
|
+
type: 'object',
|
|
2534
|
+
properties: {
|
|
2535
|
+
fact: {
|
|
2536
|
+
type: 'string',
|
|
2537
|
+
description: 'The specific fact to remember'
|
|
2538
|
+
}
|
|
2539
|
+
},
|
|
2540
|
+
required: ['fact']
|
|
2541
|
+
};
|
|
2542
|
+
break;
|
|
2543
|
+
case 'exit_plan_mode':
|
|
2544
|
+
parameters = {
|
|
2545
|
+
type: 'object',
|
|
2546
|
+
properties: {
|
|
2547
|
+
plan: {
|
|
2548
|
+
type: 'string',
|
|
2549
|
+
description: 'The plan to present'
|
|
2550
|
+
}
|
|
2551
|
+
},
|
|
2552
|
+
required: ['plan']
|
|
2553
|
+
};
|
|
2554
|
+
break;
|
|
2555
|
+
case 'xml_escape':
|
|
2556
|
+
parameters = {
|
|
2557
|
+
type: 'object',
|
|
2558
|
+
properties: {
|
|
2559
|
+
file_path: {
|
|
2560
|
+
type: 'string',
|
|
2561
|
+
description: 'The absolute path to the XML/HTML file'
|
|
2562
|
+
},
|
|
2563
|
+
escape_all: {
|
|
2564
|
+
type: 'boolean',
|
|
2565
|
+
description: 'Optional: Escape all special characters (default: false)'
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
required: ['file_path']
|
|
2569
|
+
};
|
|
2570
|
+
break;
|
|
2571
|
+
case 'image_read':
|
|
2572
|
+
parameters = {
|
|
2573
|
+
type: 'object',
|
|
2574
|
+
properties: {
|
|
2575
|
+
image_input: {
|
|
2576
|
+
type: 'string',
|
|
2577
|
+
description: 'Image file path or base64 data'
|
|
2578
|
+
},
|
|
2579
|
+
prompt: {
|
|
2580
|
+
type: 'string',
|
|
2581
|
+
description: 'Comprehensive VLM instruction'
|
|
2582
|
+
},
|
|
2583
|
+
task_brief: {
|
|
2584
|
+
type: 'string',
|
|
2585
|
+
description: 'Brief task description (max 15 words)'
|
|
2586
|
+
},
|
|
2587
|
+
input_type: {
|
|
2588
|
+
type: 'string',
|
|
2589
|
+
enum: ['file_path', 'base64'],
|
|
2590
|
+
description: 'Input type (default: file_path)'
|
|
2591
|
+
},
|
|
2592
|
+
mime_type: {
|
|
2593
|
+
type: 'string',
|
|
2594
|
+
description: 'Optional: MIME type for base64 input'
|
|
2595
|
+
}
|
|
2596
|
+
},
|
|
2597
|
+
required: ['image_input', 'prompt']
|
|
2598
|
+
};
|
|
2599
|
+
break;
|
|
2600
|
+
case 'Skill':
|
|
2601
|
+
parameters = {
|
|
2602
|
+
type: 'object',
|
|
2603
|
+
properties: {
|
|
2604
|
+
skill: {
|
|
2605
|
+
type: 'string',
|
|
2606
|
+
description: 'The skill name to execute'
|
|
2607
|
+
}
|
|
2608
|
+
},
|
|
2609
|
+
required: ['skill']
|
|
2610
|
+
};
|
|
2611
|
+
break;
|
|
2612
|
+
case 'ListSkills':
|
|
2613
|
+
parameters = {
|
|
2614
|
+
type: 'object',
|
|
2615
|
+
properties: {},
|
|
2616
|
+
required: []
|
|
2617
|
+
};
|
|
2618
|
+
break;
|
|
2619
|
+
case 'GetSkillDetails':
|
|
2620
|
+
parameters = {
|
|
2621
|
+
type: 'object',
|
|
2622
|
+
properties: {
|
|
2623
|
+
skill: {
|
|
2624
|
+
type: 'string',
|
|
2625
|
+
description: 'The skill name/id to get details for'
|
|
2626
|
+
}
|
|
2627
|
+
},
|
|
2628
|
+
required: ['skill']
|
|
2629
|
+
};
|
|
2630
|
+
break;
|
|
2631
|
+
default:
|
|
2632
|
+
// For MCP tools, use their inputSchema; for other unknown tools, keep empty schema
|
|
2633
|
+
const mcpTool = tool;
|
|
2634
|
+
if (mcpTool._isMcpTool && mcpTool.inputSchema) {
|
|
2635
|
+
// Use MCP tool's inputSchema directly
|
|
2636
|
+
parameters = {
|
|
2637
|
+
type: 'object',
|
|
2638
|
+
properties: {},
|
|
2639
|
+
required: []
|
|
2640
|
+
};
|
|
2641
|
+
if (mcpTool.inputSchema.properties) {
|
|
2642
|
+
for (const [paramName, paramDef] of Object.entries(mcpTool.inputSchema.properties)) {
|
|
2643
|
+
parameters.properties[paramName] = {
|
|
2644
|
+
type: paramDef.type || 'string',
|
|
2645
|
+
description: paramDef.description || ''
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
if (mcpTool.inputSchema.required) {
|
|
2650
|
+
parameters.required = mcpTool.inputSchema.required;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
else {
|
|
2654
|
+
parameters = {
|
|
2655
|
+
type: 'object',
|
|
2656
|
+
properties: {},
|
|
2657
|
+
required: []
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
return {
|
|
2662
|
+
type: 'function',
|
|
2663
|
+
function: {
|
|
2664
|
+
name: tool.name,
|
|
2665
|
+
description: tool.description,
|
|
2666
|
+
parameters
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
async execute(toolName, params, executionMode, indent = '') {
|
|
2672
|
+
// First try to execute as local tool
|
|
2673
|
+
const localTool = this.tools.get(toolName);
|
|
2674
|
+
if (localTool) {
|
|
2675
|
+
return await this.executeLocalTool(toolName, params, executionMode, indent);
|
|
2676
|
+
}
|
|
2677
|
+
// Fall back to MCP tool if local tool doesn't exist
|
|
2678
|
+
const { getMCPManager } = await import('./mcp.js');
|
|
2679
|
+
const mcpManager = getMCPManager();
|
|
2680
|
+
const allMcpTools = mcpManager.getAllTools();
|
|
2681
|
+
// Check if this is an MCP tool (format: serverName__toolName)
|
|
2682
|
+
if (toolName.includes('__') && allMcpTools.has(toolName)) {
|
|
2683
|
+
return await this.executeMCPTool(toolName, params, executionMode, indent);
|
|
2684
|
+
}
|
|
2685
|
+
// Try to find MCP tool with just the tool name (try each server)
|
|
2686
|
+
for (const [fullName, tool] of allMcpTools) {
|
|
2687
|
+
const [serverName, actualToolName] = fullName.split('__');
|
|
2688
|
+
if (actualToolName === toolName) {
|
|
2689
|
+
return await this.executeMCPTool(fullName, params, executionMode, indent);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
// Tool not found anywhere
|
|
2693
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* Generate guidance for LLM to choose between local and MCP tools
|
|
2697
|
+
* This returns a message that the LLM can analyze to decide which tool to use
|
|
2698
|
+
*/
|
|
2699
|
+
generateLlmToolChoiceGuidance(toolName, params, executionMode, indent) {
|
|
2700
|
+
const modeLabel = executionMode === ExecutionMode.YOLO ? 'YOLO' :
|
|
2701
|
+
executionMode === ExecutionMode.SMART ? 'SMART' :
|
|
2702
|
+
executionMode === ExecutionMode.ACCEPT_EDITS ? 'ACCEPT_EDITS' : 'DEFAULT';
|
|
2703
|
+
return Promise.resolve({
|
|
2704
|
+
success: false,
|
|
2705
|
+
needsLlmDecision: true,
|
|
2706
|
+
toolName,
|
|
2707
|
+
mode: modeLabel,
|
|
2708
|
+
message: `
|
|
2709
|
+
## Tool Choice Decision Required (${modeLabel} Mode)
|
|
2710
|
+
|
|
2711
|
+
Both **local** and **MCP** versions of "${toolName}" are available. Please analyze the requirements and decide which tool to use.
|
|
2712
|
+
|
|
2713
|
+
### Local Tool
|
|
2714
|
+
- **Pros**: No network latency, always available, no external dependencies
|
|
2715
|
+
- **Best for**: Filesystem operations, project-specific tasks, command execution
|
|
2716
|
+
|
|
2717
|
+
### MCP Tool
|
|
2718
|
+
- **Pros**: Extended capabilities (e.g., GitHub API, database access, cloud services)
|
|
2719
|
+
- **Best for**: External API calls, database operations, cloud service integration
|
|
2720
|
+
|
|
2721
|
+
### How to Specify Your Choice
|
|
2722
|
+
Retry the tool call with the \`_useTool\` parameter:
|
|
2723
|
+
|
|
2724
|
+
**To use MCP tool:**
|
|
2725
|
+
\`\`\`json
|
|
2726
|
+
{"_useTool": "mcp", ...otherParams}
|
|
2727
|
+
\`\`\`
|
|
2728
|
+
|
|
2729
|
+
**To use local tool:**
|
|
2730
|
+
\`\`\`json
|
|
2731
|
+
{"_useTool": "local", ...otherParams}
|
|
2732
|
+
\`\`\`
|
|
2733
|
+
|
|
2734
|
+
### Decision Criteria
|
|
2735
|
+
Consider:
|
|
2736
|
+
1. Does the task require external API access?
|
|
2737
|
+
2. Is network latency a concern?
|
|
2738
|
+
3. Does the MCP tool have features not available locally?
|
|
2739
|
+
4. Is the local tool sufficient for the task?
|
|
2740
|
+
|
|
2741
|
+
Make your decision based on the user's request and the above criteria.`
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Execute local tool (extracted for reuse)
|
|
2746
|
+
*/
|
|
2747
|
+
async executeLocalTool(toolName, params, executionMode, indent) {
|
|
2748
|
+
const tool = this.get(toolName);
|
|
2749
|
+
if (!tool) {
|
|
2750
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
2751
|
+
}
|
|
2752
|
+
if (!tool.allowedModes.includes(executionMode)) {
|
|
2753
|
+
throw new Error(`Tool ${toolName} is not allowed in ${executionMode} mode`);
|
|
2754
|
+
}
|
|
2755
|
+
// Smart approval mode
|
|
2756
|
+
if (executionMode === ExecutionMode.SMART) {
|
|
2757
|
+
const debugMode = process.env.DEBUG === 'smart-approval';
|
|
2758
|
+
const cancellationManager = getCancellationManager();
|
|
2759
|
+
// task tool bypasses smart approval entirely
|
|
2760
|
+
if (toolName === 'task') {
|
|
2761
|
+
if (debugMode) {
|
|
2762
|
+
const { getLogger } = await import('./logger.js');
|
|
2763
|
+
const logger = getLogger();
|
|
2764
|
+
logger.debug(`[SmartApprovalEngine] Tool '${toolName}' bypassed smart approval completely`);
|
|
2765
|
+
}
|
|
2766
|
+
return await cancellationManager.withCancellation(tool.execute(params, executionMode), `tool-${toolName}`);
|
|
2767
|
+
}
|
|
2768
|
+
const { getSmartApprovalEngine } = await import('./smart-approval.js');
|
|
2769
|
+
const { getConfigManager } = await import('./config.js');
|
|
2770
|
+
const configManager = getConfigManager();
|
|
2771
|
+
const approvalEngine = getSmartApprovalEngine(debugMode);
|
|
2772
|
+
// Evaluate tool call
|
|
2773
|
+
const result = await approvalEngine.evaluate({
|
|
2774
|
+
toolName,
|
|
2775
|
+
params,
|
|
2776
|
+
timestamp: Date.now()
|
|
2777
|
+
});
|
|
2778
|
+
// Decide whether to execute based on approval result
|
|
2779
|
+
if (result.decision === 'approved') {
|
|
2780
|
+
// Whitelist or AI approval passed, execute directly
|
|
2781
|
+
console.log('');
|
|
2782
|
+
console.log(`${indent}${colors.success(`✅ [Smart Mode] Tool '${toolName}' passed approval, executing directly`)}`);
|
|
2783
|
+
console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
|
|
2784
|
+
console.log(`${indent}${colors.textDim(` Latency: ${result.latency}ms`)}`);
|
|
2785
|
+
console.log('');
|
|
2786
|
+
return await cancellationManager.withCancellation(tool.execute(params, executionMode), `tool-${toolName}`);
|
|
2787
|
+
}
|
|
2788
|
+
else if (result.decision === 'requires_confirmation') {
|
|
2789
|
+
// Requires user confirmation
|
|
2790
|
+
const confirmed = await approvalEngine.requestConfirmation(result);
|
|
2791
|
+
if (confirmed) {
|
|
2792
|
+
console.log('');
|
|
2793
|
+
console.log(`${indent}${colors.success(`✅ [Smart Mode] User confirmed execution of tool '${toolName}'`)}`);
|
|
2794
|
+
console.log('');
|
|
2795
|
+
return await cancellationManager.withCancellation(tool.execute(params, executionMode), `tool-${toolName}`);
|
|
2796
|
+
}
|
|
2797
|
+
else {
|
|
2798
|
+
console.log('');
|
|
2799
|
+
console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled execution of tool '${toolName}'`)}`);
|
|
2800
|
+
console.log('');
|
|
2801
|
+
throw new Error(`Tool execution cancelled by user: ${toolName}`);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
else {
|
|
2805
|
+
// Rejected execution
|
|
2806
|
+
console.log('');
|
|
2807
|
+
console.log(`${indent}${colors.error(`❌ [Smart Mode] Tool '${toolName}' execution rejected`)}`);
|
|
2808
|
+
console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
|
|
2809
|
+
console.log('');
|
|
2810
|
+
throw new Error(`Tool execution rejected: ${toolName}`);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
// Other modes execute directly
|
|
2814
|
+
return await tool.execute(params, executionMode);
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Execute an MCP tool call
|
|
2818
|
+
*/
|
|
2819
|
+
async executeMCPTool(toolName, params, executionMode, indent = '') {
|
|
2820
|
+
const { getMCPManager } = await import('./mcp.js');
|
|
2821
|
+
const cancellationManager = getCancellationManager();
|
|
2822
|
+
const mcpManager = getMCPManager();
|
|
2823
|
+
// Parse the tool name (format: serverName__toolName)
|
|
2824
|
+
const [serverName, actualToolName] = toolName.split('__');
|
|
2825
|
+
// Get server info for display
|
|
2826
|
+
const server = mcpManager.getServer(serverName);
|
|
2827
|
+
const serverTools = server?.getToolNames() || [];
|
|
2828
|
+
// Display tool call info
|
|
2829
|
+
console.log('');
|
|
2830
|
+
console.log(`${indent}${colors.warning(`${icons.tool} MCP Tool Call: ${serverName}::${actualToolName}`)}`);
|
|
2831
|
+
// Smart approval mode for MCP tools
|
|
2832
|
+
if (executionMode === ExecutionMode.SMART) {
|
|
2833
|
+
const debugMode = process.env.DEBUG === 'smart-approval';
|
|
2834
|
+
const { getSmartApprovalEngine } = await import('./smart-approval.js');
|
|
2835
|
+
const approvalEngine = getSmartApprovalEngine(debugMode);
|
|
2836
|
+
// Evaluate MCP tool call
|
|
2837
|
+
const result = await approvalEngine.evaluate({
|
|
2838
|
+
toolName: `MCP[${serverName}]::${actualToolName}`,
|
|
2839
|
+
params,
|
|
2840
|
+
timestamp: Date.now()
|
|
2841
|
+
});
|
|
2842
|
+
if (result.decision === 'approved') {
|
|
2843
|
+
console.log(`${indent}${colors.success(`✅ [Smart Mode] MCP tool '${serverName}::${actualToolName}' passed approval`)}`);
|
|
2844
|
+
console.log(`${indent}${colors.textDim(` Detection method: ${result.detectionMethod === 'whitelist' ? 'Whitelist' : 'AI Review'}`)}`);
|
|
2845
|
+
}
|
|
2846
|
+
else if (result.decision === 'requires_confirmation') {
|
|
2847
|
+
const confirmed = await approvalEngine.requestConfirmation(result);
|
|
2848
|
+
if (!confirmed) {
|
|
2849
|
+
console.log(`${indent}${colors.warning(`⚠️ [Smart Mode] User cancelled MCP tool execution`)}`);
|
|
2850
|
+
throw new Error(`Tool execution cancelled by user: ${toolName}`);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
else {
|
|
2854
|
+
console.log(`${indent}${colors.error(`❌ [Smart Mode] MCP tool execution rejected`)}`);
|
|
2855
|
+
console.log(`${indent}${colors.textDim(` Reason: ${result.description}`)}`);
|
|
2856
|
+
throw new Error(`Tool execution rejected: ${toolName}`);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
// Execute the MCP tool call with cancellation support
|
|
2860
|
+
const operationId = `mcp-${serverName}-${actualToolName}-${Date.now()}`;
|
|
2861
|
+
return await cancellationManager.withCancellation(mcpManager.callTool(toolName, params), operationId);
|
|
2862
|
+
}
|
|
2863
|
+
async executeAll(toolCalls, executionMode) {
|
|
2864
|
+
const results = [];
|
|
2865
|
+
const cancellationManager = getCancellationManager();
|
|
2866
|
+
let cancelled = false;
|
|
2867
|
+
// Listen for cancellation
|
|
2868
|
+
const cancelHandler = () => {
|
|
2869
|
+
cancelled = true;
|
|
2870
|
+
};
|
|
2871
|
+
cancellationManager.on('cancelled', cancelHandler);
|
|
2872
|
+
const executePromises = toolCalls.map(async (toolCall, index) => {
|
|
2873
|
+
const { name, params } = toolCall;
|
|
2874
|
+
const operationId = `tool-${name}-${index}-${Date.now()}`;
|
|
2875
|
+
try {
|
|
2876
|
+
const result = await cancellationManager.withCancellation(this.execute(name, params, executionMode), operationId);
|
|
2877
|
+
return { tool: name, result, error: undefined };
|
|
2878
|
+
}
|
|
2879
|
+
catch (error) {
|
|
2880
|
+
if (error.message === 'Operation cancelled by user') {
|
|
2881
|
+
return { tool: name, result: undefined, error: 'Cancelled' };
|
|
2882
|
+
}
|
|
2883
|
+
return { tool: name, result: undefined, error: error.message };
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
const settledResults = await Promise.all(executePromises);
|
|
2887
|
+
cancellationManager.off('cancelled', cancelHandler);
|
|
2888
|
+
// Filter out cancelled tools and mark them appropriately
|
|
2889
|
+
for (const result of settledResults) {
|
|
2890
|
+
if (result.error === 'Cancelled' && cancelled) {
|
|
2891
|
+
// Don't add cancelled results to the final output
|
|
2892
|
+
continue;
|
|
2893
|
+
}
|
|
2894
|
+
results.push(result);
|
|
2895
|
+
}
|
|
2896
|
+
return results;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
let toolRegistryInstance = null;
|
|
2900
|
+
export function getToolRegistry() {
|
|
2901
|
+
if (!toolRegistryInstance) {
|
|
2902
|
+
toolRegistryInstance = new ToolRegistry();
|
|
2903
|
+
}
|
|
2904
|
+
return toolRegistryInstance;
|
|
2905
|
+
}
|
|
2906
|
+
//# sourceMappingURL=tools.js.map
|