@xagent-ai/cli 1.2.2 → 1.3.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/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/release.yml +76 -0
- package/.github/workflows/ci.yml +75 -0
- package/.github/workflows/release.yml +103 -0
- package/.gitmodules +3 -3
- package/README.md +326 -280
- package/README_CN.md +325 -279
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +7 -3
- package/dist/agents.js.map +1 -1
- package/dist/ai-client/factory.d.ts +40 -0
- package/dist/ai-client/factory.d.ts.map +1 -0
- package/dist/ai-client/factory.js +100 -0
- package/dist/ai-client/factory.js.map +1 -0
- package/dist/ai-client/index.d.ts +20 -0
- package/dist/ai-client/index.d.ts.map +1 -0
- package/dist/ai-client/index.js +49 -0
- package/dist/ai-client/index.js.map +1 -0
- package/dist/ai-client/providers/anthropic.d.ts +57 -0
- package/dist/ai-client/providers/anthropic.d.ts.map +1 -0
- package/dist/ai-client/providers/anthropic.js +406 -0
- package/dist/ai-client/providers/anthropic.js.map +1 -0
- package/dist/ai-client/providers/openai.d.ts +57 -0
- package/dist/ai-client/providers/openai.d.ts.map +1 -0
- package/dist/ai-client/providers/openai.js +290 -0
- package/dist/ai-client/providers/openai.js.map +1 -0
- package/dist/ai-client/providers/remote.d.ts +110 -0
- package/dist/ai-client/providers/remote.d.ts.map +1 -0
- package/dist/ai-client/providers/remote.js +352 -0
- package/dist/ai-client/providers/remote.js.map +1 -0
- package/dist/ai-client/registry.d.ts +51 -0
- package/dist/ai-client/registry.d.ts.map +1 -0
- package/dist/ai-client/registry.js +81 -0
- package/dist/ai-client/registry.js.map +1 -0
- package/dist/ai-client/types.d.ts +274 -0
- package/dist/ai-client/types.d.ts.map +1 -0
- package/dist/ai-client/types.js +90 -0
- package/dist/ai-client/types.js.map +1 -0
- package/dist/ai-client-factory.d.ts +62 -0
- package/dist/ai-client-factory.d.ts.map +1 -0
- package/dist/ai-client-factory.js +157 -0
- package/dist/ai-client-factory.js.map +1 -0
- package/dist/auth.d.ts +23 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +164 -174
- package/dist/auth.js.map +1 -1
- package/dist/cancellation.d.ts +5 -4
- package/dist/cancellation.d.ts.map +1 -1
- package/dist/cancellation.js +53 -32
- package/dist/cancellation.js.map +1 -1
- package/dist/checkpoint.d.ts +2 -1
- package/dist/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint.js +39 -6
- package/dist/checkpoint.js.map +1 -1
- package/dist/cli.js +742 -29
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +62 -25
- package/dist/config.js.map +1 -1
- package/dist/context-compressor.d.ts +82 -18
- package/dist/context-compressor.d.ts.map +1 -1
- package/dist/context-compressor.js +718 -154
- package/dist/context-compressor.js.map +1 -1
- package/dist/conversation.d.ts +1 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +8 -7
- package/dist/conversation.js.map +1 -1
- package/dist/gui-subagent/action-parser/actionParser.d.ts.map +1 -1
- package/dist/gui-subagent/action-parser/actionParser.js +6 -4
- package/dist/gui-subagent/action-parser/actionParser.js.map +1 -1
- package/dist/gui-subagent/agent/gui-agent.d.ts +39 -2
- package/dist/gui-subagent/agent/gui-agent.d.ts.map +1 -1
- package/dist/gui-subagent/agent/gui-agent.js +189 -74
- package/dist/gui-subagent/agent/gui-agent.js.map +1 -1
- package/dist/gui-subagent/index.d.ts +23 -1
- package/dist/gui-subagent/index.d.ts.map +1 -1
- package/dist/gui-subagent/index.js +6 -0
- package/dist/gui-subagent/index.js.map +1 -1
- package/dist/gui-subagent/operator/base-operator.d.ts.map +1 -1
- package/dist/gui-subagent/operator/base-operator.js +0 -1
- package/dist/gui-subagent/operator/base-operator.js.map +1 -1
- package/dist/gui-subagent/operator/computer-operator.d.ts.map +1 -1
- package/dist/gui-subagent/operator/computer-operator.js +31 -8
- package/dist/gui-subagent/operator/computer-operator.js.map +1 -1
- package/dist/gui-subagent/types/actions.d.ts +1 -1
- package/dist/gui-subagent/types/actions.d.ts.map +1 -1
- package/dist/gui-subagent/types/actions.js +0 -1
- package/dist/gui-subagent/types/actions.js.map +1 -1
- package/dist/gui-subagent/types/operator.d.ts +1 -1
- package/dist/gui-subagent/types/operator.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/input-processor.d.ts.map +1 -1
- package/dist/input-processor.js +8 -5
- package/dist/input-processor.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/mcp.d.ts +7 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +157 -49
- package/dist/mcp.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +3 -3
- package/dist/memory.js.map +1 -1
- package/dist/output-util.d.ts +27 -0
- package/dist/output-util.d.ts.map +1 -0
- package/dist/output-util.js +74 -0
- package/dist/output-util.js.map +1 -0
- package/dist/retry.js +1 -1
- package/dist/retry.js.map +1 -1
- package/dist/ripgrep.d.ts +29 -0
- package/dist/ripgrep.d.ts.map +1 -0
- package/dist/ripgrep.js +294 -0
- package/dist/ripgrep.js.map +1 -0
- package/dist/sdk-output-adapter.d.ts +34 -1
- package/dist/sdk-output-adapter.d.ts.map +1 -1
- package/dist/sdk-output-adapter.js +67 -2
- package/dist/sdk-output-adapter.js.map +1 -1
- package/dist/sdk-session.d.ts.map +1 -1
- package/dist/sdk-session.js +2 -0
- package/dist/sdk-session.js.map +1 -1
- package/dist/session-manager.js +3 -3
- package/dist/session-manager.js.map +1 -1
- package/dist/session.d.ts +116 -6
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +1416 -448
- package/dist/session.js.map +1 -1
- package/dist/shell.d.ts +33 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +126 -0
- package/dist/shell.js.map +1 -0
- package/dist/skill-installer.d.ts +38 -0
- package/dist/skill-installer.d.ts.map +1 -0
- package/dist/skill-installer.js +447 -0
- package/dist/skill-installer.js.map +1 -0
- package/dist/skill-invoker.d.ts +8 -2
- package/dist/skill-invoker.d.ts.map +1 -1
- package/dist/skill-invoker.js +36 -15
- package/dist/skill-invoker.js.map +1 -1
- package/dist/skill-loader.d.ts +8 -3
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +51 -48
- package/dist/skill-loader.js.map +1 -1
- package/dist/skill-manager.d.ts +85 -0
- package/dist/skill-manager.d.ts.map +1 -0
- package/dist/skill-manager.js +341 -0
- package/dist/skill-manager.js.map +1 -0
- package/dist/slash-commands.d.ts +39 -2
- package/dist/slash-commands.d.ts.map +1 -1
- package/dist/slash-commands.js +934 -305
- package/dist/slash-commands.js.map +1 -1
- package/dist/smart-approval.d.ts +20 -1
- package/dist/smart-approval.d.ts.map +1 -1
- package/dist/smart-approval.js +125 -56
- package/dist/smart-approval.js.map +1 -1
- package/dist/system-prompt-generator.d.ts +6 -0
- package/dist/system-prompt-generator.d.ts.map +1 -1
- package/dist/system-prompt-generator.js +86 -36
- package/dist/system-prompt-generator.js.map +1 -1
- package/dist/terminal.d.ts +28 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +82 -0
- package/dist/terminal.js.map +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +8 -7
- package/dist/theme.js.map +1 -1
- package/dist/tools.d.ts +38 -7
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +1249 -617
- package/dist/tools.js.map +1 -1
- package/dist/truncate.d.ts +55 -0
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +130 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +84 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +49 -0
- package/dist/types.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +28 -36
- package/dist/update.js.map +1 -1
- package/dist/workflow.d.ts +5 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +61 -49
- package/dist/workflow.js.map +1 -1
- package/docs/architecture/mcp-integration-guide.md +304 -194
- package/docs/architecture/overview.md +169 -169
- package/docs/architecture/tool-system-design.md +134 -134
- package/docs/cli/commands.md +349 -238
- package/docs/smart-mode.md +281 -281
- package/docs/third-party-models.md +440 -439
- package/find-skills/SKILL.md +133 -0
- package/package.json +91 -90
- package/scripts/install-ripgrep.js +241 -0
- package/src/agents.ts +7 -3
- package/src/ai-client/factory.ts +116 -0
- package/src/ai-client/index.ts +61 -0
- package/src/ai-client/providers/anthropic.ts +475 -0
- package/src/ai-client/providers/openai.ts +348 -0
- package/src/ai-client/providers/remote.ts +439 -0
- package/src/ai-client/registry.ts +97 -0
- package/src/ai-client/types.ts +364 -0
- package/src/ai-client-factory.ts +204 -0
- package/src/auth.ts +661 -614
- package/src/cancellation.ts +202 -176
- package/src/checkpoint.ts +255 -219
- package/src/cli.ts +1523 -743
- package/src/config.ts +341 -297
- package/src/context-compressor.ts +987 -290
- package/src/conversation.ts +290 -288
- package/src/gui-subagent/action-parser/actionParser.ts +318 -315
- package/src/gui-subagent/action-parser/constants.ts +14 -14
- package/src/gui-subagent/action-parser/index.ts +8 -8
- package/src/gui-subagent/action-parser/types.ts +31 -31
- package/src/gui-subagent/agent/gui-agent.ts +1234 -1089
- package/src/gui-subagent/agent/index.ts +5 -5
- package/src/gui-subagent/index.ts +185 -163
- package/src/gui-subagent/operator/base-operator.ts +244 -245
- package/src/gui-subagent/operator/computer-operator.ts +541 -520
- package/src/gui-subagent/operator/index.ts +6 -6
- package/src/gui-subagent/types/actions.ts +260 -262
- package/src/gui-subagent/types/index.ts +6 -6
- package/src/gui-subagent/types/operator.ts +106 -106
- package/src/gui-subagent/utils.ts +51 -51
- package/src/index.ts +17 -18
- package/src/input-processor.ts +8 -5
- package/src/logger.ts +436 -438
- package/src/mcp.ts +793 -682
- package/src/memory.ts +343 -344
- package/src/output-util.ts +80 -0
- package/src/retry.ts +1 -1
- package/src/ripgrep.ts +370 -0
- package/src/sdk-output-adapter.ts +842 -0
- package/src/sdk-session.ts +62 -0
- package/src/session-manager.ts +308 -308
- package/src/session.ts +1775 -573
- package/src/shell.ts +134 -0
- package/src/skill-installer.ts +518 -0
- package/src/skill-invoker.ts +959 -935
- package/src/skill-loader.ts +501 -496
- package/src/skill-manager.ts +385 -0
- package/src/slash-commands.ts +2189 -1389
- package/src/smart-approval.ts +193 -74
- package/src/system-prompt-generator.ts +91 -36
- package/src/terminal.ts +96 -0
- package/src/theme.ts +739 -738
- package/src/tools.ts +1790 -931
- package/src/truncate.ts +173 -0
- package/src/types.ts +337 -198
- package/src/update.ts +33 -40
- package/src/workflow.ts +521 -508
- package/test/cli-launch.test.ts +279 -0
- package/tsconfig.json +22 -22
- package/vitest.config.ts +21 -19
- package/dist/ai-client.d.ts +0 -86
- package/dist/ai-client.d.ts.map +0 -1
- package/dist/ai-client.js +0 -1372
- package/dist/ai-client.js.map +0 -1
- package/dist/gui-subagent/operator/browser-operator.d.ts +0 -36
- package/dist/gui-subagent/operator/browser-operator.d.ts.map +0 -1
- package/dist/gui-subagent/operator/browser-operator.js +0 -306
- package/dist/gui-subagent/operator/browser-operator.js.map +0 -1
- package/dist/gui-subagent/operator/desktop-operator.d.ts +0 -55
- package/dist/gui-subagent/operator/desktop-operator.d.ts.map +0 -1
- package/dist/gui-subagent/operator/desktop-operator.js +0 -527
- package/dist/gui-subagent/operator/desktop-operator.js.map +0 -1
- package/dist/hook.d.ts +0 -73
- package/dist/hook.d.ts.map +0 -1
- package/dist/hook.js +0 -156
- package/dist/hook.js.map +0 -1
- package/dist/input-history.d.ts +0 -24
- package/dist/input-history.d.ts.map +0 -1
- package/dist/input-history.js +0 -94
- package/dist/input-history.js.map +0 -1
- package/dist/keyboard-manager.d.ts +0 -151
- package/dist/keyboard-manager.d.ts.map +0 -1
- package/dist/keyboard-manager.js +0 -396
- package/dist/keyboard-manager.js.map +0 -1
- package/dist/print-system-prompt.d.ts +0 -2
- package/dist/print-system-prompt.d.ts.map +0 -1
- package/dist/print-system-prompt.js +0 -40
- package/dist/print-system-prompt.js.map +0 -1
- package/dist/remote-ai-client.d.ts +0 -104
- package/dist/remote-ai-client.d.ts.map +0 -1
- package/dist/remote-ai-client.js +0 -552
- package/dist/remote-ai-client.js.map +0 -1
- package/dist/sdk-session-v2.d.ts +0 -13
- package/dist/sdk-session-v2.d.ts.map +0 -1
- package/dist/sdk-session-v2.js +0 -46
- package/dist/sdk-session-v2.js.map +0 -1
- package/dist/test-boundary-conditions.d.ts.map +0 -1
- package/dist/test-boundary-conditions.js.map +0 -1
- package/dist/test-cancellation-fix.d.ts.map +0 -1
- package/dist/test-cancellation-fix.js.map +0 -1
- package/dist/test-input-history.d.ts.map +0 -1
- package/dist/test-input-history.js.map +0 -1
- package/dist/test-interaction-flow.d.ts.map +0 -1
- package/dist/test-interaction-flow.js.map +0 -1
- package/dist/test-quick.d.ts.map +0 -1
- package/dist/test-quick.js.map +0 -1
- package/dist/test-user-interaction.d.ts.map +0 -1
- package/dist/test-user-interaction.js.map +0 -1
- package/dist/tools/edit-diff.d.ts +0 -32
- package/dist/tools/edit-diff.d.ts.map +0 -1
- package/dist/tools/edit-diff.js +0 -185
- package/dist/tools/edit-diff.js.map +0 -1
- package/dist/tools/edit.d.ts +0 -11
- package/dist/tools/edit.d.ts.map +0 -1
- package/dist/tools/edit.js +0 -129
- package/dist/tools/edit.js.map +0 -1
- package/dist/unified-session.d.ts +0 -42
- package/dist/unified-session.d.ts.map +0 -1
- package/dist/unified-session.js +0 -271
- package/dist/unified-session.js.map +0 -1
- package/skills/.claude-plugin/marketplace.json +0 -45
- package/skills/README.md +0 -94
- package/skills/THIRD_PARTY_NOTICES.md +0 -405
- package/skills/skills/algorithmic-art/LICENSE.txt +0 -202
- package/skills/skills/algorithmic-art/SKILL.md +0 -405
- package/skills/skills/algorithmic-art/templates/generator_template.js +0 -223
- package/skills/skills/algorithmic-art/templates/viewer.html +0 -599
- package/skills/skills/brand-guidelines/LICENSE.txt +0 -202
- package/skills/skills/brand-guidelines/SKILL.md +0 -73
- package/skills/skills/canvas-design/LICENSE.txt +0 -202
- package/skills/skills/canvas-design/SKILL.md +0 -130
- package/skills/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +0 -93
- 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 +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +0 -93
- package/skills/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +0 -94
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +0 -93
- 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 +0 -93
- 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 +0 -93
- 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 +0 -93
- 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 +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +0 -93
- 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 +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +0 -93
- 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 +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +0 -93
- 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 +0 -93
- package/skills/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/skills/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +0 -93
- package/skills/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/skills/skills/doc-coauthoring/SKILL.md +0 -375
- package/skills/skills/docx/LICENSE.txt +0 -30
- package/skills/skills/docx/SKILL.md +0 -197
- package/skills/skills/docx/docx-js.md +0 -350
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/skills/docx/ooxml/scripts/pack.py +0 -159
- package/skills/skills/docx/ooxml/scripts/unpack.py +0 -29
- package/skills/skills/docx/ooxml/scripts/validate.py +0 -69
- package/skills/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
- package/skills/skills/docx/ooxml/scripts/validation/base.py +0 -951
- package/skills/skills/docx/ooxml/scripts/validation/docx.py +0 -274
- package/skills/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
- package/skills/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
- package/skills/skills/docx/ooxml.md +0 -610
- package/skills/skills/docx/scripts/__init__.py +0 -1
- package/skills/skills/docx/scripts/document.py +0 -1276
- package/skills/skills/docx/scripts/templates/comments.xml +0 -3
- package/skills/skills/docx/scripts/templates/commentsExtended.xml +0 -3
- package/skills/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
- package/skills/skills/docx/scripts/templates/commentsIds.xml +0 -3
- package/skills/skills/docx/scripts/templates/people.xml +0 -3
- package/skills/skills/docx/scripts/utilities.py +0 -374
- package/skills/skills/frontend-design/LICENSE.txt +0 -177
- package/skills/skills/frontend-design/SKILL.md +0 -42
- package/skills/skills/internal-comms/LICENSE.txt +0 -202
- package/skills/skills/internal-comms/SKILL.md +0 -32
- package/skills/skills/internal-comms/examples/3p-updates.md +0 -47
- package/skills/skills/internal-comms/examples/company-newsletter.md +0 -65
- package/skills/skills/internal-comms/examples/faq-answers.md +0 -30
- package/skills/skills/internal-comms/examples/general-comms.md +0 -16
- package/skills/skills/mcp-builder/LICENSE.txt +0 -202
- package/skills/skills/mcp-builder/SKILL.md +0 -236
- package/skills/skills/mcp-builder/reference/evaluation.md +0 -602
- package/skills/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
- package/skills/skills/mcp-builder/reference/node_mcp_server.md +0 -970
- package/skills/skills/mcp-builder/reference/python_mcp_server.md +0 -719
- package/skills/skills/mcp-builder/scripts/connections.py +0 -151
- package/skills/skills/mcp-builder/scripts/evaluation.py +0 -373
- package/skills/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
- package/skills/skills/mcp-builder/scripts/requirements.txt +0 -2
- package/skills/skills/pdf/LICENSE.txt +0 -30
- package/skills/skills/pdf/SKILL.md +0 -294
- package/skills/skills/pdf/forms.md +0 -205
- package/skills/skills/pdf/reference.md +0 -612
- package/skills/skills/pdf/scripts/check_bounding_boxes.py +0 -70
- package/skills/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
- package/skills/skills/pdf/scripts/check_fillable_fields.py +0 -12
- package/skills/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
- package/skills/skills/pdf/scripts/create_validation_image.py +0 -41
- package/skills/skills/pdf/scripts/extract_form_field_info.py +0 -152
- package/skills/skills/pdf/scripts/fill_fillable_fields.py +0 -114
- package/skills/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
- package/skills/skills/pptx/LICENSE.txt +0 -30
- package/skills/skills/pptx/SKILL.md +0 -484
- package/skills/skills/pptx/html2pptx.md +0 -625
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/skills/pptx/ooxml/scripts/pack.py +0 -159
- package/skills/skills/pptx/ooxml/scripts/unpack.py +0 -29
- package/skills/skills/pptx/ooxml/scripts/validate.py +0 -69
- package/skills/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
- package/skills/skills/pptx/ooxml/scripts/validation/base.py +0 -951
- package/skills/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
- package/skills/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
- package/skills/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
- package/skills/skills/pptx/ooxml.md +0 -427
- package/skills/skills/pptx/scripts/html2pptx.js +0 -979
- package/skills/skills/pptx/scripts/inventory.py +0 -1020
- package/skills/skills/pptx/scripts/rearrange.py +0 -231
- package/skills/skills/pptx/scripts/replace.py +0 -385
- package/skills/skills/pptx/scripts/thumbnail.py +0 -450
- package/skills/skills/skill-creator/LICENSE.txt +0 -202
- package/skills/skills/skill-creator/SKILL.md +0 -356
- package/skills/skills/skill-creator/references/output-patterns.md +0 -82
- package/skills/skills/skill-creator/references/workflows.md +0 -28
- package/skills/skills/skill-creator/scripts/init_skill.py +0 -303
- package/skills/skills/skill-creator/scripts/package_skill.py +0 -110
- package/skills/skills/skill-creator/scripts/quick_validate.py +0 -95
- package/skills/skills/slack-gif-creator/LICENSE.txt +0 -202
- package/skills/skills/slack-gif-creator/SKILL.md +0 -254
- package/skills/skills/slack-gif-creator/core/easing.py +0 -234
- package/skills/skills/slack-gif-creator/core/frame_composer.py +0 -176
- package/skills/skills/slack-gif-creator/core/gif_builder.py +0 -269
- package/skills/skills/slack-gif-creator/core/validators.py +0 -136
- package/skills/skills/slack-gif-creator/requirements.txt +0 -4
- package/skills/skills/theme-factory/LICENSE.txt +0 -202
- package/skills/skills/theme-factory/SKILL.md +0 -59
- package/skills/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/skills/theme-factory/themes/arctic-frost.md +0 -19
- package/skills/skills/theme-factory/themes/botanical-garden.md +0 -19
- package/skills/skills/theme-factory/themes/desert-rose.md +0 -19
- package/skills/skills/theme-factory/themes/forest-canopy.md +0 -19
- package/skills/skills/theme-factory/themes/golden-hour.md +0 -19
- package/skills/skills/theme-factory/themes/midnight-galaxy.md +0 -19
- package/skills/skills/theme-factory/themes/modern-minimalist.md +0 -19
- package/skills/skills/theme-factory/themes/ocean-depths.md +0 -19
- package/skills/skills/theme-factory/themes/sunset-boulevard.md +0 -19
- package/skills/skills/theme-factory/themes/tech-innovation.md +0 -19
- package/skills/skills/web-artifacts-builder/LICENSE.txt +0 -202
- package/skills/skills/web-artifacts-builder/SKILL.md +0 -74
- package/skills/skills/web-artifacts-builder/scripts/bundle-artifact.sh +0 -54
- package/skills/skills/web-artifacts-builder/scripts/init-artifact.sh +0 -322
- package/skills/skills/webapp-testing/LICENSE.txt +0 -202
- package/skills/skills/webapp-testing/SKILL.md +0 -96
- package/skills/skills/webapp-testing/examples/console_logging.py +0 -35
- package/skills/skills/webapp-testing/examples/element_discovery.py +0 -40
- package/skills/skills/webapp-testing/examples/static_html_automation.py +0 -33
- package/skills/skills/webapp-testing/scripts/with_server.py +0 -106
- package/skills/skills/xlsx/LICENSE.txt +0 -30
- package/skills/skills/xlsx/SKILL.md +0 -289
- package/skills/skills/xlsx/recalc.py +0 -178
- package/skills/spec/agent-skills-spec.md +0 -3
- package/skills/template/SKILL.md +0 -6
- package/src/ai-client.ts +0 -1560
- package/src/remote-ai-client.ts +0 -664
- /package/{.eslintrc.js → .eslintrc.cjs} +0 -0
package/src/session.ts
CHANGED
|
@@ -4,31 +4,59 @@ import https from 'https';
|
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import ora from 'ora';
|
|
7
|
-
import inquirer from 'inquirer';
|
|
8
7
|
import { createRequire } from 'module';
|
|
9
|
-
import
|
|
8
|
+
import path from 'path';
|
|
10
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import fsPromises from 'fs/promises';
|
|
12
|
+
import os from 'os';
|
|
11
13
|
|
|
12
14
|
const require = createRequire(import.meta.url);
|
|
13
15
|
const packageJson = require('../package.json');
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
import {
|
|
17
|
+
ExecutionMode,
|
|
18
|
+
ChatMessage,
|
|
19
|
+
ToolCall,
|
|
20
|
+
AuthType,
|
|
21
|
+
} from './types.js';
|
|
22
|
+
import type { AgentConfig, ToolCallItem } from './types.js';
|
|
23
|
+
import { createAIClient, type AIClientInterface } from './ai-client-factory.js';
|
|
24
|
+
import { detectThinkingKeywords, getThinkingTokens } from './ai-client/types.js';
|
|
25
|
+
import { TokenInvalidError } from './ai-client/types.js';
|
|
26
|
+
import { fetchDefaultModels } from './ai-client/providers/remote.js';
|
|
17
27
|
import { getConfigManager, ConfigManager } from './config.js';
|
|
18
28
|
import { AuthService, selectAuthType } from './auth.js';
|
|
19
29
|
import { getToolRegistry } from './tools.js';
|
|
20
|
-
import { getAgentManager,
|
|
30
|
+
import { getAgentManager, AgentManager } from './agents.js';
|
|
21
31
|
import { getMemoryManager, MemoryManager } from './memory.js';
|
|
22
32
|
import { getMCPManager, MCPManager } from './mcp.js';
|
|
23
33
|
import { getCheckpointManager, CheckpointManager } from './checkpoint.js';
|
|
24
34
|
import { getConversationManager, ConversationManager } from './conversation.js';
|
|
25
35
|
import { getSessionManager, SessionManager } from './session-manager.js';
|
|
26
|
-
import { SlashCommandHandler, parseInput
|
|
36
|
+
import { SlashCommandHandler, parseInput } from './slash-commands.js';
|
|
27
37
|
import { SystemPromptGenerator } from './system-prompt-generator.js';
|
|
28
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
theme,
|
|
40
|
+
icons,
|
|
41
|
+
colors,
|
|
42
|
+
styleHelpers,
|
|
43
|
+
renderMarkdown,
|
|
44
|
+
renderDiff,
|
|
45
|
+
renderLines,
|
|
46
|
+
} from './theme.js';
|
|
29
47
|
import { getCancellationManager, CancellationManager } from './cancellation.js';
|
|
30
|
-
import {
|
|
31
|
-
|
|
48
|
+
import {
|
|
49
|
+
getContextCompressor,
|
|
50
|
+
ContextCompressor,
|
|
51
|
+
CompressionResult,
|
|
52
|
+
} from './context-compressor.js';
|
|
53
|
+
import { getLogger } from './logger.js';
|
|
54
|
+
import { ensureTtySane, setupEscKeyHandler } from './terminal.js';
|
|
55
|
+
import { SdkOutputAdapter } from './sdk-output-adapter.js';
|
|
56
|
+
|
|
57
|
+
// Type aliases for backward compatibility
|
|
58
|
+
type AIClient = AIClientInterface;
|
|
59
|
+
type RemoteAIClient = AIClientInterface;
|
|
32
60
|
|
|
33
61
|
const logger = getLogger();
|
|
34
62
|
|
|
@@ -39,7 +67,7 @@ export class InteractiveSession {
|
|
|
39
67
|
private aiClient: AIClient | null = null;
|
|
40
68
|
private remoteAIClient: RemoteAIClient | null = null;
|
|
41
69
|
private conversation: ChatMessage[] = [];
|
|
42
|
-
private
|
|
70
|
+
private tool_calls: ToolCall[] = [];
|
|
43
71
|
private executionMode: ExecutionMode;
|
|
44
72
|
private slashCommandHandler: SlashCommandHandler;
|
|
45
73
|
private configManager: ConfigManager;
|
|
@@ -47,7 +75,7 @@ export class InteractiveSession {
|
|
|
47
75
|
private memoryManager: MemoryManager;
|
|
48
76
|
private mcpManager: MCPManager;
|
|
49
77
|
private checkpointManager: CheckpointManager;
|
|
50
|
-
private currentAgent:
|
|
78
|
+
private currentAgent: AgentConfig | null = null;
|
|
51
79
|
private rl: readline.Interface;
|
|
52
80
|
private cancellationManager: CancellationManager;
|
|
53
81
|
private indentLevel: number;
|
|
@@ -56,142 +84,342 @@ export class InteractiveSession {
|
|
|
56
84
|
private currentTaskId: string | null = null;
|
|
57
85
|
private taskCompleted: boolean = false;
|
|
58
86
|
private isFirstApiCall: boolean = true;
|
|
87
|
+
private sdkOutputAdapter: SdkOutputAdapter | null = null;
|
|
88
|
+
private isSdkMode: boolean = false;
|
|
89
|
+
private sdkInputBuffer: string[] = [];
|
|
90
|
+
private resolveInput: ((value: string | null) => void) | null = null;
|
|
91
|
+
private _currentRequestId: string | null = null;
|
|
92
|
+
private heartbeatTimeout: NodeJS.Timeout | null = null;
|
|
93
|
+
private heartbeatTimeoutMs: number = 300000; // 5 minutes timeout for long AI responses
|
|
94
|
+
private lastActivityTime: number = Date.now();
|
|
95
|
+
|
|
96
|
+
// SDK response handling for approvals and questions
|
|
97
|
+
private approvalPromises: Map<string, { resolve: (approved: boolean) => void; reject: (err: Error) => void }> = new Map();
|
|
98
|
+
private questionPromises: Map<string, { resolve: (answers: string[]) => void; reject: (err: Error) => void }> = new Map();
|
|
99
|
+
|
|
100
|
+
constructor(indentLevel: number = 0) {
|
|
101
|
+
this.rl = readline.createInterface({
|
|
102
|
+
input: process.stdin,
|
|
103
|
+
output: process.stdout,
|
|
104
|
+
});
|
|
59
105
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
this.
|
|
106
|
+
this.configManager = getConfigManager(process.cwd());
|
|
107
|
+
this.agentManager = getAgentManager(process.cwd());
|
|
108
|
+
this.memoryManager = getMemoryManager(process.cwd());
|
|
109
|
+
this.mcpManager = getMCPManager();
|
|
110
|
+
this.checkpointManager = getCheckpointManager(process.cwd());
|
|
111
|
+
this.conversationManager = getConversationManager();
|
|
112
|
+
this.sessionManager = getSessionManager(process.cwd());
|
|
113
|
+
this.slashCommandHandler = new SlashCommandHandler();
|
|
114
|
+
|
|
115
|
+
// Register /clear callback, clear local conversation when clearing dialogue
|
|
116
|
+
this.slashCommandHandler.setClearCallback(() => {
|
|
117
|
+
this.conversation = [];
|
|
118
|
+
this.tool_calls = [];
|
|
119
|
+
this.currentTaskId = null;
|
|
120
|
+
this.taskCompleted = false;
|
|
121
|
+
this.isFirstApiCall = true;
|
|
122
|
+
this.slashCommandHandler.setConversationHistory([]);
|
|
123
|
+
});
|
|
77
124
|
|
|
78
|
-
|
|
125
|
+
// Register MCP update callback, update system prompt
|
|
126
|
+
this.slashCommandHandler.setSystemPromptUpdateCallback(async () => {
|
|
127
|
+
await this.updateSystemPrompt();
|
|
128
|
+
});
|
|
79
129
|
|
|
80
|
-
|
|
130
|
+
// Register config update callback, update aiClient config when /auth changes config
|
|
131
|
+
this.slashCommandHandler.setConfigUpdateCallback(() => {
|
|
132
|
+
this.updateAiClientConfig();
|
|
133
|
+
});
|
|
81
134
|
|
|
82
|
-
|
|
135
|
+
this.executionMode = ExecutionMode.DEFAULT;
|
|
83
136
|
|
|
84
|
-
|
|
137
|
+
this.cancellationManager = getCancellationManager();
|
|
85
138
|
|
|
86
|
-
|
|
139
|
+
this.indentLevel = indentLevel;
|
|
87
140
|
|
|
88
|
-
|
|
89
|
-
this.slashCommandHandler.setClearCallback(() => {
|
|
90
|
-
this.conversation = [];
|
|
91
|
-
this.toolCalls = [];
|
|
92
|
-
this.currentTaskId = null;
|
|
93
|
-
this.taskCompleted = false;
|
|
94
|
-
this.isFirstApiCall = true;
|
|
95
|
-
this.slashCommandHandler.setConversationHistory([]);
|
|
96
|
-
});
|
|
141
|
+
this.indentString = ' '.repeat(indentLevel);
|
|
97
142
|
|
|
98
|
-
|
|
143
|
+
this.contextCompressor = getContextCompressor();
|
|
144
|
+
}
|
|
99
145
|
|
|
100
|
-
|
|
146
|
+
private getIndent(): string {
|
|
147
|
+
return this.indentString;
|
|
148
|
+
}
|
|
101
149
|
|
|
102
|
-
|
|
150
|
+
setAIClient(aiClient: AIClient): void {
|
|
151
|
+
this.aiClient = aiClient;
|
|
152
|
+
}
|
|
103
153
|
|
|
104
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Update aiClient config when /auth changes config (called from callback)
|
|
156
|
+
*/
|
|
157
|
+
updateAiClientConfig(): void {
|
|
158
|
+
const authConfig = this.configManager.getAuthConfig();
|
|
159
|
+
const isRemote = authConfig.type === AuthType.OAUTH_XAGENT;
|
|
105
160
|
|
|
106
|
-
|
|
161
|
+
if (isRemote) {
|
|
162
|
+
// Already in remote mode, no change needed
|
|
163
|
+
if (this.remoteAIClient !== null) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Switch to remote: clear local client, create remote client
|
|
167
|
+
this.aiClient = null;
|
|
168
|
+
this.remoteAIClient = createAIClient(authConfig);
|
|
169
|
+
} else {
|
|
170
|
+
// Already in local mode, no change needed
|
|
171
|
+
if (this.aiClient !== null) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
// Switch to local: clear remote client, create local client
|
|
175
|
+
this.remoteAIClient = null;
|
|
176
|
+
this.aiClient = createAIClient(authConfig);
|
|
177
|
+
}
|
|
107
178
|
|
|
108
|
-
|
|
179
|
+
this.slashCommandHandler.setRemoteAIClient(this.remoteAIClient);
|
|
180
|
+
}
|
|
109
181
|
|
|
110
|
-
|
|
182
|
+
setExecutionMode(mode: ExecutionMode): void {
|
|
183
|
+
this.executionMode = mode;
|
|
184
|
+
}
|
|
111
185
|
|
|
112
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Set SDK mode for programmatic access.
|
|
188
|
+
* In SDK mode, output is formatted as JSON to stdout.
|
|
189
|
+
*/
|
|
190
|
+
setSdkMode(adapter: SdkOutputAdapter): void {
|
|
191
|
+
this.isSdkMode = true;
|
|
192
|
+
this.sdkOutputAdapter = adapter;
|
|
113
193
|
|
|
114
|
-
|
|
194
|
+
// Initialize SDK mode for other modules using centralized output util
|
|
195
|
+
const { initOutputMode } = require('./output-util.js');
|
|
196
|
+
initOutputMode(true, adapter);
|
|
115
197
|
|
|
116
|
-
|
|
198
|
+
// Initialize SmartApprovalEngine in SDK mode
|
|
199
|
+
this.initSmartApprovalSdkMode(adapter).catch(() => {
|
|
200
|
+
// Silently ignore errors - not critical
|
|
201
|
+
});
|
|
117
202
|
|
|
118
|
-
|
|
203
|
+
// Initialize tool registry in SDK mode (fire and forget, doesn't need to await)
|
|
204
|
+
this.initToolRegistrySdkMode(adapter).catch(() => {
|
|
205
|
+
// Silently ignore errors - tool registry init is not critical
|
|
206
|
+
});
|
|
207
|
+
}
|
|
119
208
|
|
|
120
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Initialize SmartApprovalEngine in SDK mode.
|
|
211
|
+
*/
|
|
212
|
+
private async initSmartApprovalSdkMode(adapter: SdkOutputAdapter): Promise<void> {
|
|
213
|
+
const { getSmartApprovalEngine } = await import('./smart-approval.js');
|
|
214
|
+
const approvalEngine = getSmartApprovalEngine();
|
|
215
|
+
approvalEngine.setSdkMode(true, adapter);
|
|
216
|
+
}
|
|
121
217
|
|
|
122
|
-
|
|
123
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Initialize tool registry in SDK mode.
|
|
220
|
+
*/
|
|
221
|
+
private async initToolRegistrySdkMode(adapter: SdkOutputAdapter): Promise<void> {
|
|
222
|
+
const toolRegistry = getToolRegistry();
|
|
223
|
+
await toolRegistry.setSdkMode(true, adapter);
|
|
124
224
|
}
|
|
125
225
|
|
|
126
|
-
|
|
127
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Get SDK mode status.
|
|
228
|
+
*/
|
|
229
|
+
getIsSdkMode(): boolean {
|
|
230
|
+
return this.isSdkMode;
|
|
128
231
|
}
|
|
129
232
|
|
|
130
|
-
|
|
131
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Output assistant response - handles SDK mode and normal mode differently.
|
|
235
|
+
*/
|
|
236
|
+
private outputAssistant(content: string, reasoningContent?: string): void {
|
|
237
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
238
|
+
this.sdkOutputAdapter.outputAssistant(content, reasoningContent);
|
|
239
|
+
} else {
|
|
240
|
+
const indent = this.getIndent();
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log(`${indent}${colors.primaryBright(`${icons.robot} Assistant:`)}`);
|
|
243
|
+
console.log(
|
|
244
|
+
`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
|
|
245
|
+
);
|
|
246
|
+
console.log('');
|
|
247
|
+
const renderedContent = renderMarkdown(
|
|
248
|
+
content,
|
|
249
|
+
(process.stdout.columns || 80) - indent.length * 2
|
|
250
|
+
);
|
|
251
|
+
console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
|
|
252
|
+
console.log('');
|
|
253
|
+
}
|
|
132
254
|
}
|
|
133
255
|
|
|
134
256
|
/**
|
|
135
257
|
* Update system prompt to reflect MCP changes (called after add/remove MCP)
|
|
136
258
|
*/
|
|
137
259
|
async updateSystemPrompt(): Promise<void> {
|
|
260
|
+
// Reload skills to pick up any newly added/removed skills
|
|
261
|
+
// First reset the SkillLoader to clear all cached skills
|
|
262
|
+
const { resetSkillLoader } = await import('./skill-loader.js');
|
|
263
|
+
resetSkillLoader();
|
|
264
|
+
|
|
265
|
+
// Then reload the skill invoker
|
|
266
|
+
const skillInvoker = (await import('./skill-invoker.js')).getSkillInvoker();
|
|
267
|
+
await skillInvoker.reload();
|
|
268
|
+
|
|
138
269
|
const toolRegistry = getToolRegistry();
|
|
139
|
-
const promptGenerator = new SystemPromptGenerator(
|
|
270
|
+
const promptGenerator = new SystemPromptGenerator(
|
|
271
|
+
toolRegistry,
|
|
272
|
+
this.executionMode,
|
|
273
|
+
undefined,
|
|
274
|
+
this.mcpManager
|
|
275
|
+
);
|
|
140
276
|
|
|
141
277
|
// Use the current agent's original system prompt as base
|
|
142
|
-
const baseSystemPrompt =
|
|
278
|
+
const baseSystemPrompt =
|
|
279
|
+
this.currentAgent?.systemPrompt || 'You are xAgent, an AI-powered CLI tool.';
|
|
143
280
|
const newSystemPrompt = await promptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
144
281
|
|
|
145
282
|
// Replace old system prompt with new one
|
|
146
|
-
this.conversation = this.conversation.filter(msg => msg.role !== 'system');
|
|
283
|
+
this.conversation = this.conversation.filter((msg) => msg.role !== 'system');
|
|
147
284
|
this.conversation.unshift({
|
|
148
285
|
role: 'system',
|
|
149
286
|
content: newSystemPrompt,
|
|
150
|
-
timestamp: Date.now()
|
|
287
|
+
timestamp: Date.now(),
|
|
151
288
|
});
|
|
152
289
|
|
|
153
290
|
// Sync to slashCommandHandler
|
|
154
291
|
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
155
292
|
}
|
|
156
293
|
|
|
157
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Watch for skill updates from CLI and update system prompt accordingly
|
|
296
|
+
*/
|
|
297
|
+
private startSkillUpdateWatcher(): void {
|
|
298
|
+
const SKILL_STATE_FILE = '.skill-state.json';
|
|
299
|
+
const configManager = getConfigManager();
|
|
300
|
+
const userSkillsPath = configManager.getUserSkillsPath();
|
|
301
|
+
const stateFilePath = userSkillsPath
|
|
302
|
+
? path.join(userSkillsPath, SKILL_STATE_FILE)
|
|
303
|
+
: null;
|
|
304
|
+
|
|
305
|
+
if (!stateFilePath) {
|
|
306
|
+
return; // No user skills path configured
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let lastUpdateTime = 0;
|
|
310
|
+
|
|
311
|
+
// Initialize with current state to avoid triggering update on first check
|
|
312
|
+
try {
|
|
313
|
+
const { existsSync, readFileSync } = require('fs');
|
|
314
|
+
if (existsSync(stateFilePath)) {
|
|
315
|
+
const content = readFileSync(stateFilePath, 'utf-8');
|
|
316
|
+
const state = JSON.parse(content);
|
|
317
|
+
lastUpdateTime = state.lastSkillUpdate || 0;
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
// Silent fail
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check for updates every 2 seconds
|
|
324
|
+
const checkInterval = setInterval(async () => {
|
|
325
|
+
try {
|
|
326
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
327
|
+
if (existsSync(stateFilePath)) {
|
|
328
|
+
const content = readFileSync(stateFilePath, 'utf-8');
|
|
329
|
+
const state = JSON.parse(content);
|
|
330
|
+
|
|
331
|
+
if (state.lastSkillUpdate && state.lastSkillUpdate > lastUpdateTime) {
|
|
332
|
+
lastUpdateTime = state.lastSkillUpdate;
|
|
333
|
+
|
|
334
|
+
// Update system prompt with new skills
|
|
335
|
+
await this.updateSystemPrompt();
|
|
336
|
+
|
|
337
|
+
if (!this.isSdkMode) {
|
|
338
|
+
console.log(colors.textMuted(' 🔄 Skills updated from CLI'));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
// Silent fail - watcher is optional
|
|
344
|
+
}
|
|
345
|
+
}, 2000);
|
|
346
|
+
|
|
347
|
+
// Clean up on session end
|
|
348
|
+
(this as any)._skillWatcherInterval = checkInterval;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
setAgent(agent: AgentConfig): void {
|
|
158
352
|
this.currentAgent = agent;
|
|
159
353
|
}
|
|
160
354
|
|
|
161
|
-
async start(): Promise<void> {
|
|
355
|
+
async start(initializedCount: number = 0): Promise<void> {
|
|
162
356
|
// Set this session as the singleton for access from other modules
|
|
163
357
|
setSingletonSession(this);
|
|
164
358
|
|
|
165
359
|
// Initialize taskId for GUI operations
|
|
166
360
|
this.currentTaskId = crypto.randomUUID();
|
|
167
361
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
362
|
+
const _separator = icons.separator.repeat(60);
|
|
363
|
+
|
|
364
|
+
if (!this.isSdkMode) {
|
|
365
|
+
// Normal mode: show ASCII art welcome
|
|
366
|
+
console.log('');
|
|
367
|
+
console.log(colors.gradient('╔════════════════════════════════════════════════════════════╗'));
|
|
368
|
+
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
369
|
+
console.log(
|
|
370
|
+
colors.gradient('║') +
|
|
371
|
+
' '.repeat(13) +
|
|
372
|
+
'🤖 ' +
|
|
373
|
+
colors.gradient('XAGENT CLI') +
|
|
374
|
+
' '.repeat(32) +
|
|
375
|
+
colors.gradient(' ║')
|
|
376
|
+
);
|
|
377
|
+
console.log(
|
|
378
|
+
colors.gradient('║') +
|
|
379
|
+
' '.repeat(16) +
|
|
380
|
+
colors.textMuted(`v${packageJson.version}`) +
|
|
381
|
+
' '.repeat(36) +
|
|
382
|
+
colors.gradient(' ║')
|
|
383
|
+
);
|
|
384
|
+
console.log(colors.gradient('║') + ' '.repeat(58) + colors.gradient(' ║'));
|
|
385
|
+
console.log(colors.gradient('╚════════════════════════════════════════════════════════════╝'));
|
|
386
|
+
console.log(colors.textMuted(' AI-powered command-line assistant'));
|
|
387
|
+
|
|
388
|
+
// Show initialization message if skills were initialized
|
|
389
|
+
if (initializedCount > 0) {
|
|
390
|
+
console.log(colors.textMuted(` ✨ Initialized ${initializedCount} built-in skills`));
|
|
391
|
+
}
|
|
392
|
+
console.log('');
|
|
393
|
+
}
|
|
178
394
|
|
|
179
395
|
await this.initialize();
|
|
180
396
|
this.showWelcomeMessage();
|
|
181
397
|
|
|
398
|
+
// Start watching for skill updates from CLI
|
|
399
|
+
this.startSkillUpdateWatcher();
|
|
400
|
+
|
|
401
|
+
// SDK 模式初始化:设置输出适配器
|
|
402
|
+
if (this.isSdkMode) {
|
|
403
|
+
// Start heartbeat timeout monitoring in SDK mode
|
|
404
|
+
this.startHeartbeatMonitoring();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Set up ESC key handler using the terminal module
|
|
408
|
+
// This avoids conflicts with readline and provides clean ESC detection
|
|
409
|
+
let _escCleanup: (() => void) | undefined;
|
|
410
|
+
if (process.stdin.isTTY) {
|
|
411
|
+
_escCleanup = setupEscKeyHandler(() => {
|
|
412
|
+
if ((this as any)._isOperationInProgress) {
|
|
413
|
+
// An operation is running, let it be cancelled
|
|
414
|
+
this.cancellationManager.cancel();
|
|
415
|
+
}
|
|
416
|
+
// No operation running, ignore ESC
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
182
420
|
// Track if an operation is in progress
|
|
183
421
|
(this as any)._isOperationInProgress = false;
|
|
184
422
|
|
|
185
|
-
// Listen for ESC cancellation - only cancel operations, don't exit the program
|
|
186
|
-
const cancelHandler = () => {
|
|
187
|
-
if ((this as any)._isOperationInProgress) {
|
|
188
|
-
// An operation is running, let it be cancelled
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
// No operation running, ignore ESC or show a message
|
|
192
|
-
};
|
|
193
|
-
this.cancellationManager.on('cancelled', cancelHandler);
|
|
194
|
-
|
|
195
423
|
this.promptLoop();
|
|
196
424
|
|
|
197
425
|
// Keep the promise pending until shutdown
|
|
@@ -204,17 +432,17 @@ export class InteractiveSession {
|
|
|
204
432
|
logger.debug('\n[SESSION] ========== initialize() 开始 ==========\n');
|
|
205
433
|
|
|
206
434
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
435
|
+
// Custom spinner for initialization (like Thinking...)
|
|
436
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
437
|
+
let frameIndex = 0;
|
|
438
|
+
const validatingText = colors.textMuted('Validating authentication...');
|
|
439
|
+
|
|
440
|
+
const spinnerInterval = setInterval(() => {
|
|
441
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${validatingText}`);
|
|
442
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
443
|
+
}, 120);
|
|
216
444
|
logger.debug('[SESSION] 调用 configManager.load()...');
|
|
217
|
-
|
|
445
|
+
this.configManager.load();
|
|
218
446
|
|
|
219
447
|
logger.debug('[SESSION] Config loaded');
|
|
220
448
|
let authConfig = this.configManager.getAuthConfig();
|
|
@@ -224,13 +452,16 @@ export class InteractiveSession {
|
|
|
224
452
|
logger.debug('[SESSION] selectedAuthType (initial):', String(selectedAuthType));
|
|
225
453
|
logger.debug('[SESSION] AuthType.OAUTH_XAGENT:', String(AuthType.OAUTH_XAGENT));
|
|
226
454
|
logger.debug('[SESSION] AuthType.OPENAI_COMPATIBLE:', String(AuthType.OPENAI_COMPATIBLE));
|
|
227
|
-
logger.debug(
|
|
455
|
+
logger.debug(
|
|
456
|
+
'[SESSION] Will validate OAuth:',
|
|
457
|
+
String(!!(authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT))
|
|
458
|
+
);
|
|
228
459
|
|
|
229
460
|
// Only validate OAuth tokens, skip validation for third-party API keys
|
|
230
461
|
if (authConfig.apiKey && selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
231
462
|
clearInterval(spinnerInterval);
|
|
232
463
|
process.stdout.write('\r' + ' '.repeat(50) + '\r'); // Clear the line
|
|
233
|
-
|
|
464
|
+
|
|
234
465
|
const baseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
|
|
235
466
|
let isValid = await this.validateToken(baseUrl, authConfig.apiKey);
|
|
236
467
|
|
|
@@ -242,15 +473,14 @@ export class InteractiveSession {
|
|
|
242
473
|
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${refreshingText}`);
|
|
243
474
|
frameIndex = (frameIndex + 1) % frames.length;
|
|
244
475
|
}, 120);
|
|
245
|
-
|
|
476
|
+
|
|
246
477
|
const newToken = await this.refreshToken(baseUrl, authConfig.refreshToken);
|
|
247
478
|
clearInterval(refreshInterval);
|
|
248
479
|
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
249
480
|
|
|
250
481
|
if (newToken) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
await this.configManager.save('global');
|
|
482
|
+
this.configManager.set('apiKey', newToken);
|
|
483
|
+
this.configManager.save('global');
|
|
254
484
|
authConfig.apiKey = newToken;
|
|
255
485
|
isValid = true;
|
|
256
486
|
}
|
|
@@ -262,23 +492,21 @@ export class InteractiveSession {
|
|
|
262
492
|
console.log(colors.info('Please select an authentication method to continue.'));
|
|
263
493
|
console.log('');
|
|
264
494
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
await this.configManager.set('refreshToken', '');
|
|
269
|
-
await this.configManager.save('global');
|
|
495
|
+
this.configManager.set('apiKey', '');
|
|
496
|
+
this.configManager.set('refreshToken', '');
|
|
497
|
+
this.configManager.save('global');
|
|
270
498
|
|
|
271
|
-
|
|
499
|
+
this.configManager.load();
|
|
272
500
|
authConfig = this.configManager.getAuthConfig();
|
|
273
501
|
|
|
274
502
|
await this.setupAuthentication();
|
|
275
503
|
authConfig = this.configManager.getAuthConfig();
|
|
276
504
|
|
|
277
|
-
// Recreate readline interface after
|
|
505
|
+
// Recreate readline interface after interactive prompt
|
|
278
506
|
this.rl.close();
|
|
279
507
|
this.rl = readline.createInterface({
|
|
280
508
|
input: process.stdin,
|
|
281
|
-
output: process.stdout
|
|
509
|
+
output: process.stdout,
|
|
282
510
|
});
|
|
283
511
|
this.rl.on('close', () => {
|
|
284
512
|
// readline closed
|
|
@@ -293,11 +521,11 @@ export class InteractiveSession {
|
|
|
293
521
|
selectedAuthType = this.configManager.get('selectedAuthType');
|
|
294
522
|
logger.debug('[SESSION] selectedAuthType (after setup):', String(selectedAuthType));
|
|
295
523
|
|
|
296
|
-
// Recreate readline interface after
|
|
524
|
+
// Recreate readline interface after interactive prompt
|
|
297
525
|
this.rl.close();
|
|
298
526
|
this.rl = readline.createInterface({
|
|
299
527
|
input: process.stdin,
|
|
300
|
-
output: process.stdout
|
|
528
|
+
output: process.stdout,
|
|
301
529
|
});
|
|
302
530
|
this.rl.on('close', () => {
|
|
303
531
|
// readline closed
|
|
@@ -308,22 +536,46 @@ export class InteractiveSession {
|
|
|
308
536
|
}
|
|
309
537
|
// For OPENAI_COMPATIBLE with API key, skip validation and proceed directly
|
|
310
538
|
|
|
311
|
-
|
|
312
|
-
this.contextCompressor.setAIClient(this.aiClient);
|
|
313
|
-
|
|
314
|
-
// Initialize remote AI client for OAuth XAGENT mode
|
|
315
|
-
logger.debug('[SESSION] Final selectedAuthType:', String(selectedAuthType));
|
|
316
|
-
logger.debug('[SESSION] Creating RemoteAIClient?', String(selectedAuthType === AuthType.OAUTH_XAGENT));
|
|
539
|
+
// Initialize AI clients and set contextCompressor appropriately
|
|
317
540
|
if (selectedAuthType === AuthType.OAUTH_XAGENT) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
541
|
+
// Remote mode: fetch default models if not set
|
|
542
|
+
const currentLlm = this.configManager.get('remote_llmModelName');
|
|
543
|
+
const currentVlm = this.configManager.get('remote_vlmModelName');
|
|
544
|
+
|
|
545
|
+
if (!currentLlm || !currentVlm) {
|
|
546
|
+
const webBaseUrl = authConfig.xagentApiBaseUrl || 'https://www.xagent-colife.net';
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const defaults = await fetchDefaultModels(authConfig.apiKey || '', webBaseUrl);
|
|
550
|
+
|
|
551
|
+
if (!currentLlm && defaults.llm?.name) {
|
|
552
|
+
this.configManager.set('remote_llmModelName', defaults.llm.name);
|
|
553
|
+
}
|
|
554
|
+
if (!currentVlm && defaults.vlm?.name) {
|
|
555
|
+
this.configManager.set('remote_vlmModelName', defaults.vlm.name);
|
|
556
|
+
}
|
|
557
|
+
this.configManager.save('global');
|
|
558
|
+
} catch (error: any) {
|
|
559
|
+
logger.debug('[SESSION] Failed to fetch default models:', error.message);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Remote mode: create RemoteAIClient and use it for context compression
|
|
564
|
+
this.remoteAIClient = createAIClient(authConfig);
|
|
565
|
+
this.contextCompressor.setAIClient(this.remoteAIClient);
|
|
321
566
|
logger.debug('[DEBUG Initialize] RemoteAIClient created successfully');
|
|
322
567
|
} else {
|
|
568
|
+
// Local mode: create local AIClient
|
|
569
|
+
this.aiClient = createAIClient(authConfig);
|
|
570
|
+
this.contextCompressor.setAIClient(this.aiClient);
|
|
323
571
|
logger.debug('[DEBUG Initialize] RemoteAIClient NOT created (not OAuth XAGENT mode)');
|
|
324
572
|
}
|
|
325
573
|
|
|
326
|
-
|
|
574
|
+
// Sync remoteAIClient reference to slashCommandHandler for /provider command
|
|
575
|
+
this.slashCommandHandler.setRemoteAIClient(this.remoteAIClient);
|
|
576
|
+
|
|
577
|
+
this.executionMode =
|
|
578
|
+
this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
327
579
|
|
|
328
580
|
await this.agentManager.loadAgents();
|
|
329
581
|
await this.memoryManager.loadMemory();
|
|
@@ -343,25 +595,49 @@ export class InteractiveSession {
|
|
|
343
595
|
|
|
344
596
|
const mcpServers = this.configManager.getMcpServers();
|
|
345
597
|
Object.entries(mcpServers).forEach(([name, config]) => {
|
|
346
|
-
|
|
598
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
599
|
+
this.sdkOutputAdapter.outputMCPRegistering(name, config.transport || 'stdio');
|
|
600
|
+
} else {
|
|
601
|
+
console.log(`📝 Registering MCP server: ${name} (${config.transport})`);
|
|
602
|
+
}
|
|
347
603
|
this.mcpManager.registerServer(name, config);
|
|
348
604
|
});
|
|
349
605
|
|
|
350
606
|
// Eagerly connect to MCP servers to get tool definitions
|
|
351
607
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
352
608
|
try {
|
|
353
|
-
|
|
609
|
+
const serverCount = Object.keys(mcpServers).length;
|
|
610
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
611
|
+
this.sdkOutputAdapter.outputMCPLoading(serverCount);
|
|
612
|
+
} else {
|
|
613
|
+
console.log(
|
|
614
|
+
`${colors.info(`${icons.brain} Connecting to ${serverCount} MCP server(s)...`)}`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
354
617
|
await this.mcpManager.connectAllServers();
|
|
355
|
-
const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s: any) =>
|
|
618
|
+
const connectedCount = Array.from(this.mcpManager.getAllServers()).filter((s: any) =>
|
|
619
|
+
s.isServerConnected()
|
|
620
|
+
).length;
|
|
356
621
|
const mcpTools = this.mcpManager.getToolDefinitions();
|
|
357
|
-
|
|
622
|
+
|
|
623
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
624
|
+
this.sdkOutputAdapter.outputMCPConnected(serverCount, connectedCount, mcpTools.length);
|
|
625
|
+
} else {
|
|
626
|
+
console.log(
|
|
627
|
+
`${colors.success(`✓ ${connectedCount}/${serverCount} MCP server(s) connected (${mcpTools.length} tools available)`)}`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
358
630
|
|
|
359
631
|
// Register MCP tools with the tool registry (hide MCP origin from LLM)
|
|
360
632
|
const toolRegistry = getToolRegistry();
|
|
361
633
|
const allMcpTools = this.mcpManager.getAllTools();
|
|
362
634
|
toolRegistry.registerMCPTools(allMcpTools);
|
|
363
635
|
} catch (error: any) {
|
|
364
|
-
|
|
636
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
637
|
+
this.sdkOutputAdapter.outputMCPConnectionFailed(error.message);
|
|
638
|
+
} else {
|
|
639
|
+
console.log(`${colors.warning(`⚠ MCP connection failed: ${error.message}`)}`);
|
|
640
|
+
}
|
|
365
641
|
}
|
|
366
642
|
}
|
|
367
643
|
|
|
@@ -375,9 +651,13 @@ export class InteractiveSession {
|
|
|
375
651
|
await this.checkpointManager.initialize();
|
|
376
652
|
}
|
|
377
653
|
|
|
378
|
-
this.currentAgent = this.agentManager.getAgent('general-purpose');
|
|
654
|
+
this.currentAgent = this.agentManager.getAgent('general-purpose') ?? null;
|
|
379
655
|
|
|
380
|
-
|
|
656
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
657
|
+
this.sdkOutputAdapter.outputSuccess('Initialization complete');
|
|
658
|
+
} else {
|
|
659
|
+
console.log(colors.success('✔ Initialization complete'));
|
|
660
|
+
}
|
|
381
661
|
} catch (error: any) {
|
|
382
662
|
const spinner = ora({ text: '', spinner: 'dots', color: 'red' }).start();
|
|
383
663
|
spinner.fail(colors.error(`Initialization failed: ${error.message}`));
|
|
@@ -392,7 +672,7 @@ export class InteractiveSession {
|
|
|
392
672
|
private async validateToken(baseUrl: string, apiKey: string): Promise<boolean> {
|
|
393
673
|
logger.debug('[SESSION] validateToken called with baseUrl:', baseUrl);
|
|
394
674
|
logger.debug('[SESSION] apiKey exists:', apiKey ? 'yes' : 'no');
|
|
395
|
-
|
|
675
|
+
|
|
396
676
|
try {
|
|
397
677
|
// For OAuth XAGENT auth, use /api/auth/me endpoint
|
|
398
678
|
const url = `${baseUrl}/api/auth/me`;
|
|
@@ -402,11 +682,11 @@ export class InteractiveSession {
|
|
|
402
682
|
|
|
403
683
|
const response = await axios.get(url, {
|
|
404
684
|
headers: {
|
|
405
|
-
|
|
406
|
-
'Content-Type': 'application/json'
|
|
685
|
+
Authorization: `Bearer ${apiKey}`,
|
|
686
|
+
'Content-Type': 'application/json',
|
|
407
687
|
},
|
|
408
688
|
httpsAgent,
|
|
409
|
-
timeout: 10000
|
|
689
|
+
timeout: 10000,
|
|
410
690
|
});
|
|
411
691
|
|
|
412
692
|
logger.debug('[SESSION] Validation response status:', String(response.status));
|
|
@@ -428,10 +708,14 @@ export class InteractiveSession {
|
|
|
428
708
|
const url = `${baseUrl}/api/auth/refresh`;
|
|
429
709
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
430
710
|
|
|
431
|
-
const response = await axios.post(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
711
|
+
const response = await axios.post(
|
|
712
|
+
url,
|
|
713
|
+
{ refreshToken },
|
|
714
|
+
{
|
|
715
|
+
httpsAgent,
|
|
716
|
+
timeout: 10000,
|
|
717
|
+
}
|
|
718
|
+
);
|
|
435
719
|
|
|
436
720
|
if (response.status === 200) {
|
|
437
721
|
const data = response.data as { token?: string; refreshToken?: string };
|
|
@@ -439,7 +723,7 @@ export class InteractiveSession {
|
|
|
439
723
|
} else {
|
|
440
724
|
return null;
|
|
441
725
|
}
|
|
442
|
-
} catch
|
|
726
|
+
} catch {
|
|
443
727
|
return null;
|
|
444
728
|
}
|
|
445
729
|
}
|
|
@@ -454,11 +738,15 @@ export class InteractiveSession {
|
|
|
454
738
|
const authType = await selectAuthType();
|
|
455
739
|
this.configManager.set('selectedAuthType', authType);
|
|
456
740
|
|
|
741
|
+
// Get xagentApiBaseUrl from config (respects XAGENT_BASE_URL env var)
|
|
742
|
+
const config = this.configManager.getAuthConfig();
|
|
743
|
+
|
|
457
744
|
const authService = new AuthService({
|
|
458
745
|
type: authType,
|
|
459
746
|
apiKey: '',
|
|
460
747
|
baseUrl: '',
|
|
461
|
-
modelName: ''
|
|
748
|
+
modelName: '',
|
|
749
|
+
xagentApiBaseUrl: config.xagentApiBaseUrl,
|
|
462
750
|
});
|
|
463
751
|
|
|
464
752
|
const success = await authService.authenticate();
|
|
@@ -472,37 +760,118 @@ export class InteractiveSession {
|
|
|
472
760
|
|
|
473
761
|
const authConfig = authService.getAuthConfig();
|
|
474
762
|
|
|
763
|
+
// Clear modelName for remote mode
|
|
764
|
+
if (authType === AuthType.OAUTH_XAGENT) {
|
|
765
|
+
authConfig.modelName = '';
|
|
766
|
+
}
|
|
767
|
+
|
|
475
768
|
// VLM configuration is optional - only show for non-OAuth (local) mode
|
|
476
769
|
// Remote mode uses backend VLM configuration
|
|
477
770
|
if (authType !== AuthType.OAUTH_XAGENT) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
771
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
772
|
+
this.sdkOutputAdapter.outputInfo('VLM configuration is optional. You can configure it later using the /model command if needed.');
|
|
773
|
+
} else {
|
|
774
|
+
console.log('');
|
|
775
|
+
console.log(colors.info(`${icons.info} VLM configuration is optional.`));
|
|
776
|
+
console.log(colors.info(`You can configure it later using the /model command if needed.`));
|
|
777
|
+
console.log('');
|
|
778
|
+
}
|
|
482
779
|
}
|
|
483
780
|
|
|
484
|
-
|
|
485
|
-
|
|
781
|
+
this.configManager.setAuthConfig(authConfig);
|
|
782
|
+
|
|
783
|
+
// Set default remote model settings if not already set
|
|
784
|
+
if (authType === AuthType.OAUTH_XAGENT) {
|
|
785
|
+
if (!this.configManager.get('remote_llmModelName')) {
|
|
786
|
+
this.configManager.set('remote_llmModelName', '');
|
|
787
|
+
}
|
|
788
|
+
if (!this.configManager.get('remote_vlmModelName')) {
|
|
789
|
+
this.configManager.set('remote_vlmModelName', '');
|
|
790
|
+
}
|
|
791
|
+
this.configManager.save('global');
|
|
792
|
+
}
|
|
486
793
|
}
|
|
487
794
|
|
|
488
795
|
private showWelcomeMessage(): void {
|
|
489
796
|
const language = this.configManager.getLanguage();
|
|
490
797
|
const separator = icons.separator.repeat(40);
|
|
491
798
|
|
|
492
|
-
|
|
493
|
-
|
|
799
|
+
if (!this.isSdkMode) {
|
|
800
|
+
console.log('');
|
|
801
|
+
console.log(colors.border(separator));
|
|
494
802
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
803
|
+
if (language === 'zh') {
|
|
804
|
+
console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
|
|
805
|
+
console.log(colors.textMuted('Type /help to see available commands'));
|
|
806
|
+
} else {
|
|
807
|
+
console.log(colors.primaryBright(`${icons.sparkles} Welcome to XAGENT CLI!`));
|
|
808
|
+
console.log(colors.textMuted('Type /help to see available commands'));
|
|
809
|
+
}
|
|
501
810
|
|
|
502
|
-
|
|
503
|
-
|
|
811
|
+
console.log(colors.border(separator));
|
|
812
|
+
console.log('');
|
|
813
|
+
}
|
|
504
814
|
|
|
505
815
|
this.showExecutionMode();
|
|
816
|
+
|
|
817
|
+
// In SDK mode, output ready signal
|
|
818
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
819
|
+
this.sdkOutputAdapter.outputReady();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Start heartbeat timeout monitoring in SDK mode
|
|
825
|
+
*/
|
|
826
|
+
private startHeartbeatMonitoring(): void {
|
|
827
|
+
this.stopHeartbeatMonitoring();
|
|
828
|
+
|
|
829
|
+
// Check heartbeat timeout periodically
|
|
830
|
+
this.heartbeatTimeout = setInterval(() => {
|
|
831
|
+
const elapsed = Date.now() - this.lastActivityTime;
|
|
832
|
+
|
|
833
|
+
if (elapsed > this.heartbeatTimeoutMs) {
|
|
834
|
+
// Heartbeat timeout - no activity for too long
|
|
835
|
+
this.sdkOutputAdapter?.output({
|
|
836
|
+
type: 'error',
|
|
837
|
+
subtype: 'heartbeat_timeout',
|
|
838
|
+
timestamp: Date.now(),
|
|
839
|
+
data: {
|
|
840
|
+
message: 'Heartbeat timeout - no activity detected',
|
|
841
|
+
elapsed_ms: elapsed,
|
|
842
|
+
timeout_ms: this.heartbeatTimeoutMs
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Stop monitoring
|
|
847
|
+
this.stopHeartbeatMonitoring();
|
|
848
|
+
}
|
|
849
|
+
}, 30000); // Check every 30 seconds
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Stop heartbeat timeout monitoring
|
|
854
|
+
*/
|
|
855
|
+
private stopHeartbeatMonitoring(): void {
|
|
856
|
+
if (this.heartbeatTimeout) {
|
|
857
|
+
clearInterval(this.heartbeatTimeout);
|
|
858
|
+
this.heartbeatTimeout = null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Reset heartbeat timeout (called on activity)
|
|
864
|
+
*/
|
|
865
|
+
private resetHeartbeatTimeout(): void {
|
|
866
|
+
this.lastActivityTime = Date.now();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Stop heartbeat monitoring (public method for cleanup)
|
|
871
|
+
* Used by SDK session to clean up when session ends
|
|
872
|
+
*/
|
|
873
|
+
public stopHeartbeatMonitor(): void {
|
|
874
|
+
this.stopHeartbeatMonitoring();
|
|
506
875
|
}
|
|
507
876
|
|
|
508
877
|
private showExecutionMode(): void {
|
|
@@ -510,36 +879,75 @@ export class InteractiveSession {
|
|
|
510
879
|
[ExecutionMode.YOLO]: {
|
|
511
880
|
color: colors.error,
|
|
512
881
|
icon: icons.fire,
|
|
513
|
-
description: 'Execute commands without confirmation'
|
|
882
|
+
description: 'Execute commands without confirmation',
|
|
514
883
|
},
|
|
515
884
|
[ExecutionMode.ACCEPT_EDITS]: {
|
|
516
885
|
color: colors.warning,
|
|
517
886
|
icon: icons.check,
|
|
518
|
-
description: 'Accept all edits automatically'
|
|
887
|
+
description: 'Accept all edits automatically',
|
|
519
888
|
},
|
|
520
889
|
[ExecutionMode.PLAN]: {
|
|
521
890
|
color: colors.info,
|
|
522
891
|
icon: icons.brain,
|
|
523
|
-
description: 'Plan before executing'
|
|
892
|
+
description: 'Plan before executing',
|
|
524
893
|
},
|
|
525
894
|
[ExecutionMode.DEFAULT]: {
|
|
526
895
|
color: colors.success,
|
|
527
896
|
icon: icons.bolt,
|
|
528
|
-
description: 'Safe execution with confirmations'
|
|
897
|
+
description: 'Safe execution with confirmations',
|
|
529
898
|
},
|
|
530
899
|
[ExecutionMode.SMART]: {
|
|
531
900
|
color: colors.primaryBright,
|
|
532
901
|
icon: icons.sparkles,
|
|
533
|
-
description: 'Smart approval with intelligent security checks'
|
|
534
|
-
}
|
|
902
|
+
description: 'Smart approval with intelligent security checks',
|
|
903
|
+
},
|
|
535
904
|
};
|
|
536
905
|
|
|
537
906
|
const config = modeConfig[this.executionMode];
|
|
538
907
|
const modeName = this.executionMode;
|
|
539
908
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
909
|
+
if (!this.isSdkMode) {
|
|
910
|
+
console.log(colors.textMuted(`${icons.info} Current Mode:`));
|
|
911
|
+
console.log(` ${config.color(config.icon)} ${styleHelpers.text.bold(config.color(modeName))}`);
|
|
912
|
+
console.log(` ${colors.textDim(` ${config.description}`)}`);
|
|
913
|
+
console.log('');
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
this.showRemoteModelInfo();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private showRemoteModelInfo(): void {
|
|
920
|
+
const authConfig = this.configManager.getAuthConfig();
|
|
921
|
+
const isRemote = authConfig.type === AuthType.OAUTH_XAGENT;
|
|
922
|
+
|
|
923
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
924
|
+
// SDK 模式:通过 adapter 输出
|
|
925
|
+
if (isRemote) {
|
|
926
|
+
const llmModel = authConfig.remote_llmModelName || 'Not set';
|
|
927
|
+
const vlmModel = authConfig.remote_vlmModelName || 'Not set';
|
|
928
|
+
this.sdkOutputAdapter.outputInfo(`Remote Models - LLM: ${llmModel}, VLM: ${vlmModel}`);
|
|
929
|
+
} else {
|
|
930
|
+
const modelName = authConfig.modelName || 'Not set';
|
|
931
|
+
const guiSubagentModel = this.configManager.get('guiSubagentModel') || 'Not set';
|
|
932
|
+
this.sdkOutputAdapter.outputInfo(`Local Models - LLM: ${modelName}, VLM: ${guiSubagentModel}`);
|
|
933
|
+
}
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// 正常模式:控制台输出
|
|
938
|
+
if (isRemote) {
|
|
939
|
+
const llmModel = authConfig.remote_llmModelName || colors.textMuted('Not set');
|
|
940
|
+
const vlmModel = authConfig.remote_vlmModelName || colors.textMuted('Not set');
|
|
941
|
+
console.log(colors.textMuted(`${icons.brain} Remote Models:`));
|
|
942
|
+
console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('LLM:')} ${llmModel}`);
|
|
943
|
+
console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('VLM:')} ${vlmModel}`);
|
|
944
|
+
} else {
|
|
945
|
+
const modelName = authConfig.modelName || colors.textMuted('Not set');
|
|
946
|
+
const guiSubagentModel = this.configManager.get('guiSubagentModel') || colors.textMuted('Not set');
|
|
947
|
+
console.log(colors.textMuted(`${icons.brain} Local Models:`));
|
|
948
|
+
console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('LLM:')} ${modelName}`);
|
|
949
|
+
console.log(` ${colors.primaryBright(icons.arrowRight)} ${colors.info('VLM:')} ${guiSubagentModel}`);
|
|
950
|
+
}
|
|
543
951
|
console.log('');
|
|
544
952
|
}
|
|
545
953
|
|
|
@@ -549,21 +957,24 @@ export class InteractiveSession {
|
|
|
549
957
|
return;
|
|
550
958
|
}
|
|
551
959
|
|
|
960
|
+
// In SDK mode, use a different input loop
|
|
961
|
+
if (this.isSdkMode) {
|
|
962
|
+
await this.sdkPromptLoop();
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
552
966
|
// Recreate readline interface for input
|
|
553
967
|
if (this.rl) {
|
|
554
968
|
this.rl.close();
|
|
555
969
|
}
|
|
556
970
|
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
process.stdin.resume();
|
|
562
|
-
readline.emitKeypressEvents(process.stdin);
|
|
971
|
+
// Ensure TTY is in proper state for input handling
|
|
972
|
+
// This handles any state left by @clack/prompts or other interactions
|
|
973
|
+
ensureTtySane();
|
|
563
974
|
|
|
564
975
|
this.rl = readline.createInterface({
|
|
565
976
|
input: process.stdin,
|
|
566
|
-
output: process.stdout
|
|
977
|
+
output: process.stdout,
|
|
567
978
|
});
|
|
568
979
|
|
|
569
980
|
const prompt = `${colors.primaryBright('❯')} `;
|
|
@@ -583,28 +994,392 @@ export class InteractiveSession {
|
|
|
583
994
|
}
|
|
584
995
|
|
|
585
996
|
private async handleInput(input: string): Promise<void> {
|
|
997
|
+
// Reset heartbeat timeout on any input activity
|
|
998
|
+
this.resetHeartbeatTimeout();
|
|
999
|
+
|
|
586
1000
|
const trimmedInput = input.trim();
|
|
587
1001
|
|
|
588
1002
|
if (!trimmedInput) {
|
|
589
1003
|
return;
|
|
590
1004
|
}
|
|
591
1005
|
|
|
1006
|
+
// Check for SDK JSON message format
|
|
1007
|
+
if (this.isSdkMode) {
|
|
1008
|
+
const { isSdkMessage, parseSdkMessage } = await import('./types.js');
|
|
1009
|
+
|
|
1010
|
+
if (isSdkMessage(trimmedInput)) {
|
|
1011
|
+
const sdkMessage = parseSdkMessage(trimmedInput);
|
|
1012
|
+
|
|
1013
|
+
if (sdkMessage) {
|
|
1014
|
+
if (sdkMessage.type === 'ping') {
|
|
1015
|
+
// Handle ping - respond with pong
|
|
1016
|
+
await this.handlePing(sdkMessage);
|
|
1017
|
+
return;
|
|
1018
|
+
} else if (sdkMessage.type === 'control_request') {
|
|
1019
|
+
// Handle control request
|
|
1020
|
+
await this.handleControlRequest(sdkMessage);
|
|
1021
|
+
return;
|
|
1022
|
+
} else if (sdkMessage.type === 'user') {
|
|
1023
|
+
// Store request_id for tracking
|
|
1024
|
+
this._currentRequestId = sdkMessage.request_id || null;
|
|
1025
|
+
// Handle user message from SDK
|
|
1026
|
+
await this.processUserMessage(sdkMessage.content);
|
|
1027
|
+
return;
|
|
1028
|
+
} else if (sdkMessage.type === 'approval_response') {
|
|
1029
|
+
// Handle approval response
|
|
1030
|
+
await this.handleApprovalResponse(sdkMessage);
|
|
1031
|
+
return;
|
|
1032
|
+
} else if (sdkMessage.type === 'question_response') {
|
|
1033
|
+
// Handle question response
|
|
1034
|
+
await this.handleQuestionResponse(sdkMessage);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
// Not a JSON SDK message, treat as regular text
|
|
1040
|
+
await this.processUserMessage(trimmedInput);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
592
1045
|
if (trimmedInput.startsWith('/')) {
|
|
593
1046
|
const handled = await this.slashCommandHandler.handleCommand(trimmedInput);
|
|
594
1047
|
if (handled) {
|
|
595
|
-
this.executionMode =
|
|
1048
|
+
this.executionMode =
|
|
1049
|
+
this.configManager.getApprovalMode() || this.configManager.getExecutionMode();
|
|
596
1050
|
// Sync conversation history to slashCommandHandler
|
|
597
1051
|
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
598
1052
|
}
|
|
599
1053
|
return;
|
|
600
1054
|
}
|
|
601
1055
|
|
|
602
|
-
if (trimmedInput.startsWith('$')) {
|
|
603
|
-
await this.handleSubAgentCommand(trimmedInput);
|
|
604
|
-
return;
|
|
1056
|
+
if (trimmedInput.startsWith('$')) {
|
|
1057
|
+
await this.handleSubAgentCommand(trimmedInput);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
await this.processUserMessage(trimmedInput);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* SDK prompt loop - reads input from stdin without showing prompt
|
|
1066
|
+
*/
|
|
1067
|
+
private async sdkPromptLoop(): Promise<void> {
|
|
1068
|
+
// Read input from stdin directly without outputting prompt
|
|
1069
|
+
const input = await this.readSdkInput();
|
|
1070
|
+
|
|
1071
|
+
if ((this as any)._isShuttingDown || input === null) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
try {
|
|
1076
|
+
await this.handleInput(input);
|
|
1077
|
+
} catch (err: any) {
|
|
1078
|
+
this.sdkOutputAdapter?.output({
|
|
1079
|
+
type: 'error',
|
|
1080
|
+
subtype: 'general',
|
|
1081
|
+
timestamp: Date.now(),
|
|
1082
|
+
data: { message: err.message }
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// Continue the loop
|
|
1087
|
+
this.sdkPromptLoop();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
private sdkRl: readline.Interface | null = null;
|
|
1091
|
+
private sdkInputProcessing: boolean = false;
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Check if a line is an SDK control message (approval_response, question_response)
|
|
1095
|
+
*/
|
|
1096
|
+
private isSdkControlMessage(line: string): boolean {
|
|
1097
|
+
try {
|
|
1098
|
+
const parsed = JSON.parse(line);
|
|
1099
|
+
return parsed.type === 'approval_response' || parsed.type === 'question_response';
|
|
1100
|
+
} catch {
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Process an SDK input line (returns true if processed)
|
|
1107
|
+
*/
|
|
1108
|
+
private async processSdkInputLine(line: string): Promise<boolean> {
|
|
1109
|
+
const { isSdkMessage, parseSdkMessage } = await import('./types.js');
|
|
1110
|
+
|
|
1111
|
+
if (isSdkMessage(line)) {
|
|
1112
|
+
const sdkMessage = parseSdkMessage(line);
|
|
1113
|
+
|
|
1114
|
+
if (sdkMessage) {
|
|
1115
|
+
if (sdkMessage.type === 'ping') {
|
|
1116
|
+
await this.handlePing(sdkMessage);
|
|
1117
|
+
return true;
|
|
1118
|
+
} else if (sdkMessage.type === 'control_request') {
|
|
1119
|
+
await this.handleControlRequest(sdkMessage);
|
|
1120
|
+
return true;
|
|
1121
|
+
} else if (sdkMessage.type === 'user') {
|
|
1122
|
+
this._currentRequestId = sdkMessage.request_id || null;
|
|
1123
|
+
await this.processUserMessage(sdkMessage.content);
|
|
1124
|
+
return true;
|
|
1125
|
+
} else if (sdkMessage.type === 'approval_response') {
|
|
1126
|
+
await this.handleApprovalResponse(sdkMessage);
|
|
1127
|
+
return true;
|
|
1128
|
+
} else if (sdkMessage.type === 'question_response') {
|
|
1129
|
+
await this.handleQuestionResponse(sdkMessage);
|
|
1130
|
+
return true;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Read a line of input from stdin (SDK mode)
|
|
1139
|
+
* Uses readline 'line' event for reliable stdin reading
|
|
1140
|
+
* SDK control messages (approval_response, question_response) bypass the main loop
|
|
1141
|
+
*/
|
|
1142
|
+
private readSdkInput(): Promise<string | null> {
|
|
1143
|
+
return new Promise((resolve) => {
|
|
1144
|
+
// Create readline interface if not exists
|
|
1145
|
+
if (!this.sdkRl) {
|
|
1146
|
+
this.sdkRl = readline.createInterface({
|
|
1147
|
+
input: process.stdin,
|
|
1148
|
+
crlfDelay: Infinity,
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
// Handle line events - SDK control messages bypass normal flow
|
|
1152
|
+
this.sdkRl.on('line', async (line) => {
|
|
1153
|
+
const cleanLine = line
|
|
1154
|
+
.replace(/^\uFEFF/, '')
|
|
1155
|
+
// eslint-disable-next-line no-control-regex
|
|
1156
|
+
.replace(/[\x00-\x1F\x7F-\x9F]/g, '');
|
|
1157
|
+
|
|
1158
|
+
// Check if this is an SDK control message
|
|
1159
|
+
if (this.isSdkControlMessage(cleanLine)) {
|
|
1160
|
+
// Process immediately, don't wait for main loop
|
|
1161
|
+
// We need to set a flag to prevent re-entrancy issues
|
|
1162
|
+
if (this.sdkInputProcessing) {
|
|
1163
|
+
// Already processing, queue it
|
|
1164
|
+
this.sdkInputBuffer.push(cleanLine);
|
|
1165
|
+
} else {
|
|
1166
|
+
this.sdkInputProcessing = true;
|
|
1167
|
+
try {
|
|
1168
|
+
await this.processSdkInputLine(cleanLine);
|
|
1169
|
+
} finally {
|
|
1170
|
+
this.sdkInputProcessing = false;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Regular input
|
|
1177
|
+
if (this.resolveInput) {
|
|
1178
|
+
// Immediate handler available, resolve immediately
|
|
1179
|
+
this.resolveInput(cleanLine);
|
|
1180
|
+
this.resolveInput = null;
|
|
1181
|
+
} else {
|
|
1182
|
+
// No handler available, queue the message
|
|
1183
|
+
this.sdkInputBuffer.push(cleanLine);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
// Handle close events
|
|
1188
|
+
this.sdkRl.on('close', () => {
|
|
1189
|
+
if (this.resolveInput) {
|
|
1190
|
+
this.resolveInput(null);
|
|
1191
|
+
this.resolveInput = null;
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// Handle errors
|
|
1196
|
+
this.sdkRl.on('error', () => {
|
|
1197
|
+
if (this.resolveInput) {
|
|
1198
|
+
this.resolveInput(null);
|
|
1199
|
+
this.resolveInput = null;
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Check for SDK control messages in buffer first
|
|
1205
|
+
for (let i = 0; i < this.sdkInputBuffer.length; i++) {
|
|
1206
|
+
const line = this.sdkInputBuffer[i];
|
|
1207
|
+
if (this.isSdkControlMessage(line)) {
|
|
1208
|
+
this.sdkInputBuffer.splice(i, 1);
|
|
1209
|
+
resolve(line);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
if (this.sdkInputBuffer.length > 0) {
|
|
1215
|
+
const line = this.sdkInputBuffer.shift()!;
|
|
1216
|
+
resolve(line);
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Set up the resolve callback
|
|
1221
|
+
this.resolveInput = (value: string | null) => {
|
|
1222
|
+
resolve(value);
|
|
1223
|
+
};
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Handle SDK ping messages (heartbeat)
|
|
1229
|
+
*/
|
|
1230
|
+
private async handlePing(pingMessage: any): Promise<void> {
|
|
1231
|
+
const requestId = pingMessage.request_id || `ping_${Date.now()}`;
|
|
1232
|
+
|
|
1233
|
+
// Reset activity timestamp on ping (heartbeat activity)
|
|
1234
|
+
this.lastActivityTime = Date.now();
|
|
1235
|
+
|
|
1236
|
+
// Send pong response through SDK adapter for consistency
|
|
1237
|
+
this.sdkOutputAdapter?.output({
|
|
1238
|
+
type: 'system',
|
|
1239
|
+
subtype: 'pong',
|
|
1240
|
+
timestamp: Date.now(),
|
|
1241
|
+
data: {
|
|
1242
|
+
type: 'pong',
|
|
1243
|
+
request_id: requestId,
|
|
1244
|
+
timestamp: Date.now()
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* Handle SDK control requests
|
|
1251
|
+
*/
|
|
1252
|
+
private async handleControlRequest(request: any): Promise<void> {
|
|
1253
|
+
// Update activity to prevent heartbeat timeout during control requests
|
|
1254
|
+
this.lastActivityTime = Date.now();
|
|
1255
|
+
|
|
1256
|
+
const { request_id, request: req } = request;
|
|
1257
|
+
|
|
1258
|
+
switch (req.subtype) {
|
|
1259
|
+
case 'interrupt':
|
|
1260
|
+
this.sdkOutputAdapter?.outputSystem('interrupt', { request_id });
|
|
1261
|
+
(this as any)._isShuttingDown = true;
|
|
1262
|
+
process.exit(0);
|
|
1263
|
+
break;
|
|
1264
|
+
|
|
1265
|
+
case 'set_permission_mode':
|
|
1266
|
+
{
|
|
1267
|
+
const { ExecutionMode } = await import('./types.js');
|
|
1268
|
+
const modeMap: Record<string, ExecutionMode> = {
|
|
1269
|
+
'default': ExecutionMode.DEFAULT,
|
|
1270
|
+
'acceptEdits': ExecutionMode.ACCEPT_EDITS,
|
|
1271
|
+
'plan': ExecutionMode.PLAN,
|
|
1272
|
+
'bypassPermissions': ExecutionMode.YOLO,
|
|
1273
|
+
};
|
|
1274
|
+
const mode = modeMap[req.mode] || ExecutionMode.SMART;
|
|
1275
|
+
this.executionMode = mode;
|
|
1276
|
+
this.sdkOutputAdapter?.outputSystem('permission_mode_changed', {
|
|
1277
|
+
request_id,
|
|
1278
|
+
mode: req.mode
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
break;
|
|
1282
|
+
|
|
1283
|
+
case 'set_model':
|
|
1284
|
+
this.sdkOutputAdapter?.outputSystem('model_changed', {
|
|
1285
|
+
request_id,
|
|
1286
|
+
model: req.model
|
|
1287
|
+
});
|
|
1288
|
+
break;
|
|
1289
|
+
|
|
1290
|
+
default:
|
|
1291
|
+
this.sdkOutputAdapter?.outputWarning(`Unknown control request: ${req.subtype}`);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Handle SDK approval responses
|
|
1297
|
+
*/
|
|
1298
|
+
private async handleApprovalResponse(response: {
|
|
1299
|
+
request_id: string;
|
|
1300
|
+
approved: boolean;
|
|
1301
|
+
}): Promise<void> {
|
|
1302
|
+
const { request_id, approved } = response;
|
|
1303
|
+
|
|
1304
|
+
const pending = this.approvalPromises.get(request_id);
|
|
1305
|
+
if (pending) {
|
|
1306
|
+
pending.resolve(approved);
|
|
1307
|
+
this.approvalPromises.delete(request_id);
|
|
1308
|
+
} else {
|
|
1309
|
+
this.sdkOutputAdapter?.outputWarning(`Unknown approval request ID: ${request_id}`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Handle SDK question responses
|
|
1315
|
+
*/
|
|
1316
|
+
private async handleQuestionResponse(response: {
|
|
1317
|
+
request_id: string;
|
|
1318
|
+
answers: string[];
|
|
1319
|
+
}): Promise<void> {
|
|
1320
|
+
const { request_id, answers } = response;
|
|
1321
|
+
|
|
1322
|
+
const pending = this.questionPromises.get(request_id);
|
|
1323
|
+
if (pending) {
|
|
1324
|
+
pending.resolve(answers);
|
|
1325
|
+
this.questionPromises.delete(request_id);
|
|
1326
|
+
} else {
|
|
1327
|
+
this.sdkOutputAdapter?.outputWarning(`Unknown question request ID: ${request_id}`);
|
|
605
1328
|
}
|
|
1329
|
+
}
|
|
606
1330
|
|
|
607
|
-
|
|
1331
|
+
/**
|
|
1332
|
+
* Wait for SDK approval response
|
|
1333
|
+
*/
|
|
1334
|
+
async waitForApprovalResponse(requestId: string, timeoutMs: number = 300000): Promise<boolean> {
|
|
1335
|
+
return new Promise((resolve, reject) => {
|
|
1336
|
+
const timeout = setTimeout(() => {
|
|
1337
|
+
const pending = this.approvalPromises.get(requestId);
|
|
1338
|
+
if (pending) {
|
|
1339
|
+
pending.reject(new Error('Approval request timeout'));
|
|
1340
|
+
this.approvalPromises.delete(requestId);
|
|
1341
|
+
}
|
|
1342
|
+
resolve(false);
|
|
1343
|
+
}, timeoutMs);
|
|
1344
|
+
|
|
1345
|
+
this.approvalPromises.set(requestId, {
|
|
1346
|
+
resolve: (approved: boolean) => {
|
|
1347
|
+
clearTimeout(timeout);
|
|
1348
|
+
resolve(approved);
|
|
1349
|
+
},
|
|
1350
|
+
reject: (err: Error) => {
|
|
1351
|
+
clearTimeout(timeout);
|
|
1352
|
+
reject(err);
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Wait for SDK question response
|
|
1360
|
+
*/
|
|
1361
|
+
async waitForQuestionResponse(requestId: string, timeoutMs: number = 300000): Promise<string[]> {
|
|
1362
|
+
return new Promise((resolve, reject) => {
|
|
1363
|
+
const timeout = setTimeout(() => {
|
|
1364
|
+
const pending = this.questionPromises.get(requestId);
|
|
1365
|
+
if (pending) {
|
|
1366
|
+
pending.reject(new Error('Question request timeout'));
|
|
1367
|
+
this.questionPromises.delete(requestId);
|
|
1368
|
+
}
|
|
1369
|
+
resolve([]); // Return empty array on timeout instead of rejecting
|
|
1370
|
+
}, timeoutMs);
|
|
1371
|
+
|
|
1372
|
+
this.questionPromises.set(requestId, {
|
|
1373
|
+
resolve: (answers: string[]) => {
|
|
1374
|
+
clearTimeout(timeout);
|
|
1375
|
+
resolve(answers);
|
|
1376
|
+
},
|
|
1377
|
+
reject: (err: Error) => {
|
|
1378
|
+
clearTimeout(timeout);
|
|
1379
|
+
reject(err);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
608
1383
|
}
|
|
609
1384
|
|
|
610
1385
|
private async handleSubAgentCommand(input: string): Promise<void> {
|
|
@@ -622,7 +1397,9 @@ export class InteractiveSession {
|
|
|
622
1397
|
}
|
|
623
1398
|
|
|
624
1399
|
console.log('');
|
|
625
|
-
console.log(
|
|
1400
|
+
console.log(
|
|
1401
|
+
colors.primaryBright(`${icons.robot} Using agent: ${agent.name || agent.agentType}`)
|
|
1402
|
+
);
|
|
626
1403
|
console.log(colors.border(icons.separator.repeat(40)));
|
|
627
1404
|
console.log('');
|
|
628
1405
|
|
|
@@ -630,11 +1407,11 @@ export class InteractiveSession {
|
|
|
630
1407
|
await this.processUserMessage(task, agent);
|
|
631
1408
|
}
|
|
632
1409
|
|
|
633
|
-
public async processUserMessage(message: string,
|
|
634
|
-
const inputs = parseInput(message);
|
|
635
|
-
const textInput = inputs.find(i => i.type === 'text');
|
|
636
|
-
const fileInputs = inputs.filter(i => i.type === 'file');
|
|
637
|
-
const commandInput = inputs.find(i => i.type === 'command');
|
|
1410
|
+
public async processUserMessage(message: string, _agent?: AgentConfig): Promise<void> {
|
|
1411
|
+
const inputs = await parseInput(message);
|
|
1412
|
+
const textInput = inputs.find((i) => i.type === 'text');
|
|
1413
|
+
const fileInputs = inputs.filter((i) => i.type === 'file');
|
|
1414
|
+
const commandInput = inputs.find((i) => i.type === 'command');
|
|
638
1415
|
|
|
639
1416
|
if (commandInput) {
|
|
640
1417
|
await this.executeShellCommand(commandInput.content);
|
|
@@ -647,10 +1424,16 @@ export class InteractiveSession {
|
|
|
647
1424
|
const toolRegistry = getToolRegistry();
|
|
648
1425
|
for (const fileInput of fileInputs) {
|
|
649
1426
|
try {
|
|
650
|
-
const content = await toolRegistry.execute(
|
|
1427
|
+
const content = await toolRegistry.execute(
|
|
1428
|
+
'Read',
|
|
1429
|
+
{ filePath: fileInput.content },
|
|
1430
|
+
this.executionMode
|
|
1431
|
+
);
|
|
651
1432
|
userContent += `\n\n--- File: ${fileInput.content} ---\n${content}`;
|
|
652
1433
|
} catch (error: any) {
|
|
653
|
-
console.log(
|
|
1434
|
+
console.log(
|
|
1435
|
+
chalk.yellow(`Warning: Failed to read file ${fileInput.content}: ${error.message}`)
|
|
1436
|
+
);
|
|
654
1437
|
}
|
|
655
1438
|
}
|
|
656
1439
|
}
|
|
@@ -660,7 +1443,7 @@ export class InteractiveSession {
|
|
|
660
1443
|
type: 'text' as const,
|
|
661
1444
|
content: userContent,
|
|
662
1445
|
rawInput: message,
|
|
663
|
-
timestamp: Date.now()
|
|
1446
|
+
timestamp: Date.now(),
|
|
664
1447
|
};
|
|
665
1448
|
await this.sessionManager.addInput(sessionInput);
|
|
666
1449
|
|
|
@@ -677,29 +1460,23 @@ export class InteractiveSession {
|
|
|
677
1460
|
const userMessage: ChatMessage = {
|
|
678
1461
|
role: 'user',
|
|
679
1462
|
content: userContent,
|
|
680
|
-
timestamp: Date.now()
|
|
1463
|
+
timestamp: Date.now(),
|
|
681
1464
|
};
|
|
682
1465
|
|
|
683
|
-
// Save last user message for recovery after compression
|
|
684
|
-
const lastUserMessage = userMessage;
|
|
685
|
-
|
|
686
1466
|
this.conversation.push(userMessage);
|
|
687
1467
|
await this.conversationManager.addMessage(userMessage);
|
|
688
1468
|
|
|
689
|
-
// Check if context compression is needed
|
|
690
|
-
await this.checkAndCompressContext(lastUserMessage);
|
|
691
|
-
|
|
692
1469
|
// Use remote AI client if available (OAuth XAGENT mode)
|
|
693
1470
|
const currentSelectedAuthType = this.configManager.get('selectedAuthType');
|
|
694
|
-
logger.debug(
|
|
695
|
-
|
|
696
|
-
|
|
1471
|
+
logger.debug(
|
|
1472
|
+
`[DEBUG] processUserMessage: remoteAIClient exists=${!!this.remoteAIClient}, selectedAuthType=${currentSelectedAuthType}`
|
|
1473
|
+
);
|
|
697
1474
|
|
|
698
1475
|
if (this.remoteAIClient) {
|
|
699
|
-
logger.debug('[DEBUG
|
|
1476
|
+
logger.debug('[DEBUG] Using generateRemoteResponse (remote mode)');
|
|
700
1477
|
await this.generateRemoteResponse(thinkingTokens);
|
|
701
1478
|
} else {
|
|
702
|
-
logger.debug('[DEBUG
|
|
1479
|
+
logger.debug('[DEBUG] Using generateResponse (local mode)');
|
|
703
1480
|
await this.generateResponse(thinkingTokens);
|
|
704
1481
|
}
|
|
705
1482
|
}
|
|
@@ -709,7 +1486,15 @@ export class InteractiveSession {
|
|
|
709
1486
|
const thinkingConfig = this.configManager.getThinkingConfig();
|
|
710
1487
|
const displayMode = thinkingConfig.displayMode || 'compact';
|
|
711
1488
|
|
|
712
|
-
|
|
1489
|
+
// SDK 模式:使用 adapter 输出
|
|
1490
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
1491
|
+
this.sdkOutputAdapter.outputThinking(reasoningContent, displayMode);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const separator = icons.separator.repeat(
|
|
1496
|
+
Math.min(60, process.stdout.columns || 80) - indent.length
|
|
1497
|
+
);
|
|
713
1498
|
|
|
714
1499
|
console.log('');
|
|
715
1500
|
console.log(`${indent}${colors.border(separator)}`);
|
|
@@ -722,23 +1507,27 @@ export class InteractiveSession {
|
|
|
722
1507
|
console.log(`${indent}${colors.textDim(reasoningContent.replace(/^/gm, indent))}`);
|
|
723
1508
|
break;
|
|
724
1509
|
|
|
725
|
-
case 'compact':
|
|
1510
|
+
case 'compact': {
|
|
726
1511
|
// Compact display, truncate partial content
|
|
727
1512
|
const maxLength = 500;
|
|
728
|
-
const truncatedContent =
|
|
729
|
-
|
|
730
|
-
|
|
1513
|
+
const truncatedContent =
|
|
1514
|
+
reasoningContent.length > maxLength
|
|
1515
|
+
? reasoningContent.substring(0, maxLength) + '... (truncated)'
|
|
1516
|
+
: reasoningContent;
|
|
731
1517
|
|
|
732
1518
|
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking Process:`)}`);
|
|
733
1519
|
console.log('');
|
|
734
1520
|
console.log(`${indent}${colors.textDim(truncatedContent.replace(/^/gm, indent))}`);
|
|
735
1521
|
console.log(`${indent}${colors.textDim(`[${reasoningContent.length} chars total]`)}`);
|
|
736
1522
|
break;
|
|
1523
|
+
}
|
|
737
1524
|
|
|
738
1525
|
case 'indicator':
|
|
739
1526
|
// Show indicator only
|
|
740
1527
|
console.log(`${indent}${colors.textDim(`${icons.brain} Thinking process completed`)}`);
|
|
741
|
-
console.log(
|
|
1528
|
+
console.log(
|
|
1529
|
+
`${indent}${colors.textDim(`[${reasoningContent.length} chars of reasoning]`)}`
|
|
1530
|
+
);
|
|
742
1531
|
break;
|
|
743
1532
|
|
|
744
1533
|
default:
|
|
@@ -754,70 +1543,131 @@ export class InteractiveSession {
|
|
|
754
1543
|
/**
|
|
755
1544
|
* Check and compress conversation context
|
|
756
1545
|
*/
|
|
757
|
-
private async checkAndCompressContext(
|
|
1546
|
+
private async checkAndCompressContext(): Promise<void> {
|
|
758
1547
|
const compressionConfig = this.configManager.getContextCompressionConfig();
|
|
759
1548
|
|
|
760
1549
|
if (!compressionConfig.enabled) {
|
|
761
1550
|
return;
|
|
762
1551
|
}
|
|
763
1552
|
|
|
764
|
-
const
|
|
1553
|
+
const indent = this.getIndent();
|
|
1554
|
+
const _currentTokens = this.contextCompressor.estimateContextTokens(this.conversation);
|
|
1555
|
+
const currentMessages = this.conversation.length;
|
|
1556
|
+
const { needsCompression, reason, tokenCount } = this.contextCompressor.needsCompression(
|
|
765
1557
|
this.conversation,
|
|
766
1558
|
compressionConfig
|
|
767
1559
|
);
|
|
768
1560
|
|
|
769
|
-
if (needsCompression) {
|
|
770
|
-
|
|
1561
|
+
if (!needsCompression) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// Extract threshold and contextWindow from reason
|
|
1566
|
+
const thresholdMatch = reason.match(/budget\s*\((\d+)/);
|
|
1567
|
+
const contextWindowMatch = reason.match(/contextWindow:\s*(\d+)/);
|
|
1568
|
+
const threshold = thresholdMatch ? parseInt(thresholdMatch[1], 10) : 0;
|
|
1569
|
+
const contextWindow = contextWindowMatch ? parseInt(contextWindowMatch[1], 10) : 0;
|
|
1570
|
+
|
|
1571
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
1572
|
+
this.sdkOutputAdapter.outputContextCompressionTriggered(reason);
|
|
1573
|
+
} else {
|
|
771
1574
|
console.log('');
|
|
772
|
-
console.log(
|
|
1575
|
+
console.log(
|
|
1576
|
+
`${indent}${colors.success(`${icons.sparkles} Compressing context (${currentMessages} msgs, ${tokenCount.toLocaleString()} > ${threshold.toLocaleString()}/${contextWindow.toLocaleString()} tokens, ${Math.round((tokenCount / contextWindow) * 100)}% of context window)...`)}`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
773
1579
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1580
|
+
const toolRegistry = getToolRegistry();
|
|
1581
|
+
const baseSystemPrompt = this.currentAgent?.systemPrompt || 'You are a helpful AI assistant.';
|
|
1582
|
+
const systemPromptGenerator = new SystemPromptGenerator(toolRegistry, this.executionMode);
|
|
1583
|
+
const enhancedSystemPrompt =
|
|
1584
|
+
await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
1585
|
+
|
|
1586
|
+
const result: CompressionResult = await this.contextCompressor.compressContext(
|
|
1587
|
+
this.conversation,
|
|
1588
|
+
enhancedSystemPrompt,
|
|
1589
|
+
compressionConfig
|
|
1590
|
+
);
|
|
1591
|
+
|
|
1592
|
+
if (result.wasCompressed) {
|
|
1593
|
+
this.conversation = result.compressedMessages;
|
|
1594
|
+
const reductionPercent = Math.round((1 - result.compressedSize / result.originalSize) * 100);
|
|
1595
|
+
|
|
1596
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
1597
|
+
this.sdkOutputAdapter.outputContextCompressionResult(
|
|
1598
|
+
result.originalSize,
|
|
1599
|
+
result.compressedSize,
|
|
1600
|
+
reductionPercent,
|
|
1601
|
+
result.originalMessageCount,
|
|
1602
|
+
result.compressedMessageCount
|
|
1603
|
+
);
|
|
1604
|
+
} else {
|
|
1605
|
+
console.log(
|
|
1606
|
+
`${indent}${colors.success(`${icons.success} Compressed ${result.originalMessageCount} → ${result.compressedMessageCount} messages (${reductionPercent}% smaller)`)}`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Summary is embedded in first user message, look for it
|
|
1611
|
+
// The format is: "[Conversation Summary - X messages compressed]\n\n${summary}"
|
|
1612
|
+
let summaryMessage: ChatMessage | undefined = result.compressedMessages.find(
|
|
1613
|
+
(m) => m.role === 'user' && m.content.includes('[Conversation Summary')
|
|
783
1614
|
);
|
|
784
1615
|
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1616
|
+
if (summaryMessage) {
|
|
1617
|
+
// Extract summary content after the header
|
|
1618
|
+
const match = summaryMessage.content.match(/\[Conversation Summary.*?\]:\n\n(.+)/s);
|
|
1619
|
+
if (match) {
|
|
1620
|
+
summaryMessage = {
|
|
1621
|
+
role: 'assistant',
|
|
1622
|
+
content: match[1],
|
|
1623
|
+
timestamp: summaryMessage.timestamp,
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
789
1627
|
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
let summaryContent = summaryMessage.content;
|
|
795
|
-
const isTruncated = summaryContent.length > maxPreviewLength;
|
|
1628
|
+
if (summaryMessage && summaryMessage.content) {
|
|
1629
|
+
const maxPreviewLength = 800;
|
|
1630
|
+
let summaryContent = summaryMessage.content;
|
|
1631
|
+
const isTruncated = summaryContent.length > maxPreviewLength;
|
|
796
1632
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1633
|
+
if (isTruncated) {
|
|
1634
|
+
summaryContent = summaryContent.substring(0, maxPreviewLength) + '\n...';
|
|
1635
|
+
}
|
|
800
1636
|
|
|
1637
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
1638
|
+
this.sdkOutputAdapter.outputContextCompressionSummary(
|
|
1639
|
+
'Conversation compressed successfully',
|
|
1640
|
+
summaryContent,
|
|
1641
|
+
isTruncated,
|
|
1642
|
+
summaryMessage.content.length
|
|
1643
|
+
);
|
|
1644
|
+
} else {
|
|
801
1645
|
console.log('');
|
|
802
|
-
console.log(
|
|
803
|
-
|
|
1646
|
+
console.log(
|
|
1647
|
+
`${indent}${theme.predefinedStyles.title(`${icons.sparkles} Conversation Summary`)}`
|
|
1648
|
+
);
|
|
1649
|
+
const separator = icons.separator.repeat(
|
|
1650
|
+
Math.min(60, process.stdout.columns || 80) - indent.length * 2
|
|
1651
|
+
);
|
|
804
1652
|
console.log(`${indent}${colors.border(separator)}`);
|
|
805
|
-
const renderedSummary = renderMarkdown(
|
|
806
|
-
|
|
1653
|
+
const renderedSummary = renderMarkdown(
|
|
1654
|
+
summaryContent,
|
|
1655
|
+
(process.stdout.columns || 80) - indent.length * 4
|
|
1656
|
+
);
|
|
1657
|
+
console.log(
|
|
1658
|
+
`${indent}${theme.predefinedStyles.dim(renderedSummary).replace(/^/gm, indent)}`
|
|
1659
|
+
);
|
|
807
1660
|
if (isTruncated) {
|
|
808
|
-
console.log(
|
|
1661
|
+
console.log(
|
|
1662
|
+
`${indent}${colors.textMuted(`(... ${summaryMessage.content.length - maxPreviewLength} more chars hidden)`)}`
|
|
1663
|
+
);
|
|
809
1664
|
}
|
|
810
1665
|
console.log(`${indent}${colors.border(separator)}`);
|
|
811
1666
|
}
|
|
812
|
-
|
|
813
|
-
// Restore user messages after compression, ensuring user message exists for API calls
|
|
814
|
-
if (lastUserMessage) {
|
|
815
|
-
this.conversation.push(lastUserMessage);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// Sync compressed conversation history to slashCommandHandler
|
|
819
|
-
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
820
1667
|
}
|
|
1668
|
+
|
|
1669
|
+
// Sync compressed conversation history to slashCommandHandler
|
|
1670
|
+
this.slashCommandHandler.setConversationHistory(this.conversation);
|
|
821
1671
|
}
|
|
822
1672
|
}
|
|
823
1673
|
|
|
@@ -826,7 +1676,9 @@ export class InteractiveSession {
|
|
|
826
1676
|
console.log('');
|
|
827
1677
|
console.log(`${indent}${colors.textMuted(`${icons.code} Executing:`)}`);
|
|
828
1678
|
console.log(`${indent}${colors.codeText(` $ ${command}`)}`);
|
|
829
|
-
console.log(
|
|
1679
|
+
console.log(
|
|
1680
|
+
`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`
|
|
1681
|
+
);
|
|
830
1682
|
console.log('');
|
|
831
1683
|
|
|
832
1684
|
const toolRegistry = getToolRegistry();
|
|
@@ -846,17 +1698,17 @@ export class InteractiveSession {
|
|
|
846
1698
|
tool: 'Bash',
|
|
847
1699
|
params: { command },
|
|
848
1700
|
result,
|
|
849
|
-
timestamp: Date.now()
|
|
1701
|
+
timestamp: Date.now(),
|
|
850
1702
|
};
|
|
851
1703
|
|
|
852
|
-
this.
|
|
1704
|
+
this.tool_calls.push(toolCall);
|
|
853
1705
|
|
|
854
1706
|
// Record command execution to session manager
|
|
855
1707
|
await this.sessionManager.addInput({
|
|
856
1708
|
type: 'command',
|
|
857
1709
|
content: command,
|
|
858
1710
|
rawInput: command,
|
|
859
|
-
timestamp: Date.now()
|
|
1711
|
+
timestamp: Date.now(),
|
|
860
1712
|
});
|
|
861
1713
|
|
|
862
1714
|
await this.sessionManager.addOutput({
|
|
@@ -865,7 +1717,7 @@ export class InteractiveSession {
|
|
|
865
1717
|
toolName: 'Bash',
|
|
866
1718
|
toolParams: { command },
|
|
867
1719
|
toolResult: result,
|
|
868
|
-
timestamp: Date.now()
|
|
1720
|
+
timestamp: Date.now(),
|
|
869
1721
|
});
|
|
870
1722
|
} catch (error: any) {
|
|
871
1723
|
console.log(`${indent}${colors.error(`Command execution failed: ${error.message}`)}`);
|
|
@@ -892,12 +1744,24 @@ export class InteractiveSession {
|
|
|
892
1744
|
/**
|
|
893
1745
|
* Create remote mode LLM caller
|
|
894
1746
|
*/
|
|
895
|
-
private createRemoteCaller(taskId: string,
|
|
1747
|
+
private createRemoteCaller(taskId: string, _status: 'begin' | 'continue') {
|
|
896
1748
|
const client = this.remoteAIClient!;
|
|
1749
|
+
|
|
1750
|
+
|
|
897
1751
|
return {
|
|
898
|
-
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
899
|
-
|
|
900
|
-
|
|
1752
|
+
chatCompletion: (messages: ChatMessage[], options: any) => {
|
|
1753
|
+
// Must fetch authConfig inside the closure, otherwise it captures stale config
|
|
1754
|
+
const authConfig = this.configManager.getAuthConfig();
|
|
1755
|
+
logger.debug(`[DEBUG] createRemoteCaller: llmModelName=${authConfig.remote_llmModelName}, vlmModelName=${authConfig.remote_vlmModelName}`);
|
|
1756
|
+
return client.chatCompletion(messages, {
|
|
1757
|
+
...options,
|
|
1758
|
+
taskId,
|
|
1759
|
+
status: options.isFirstApiCall ? 'begin' : 'continue',
|
|
1760
|
+
llmModelName: authConfig.remote_llmModelName,
|
|
1761
|
+
vlmModelName: authConfig.remote_vlmModelName
|
|
1762
|
+
});
|
|
1763
|
+
},
|
|
1764
|
+
isRemote: true,
|
|
901
1765
|
};
|
|
902
1766
|
}
|
|
903
1767
|
|
|
@@ -907,39 +1771,32 @@ export class InteractiveSession {
|
|
|
907
1771
|
private createLocalCaller() {
|
|
908
1772
|
const client = this.aiClient!;
|
|
909
1773
|
return {
|
|
910
|
-
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
1774
|
+
chatCompletion: (messages: ChatMessage[], options: any) =>
|
|
911
1775
|
client.chatCompletion(messages as any, options),
|
|
912
|
-
isRemote: false
|
|
1776
|
+
isRemote: false,
|
|
913
1777
|
};
|
|
914
1778
|
}
|
|
915
1779
|
|
|
916
|
-
private async generateResponse(
|
|
1780
|
+
private async generateResponse(
|
|
1781
|
+
thinkingTokens: number = 0,
|
|
1782
|
+
_customAIClient?: AIClient,
|
|
1783
|
+
existingTaskId?: string
|
|
1784
|
+
): Promise<void> {
|
|
917
1785
|
// Use existing taskId or create new one for this user interaction
|
|
918
1786
|
// If taskId already exists (e.g., from tool calls), reuse it
|
|
919
1787
|
const taskId = existingTaskId || this.currentTaskId || crypto.randomUUID();
|
|
920
1788
|
this.currentTaskId = taskId;
|
|
921
|
-
this.isFirstApiCall = true;
|
|
922
|
-
|
|
923
|
-
// Determine status based on whether this is the first API call
|
|
924
|
-
const status: 'begin' | 'continue' = this.isFirstApiCall ? 'begin' : 'continue';
|
|
925
1789
|
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
let isRemote = false;
|
|
1790
|
+
// isFirstApiCall is reset in generateRemoteResponse for new tasks
|
|
1791
|
+
// For continuation calls (existingTaskId provided), keep previous value
|
|
929
1792
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
} else {
|
|
936
|
-
// Use unified LLM Caller with taskId (automatically selects local or remote mode)
|
|
937
|
-
const caller = this.createLLMCaller(taskId, status);
|
|
938
|
-
chatCompletion = caller.chatCompletion;
|
|
939
|
-
isRemote = caller.isRemote;
|
|
940
|
-
}
|
|
1793
|
+
// Use unified LLM Caller with taskId (automatically selects local or remote mode)
|
|
1794
|
+
const status: 'begin' | 'continue' = this.isFirstApiCall ? 'begin' : 'continue';
|
|
1795
|
+
const caller = this.createLLMCaller(taskId, status);
|
|
1796
|
+
const chatCompletion = caller.chatCompletion;
|
|
1797
|
+
const isRemote = caller.isRemote;
|
|
941
1798
|
|
|
942
|
-
if (!isRemote && !this.aiClient
|
|
1799
|
+
if (!isRemote && !this.aiClient) {
|
|
943
1800
|
console.log(colors.error('AI client not initialized'));
|
|
944
1801
|
return;
|
|
945
1802
|
}
|
|
@@ -947,17 +1804,25 @@ export class InteractiveSession {
|
|
|
947
1804
|
// Mark that an operation is in progress
|
|
948
1805
|
(this as any)._isOperationInProgress = true;
|
|
949
1806
|
|
|
950
|
-
const indent = this.getIndent();
|
|
951
1807
|
const thinkingText = colors.textMuted(`Thinking... (Press ESC to cancel)`);
|
|
952
1808
|
const icon = colors.primary(icons.brain);
|
|
953
1809
|
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
954
1810
|
let frameIndex = 0;
|
|
955
1811
|
|
|
1812
|
+
// SDK 模式下不显示 spinner
|
|
1813
|
+
const showThinkingSpinner = !this.isSdkMode;
|
|
1814
|
+
let spinnerInterval: NodeJS.Timeout | null = null;
|
|
1815
|
+
|
|
956
1816
|
// Custom spinner: only icon rotates, text stays static
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1817
|
+
if (showThinkingSpinner) {
|
|
1818
|
+
spinnerInterval = setInterval(() => {
|
|
1819
|
+
process.stdout.write(`\r${colors.primary(frames[frameIndex])} ${icon} ${thinkingText}`);
|
|
1820
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
1821
|
+
}, 120);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
let content = '';
|
|
1825
|
+
let reasoningContent = '';
|
|
961
1826
|
|
|
962
1827
|
try {
|
|
963
1828
|
const memory = await this.memoryManager.loadMemory();
|
|
@@ -971,17 +1836,28 @@ export class InteractiveSession {
|
|
|
971
1836
|
const toolDefinitions = toolRegistry.getToolDefinitions();
|
|
972
1837
|
|
|
973
1838
|
// Available tools for this session
|
|
974
|
-
const availableTools =
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1839
|
+
const availableTools =
|
|
1840
|
+
this.executionMode !== ExecutionMode.DEFAULT && allowedToolNames.length > 0
|
|
1841
|
+
? toolDefinitions.filter(
|
|
1842
|
+
(tool): tool is { function: { name: string } } =>
|
|
1843
|
+
typeof tool.function?.name === 'string' &&
|
|
1844
|
+
allowedToolNames.includes(tool.function.name)
|
|
1845
|
+
)
|
|
1846
|
+
: toolDefinitions;
|
|
1847
|
+
|
|
1848
|
+
const baseSystemPrompt = this.currentAgent?.systemPrompt ?? '';
|
|
1849
|
+
const systemPromptGenerator = new SystemPromptGenerator(
|
|
1850
|
+
toolRegistry,
|
|
1851
|
+
this.executionMode,
|
|
1852
|
+
undefined,
|
|
1853
|
+
this.mcpManager
|
|
1854
|
+
);
|
|
1855
|
+
const enhancedSystemPrompt =
|
|
1856
|
+
await systemPromptGenerator.generateEnhancedSystemPrompt(baseSystemPrompt);
|
|
981
1857
|
|
|
982
1858
|
const messages: ChatMessage[] = [
|
|
983
1859
|
{ role: 'system', content: `${enhancedSystemPrompt}\n\n${memory}`, timestamp: Date.now() },
|
|
984
|
-
...this.conversation
|
|
1860
|
+
...this.conversation,
|
|
985
1861
|
];
|
|
986
1862
|
|
|
987
1863
|
const operationId = `ai-response-${Date.now()}`;
|
|
@@ -989,7 +1865,8 @@ export class InteractiveSession {
|
|
|
989
1865
|
chatCompletion(messages, {
|
|
990
1866
|
tools: availableTools,
|
|
991
1867
|
toolChoice: availableTools.length > 0 ? 'auto' : 'none',
|
|
992
|
-
thinkingTokens
|
|
1868
|
+
thinkingTokens,
|
|
1869
|
+
isFirstApiCall: this.isFirstApiCall,
|
|
993
1870
|
}),
|
|
994
1871
|
operationId
|
|
995
1872
|
);
|
|
@@ -997,34 +1874,27 @@ export class InteractiveSession {
|
|
|
997
1874
|
// Mark that first API call is complete
|
|
998
1875
|
this.isFirstApiCall = false;
|
|
999
1876
|
|
|
1000
|
-
clearInterval(spinnerInterval);
|
|
1877
|
+
if (spinnerInterval) clearInterval(spinnerInterval);
|
|
1001
1878
|
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r'); // Clear spinner line
|
|
1002
1879
|
|
|
1003
1880
|
const assistantMessage = response.choices[0].message;
|
|
1004
1881
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
: '';
|
|
1008
|
-
const reasoningContent = assistantMessage.reasoning_content || '';
|
|
1882
|
+
content = typeof assistantMessage.content === 'string' ? assistantMessage.content : '';
|
|
1883
|
+
reasoningContent = assistantMessage.reasoning_content || '';
|
|
1009
1884
|
// Display reasoning content if available and thinking mode is enabled
|
|
1010
1885
|
if (reasoningContent && this.configManager.getThinkingConfig().enabled) {
|
|
1011
1886
|
this.displayThinkingContent(reasoningContent);
|
|
1012
1887
|
}
|
|
1013
1888
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
console.log(`${indent}${colors.border(icons.separator.repeat(Math.min(60, process.stdout.columns || 80) - indent.length))}`);
|
|
1017
|
-
console.log('');
|
|
1018
|
-
const renderedContent = renderMarkdown(content, (process.stdout.columns || 80) - indent.length * 2);
|
|
1019
|
-
console.log(`${indent}${renderedContent.replace(/^/gm, indent)}`);
|
|
1020
|
-
console.log('');
|
|
1889
|
+
// Output assistant response
|
|
1890
|
+
this.outputAssistant(content, reasoningContent);
|
|
1021
1891
|
|
|
1022
1892
|
this.conversation.push({
|
|
1023
1893
|
role: 'assistant',
|
|
1024
1894
|
content,
|
|
1025
1895
|
timestamp: Date.now(),
|
|
1026
|
-
reasoningContent,
|
|
1027
|
-
|
|
1896
|
+
reasoning_content: reasoningContent,
|
|
1897
|
+
tool_calls: assistantMessage.tool_calls,
|
|
1028
1898
|
});
|
|
1029
1899
|
|
|
1030
1900
|
// Record output to session manager
|
|
@@ -1032,43 +1902,63 @@ export class InteractiveSession {
|
|
|
1032
1902
|
role: 'assistant',
|
|
1033
1903
|
content,
|
|
1034
1904
|
timestamp: Date.now(),
|
|
1035
|
-
reasoningContent,
|
|
1036
|
-
|
|
1905
|
+
reasoning_content: reasoningContent,
|
|
1906
|
+
tool_calls: assistantMessage.tool_calls,
|
|
1037
1907
|
});
|
|
1038
1908
|
|
|
1039
1909
|
if (assistantMessage.tool_calls) {
|
|
1040
|
-
await this.handleToolCalls(assistantMessage.tool_calls);
|
|
1910
|
+
await this.handleToolCalls(assistantMessage.tool_calls as unknown as import('./types.js').ToolCallItem[]);
|
|
1911
|
+
} else {
|
|
1912
|
+
await this.checkAndCompressContext();
|
|
1041
1913
|
}
|
|
1042
1914
|
|
|
1043
1915
|
if (this.checkpointManager.isEnabled()) {
|
|
1044
1916
|
await this.checkpointManager.createCheckpoint(
|
|
1045
1917
|
`Response generated at ${new Date().toLocaleString()}`,
|
|
1046
1918
|
[...this.conversation],
|
|
1047
|
-
[...this.
|
|
1919
|
+
[...this.tool_calls]
|
|
1048
1920
|
);
|
|
1049
1921
|
}
|
|
1050
1922
|
|
|
1923
|
+
// Signal request completion to SDK (no tools to execute)
|
|
1924
|
+
if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
|
|
1925
|
+
this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, 'success');
|
|
1926
|
+
this._currentRequestId = null;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1051
1929
|
// Operation completed successfully, clear the flag
|
|
1052
1930
|
(this as any)._isOperationInProgress = false;
|
|
1053
1931
|
} catch (error: any) {
|
|
1054
|
-
clearInterval(spinnerInterval);
|
|
1932
|
+
if (spinnerInterval) clearInterval(spinnerInterval);
|
|
1055
1933
|
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
1056
1934
|
|
|
1057
1935
|
// Clear the operation flag
|
|
1058
1936
|
(this as any)._isOperationInProgress = false;
|
|
1059
1937
|
|
|
1938
|
+
// Signal request completion to SDK
|
|
1939
|
+
if (this.isSdkMode && this.sdkOutputAdapter && this._currentRequestId) {
|
|
1940
|
+
const status = error.message === 'Operation cancelled by user' ? 'cancelled' : 'error';
|
|
1941
|
+
this.sdkOutputAdapter.outputRequestDone(this._currentRequestId, status);
|
|
1942
|
+
this._currentRequestId = null;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1060
1945
|
if (error.message === 'Operation cancelled by user') {
|
|
1061
|
-
//
|
|
1946
|
+
// Notify backend to cancel the task
|
|
1062
1947
|
if (this.remoteAIClient && this.currentTaskId) {
|
|
1063
|
-
await this.remoteAIClient.cancelTask(this.currentTaskId).catch(() => {});
|
|
1948
|
+
await this.remoteAIClient.cancelTask?.(this.currentTaskId).catch(() => {});
|
|
1064
1949
|
}
|
|
1065
1950
|
return;
|
|
1066
1951
|
}
|
|
1067
1952
|
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1953
|
+
// Distinguish error types: timeout vs other failures
|
|
1954
|
+
const isTimeout = error.message.includes('timeout') || error.message.includes('Timeout');
|
|
1955
|
+
const failureReason = isTimeout ? 'timeout' : 'failure';
|
|
1956
|
+
|
|
1957
|
+
logger.debug(
|
|
1958
|
+
`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}, reason: ${failureReason}`
|
|
1959
|
+
);
|
|
1070
1960
|
if (this.remoteAIClient && this.currentTaskId) {
|
|
1071
|
-
await this.remoteAIClient.
|
|
1961
|
+
await this.remoteAIClient.failTask?.(this.currentTaskId, failureReason).catch(() => {});
|
|
1072
1962
|
}
|
|
1073
1963
|
|
|
1074
1964
|
console.log(colors.error(`Error: ${error.message}`));
|
|
@@ -1082,20 +1972,24 @@ export class InteractiveSession {
|
|
|
1082
1972
|
* @param thinkingTokens - Optional thinking tokens config
|
|
1083
1973
|
* @param existingTaskId - Optional existing taskId to reuse (for tool call continuation)
|
|
1084
1974
|
*/
|
|
1085
|
-
private async generateRemoteResponse(
|
|
1975
|
+
private async generateRemoteResponse(
|
|
1976
|
+
thinkingTokens: number = 0,
|
|
1977
|
+
existingTaskId?: string
|
|
1978
|
+
): Promise<void> {
|
|
1086
1979
|
// Reuse existing taskId or create new one for this user interaction
|
|
1087
1980
|
const taskId = existingTaskId || crypto.randomUUID();
|
|
1088
1981
|
this.currentTaskId = taskId;
|
|
1089
|
-
logger.debug(
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (!existingTaskId) {
|
|
1093
|
-
this.isFirstApiCall = true;
|
|
1094
|
-
}
|
|
1982
|
+
logger.debug(
|
|
1983
|
+
`[Session] generateRemoteResponse: taskId=${taskId}, existingTaskId=${!!existingTaskId}`
|
|
1984
|
+
);
|
|
1095
1985
|
|
|
1096
|
-
//
|
|
1097
|
-
|
|
1098
|
-
|
|
1986
|
+
// Each new user message is a fresh task - always set isFirstApiCall = true
|
|
1987
|
+
// This ensures status is 'begin' for every user message
|
|
1988
|
+
this.isFirstApiCall = true;
|
|
1989
|
+
const status: 'begin' | 'continue' = 'begin';
|
|
1990
|
+
logger.debug(
|
|
1991
|
+
`[Session] Status for this call: ${status}, isFirstApiCall=${this.isFirstApiCall}`
|
|
1992
|
+
);
|
|
1099
1993
|
|
|
1100
1994
|
// Check if remote client is available
|
|
1101
1995
|
if (!this.remoteAIClient) {
|
|
@@ -1104,20 +1998,24 @@ export class InteractiveSession {
|
|
|
1104
1998
|
}
|
|
1105
1999
|
|
|
1106
2000
|
try {
|
|
1107
|
-
//
|
|
1108
|
-
|
|
2001
|
+
// Use unified generateResponse without passing customAIClient,
|
|
2002
|
+
// let createLLMCaller handle remote/local selection
|
|
2003
|
+
await this.generateResponse(thinkingTokens, undefined, taskId);
|
|
1109
2004
|
|
|
1110
2005
|
// Mark task as completed (发送 status: 'end')
|
|
1111
2006
|
logger.debug(`[Session] Task completed: taskId=${this.currentTaskId}`);
|
|
1112
|
-
if (this.currentTaskId) {
|
|
1113
|
-
await this.remoteAIClient.completeTask(this.currentTaskId);
|
|
2007
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
2008
|
+
await this.remoteAIClient.completeTask?.(this.currentTaskId);
|
|
1114
2009
|
}
|
|
1115
|
-
|
|
1116
2010
|
} catch (error: any) {
|
|
1117
2011
|
// Clear the operation flag
|
|
1118
2012
|
(this as any)._isOperationInProgress = false;
|
|
1119
2013
|
|
|
1120
2014
|
if (error.message === 'Operation cancelled by user') {
|
|
2015
|
+
// Notify backend to cancel the task
|
|
2016
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
2017
|
+
await this.remoteAIClient.cancelTask?.(this.currentTaskId).catch(() => {});
|
|
2018
|
+
}
|
|
1121
2019
|
return;
|
|
1122
2020
|
}
|
|
1123
2021
|
|
|
@@ -1129,28 +2027,29 @@ export class InteractiveSession {
|
|
|
1129
2027
|
console.log('');
|
|
1130
2028
|
|
|
1131
2029
|
// Clear invalid credentials and persist
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
2030
|
+
this.configManager.set('apiKey', '');
|
|
2031
|
+
this.configManager.set('refreshToken', '');
|
|
2032
|
+
this.configManager.save('global');
|
|
1135
2033
|
|
|
1136
|
-
logger.debug(
|
|
2034
|
+
logger.debug(
|
|
2035
|
+
'[DEBUG generateRemoteResponse] Cleared invalid credentials, starting re-authentication...'
|
|
2036
|
+
);
|
|
1137
2037
|
|
|
1138
2038
|
// Re-authenticate
|
|
1139
2039
|
await this.setupAuthentication();
|
|
1140
2040
|
|
|
1141
2041
|
// Reload config to ensure we have the latest authConfig
|
|
1142
|
-
|
|
1143
|
-
await this.configManager.load();
|
|
2042
|
+
this.configManager.load();
|
|
1144
2043
|
const authConfig = this.configManager.getAuthConfig();
|
|
1145
2044
|
|
|
1146
2045
|
logger.debug('[DEBUG generateRemoteResponse] After re-auth:');
|
|
1147
|
-
logger.debug(' - authConfig.apiKey exists:',
|
|
2046
|
+
logger.debug(' - authConfig.apiKey exists:', authConfig.apiKey ? 'true' : 'false');
|
|
1148
2047
|
|
|
1149
|
-
// Recreate readline interface after
|
|
2048
|
+
// Recreate readline interface after interactive prompt
|
|
1150
2049
|
this.rl.close();
|
|
1151
2050
|
this.rl = readline.createInterface({
|
|
1152
2051
|
input: process.stdin,
|
|
1153
|
-
output: process.stdout
|
|
2052
|
+
output: process.stdout,
|
|
1154
2053
|
});
|
|
1155
2054
|
this.rl.on('close', () => {
|
|
1156
2055
|
logger.debug('DEBUG: readline interface closed');
|
|
@@ -1158,13 +2057,19 @@ export class InteractiveSession {
|
|
|
1158
2057
|
|
|
1159
2058
|
// Reinitialize RemoteAIClient with new token
|
|
1160
2059
|
if (authConfig.apiKey) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
2060
|
+
logger.debug(
|
|
2061
|
+
'[DEBUG generateRemoteResponse] Reinitializing RemoteAIClient with new token'
|
|
2062
|
+
);
|
|
2063
|
+
this.remoteAIClient = createAIClient(authConfig);
|
|
1164
2064
|
} else {
|
|
1165
|
-
logger.debug(
|
|
2065
|
+
logger.debug(
|
|
2066
|
+
'[DEBUG generateRemoteResponse] WARNING: No apiKey after re-authentication!'
|
|
2067
|
+
);
|
|
1166
2068
|
}
|
|
1167
2069
|
|
|
2070
|
+
// Sync remoteAIClient reference to slashCommandHandler for /provider command
|
|
2071
|
+
this.slashCommandHandler.setRemoteAIClient(this.remoteAIClient);
|
|
2072
|
+
|
|
1168
2073
|
// Retry the current operation
|
|
1169
2074
|
console.log('');
|
|
1170
2075
|
console.log(colors.info('Retrying with new authentication...'));
|
|
@@ -1172,10 +2077,15 @@ export class InteractiveSession {
|
|
|
1172
2077
|
return this.generateRemoteResponse(thinkingTokens);
|
|
1173
2078
|
}
|
|
1174
2079
|
|
|
1175
|
-
//
|
|
1176
|
-
|
|
2080
|
+
// Distinguish error types: timeout vs other failures
|
|
2081
|
+
const isTimeout = error.message.includes('timeout') || error.message.includes('Timeout');
|
|
2082
|
+
const failureReason = isTimeout ? 'timeout' : 'failure';
|
|
2083
|
+
|
|
2084
|
+
logger.debug(
|
|
2085
|
+
`[Session] Task failed: taskId=${this.currentTaskId}, error: ${error.message}, reason: ${failureReason}`
|
|
2086
|
+
);
|
|
1177
2087
|
if (this.remoteAIClient && this.currentTaskId) {
|
|
1178
|
-
await this.remoteAIClient.
|
|
2088
|
+
await this.remoteAIClient.failTask?.(this.currentTaskId, failureReason).catch(() => {});
|
|
1179
2089
|
}
|
|
1180
2090
|
|
|
1181
2091
|
console.log(colors.error(`Error: ${error.message}`));
|
|
@@ -1183,7 +2093,10 @@ export class InteractiveSession {
|
|
|
1183
2093
|
}
|
|
1184
2094
|
}
|
|
1185
2095
|
|
|
1186
|
-
private async handleToolCalls(
|
|
2096
|
+
private async handleToolCalls(
|
|
2097
|
+
toolCalls: ToolCallItem[],
|
|
2098
|
+
onComplete?: () => Promise<void>
|
|
2099
|
+
): Promise<void> {
|
|
1187
2100
|
// Mark that tool execution is in progress
|
|
1188
2101
|
(this as any)._isOperationInProgress = true;
|
|
1189
2102
|
|
|
@@ -1198,7 +2111,7 @@ export class InteractiveSession {
|
|
|
1198
2111
|
let parsedParams: any;
|
|
1199
2112
|
try {
|
|
1200
2113
|
parsedParams = typeof params === 'string' ? JSON.parse(params) : params;
|
|
1201
|
-
} catch
|
|
2114
|
+
} catch {
|
|
1202
2115
|
parsedParams = params;
|
|
1203
2116
|
}
|
|
1204
2117
|
|
|
@@ -1207,232 +2120,302 @@ export class InteractiveSession {
|
|
|
1207
2120
|
|
|
1208
2121
|
// Display all tool calls info
|
|
1209
2122
|
for (const { name, params } of preparedToolCalls) {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
|
|
2123
|
+
// SDK mode: use adapter output
|
|
2124
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
2125
|
+
this.sdkOutputAdapter.outputToolStart(name, params);
|
|
1214
2126
|
} else {
|
|
1215
|
-
|
|
1216
|
-
|
|
2127
|
+
// Normal mode: console output
|
|
2128
|
+
if (showToolDetails) {
|
|
2129
|
+
console.log('');
|
|
2130
|
+
console.log(`${indent}${colors.warning(`${icons.tool} Tool Call: ${name}`)}`);
|
|
2131
|
+
console.log(`${indent}${colors.textDim(JSON.stringify(params, null, 2))}`);
|
|
2132
|
+
} else {
|
|
2133
|
+
const toolDescription = this.getToolDescription(name, params);
|
|
2134
|
+
console.log(`${indent}${colors.textMuted(`${icons.loading} ${toolDescription}`)}`);
|
|
2135
|
+
}
|
|
1217
2136
|
}
|
|
1218
2137
|
}
|
|
1219
2138
|
|
|
1220
2139
|
// Execute all tools in parallel
|
|
1221
2140
|
const results = await toolRegistry.executeAll(
|
|
1222
|
-
preparedToolCalls.map(tc => ({ name: tc.name, params: tc.params })),
|
|
2141
|
+
preparedToolCalls.map((tc) => ({ name: tc.name, params: tc.params })),
|
|
1223
2142
|
this.executionMode
|
|
1224
2143
|
);
|
|
1225
2144
|
|
|
1226
|
-
//
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
2145
|
+
// Create a map to store results by tool call index to maintain original order
|
|
2146
|
+
const resultsByIndex = new Map<number, { tool: string; result: any; error?: string }>();
|
|
2147
|
+
const usedIndices = new Set<number>();
|
|
2148
|
+
|
|
2149
|
+
for (const result of results) {
|
|
2150
|
+
// Find the first unused original index in preparedToolCalls that matches the tool name
|
|
2151
|
+
const originalIndex = preparedToolCalls.findIndex((tc, idx) =>
|
|
2152
|
+
tc.name === result.tool && !usedIndices.has(idx)
|
|
2153
|
+
);
|
|
2154
|
+
if (originalIndex !== -1) {
|
|
2155
|
+
usedIndices.add(originalIndex);
|
|
2156
|
+
resultsByIndex.set(originalIndex, result);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
1231
2159
|
|
|
1232
|
-
|
|
2160
|
+
// Process results in the original tool_calls order (critical for Anthropic format APIs)
|
|
2161
|
+
let _hasError = false;
|
|
2162
|
+
for (let i = 0; i < preparedToolCalls.length; i++) {
|
|
2163
|
+
const toolCall = preparedToolCalls[i];
|
|
2164
|
+
const { name: tool, params } = toolCall;
|
|
2165
|
+
|
|
2166
|
+
const resultData = resultsByIndex.get(i);
|
|
2167
|
+
const result = resultData?.result;
|
|
2168
|
+
const error = resultData?.error;
|
|
1233
2169
|
|
|
1234
2170
|
if (error) {
|
|
1235
2171
|
if (error === 'Operation cancelled by user') {
|
|
2172
|
+
// Notify backend to cancel the task
|
|
2173
|
+
if (this.remoteAIClient && this.currentTaskId) {
|
|
2174
|
+
await this.remoteAIClient.cancelTask?.(this.currentTaskId).catch(() => {});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// 清理 conversation 中未完成的 tool_call
|
|
2178
|
+
this.cleanupIncompleteToolCalls();
|
|
2179
|
+
|
|
1236
2180
|
(this as any)._isOperationInProgress = false;
|
|
1237
2181
|
return;
|
|
1238
2182
|
}
|
|
1239
2183
|
|
|
1240
|
-
|
|
2184
|
+
_hasError = true;
|
|
1241
2185
|
|
|
1242
|
-
|
|
1243
|
-
|
|
2186
|
+
// SDK mode: use adapter output
|
|
2187
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
2188
|
+
this.sdkOutputAdapter.outputToolError(tool, error);
|
|
2189
|
+
} else {
|
|
2190
|
+
// Normal mode: console output
|
|
2191
|
+
console.log('');
|
|
2192
|
+
console.log(`${indent}${colors.error(`${icons.cross} Tool Error: ${tool} - ${error}`)}`);
|
|
2193
|
+
}
|
|
1244
2194
|
|
|
1245
|
-
//
|
|
2195
|
+
// Add detailed error info including tool name and params for AI understanding and correction
|
|
1246
2196
|
this.conversation.push({
|
|
1247
2197
|
role: 'tool',
|
|
1248
2198
|
content: JSON.stringify({
|
|
1249
2199
|
name: tool,
|
|
1250
2200
|
parameters: params,
|
|
1251
|
-
error: error
|
|
2201
|
+
error: error,
|
|
1252
2202
|
}),
|
|
1253
2203
|
tool_call_id: toolCall.id,
|
|
1254
|
-
timestamp: Date.now()
|
|
2204
|
+
timestamp: Date.now(),
|
|
1255
2205
|
});
|
|
1256
2206
|
} else {
|
|
2207
|
+
// SDK mode: output tool result via adapter
|
|
2208
|
+
if (this.isSdkMode && this.sdkOutputAdapter) {
|
|
2209
|
+
this.sdkOutputAdapter.outputToolResult(tool, result);
|
|
2210
|
+
}
|
|
2211
|
+
|
|
1257
2212
|
// Use correct indent for gui-subagent tasks
|
|
1258
2213
|
const isGuiSubagent = tool === 'task' && params?.subagent_type === 'gui-subagent';
|
|
1259
2214
|
const displayIndent = isGuiSubagent ? indent + ' ' : indent;
|
|
1260
2215
|
|
|
1261
|
-
//
|
|
1262
|
-
|
|
2216
|
+
// Normal mode: console output (SDK mode already output via adapter above)
|
|
2217
|
+
if (!this.isSdkMode || !this.sdkOutputAdapter) {
|
|
2218
|
+
// Always show details for todo tools so users can see their task lists
|
|
2219
|
+
const isTodoTool = tool === 'todo_write' || tool === 'todo_read';
|
|
1263
2220
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
2221
|
+
// Special handling for edit tool with diff
|
|
2222
|
+
const isEditTool = tool === 'Edit';
|
|
2223
|
+
const hasDiff = isEditTool && result?.diff;
|
|
1267
2224
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
2225
|
+
// Special handling for Write tool with file preview
|
|
2226
|
+
const isWriteTool = tool === 'Write';
|
|
2227
|
+
const hasFilePreview = isWriteTool && result?.preview;
|
|
1271
2228
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
2229
|
+
// Special handling for DeleteFile tool
|
|
2230
|
+
const isDeleteTool = tool === 'DeleteFile';
|
|
2231
|
+
const hasDeleteInfo = isDeleteTool && result?.filePath;
|
|
1275
2232
|
|
|
1276
|
-
|
|
1277
|
-
|
|
2233
|
+
// Special handling for task tool (subagent)
|
|
2234
|
+
const isTaskTool = tool === 'task' && params?.subagent_type;
|
|
1278
2235
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
if (result?.message) {
|
|
1291
|
-
console.log(`${displayIndent}${colors.textDim(result.message)}`);
|
|
1292
|
-
}
|
|
1293
|
-
} else if (hasDiff) {
|
|
1294
|
-
// Show edit result with diff
|
|
1295
|
-
console.log('');
|
|
1296
|
-
const diffOutput = renderDiff(result.diff);
|
|
1297
|
-
const indentedDiff = diffOutput.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
|
|
1298
|
-
console.log(`${indentedDiff}`);
|
|
1299
|
-
} else if (hasFilePreview) {
|
|
1300
|
-
// Show new file content in diff-like style
|
|
1301
|
-
console.log('');
|
|
1302
|
-
console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
|
|
1303
|
-
console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
|
|
1304
|
-
console.log('');
|
|
1305
|
-
console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
|
|
1306
|
-
} else if (hasDeleteInfo) {
|
|
1307
|
-
// Show DeleteFile result
|
|
1308
|
-
console.log('');
|
|
1309
|
-
console.log(`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`);
|
|
1310
|
-
} else if (isTaskTool) {
|
|
1311
|
-
// Special handling for task tool (subagent) - show friendly summary
|
|
1312
|
-
console.log('');
|
|
1313
|
-
const subagentType = params.subagent_type;
|
|
1314
|
-
const subagentName = params.description || (params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
|
|
1315
|
-
|
|
1316
|
-
if (result?.success) {
|
|
1317
|
-
console.log(`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`);
|
|
1318
|
-
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
1319
|
-
if (result.message) {
|
|
1320
|
-
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
1321
|
-
}
|
|
1322
|
-
} else if (result?.cancelled) {
|
|
1323
|
-
console.log(`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`);
|
|
1324
|
-
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
1325
|
-
} else {
|
|
1326
|
-
console.log(`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`);
|
|
1327
|
-
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
2236
|
+
// Check if tool is an MCP wrapper tool by looking up in tool registry
|
|
2237
|
+
const { getToolRegistry } = await import('./tools.js');
|
|
2238
|
+
const toolRegistry = getToolRegistry();
|
|
2239
|
+
const toolDef = toolRegistry.get(tool);
|
|
2240
|
+
const isMcpTool = toolDef && (toolDef as any)._isMcpTool === true;
|
|
2241
|
+
|
|
2242
|
+
if (isTodoTool) {
|
|
2243
|
+
console.log('');
|
|
2244
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Todo List:`)}`);
|
|
2245
|
+
console.log(this.renderTodoList(result?.todos || [], displayIndent));
|
|
2246
|
+
// Show summary if available
|
|
1328
2247
|
if (result?.message) {
|
|
1329
|
-
console.log(`${displayIndent}${colors.textDim(
|
|
2248
|
+
console.log(`${displayIndent}${colors.textDim(result.message)}`);
|
|
2249
|
+
}
|
|
2250
|
+
} else if (hasDiff) {
|
|
2251
|
+
// Show edit result with diff
|
|
2252
|
+
console.log('');
|
|
2253
|
+
const diffOutput = renderDiff(result.diff);
|
|
2254
|
+
const indentedDiff = diffOutput
|
|
2255
|
+
.split('\n')
|
|
2256
|
+
.map((line) => `${displayIndent} ${line}`)
|
|
2257
|
+
.join('\n');
|
|
2258
|
+
console.log(`${indentedDiff}`);
|
|
2259
|
+
} else if (hasFilePreview) {
|
|
2260
|
+
// Show new file content in diff-like style
|
|
2261
|
+
console.log('');
|
|
2262
|
+
console.log(`${displayIndent}${colors.success(`${icons.file} ${result.filePath}`)}`);
|
|
2263
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.lineCount} lines`)}`);
|
|
2264
|
+
console.log('');
|
|
2265
|
+
console.log(renderLines(result.preview, { maxLines: 10, indent: displayIndent + ' ' }));
|
|
2266
|
+
} else if (hasDeleteInfo) {
|
|
2267
|
+
// Show DeleteFile result
|
|
2268
|
+
console.log('');
|
|
2269
|
+
console.log(
|
|
2270
|
+
`${displayIndent}${colors.success(`${icons.check} Deleted: ${result.filePath}`)}`
|
|
2271
|
+
);
|
|
2272
|
+
} else if (isTaskTool) {
|
|
2273
|
+
// Special handling for task tool (subagent) - show friendly summary
|
|
2274
|
+
console.log('');
|
|
2275
|
+
const subagentType = params.subagent_type;
|
|
2276
|
+
const subagentName =
|
|
2277
|
+
params.description ||
|
|
2278
|
+
(params.prompt ? params.prompt.substring(0, 50).replace(/\n/g, ' ') : 'Unknown task');
|
|
2279
|
+
|
|
2280
|
+
if (result?.success) {
|
|
2281
|
+
console.log(
|
|
2282
|
+
`${displayIndent}${colors.success(`${icons.check} ${subagentType}: Completed`)}`
|
|
2283
|
+
);
|
|
2284
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
2285
|
+
if (result.message) {
|
|
2286
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
2287
|
+
}
|
|
2288
|
+
} else if (result?.cancelled) {
|
|
2289
|
+
console.log(
|
|
2290
|
+
`${displayIndent}${colors.warning(`${icons.cross} ${subagentType}: Cancelled`)}`
|
|
2291
|
+
);
|
|
2292
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
2293
|
+
} else {
|
|
2294
|
+
console.log(
|
|
2295
|
+
`${displayIndent}${colors.error(`${icons.cross} ${subagentType}: Failed`)}`
|
|
2296
|
+
);
|
|
2297
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${subagentName}`)}`);
|
|
2298
|
+
if (result?.message) {
|
|
2299
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
} else if (isMcpTool) {
|
|
2303
|
+
// Special handling for MCP tools - show friendly summary
|
|
2304
|
+
console.log('');
|
|
2305
|
+
// Extract server name and tool name from tool name (format: serverName__toolName)
|
|
2306
|
+
let serverName = 'MCP';
|
|
2307
|
+
let toolDisplayName = tool;
|
|
2308
|
+
if (tool.includes('__')) {
|
|
2309
|
+
const parts = tool.split('__');
|
|
2310
|
+
serverName = parts[0];
|
|
2311
|
+
toolDisplayName = parts.slice(1).join('__');
|
|
1330
2312
|
}
|
|
1331
|
-
}
|
|
1332
|
-
} else if (isMcpTool) {
|
|
1333
|
-
// Special handling for MCP tools - show friendly summary
|
|
1334
|
-
console.log('');
|
|
1335
|
-
// Extract server name and tool name from tool name (format: serverName__toolName)
|
|
1336
|
-
let serverName = 'MCP';
|
|
1337
|
-
let toolDisplayName = tool;
|
|
1338
|
-
if (tool.includes('__')) {
|
|
1339
|
-
const parts = tool.split('__');
|
|
1340
|
-
serverName = parts[0];
|
|
1341
|
-
toolDisplayName = parts.slice(1).join('__');
|
|
1342
|
-
}
|
|
1343
2313
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
2314
|
+
// Try to extract meaningful content from MCP result
|
|
2315
|
+
let summary = '';
|
|
2316
|
+
if (result?.content && Array.isArray(result.content) && result.content.length > 0) {
|
|
2317
|
+
const firstBlock = result.content[0];
|
|
2318
|
+
if (firstBlock?.type === 'text' && firstBlock?.text) {
|
|
2319
|
+
const text = firstBlock.text;
|
|
2320
|
+
if (typeof text === 'string') {
|
|
2321
|
+
// Detect HTML content
|
|
2322
|
+
if (text.trim().startsWith('<!DOCTYPE') || text.trim().startsWith('<html')) {
|
|
2323
|
+
summary = '[HTML content fetched]';
|
|
2324
|
+
} else {
|
|
2325
|
+
// Try to parse if it's JSON
|
|
2326
|
+
try {
|
|
2327
|
+
const parsed = JSON.parse(text);
|
|
2328
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0]?.title) {
|
|
2329
|
+
// Search results format
|
|
2330
|
+
summary = `Found ${parsed.length} result(s)`;
|
|
2331
|
+
} else if (parsed?.message) {
|
|
2332
|
+
summary = parsed.message;
|
|
2333
|
+
} else if (typeof parsed === 'string') {
|
|
2334
|
+
summary = parsed.substring(0, 100);
|
|
2335
|
+
}
|
|
2336
|
+
} catch {
|
|
2337
|
+
// Not JSON, use as-is with truncation
|
|
2338
|
+
summary = text.substring(0, 100);
|
|
1365
2339
|
}
|
|
1366
|
-
} catch {
|
|
1367
|
-
// Not JSON, use as-is with truncation
|
|
1368
|
-
summary = text.substring(0, 100);
|
|
1369
2340
|
}
|
|
1370
2341
|
}
|
|
1371
2342
|
}
|
|
2343
|
+
} else if (result?.message) {
|
|
2344
|
+
summary = result.message;
|
|
1372
2345
|
}
|
|
1373
|
-
} else if (result?.message) {
|
|
1374
|
-
summary = result.message;
|
|
1375
|
-
}
|
|
1376
2346
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
console.log(`${displayIndent}${colors.textDim(` ${
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
console.log(`${displayIndent}${colors.textDim(` ${
|
|
2347
|
+
if (result?.success !== false) {
|
|
2348
|
+
console.log(
|
|
2349
|
+
`${displayIndent}${colors.success(`${icons.check} ${serverName}: Success`)}`
|
|
2350
|
+
);
|
|
2351
|
+
console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
|
|
2352
|
+
if (summary) {
|
|
2353
|
+
console.log(`${displayIndent}${colors.textDim(` ${summary}`)}`);
|
|
2354
|
+
}
|
|
2355
|
+
} else {
|
|
2356
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} ${serverName}: Failed`)}`);
|
|
2357
|
+
console.log(`${displayIndent}${colors.textDim(` Tool: ${toolDisplayName}`)}`);
|
|
2358
|
+
if (result?.message || result?.error) {
|
|
2359
|
+
console.log(
|
|
2360
|
+
`${displayIndent}${colors.textDim(` ${result?.message || result?.error}`)}`
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
1388
2363
|
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
2364
|
+
} else if (tool === 'InvokeSkill') {
|
|
2365
|
+
// Special handling for InvokeSkill - show friendly summary
|
|
2366
|
+
console.log('');
|
|
2367
|
+
const skillName = params?.skillId || 'Unknown skill';
|
|
2368
|
+
const taskDesc = params?.taskDescription || '';
|
|
2369
|
+
|
|
2370
|
+
if (result?.success) {
|
|
2371
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Skill: Completed`)}`);
|
|
2372
|
+
console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
|
|
2373
|
+
if (taskDesc) {
|
|
2374
|
+
const truncatedTask =
|
|
2375
|
+
taskDesc.length > 60 ? taskDesc.substring(0, 60) + '...' : taskDesc;
|
|
2376
|
+
console.log(`${displayIndent}${colors.textDim(` Task: ${truncatedTask}`)}`);
|
|
2377
|
+
}
|
|
2378
|
+
} else {
|
|
2379
|
+
console.log(`${displayIndent}${colors.error(`${icons.cross} Skill: Failed`)}`);
|
|
2380
|
+
console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
|
|
2381
|
+
if (result?.message) {
|
|
2382
|
+
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
2383
|
+
}
|
|
1402
2384
|
}
|
|
2385
|
+
} else if (showToolDetails) {
|
|
2386
|
+
console.log('');
|
|
2387
|
+
console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
|
|
2388
|
+
console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
|
|
2389
|
+
} else if (result && result.success === false) {
|
|
2390
|
+
// GUI task or other tool failed
|
|
2391
|
+
console.log(
|
|
2392
|
+
`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`
|
|
2393
|
+
);
|
|
2394
|
+
} else if (result) {
|
|
2395
|
+
// Show brief preview by default (consistent with subagent behavior)
|
|
2396
|
+
const resultPreview =
|
|
2397
|
+
typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
2398
|
+
const truncatedPreview =
|
|
2399
|
+
resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
|
|
2400
|
+
// Indent the preview
|
|
2401
|
+
const indentedPreview = truncatedPreview
|
|
2402
|
+
.split('\n')
|
|
2403
|
+
.map((line) => `${displayIndent} ${line}`)
|
|
2404
|
+
.join('\n');
|
|
2405
|
+
console.log(`${indentedPreview}`);
|
|
1403
2406
|
} else {
|
|
1404
|
-
console.log(`${displayIndent}${colors.
|
|
1405
|
-
console.log(`${displayIndent}${colors.textDim(` Skill: ${skillName}`)}`);
|
|
1406
|
-
if (result?.message) {
|
|
1407
|
-
console.log(`${displayIndent}${colors.textDim(` ${result.message}`)}`);
|
|
1408
|
-
}
|
|
2407
|
+
console.log(`${displayIndent}${colors.textDim('(no result)')}`);
|
|
1409
2408
|
}
|
|
1410
|
-
} else if (showToolDetails) {
|
|
1411
|
-
console.log('');
|
|
1412
|
-
console.log(`${displayIndent}${colors.success(`${icons.check} Tool Result:`)}`);
|
|
1413
|
-
console.log(`${displayIndent}${colors.textDim(JSON.stringify(result, null, 2))}`);
|
|
1414
|
-
} else if (result && result.success === false) {
|
|
1415
|
-
// GUI task or other tool failed
|
|
1416
|
-
console.log(`${displayIndent}${colors.error(`${icons.cross} ${result.message || 'Failed'}`)}`);
|
|
1417
|
-
} else if (result) {
|
|
1418
|
-
// Show brief preview by default (consistent with subagent behavior)
|
|
1419
|
-
const resultPreview = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
1420
|
-
const truncatedPreview = resultPreview.length > 200 ? resultPreview.substring(0, 200) + '...' : resultPreview;
|
|
1421
|
-
// Indent the preview
|
|
1422
|
-
const indentedPreview = truncatedPreview.split('\n').map(line => `${displayIndent} ${line}`).join('\n');
|
|
1423
|
-
console.log(`${indentedPreview}`);
|
|
1424
|
-
} else {
|
|
1425
|
-
console.log(`${displayIndent}${colors.textDim('(no result)')}`);
|
|
1426
2409
|
}
|
|
1427
2410
|
|
|
1428
2411
|
const toolCallRecord: ToolCall = {
|
|
1429
2412
|
tool,
|
|
1430
2413
|
params,
|
|
1431
2414
|
result,
|
|
1432
|
-
timestamp: Date.now()
|
|
2415
|
+
timestamp: Date.now(),
|
|
1433
2416
|
};
|
|
1434
2417
|
|
|
1435
|
-
this.
|
|
2418
|
+
this.tool_calls.push(toolCallRecord);
|
|
1436
2419
|
|
|
1437
2420
|
// Record tool output to session manager
|
|
1438
2421
|
await this.sessionManager.addOutput({
|
|
@@ -1441,82 +2424,122 @@ export class InteractiveSession {
|
|
|
1441
2424
|
toolName: tool,
|
|
1442
2425
|
toolParams: params,
|
|
1443
2426
|
toolResult: result,
|
|
1444
|
-
timestamp: Date.now()
|
|
2427
|
+
timestamp: Date.now(),
|
|
1445
2428
|
});
|
|
1446
2429
|
|
|
1447
|
-
//
|
|
2430
|
+
// Unified message format with tool name and params
|
|
2431
|
+
// Format: OpenAI-compatible tool result with plain text content
|
|
1448
2432
|
this.conversation.push({
|
|
1449
2433
|
role: 'tool',
|
|
1450
|
-
content: JSON.stringify(
|
|
1451
|
-
name: tool,
|
|
1452
|
-
parameters: params,
|
|
1453
|
-
result: result
|
|
1454
|
-
}),
|
|
2434
|
+
content: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
|
|
1455
2435
|
tool_call_id: toolCall.id,
|
|
1456
|
-
timestamp: Date.now()
|
|
2436
|
+
timestamp: Date.now(),
|
|
1457
2437
|
});
|
|
1458
2438
|
}
|
|
1459
2439
|
}
|
|
1460
2440
|
|
|
1461
2441
|
// Logic: Only skip returning results to main agent when user explicitly cancelled (ESC)
|
|
1462
2442
|
// For all other cases (success, failure, errors), always return results for further processing
|
|
1463
|
-
const guiSubagentCancelled = preparedToolCalls.some(
|
|
2443
|
+
const guiSubagentCancelled = preparedToolCalls.some(
|
|
2444
|
+
(tc) =>
|
|
2445
|
+
tc.name === 'task' &&
|
|
2446
|
+
tc.params?.subagent_type === 'gui-subagent' &&
|
|
2447
|
+
results.some((r) => r.tool === 'task' && (r.result as any)?.cancelled === true)
|
|
2448
|
+
);
|
|
1464
2449
|
|
|
1465
2450
|
// If GUI agent was cancelled by user, don't continue generating response
|
|
1466
2451
|
// This avoids wasting API calls and tokens on cancelled tasks
|
|
1467
2452
|
if (guiSubagentCancelled) {
|
|
1468
|
-
console.log('');
|
|
1469
|
-
console.log(`${indent}${colors.textMuted('GUI task cancelled by user')}`);
|
|
1470
2453
|
(this as any)._isOperationInProgress = false;
|
|
1471
2454
|
return;
|
|
1472
2455
|
}
|
|
1473
2456
|
|
|
1474
|
-
//
|
|
1475
|
-
if (hasError) {
|
|
1476
|
-
(this as any)._isOperationInProgress = false;
|
|
1477
|
-
// 不再抛出异常,而是将错误结果返回给 AI,让 AI 决定如何处理
|
|
1478
|
-
// 这样可以避免工具错误导致程序退出
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// Continue based on mode - 统一处理,无论是否有错误
|
|
2457
|
+
// Continue based on mode - unified handling for both success and error cases
|
|
1482
2458
|
if (onComplete) {
|
|
2459
|
+
await this.checkAndCompressContext();
|
|
1483
2460
|
// Remote mode: use provided callback
|
|
1484
2461
|
await onComplete();
|
|
1485
2462
|
} else {
|
|
2463
|
+
await this.checkAndCompressContext();
|
|
1486
2464
|
// Local mode: default behavior - continue with generateResponse
|
|
1487
2465
|
await this.generateResponse();
|
|
1488
2466
|
}
|
|
1489
2467
|
}
|
|
1490
2468
|
|
|
2469
|
+
/**
|
|
2470
|
+
* Clean up incomplete tool calls from conversation after cancellation
|
|
2471
|
+
* This removes assistant messages with tool_calls that don't have corresponding tool_results
|
|
2472
|
+
*/
|
|
2473
|
+
private async cleanupIncompleteToolCalls(): Promise<void> {
|
|
2474
|
+
// 从后往前找到包含 tool_calls 的 assistant 消息
|
|
2475
|
+
for (let i = this.conversation.length - 1; i >= 0; i--) {
|
|
2476
|
+
const msg = this.conversation[i];
|
|
2477
|
+
|
|
2478
|
+
if (msg.role === 'assistant' && msg.tool_calls?.length) {
|
|
2479
|
+
// 收集所有 tool_call IDs
|
|
2480
|
+
const allToolCallIds = new Set(msg.tool_calls.map((tc: any) => tc.id));
|
|
2481
|
+
|
|
2482
|
+
// 找出哪些 tool_call IDs 已经有对应的 tool_result
|
|
2483
|
+
const completedToolCallIds = new Set<string>();
|
|
2484
|
+
for (let k = i + 1; k < this.conversation.length; k++) {
|
|
2485
|
+
const resultMsg = this.conversation[k];
|
|
2486
|
+
if (resultMsg.role === 'tool' && resultMsg.tool_call_id) {
|
|
2487
|
+
completedToolCallIds.add(resultMsg.tool_call_id);
|
|
2488
|
+
} else if (resultMsg.role === 'user' || resultMsg.role === 'assistant') {
|
|
2489
|
+
break; // 遇到下一个角色消息就停止
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
// 找出未完成的 tool_call IDs
|
|
2494
|
+
const incompleteToolCallIds = [...allToolCallIds].filter(
|
|
2495
|
+
id => !completedToolCallIds.has(id)
|
|
2496
|
+
);
|
|
2497
|
+
|
|
2498
|
+
// 如果所有 tool_call 都已完成,不需要清理
|
|
2499
|
+
if (incompleteToolCallIds.length === 0) {
|
|
2500
|
+
break;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// 只移除未完成的 tool_call,不移除已完成的 tool_result
|
|
2504
|
+
msg.tool_calls = msg.tool_calls.filter(
|
|
2505
|
+
(tc: any) => !incompleteToolCallIds.includes(tc.id)
|
|
2506
|
+
);
|
|
2507
|
+
|
|
2508
|
+
break;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
1491
2513
|
/**
|
|
1492
2514
|
* Get user-friendly description for tool
|
|
1493
2515
|
*/
|
|
1494
2516
|
private getToolDescription(toolName: string, params: any): string {
|
|
1495
2517
|
const descriptions: Record<string, (params: any) => string> = {
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
2518
|
+
Read: (p) => `Read file: ${this.truncatePath(p.filePath)}`,
|
|
2519
|
+
Write: (p) => `Write file: ${this.truncatePath(p.filePath)}`,
|
|
2520
|
+
Grep: (p) => `Search text: "${p.pattern}"`,
|
|
2521
|
+
Bash: (p) => `Execute command: ${this.truncateCommand(p.command)}`,
|
|
2522
|
+
ListDirectory: (p) => `List directory: ${this.truncatePath(p.path || '.')}`,
|
|
2523
|
+
SearchFiles: (p) => `Search files: ${p.pattern}`,
|
|
2524
|
+
DeleteFile: (p) => `Delete file: ${this.truncatePath(p.filePath)}`,
|
|
2525
|
+
CreateDirectory: (p) => `Create directory: ${this.truncatePath(p.dirPath)}`,
|
|
2526
|
+
Edit: (p) => `Edit text: ${this.truncatePath(p.file_path)}`,
|
|
2527
|
+
web_search: (p) => `Web search: "${p.query}"`,
|
|
2528
|
+
todo_write: () => `Update todo list`,
|
|
2529
|
+
todo_read: () => `Read todo list`,
|
|
2530
|
+
task: (p) => `Launch subtask: ${p.description}`,
|
|
2531
|
+
ReadBashOutput: (p) => `Read task output: ${p.task_id}`,
|
|
2532
|
+
web_fetch: () => `Fetch web content`,
|
|
2533
|
+
ask_user_question: () => `Ask user`,
|
|
2534
|
+
save_memory: () => `Save memory`,
|
|
2535
|
+
exit_plan_mode: () => `Complete plan`,
|
|
2536
|
+
xml_escape: (p) => `XML escape: ${this.truncatePath(p.file_path)}`,
|
|
2537
|
+
image_read: (p) => `Read image: ${this.truncatePath(p.image_input)}`,
|
|
1516
2538
|
// 'Skill': (p) => `Execute skill: ${p.skill}`,
|
|
1517
2539
|
// 'ListSkills': () => `List available skills`,
|
|
1518
2540
|
// 'GetSkillDetails': (p) => `Get skill details: ${p.skill}`,
|
|
1519
|
-
|
|
2541
|
+
InvokeSkill: (p) =>
|
|
2542
|
+
`Invoke skill: ${p.skillId} - ${this.truncatePath(p.taskDescription || '', 40)}`,
|
|
1520
2543
|
};
|
|
1521
2544
|
|
|
1522
2545
|
const getDescription = descriptions[toolName];
|
|
@@ -1549,11 +2572,14 @@ export class InteractiveSession {
|
|
|
1549
2572
|
return `${indent}${colors.textMuted('No tasks')}`;
|
|
1550
2573
|
}
|
|
1551
2574
|
|
|
1552
|
-
const statusConfig: Record<
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
2575
|
+
const statusConfig: Record<
|
|
2576
|
+
string,
|
|
2577
|
+
{ icon: string; color: (text: string) => string; label: string }
|
|
2578
|
+
> = {
|
|
2579
|
+
pending: { icon: icons.circle, color: colors.textMuted, label: 'Pending' },
|
|
2580
|
+
in_progress: { icon: icons.loading, color: colors.warning, label: 'In Progress' },
|
|
2581
|
+
completed: { icon: icons.success, color: colors.success, label: 'Completed' },
|
|
2582
|
+
failed: { icon: icons.error, color: colors.error, label: 'Failed' },
|
|
1557
2583
|
};
|
|
1558
2584
|
|
|
1559
2585
|
const lines: string[] = [];
|
|
@@ -1592,18 +2618,18 @@ export class InteractiveSession {
|
|
|
1592
2618
|
// const messages = data as any[];
|
|
1593
2619
|
// const tools = extra as any[];
|
|
1594
2620
|
//
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2621
|
+
// // System prompt
|
|
2622
|
+
// const systemMsg = messages.find((m: any) => m.role === 'system');
|
|
2623
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🟫 SYSTEM: ' +
|
|
2624
|
+
// colors.textMuted(systemMsg?.content?.toString().substring(0, 50) || '(none)') + ' '.repeat(3) + colors.border(boxChar.vertical));
|
|
2625
|
+
//
|
|
2626
|
+
// // Messages count
|
|
2627
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 💬 MESSAGES: ' +
|
|
2628
|
+
// colors.text(messages.length.toString()) + ' items' + ' '.repeat(40) + colors.border(boxChar.vertical));
|
|
2629
|
+
//
|
|
2630
|
+
// // Tools count
|
|
2631
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOLS: ' +
|
|
2632
|
+
// colors.text((tools?.length || 0).toString()) + '' + ' '.repeat(43) + colors.border(boxChar.vertical)); //
|
|
1607
2633
|
// // Show last 2 messages
|
|
1608
2634
|
// const recentMessages = messages.slice(-2);
|
|
1609
2635
|
// for (const msg of recentMessages) {
|
|
@@ -1627,8 +2653,8 @@ export class InteractiveSession {
|
|
|
1627
2653
|
// colors.text(`Prompt: ${response.usage?.prompt_tokens || '?'}, Completion: ${response.usage?.completion_tokens || '?'}`) +
|
|
1628
2654
|
// ' '.repeat(15) + colors.border(boxChar.vertical));
|
|
1629
2655
|
//
|
|
1630
|
-
|
|
1631
|
-
|
|
2656
|
+
// console.log(colors.border(`${boxChar.vertical}`) + ' 🔧 TOOL_CALLS: ' +
|
|
2657
|
+
// colors.text((message.tool_calls?.length || 0).toString()) + '' + ' '.repeat(37) + colors.border(boxChar.vertical));
|
|
1632
2658
|
//
|
|
1633
2659
|
// // Content preview
|
|
1634
2660
|
// const contentStr = typeof message.content === 'string'
|
|
@@ -1676,7 +2702,174 @@ export class InteractiveSession {
|
|
|
1676
2702
|
}
|
|
1677
2703
|
}
|
|
1678
2704
|
|
|
2705
|
+
/**
|
|
2706
|
+
* Clean up stale temporary workspaces from previous sessions.
|
|
2707
|
+
* Called at startup to ensure no leftover temp files accumulate.
|
|
2708
|
+
*/
|
|
2709
|
+
async function cleanupStaleWorkspaces(): Promise<void> {
|
|
2710
|
+
try {
|
|
2711
|
+
const configManager = getConfigManager();
|
|
2712
|
+
const workspacePath = configManager.getWorkspacePath();
|
|
2713
|
+
// Use default workspace path if workspacePath is empty or undefined
|
|
2714
|
+
const effectiveWorkspacePath = (workspacePath && workspacePath.trim())
|
|
2715
|
+
? workspacePath
|
|
2716
|
+
: os.homedir();
|
|
2717
|
+
const baseWorkspaceDir = path.join(effectiveWorkspacePath, '.xagent', 'workspace');
|
|
2718
|
+
|
|
2719
|
+
// First, verify the workspace directory exists before attempting cleanup
|
|
2720
|
+
try {
|
|
2721
|
+
const stats = await fsPromises.stat(baseWorkspaceDir);
|
|
2722
|
+
if (!stats.isDirectory()) {
|
|
2723
|
+
return; // Not a directory, skip cleanup
|
|
2724
|
+
}
|
|
2725
|
+
} catch {
|
|
2726
|
+
// Directory doesn't exist - nothing to clean
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
try {
|
|
2731
|
+
const entries = await fsPromises.readdir(baseWorkspaceDir, { withFileTypes: true });
|
|
2732
|
+
|
|
2733
|
+
let cleanedCount = 0;
|
|
2734
|
+
for (const entry of entries) {
|
|
2735
|
+
if (entry.isDirectory()) {
|
|
2736
|
+
const fullPath = path.join(baseWorkspaceDir, entry.name);
|
|
2737
|
+
try {
|
|
2738
|
+
await fsPromises.rm(fullPath, { recursive: true, force: true });
|
|
2739
|
+
cleanedCount++;
|
|
2740
|
+
} catch {
|
|
2741
|
+
// Skip directories that can't be removed (in use or permission issues)
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
if (cleanedCount > 0) {
|
|
2747
|
+
console.log(colors.textDim(`🧹 Cleaned up ${cleanedCount} stale workspace(s)`));
|
|
2748
|
+
}
|
|
2749
|
+
} catch {
|
|
2750
|
+
// Can't read directory - skip cleanup
|
|
2751
|
+
}
|
|
2752
|
+
} catch {
|
|
2753
|
+
// Config not available - skip cleanup
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
/**
|
|
2758
|
+
* Initialize built-in skills on first run.
|
|
2759
|
+
* Checks if user skills directory is empty or doesn't exist,
|
|
2760
|
+
* then copies all built-in skills including the protected find-skills.
|
|
2761
|
+
* @returns Number of skills initialized, or 0 if no initialization was needed.
|
|
2762
|
+
*/
|
|
2763
|
+
async function initializeSkillsOnDemand(): Promise<number> {
|
|
2764
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
2765
|
+
const __dirname = path.dirname(__filename);
|
|
2766
|
+
|
|
2767
|
+
// Get user skills directory (respects OS-specific paths)
|
|
2768
|
+
const configManager = getConfigManager();
|
|
2769
|
+
const userSkillsPath = configManager.getUserSkillsPath() || path.join(os.homedir(), '.xagent', 'skills');
|
|
2770
|
+
|
|
2771
|
+
// Check if user skills directory exists and has skills
|
|
2772
|
+
let hasSkills = false;
|
|
2773
|
+
try {
|
|
2774
|
+
const entries = await fsPromises.readdir(userSkillsPath, { withFileTypes: true });
|
|
2775
|
+
hasSkills = entries.some(e => e.isDirectory());
|
|
2776
|
+
} catch {
|
|
2777
|
+
hasSkills = false;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// If skills already exist, skip initialization
|
|
2781
|
+
if (hasSkills) {
|
|
2782
|
+
return 0;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
// Ensure user skills directory exists
|
|
2786
|
+
await fsPromises.mkdir(userSkillsPath, { recursive: true });
|
|
2787
|
+
|
|
2788
|
+
// Define skill source directories
|
|
2789
|
+
const builtinSkillsDir = path.join(__dirname, '..', 'skills', 'skills');
|
|
2790
|
+
const findSkillsDir = path.join(__dirname, '..', 'find-skills');
|
|
2791
|
+
|
|
2792
|
+
const skillsToInstall: { source: string; name: string }[] = [];
|
|
2793
|
+
|
|
2794
|
+
// Add find-skills from root directory
|
|
2795
|
+
if (fs.existsSync(findSkillsDir) && fs.existsSync(path.join(findSkillsDir, 'SKILL.md'))) {
|
|
2796
|
+
skillsToInstall.push({ source: findSkillsDir, name: 'find-skills' });
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// Add skills from skills/skills directory
|
|
2800
|
+
if (fs.existsSync(builtinSkillsDir)) {
|
|
2801
|
+
const entries = fs.readdirSync(builtinSkillsDir, { withFileTypes: true });
|
|
2802
|
+
for (const entry of entries) {
|
|
2803
|
+
if (entry.isDirectory()) {
|
|
2804
|
+
const skillPath = path.join(builtinSkillsDir, entry.name);
|
|
2805
|
+
if (fs.existsSync(path.join(skillPath, 'SKILL.md'))) {
|
|
2806
|
+
skillsToInstall.push({ source: skillPath, name: entry.name });
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
if (skillsToInstall.length === 0) {
|
|
2813
|
+
return 0;
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// Create user skills directory (already done above, but ensure it exists)
|
|
2817
|
+
await fsPromises.mkdir(userSkillsPath, { recursive: true });
|
|
2818
|
+
|
|
2819
|
+
// Copy all skills
|
|
2820
|
+
for (const { source, name } of skillsToInstall) {
|
|
2821
|
+
const destPath = path.join(userSkillsPath, name);
|
|
2822
|
+
if (!fs.existsSync(destPath)) {
|
|
2823
|
+
await copyDirectoryRecursiveAsync(source, destPath);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
return skillsToInstall.length;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
// Synchronous version (kept for backwards compatibility)
|
|
2831
|
+
function _copyDirectoryRecursive(src: string, dest: string): void {
|
|
2832
|
+
if (!fs.existsSync(dest)) {
|
|
2833
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
2837
|
+
for (const entry of entries) {
|
|
2838
|
+
const srcPath = path.join(src, entry.name);
|
|
2839
|
+
const destPath = path.join(dest, entry.name);
|
|
2840
|
+
|
|
2841
|
+
if (entry.isDirectory()) {
|
|
2842
|
+
_copyDirectoryRecursive(srcPath, destPath);
|
|
2843
|
+
} else if (entry.isFile()) {
|
|
2844
|
+
fs.copyFileSync(srcPath, destPath);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
// Asynchronous version for concurrent-safe initialization
|
|
2850
|
+
async function copyDirectoryRecursiveAsync(src: string, dest: string): Promise<void> {
|
|
2851
|
+
await fsPromises.mkdir(dest, { recursive: true });
|
|
2852
|
+
const entries = await fsPromises.readdir(src, { withFileTypes: true });
|
|
2853
|
+
|
|
2854
|
+
for (const entry of entries) {
|
|
2855
|
+
const srcPath = path.join(src, entry.name);
|
|
2856
|
+
const destPath = path.join(dest, entry.name);
|
|
2857
|
+
|
|
2858
|
+
if (entry.isDirectory()) {
|
|
2859
|
+
await copyDirectoryRecursiveAsync(srcPath, destPath);
|
|
2860
|
+
} else if (entry.isFile()) {
|
|
2861
|
+
await fsPromises.copyFile(srcPath, destPath);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
|
|
1679
2866
|
export async function startInteractiveSession(): Promise<void> {
|
|
2867
|
+
// Clean up any leftover temp workspaces from previous sessions
|
|
2868
|
+
await cleanupStaleWorkspaces();
|
|
2869
|
+
|
|
2870
|
+
// Initialize built-in skills on first run (silent, returns count)
|
|
2871
|
+
const initializedCount = await initializeSkillsOnDemand();
|
|
2872
|
+
|
|
1680
2873
|
const session = new InteractiveSession();
|
|
1681
2874
|
|
|
1682
2875
|
// Flag to control shutdown
|
|
@@ -1721,6 +2914,15 @@ export async function startInteractiveSession(): Promise<void> {
|
|
|
1721
2914
|
process.exit(0);
|
|
1722
2915
|
});
|
|
1723
2916
|
|
|
2917
|
+
await session.start(initializedCount);
|
|
2918
|
+
// Check for updates on startup
|
|
2919
|
+
try {
|
|
2920
|
+
const { checkUpdatesOnStartup } = await import('./update.js');
|
|
2921
|
+
await checkUpdatesOnStartup();
|
|
2922
|
+
} catch {
|
|
2923
|
+
// Silently ignore update check failures
|
|
2924
|
+
}
|
|
2925
|
+
|
|
1724
2926
|
await session.start();
|
|
1725
2927
|
}
|
|
1726
2928
|
|