@umsai/ums-code 0.3.0-v1 → 0.5.0-v1
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/dist/package.json +4 -4
- package/dist/src/{zed-integration → acp-integration}/acp.d.ts +7 -0
- package/dist/src/{zed-integration → acp-integration}/acp.js +25 -0
- package/dist/src/acp-integration/acp.js.map +1 -0
- package/dist/src/acp-integration/acpAgent.d.ts +10 -0
- package/dist/src/acp-integration/acpAgent.js +238 -0
- package/dist/src/acp-integration/acpAgent.js.map +1 -0
- package/dist/src/{zed-integration → acp-integration}/schema.d.ts +1030 -43
- package/dist/src/{zed-integration → acp-integration}/schema.js +81 -2
- package/dist/src/acp-integration/schema.js.map +1 -0
- package/dist/src/{zed-integration/fileSystemService.d.ts → acp-integration/service/filesystem.d.ts} +1 -1
- package/dist/src/{zed-integration/fileSystemService.js → acp-integration/service/filesystem.js} +14 -1
- package/dist/src/acp-integration/service/filesystem.js.map +1 -0
- package/dist/src/acp-integration/service/filesystem.test.d.ts +6 -0
- package/dist/src/acp-integration/service/filesystem.test.js +39 -0
- package/dist/src/acp-integration/service/filesystem.test.js.map +1 -0
- package/dist/src/acp-integration/session/HistoryReplayer.d.ts +51 -0
- package/dist/src/acp-integration/session/HistoryReplayer.js +164 -0
- package/dist/src/acp-integration/session/HistoryReplayer.js.map +1 -0
- package/dist/src/acp-integration/session/HistoryReplayer.test.d.ts +6 -0
- package/dist/src/acp-integration/session/HistoryReplayer.test.js +374 -0
- package/dist/src/acp-integration/session/HistoryReplayer.test.js.map +1 -0
- package/dist/src/acp-integration/session/Session.d.ts +66 -0
- package/dist/src/acp-integration/session/Session.js +760 -0
- package/dist/src/acp-integration/session/Session.js.map +1 -0
- package/dist/src/acp-integration/session/SubAgentTracker.d.ts +51 -0
- package/dist/src/acp-integration/session/SubAgentTracker.js +257 -0
- package/dist/src/acp-integration/session/SubAgentTracker.js.map +1 -0
- package/dist/src/acp-integration/session/SubAgentTracker.test.d.ts +6 -0
- package/dist/src/acp-integration/session/SubAgentTracker.test.js +369 -0
- package/dist/src/acp-integration/session/SubAgentTracker.test.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/BaseEmitter.d.ts +27 -0
- package/dist/src/acp-integration/session/emitters/BaseEmitter.js +34 -0
- package/dist/src/acp-integration/session/emitters/BaseEmitter.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/MessageEmitter.d.ts +41 -0
- package/dist/src/acp-integration/session/emitters/MessageEmitter.js +77 -0
- package/dist/src/acp-integration/session/emitters/MessageEmitter.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/MessageEmitter.test.d.ts +6 -0
- package/dist/src/acp-integration/session/emitters/MessageEmitter.test.js +174 -0
- package/dist/src/acp-integration/session/emitters/MessageEmitter.test.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/PlanEmitter.d.ts +39 -0
- package/dist/src/acp-integration/session/emitters/PlanEmitter.js +83 -0
- package/dist/src/acp-integration/session/emitters/PlanEmitter.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/PlanEmitter.test.d.ts +6 -0
- package/dist/src/acp-integration/session/emitters/PlanEmitter.test.js +176 -0
- package/dist/src/acp-integration/session/emitters/PlanEmitter.test.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/ToolCallEmitter.d.ts +80 -0
- package/dist/src/acp-integration/session/emitters/ToolCallEmitter.js +248 -0
- package/dist/src/acp-integration/session/emitters/ToolCallEmitter.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/ToolCallEmitter.test.d.ts +6 -0
- package/dist/src/acp-integration/session/emitters/ToolCallEmitter.test.js +561 -0
- package/dist/src/acp-integration/session/emitters/ToolCallEmitter.test.js.map +1 -0
- package/dist/src/acp-integration/session/emitters/index.d.ts +9 -0
- package/dist/src/acp-integration/session/emitters/index.js +10 -0
- package/dist/src/acp-integration/session/emitters/index.js.map +1 -0
- package/dist/src/acp-integration/session/index.d.ts +24 -0
- package/dist/src/acp-integration/session/index.js +16 -0
- package/dist/src/acp-integration/session/index.js.map +1 -0
- package/dist/src/acp-integration/session/types.d.ts +71 -0
- package/dist/src/acp-integration/session/types.js +7 -0
- package/dist/src/acp-integration/session/types.js.map +1 -0
- package/dist/src/config/auth.d.ts +1 -0
- package/dist/src/config/auth.js +3 -0
- package/dist/src/config/auth.js.map +1 -1
- package/dist/src/config/config.d.ts +11 -3
- package/dist/src/config/config.js +84 -8
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/extension.js +0 -2
- package/dist/src/config/extension.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +12 -0
- package/dist/src/config/settingsSchema.js +10 -0
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/core/auth.d.ts +1 -1
- package/dist/src/core/auth.js +3 -2
- package/dist/src/core/auth.js.map +1 -1
- package/dist/src/gemini.js +38 -18
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +7 -0
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/i18n/index.d.ts +1 -1
- package/dist/src/i18n/index.js +4 -0
- package/dist/src/i18n/index.js.map +1 -1
- package/dist/src/i18n/locales/en.js +1 -13
- package/dist/src/i18n/locales/ru.js +1121 -0
- package/dist/src/i18n/locales/zh.js +1 -10
- package/dist/src/nonInteractive/control/ControlDispatcher.d.ts +24 -1
- package/dist/src/nonInteractive/control/ControlDispatcher.js +46 -17
- package/dist/src/nonInteractive/control/ControlDispatcher.js.map +1 -1
- package/dist/src/nonInteractive/control/ControlService.d.ts +2 -1
- package/dist/src/nonInteractive/control/ControlService.js +22 -37
- package/dist/src/nonInteractive/control/ControlService.js.map +1 -1
- package/dist/src/nonInteractive/control/controllers/baseController.d.ts +2 -1
- package/dist/src/nonInteractive/control/controllers/baseController.js +38 -5
- package/dist/src/nonInteractive/control/controllers/baseController.js.map +1 -1
- package/dist/src/nonInteractive/control/controllers/permissionController.d.ts +1 -22
- package/dist/src/nonInteractive/control/controllers/permissionController.js +38 -38
- package/dist/src/nonInteractive/control/controllers/permissionController.js.map +1 -1
- package/dist/src/nonInteractive/control/controllers/sdkMcpController.d.ts +54 -0
- package/dist/src/nonInteractive/control/controllers/sdkMcpController.js +84 -0
- package/dist/src/nonInteractive/control/controllers/sdkMcpController.js.map +1 -0
- package/dist/src/nonInteractive/control/controllers/systemController.d.ts +16 -6
- package/dist/src/nonInteractive/control/controllers/systemController.js +189 -44
- package/dist/src/nonInteractive/control/controllers/systemController.js.map +1 -1
- package/dist/src/nonInteractive/control/types/serviceAPIs.d.ts +1 -16
- package/dist/src/nonInteractive/io/BaseJsonOutputAdapter.d.ts +11 -0
- package/dist/src/nonInteractive/io/BaseJsonOutputAdapter.js +54 -2
- package/dist/src/nonInteractive/io/BaseJsonOutputAdapter.js.map +1 -1
- package/dist/src/nonInteractive/session.d.ts +0 -16
- package/dist/src/nonInteractive/session.js +317 -321
- package/dist/src/nonInteractive/session.js.map +1 -1
- package/dist/src/nonInteractive/session.test.js +6 -0
- package/dist/src/nonInteractive/session.test.js.map +1 -1
- package/dist/src/nonInteractive/types.d.ts +57 -3
- package/dist/src/nonInteractive/types.js +0 -1
- package/dist/src/nonInteractive/types.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +25 -46
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/services/BuiltinCommandLoader.js +5 -6
- package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
- package/dist/src/services/BuiltinCommandLoader.test.js +0 -3
- package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
- package/dist/src/test-utils/mockCommandContext.js +11 -2
- package/dist/src/test-utils/mockCommandContext.js.map +1 -1
- package/dist/src/ui/AppContainer.js +39 -36
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/auth/useAuth.js +20 -1
- package/dist/src/ui/auth/useAuth.js.map +1 -1
- package/dist/src/ui/commands/clearCommand.js +22 -10
- package/dist/src/ui/commands/clearCommand.js.map +1 -1
- package/dist/src/ui/commands/languageCommand.js +41 -8
- package/dist/src/ui/commands/languageCommand.js.map +1 -1
- package/dist/src/ui/commands/languageCommand.test.d.ts +6 -0
- package/dist/src/ui/commands/languageCommand.test.js +463 -0
- package/dist/src/ui/commands/languageCommand.test.js.map +1 -0
- package/dist/src/ui/commands/modelCommand.js +4 -2
- package/dist/src/ui/commands/modelCommand.js.map +1 -1
- package/dist/src/ui/commands/quitCommand.d.ts +0 -1
- package/dist/src/ui/commands/quitCommand.js +0 -27
- package/dist/src/ui/commands/quitCommand.js.map +1 -1
- package/dist/src/ui/commands/reviewCommand.d.ts +9 -0
- package/dist/src/ui/commands/reviewCommand.js +1267 -0
- package/dist/src/ui/commands/reviewCommand.js.map +1 -0
- package/dist/src/ui/commands/reviewCommand.test.d.ts +6 -0
- package/dist/src/ui/commands/reviewCommand.test.js +545 -0
- package/dist/src/ui/commands/reviewCommand.test.js.map +1 -0
- package/dist/src/ui/commands/setupGithubCommand.js +11 -10
- package/dist/src/ui/commands/setupGithubCommand.js.map +1 -1
- package/dist/src/ui/commands/setupGithubCommand.test.js +14 -14
- package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -1
- package/dist/src/ui/commands/types.d.ts +4 -9
- package/dist/src/ui/commands/types.js.map +1 -1
- package/dist/src/ui/commands/ums/unittestCommand.d.ts +9 -0
- package/dist/src/ui/commands/ums/unittestCommand.js +387 -0
- package/dist/src/ui/commands/ums/unittestCommand.js.map +1 -0
- package/dist/src/ui/components/Composer.test.js +1 -2
- package/dist/src/ui/components/Composer.test.js.map +1 -1
- package/dist/src/ui/components/ContextUsageDisplay.d.ts +3 -2
- package/dist/src/ui/components/ContextUsageDisplay.js +8 -2
- package/dist/src/ui/components/ContextUsageDisplay.js.map +1 -1
- package/dist/src/ui/components/DialogManager.js +3 -19
- package/dist/src/ui/components/DialogManager.js.map +1 -1
- package/dist/src/ui/components/Footer.js +2 -3
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/Help.js +13 -2
- package/dist/src/ui/components/Help.js.map +1 -1
- package/dist/src/ui/components/Help.test.js +6 -0
- package/dist/src/ui/components/Help.test.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +3 -1
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +6 -4
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.d.ts +3 -1
- package/dist/src/ui/components/ModelDialog.js +25 -5
- package/dist/src/ui/components/ModelDialog.js.map +1 -1
- package/dist/src/ui/components/OpenAIKeyPrompt.d.ts +3 -0
- package/dist/src/ui/components/OpenAIKeyPrompt.js +1 -0
- package/dist/src/ui/components/OpenAIKeyPrompt.js.map +1 -1
- package/dist/src/ui/components/ResumeSessionPicker.d.ts +10 -0
- package/dist/src/ui/components/ResumeSessionPicker.js +249 -0
- package/dist/src/ui/components/ResumeSessionPicker.js.map +1 -0
- package/dist/src/ui/components/SessionSummaryDisplay.js +10 -2
- package/dist/src/ui/components/SessionSummaryDisplay.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.test.js +2 -2
- package/dist/src/ui/components/SuggestionsDisplay.js +3 -2
- package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
- package/dist/src/ui/components/messages/GeminiThoughtMessage.d.ts +18 -0
- package/dist/src/ui/components/messages/GeminiThoughtMessage.js +14 -0
- package/dist/src/ui/components/messages/GeminiThoughtMessage.js.map +1 -0
- package/dist/src/ui/components/messages/GeminiThoughtMessageContent.d.ts +18 -0
- package/dist/src/ui/components/messages/GeminiThoughtMessageContent.js +14 -0
- package/dist/src/ui/components/messages/GeminiThoughtMessageContent.js.map +1 -0
- package/dist/src/ui/components/subagents/manage/AgentEditStep.js +4 -1
- package/dist/src/ui/components/subagents/manage/AgentEditStep.js.map +1 -1
- package/dist/src/ui/components/subagents/manage/AgentSelectionStep.js +2 -2
- package/dist/src/ui/components/subagents/manage/AgentSelectionStep.js.map +1 -1
- package/dist/src/ui/components/ums/UMSKeyPrompt.d.ts +1 -1
- package/dist/src/ui/components/ums/UMSKeyPrompt.js +23 -3
- package/dist/src/ui/components/ums/UMSKeyPrompt.js.map +1 -1
- package/dist/src/ui/components/ums/umsStartupWarnings.d.ts +6 -0
- package/dist/src/ui/components/ums/umsStartupWarnings.js +47 -0
- package/dist/src/ui/components/ums/umsStartupWarnings.js.map +1 -0
- package/dist/src/ui/contexts/SessionContext.d.ts +2 -0
- package/dist/src/ui/contexts/SessionContext.js +18 -10
- package/dist/src/ui/contexts/SessionContext.js.map +1 -1
- package/dist/src/ui/contexts/UIStateContext.d.ts +1 -3
- package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.d.ts +2 -8
- package/dist/src/ui/hooks/slashCommandProcessor.js +68 -101
- package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useAttentionNotifications.d.ts +3 -1
- package/dist/src/ui/hooks/useAttentionNotifications.js +10 -5
- package/dist/src/ui/hooks/useAttentionNotifications.js.map +1 -1
- package/dist/src/ui/hooks/useAttentionNotifications.test.js +39 -2
- package/dist/src/ui/hooks/useAttentionNotifications.test.js.map +1 -1
- package/dist/src/ui/hooks/useDialogClose.d.ts +0 -3
- package/dist/src/ui/hooks/useDialogClose.js +0 -2
- package/dist/src/ui/hooks/useDialogClose.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +49 -9
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useLogger.d.ts +1 -1
- package/dist/src/ui/hooks/useLogger.js +6 -3
- package/dist/src/ui/hooks/useLogger.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +2 -2
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/useSlashCompletion.js +9 -1
- package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
- package/dist/src/ui/hooks/useSlashCompletion.test.js +36 -31
- package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +1 -0
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/models/availableModels.d.ts +8 -2
- package/dist/src/ui/models/availableModels.js +24 -35
- package/dist/src/ui/models/availableModels.js.map +1 -1
- package/dist/src/ui/noninteractive/nonInteractiveUi.js +0 -1
- package/dist/src/ui/noninteractive/nonInteractiveUi.js.map +1 -1
- package/dist/src/ui/types.d.ts +9 -14
- package/dist/src/ui/types.js +0 -1
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/ui/utils/InlineMarkdownRenderer.d.ts +1 -0
- package/dist/src/ui/utils/InlineMarkdownRenderer.js +2 -2
- package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
- package/dist/src/ui/utils/MarkdownDisplay.d.ts +1 -0
- package/dist/src/ui/utils/MarkdownDisplay.js +13 -13
- package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
- package/dist/src/ui/utils/formatters.d.ts +6 -0
- package/dist/src/ui/utils/formatters.js +31 -0
- package/dist/src/ui/utils/formatters.js.map +1 -1
- package/dist/src/ui/utils/formatters.test.js +51 -2
- package/dist/src/ui/utils/formatters.test.js.map +1 -1
- package/dist/src/ui/utils/resumeHistoryUtils.d.ts +19 -0
- package/dist/src/ui/utils/resumeHistoryUtils.js +263 -0
- package/dist/src/ui/utils/resumeHistoryUtils.js.map +1 -0
- package/dist/src/ui/utils/resumeHistoryUtils.test.d.ts +6 -0
- package/dist/src/ui/utils/resumeHistoryUtils.test.js +248 -0
- package/dist/src/ui/utils/resumeHistoryUtils.test.js.map +1 -0
- package/dist/src/utils/attentionNotification.d.ts +1 -0
- package/dist/src/utils/attentionNotification.js +4 -0
- package/dist/src/utils/attentionNotification.js.map +1 -1
- package/dist/src/utils/gitUtils.js +3 -3
- package/dist/src/utils/gitUtils.js.map +1 -1
- package/dist/src/utils/nonInteractiveHelpers.js +1 -1
- package/dist/src/utils/nonInteractiveHelpers.js.map +1 -1
- package/dist/src/utils/nonInteractiveHelpers.test.js +1 -1
- package/dist/src/utils/nonInteractiveHelpers.test.js.map +1 -1
- package/dist/src/validateNonInterActiveAuth.js +1 -1
- package/dist/src/validateNonInterActiveAuth.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/dist/src/nonInteractive/control/controllers/mcpController.d.ts +0 -42
- package/dist/src/nonInteractive/control/controllers/mcpController.js +0 -205
- package/dist/src/nonInteractive/control/controllers/mcpController.js.map +0 -1
- package/dist/src/ui/commands/chatCommand.d.ts +0 -9
- package/dist/src/ui/commands/chatCommand.js +0 -351
- package/dist/src/ui/commands/chatCommand.js.map +0 -1
- package/dist/src/ui/commands/corgiCommand.d.ts +0 -7
- package/dist/src/ui/commands/corgiCommand.js +0 -16
- package/dist/src/ui/commands/corgiCommand.js.map +0 -1
- package/dist/src/ui/components/QuitConfirmationDialog.d.ts +0 -17
- package/dist/src/ui/components/QuitConfirmationDialog.js +0 -49
- package/dist/src/ui/components/QuitConfirmationDialog.js.map +0 -1
- package/dist/src/ui/hooks/usePromptCompletion.d.ts +0 -23
- package/dist/src/ui/hooks/usePromptCompletion.js +0 -177
- package/dist/src/ui/hooks/usePromptCompletion.js.map +0 -1
- package/dist/src/ui/hooks/useQuitConfirmation.d.ts +0 -14
- package/dist/src/ui/hooks/useQuitConfirmation.js +0 -36
- package/dist/src/ui/hooks/useQuitConfirmation.js.map +0 -1
- package/dist/src/zed-integration/acp.js.map +0 -1
- package/dist/src/zed-integration/fileSystemService.js.map +0 -1
- package/dist/src/zed-integration/schema.js.map +0 -1
- package/dist/src/zed-integration/zedIntegration.d.ts +0 -17
- package/dist/src/zed-integration/zedIntegration.js +0 -1135
- package/dist/src/zed-integration/zedIntegration.js.map +0 -1
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Qwen
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { ApprovalMode, convertToFunctionResponse, DiscoveredMCPTool, StreamEventType, ToolConfirmationOutcome, logToolCall, logUserPrompt, getErrorStatus, isWithinRoot, isNodeError, TaskTool, UserPromptEvent, TodoWriteTool, ExitPlanModeTool, } from '@umsai/ums-code-core';
|
|
7
|
+
import * as acp from '../acp.js';
|
|
8
|
+
import * as fs from 'node:fs/promises';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
12
|
+
import { handleSlashCommand, getAvailableCommands, } from '../../nonInteractiveCliCommands.js';
|
|
13
|
+
import { isSlashCommand } from '../../ui/utils/commandUtils.js';
|
|
14
|
+
import { HistoryReplayer } from './HistoryReplayer.js';
|
|
15
|
+
import { ToolCallEmitter } from './emitters/ToolCallEmitter.js';
|
|
16
|
+
import { PlanEmitter } from './emitters/PlanEmitter.js';
|
|
17
|
+
import { MessageEmitter } from './emitters/MessageEmitter.js';
|
|
18
|
+
import { SubAgentTracker } from './SubAgentTracker.js';
|
|
19
|
+
/**
|
|
20
|
+
* Built-in commands that are allowed in ACP integration mode.
|
|
21
|
+
* Only safe, read-only commands that don't require interactive UI.
|
|
22
|
+
*/
|
|
23
|
+
export const ALLOWED_BUILTIN_COMMANDS_FOR_ACP = ['init'];
|
|
24
|
+
/**
|
|
25
|
+
* Session represents an active conversation session with the AI model.
|
|
26
|
+
* It uses modular components for consistent event emission:
|
|
27
|
+
* - HistoryReplayer for replaying past conversations
|
|
28
|
+
* - ToolCallEmitter for tool-related session updates
|
|
29
|
+
* - PlanEmitter for todo/plan updates
|
|
30
|
+
* - SubAgentTracker for tracking sub-agent tool calls
|
|
31
|
+
*/
|
|
32
|
+
export class Session {
|
|
33
|
+
chat;
|
|
34
|
+
config;
|
|
35
|
+
client;
|
|
36
|
+
settings;
|
|
37
|
+
pendingPrompt = null;
|
|
38
|
+
turn = 0;
|
|
39
|
+
// Modular components
|
|
40
|
+
historyReplayer;
|
|
41
|
+
toolCallEmitter;
|
|
42
|
+
planEmitter;
|
|
43
|
+
messageEmitter;
|
|
44
|
+
// Implement SessionContext interface
|
|
45
|
+
sessionId;
|
|
46
|
+
constructor(id, chat, config, client, settings) {
|
|
47
|
+
this.chat = chat;
|
|
48
|
+
this.config = config;
|
|
49
|
+
this.client = client;
|
|
50
|
+
this.settings = settings;
|
|
51
|
+
this.sessionId = id;
|
|
52
|
+
// Initialize modular components with this session as context
|
|
53
|
+
this.toolCallEmitter = new ToolCallEmitter(this);
|
|
54
|
+
this.planEmitter = new PlanEmitter(this);
|
|
55
|
+
this.historyReplayer = new HistoryReplayer(this);
|
|
56
|
+
this.messageEmitter = new MessageEmitter(this);
|
|
57
|
+
}
|
|
58
|
+
getId() {
|
|
59
|
+
return this.sessionId;
|
|
60
|
+
}
|
|
61
|
+
getConfig() {
|
|
62
|
+
return this.config;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Replays conversation history to the client using modular components.
|
|
66
|
+
* Delegates to HistoryReplayer for consistent event emission.
|
|
67
|
+
*/
|
|
68
|
+
async replayHistory(records) {
|
|
69
|
+
await this.historyReplayer.replay(records);
|
|
70
|
+
}
|
|
71
|
+
async cancelPendingPrompt() {
|
|
72
|
+
if (!this.pendingPrompt) {
|
|
73
|
+
throw new Error('Not currently generating');
|
|
74
|
+
}
|
|
75
|
+
this.pendingPrompt.abort();
|
|
76
|
+
this.pendingPrompt = null;
|
|
77
|
+
}
|
|
78
|
+
async prompt(params) {
|
|
79
|
+
this.pendingPrompt?.abort();
|
|
80
|
+
const pendingSend = new AbortController();
|
|
81
|
+
this.pendingPrompt = pendingSend;
|
|
82
|
+
// Increment turn counter for each user prompt
|
|
83
|
+
this.turn += 1;
|
|
84
|
+
const chat = this.chat;
|
|
85
|
+
const promptId = this.config.getSessionId() + '########' + this.turn;
|
|
86
|
+
// Extract text from all text blocks to construct the full prompt text for logging
|
|
87
|
+
const promptText = params.prompt
|
|
88
|
+
.filter((block) => block.type === 'text')
|
|
89
|
+
.map((block) => (block.type === 'text' ? block.text : ''))
|
|
90
|
+
.join(' ');
|
|
91
|
+
// Log user prompt
|
|
92
|
+
logUserPrompt(this.config, new UserPromptEvent(promptText.length, promptId, this.config.getContentGeneratorConfig()?.authType, promptText));
|
|
93
|
+
// record user message for session management
|
|
94
|
+
this.config.getChatRecordingService()?.recordUserMessage(promptText);
|
|
95
|
+
// Check if the input contains a slash command
|
|
96
|
+
// Extract text from the first text block if present
|
|
97
|
+
const firstTextBlock = params.prompt.find((block) => block.type === 'text');
|
|
98
|
+
const inputText = firstTextBlock?.text || '';
|
|
99
|
+
let parts;
|
|
100
|
+
if (isSlashCommand(inputText)) {
|
|
101
|
+
// Handle slash command - allow specific built-in commands for ACP integration
|
|
102
|
+
const slashCommandResult = await handleSlashCommand(inputText, pendingSend, this.config, this.settings, ALLOWED_BUILTIN_COMMANDS_FOR_ACP);
|
|
103
|
+
if (slashCommandResult) {
|
|
104
|
+
// Use the result from the slash command
|
|
105
|
+
parts = slashCommandResult;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Slash command didn't return a prompt, continue with normal processing
|
|
109
|
+
parts = await this.#resolvePrompt(params.prompt, pendingSend.signal);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Normal processing for non-slash commands
|
|
114
|
+
parts = await this.#resolvePrompt(params.prompt, pendingSend.signal);
|
|
115
|
+
}
|
|
116
|
+
let nextMessage = { role: 'user', parts };
|
|
117
|
+
while (nextMessage !== null) {
|
|
118
|
+
if (pendingSend.signal.aborted) {
|
|
119
|
+
chat.addHistory(nextMessage);
|
|
120
|
+
return { stopReason: 'cancelled' };
|
|
121
|
+
}
|
|
122
|
+
const functionCalls = [];
|
|
123
|
+
let usageMetadata = null;
|
|
124
|
+
const streamStartTime = Date.now();
|
|
125
|
+
try {
|
|
126
|
+
const responseStream = await chat.sendMessageStream(this.config.getModel(), {
|
|
127
|
+
message: nextMessage?.parts ?? [],
|
|
128
|
+
config: {
|
|
129
|
+
abortSignal: pendingSend.signal,
|
|
130
|
+
},
|
|
131
|
+
}, promptId);
|
|
132
|
+
nextMessage = null;
|
|
133
|
+
for await (const resp of responseStream) {
|
|
134
|
+
if (pendingSend.signal.aborted) {
|
|
135
|
+
return { stopReason: 'cancelled' };
|
|
136
|
+
}
|
|
137
|
+
if (resp.type === StreamEventType.CHUNK &&
|
|
138
|
+
resp.value.candidates &&
|
|
139
|
+
resp.value.candidates.length > 0) {
|
|
140
|
+
const candidate = resp.value.candidates[0];
|
|
141
|
+
for (const part of candidate.content?.parts ?? []) {
|
|
142
|
+
if (!part.text) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
this.messageEmitter.emitMessage(part.text, 'assistant', part.thought);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (resp.type === StreamEventType.CHUNK && resp.value.usageMetadata) {
|
|
149
|
+
usageMetadata = resp.value.usageMetadata;
|
|
150
|
+
}
|
|
151
|
+
if (resp.type === StreamEventType.CHUNK && resp.value.functionCalls) {
|
|
152
|
+
functionCalls.push(...resp.value.functionCalls);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (getErrorStatus(error) === 429) {
|
|
158
|
+
throw new acp.RequestError(429, 'Rate limit exceeded. Try again later.');
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
if (usageMetadata) {
|
|
163
|
+
const durationMs = Date.now() - streamStartTime;
|
|
164
|
+
await this.messageEmitter.emitUsageMetadata(usageMetadata, '', durationMs);
|
|
165
|
+
}
|
|
166
|
+
if (functionCalls.length > 0) {
|
|
167
|
+
const toolResponseParts = [];
|
|
168
|
+
for (const fc of functionCalls) {
|
|
169
|
+
const response = await this.runTool(pendingSend.signal, promptId, fc);
|
|
170
|
+
toolResponseParts.push(...response);
|
|
171
|
+
}
|
|
172
|
+
nextMessage = { role: 'user', parts: toolResponseParts };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { stopReason: 'end_turn' };
|
|
176
|
+
}
|
|
177
|
+
async sendUpdate(update) {
|
|
178
|
+
const params = {
|
|
179
|
+
sessionId: this.sessionId,
|
|
180
|
+
update,
|
|
181
|
+
};
|
|
182
|
+
await this.client.sessionUpdate(params);
|
|
183
|
+
}
|
|
184
|
+
async sendAvailableCommandsUpdate() {
|
|
185
|
+
const abortController = new AbortController();
|
|
186
|
+
try {
|
|
187
|
+
const slashCommands = await getAvailableCommands(this.config, this.settings, abortController.signal, ALLOWED_BUILTIN_COMMANDS_FOR_ACP);
|
|
188
|
+
// Convert SlashCommand[] to AvailableCommand[] format for ACP protocol
|
|
189
|
+
const availableCommands = slashCommands.map((cmd) => ({
|
|
190
|
+
name: cmd.name,
|
|
191
|
+
description: cmd.description,
|
|
192
|
+
input: null,
|
|
193
|
+
}));
|
|
194
|
+
const update = {
|
|
195
|
+
sessionUpdate: 'available_commands_update',
|
|
196
|
+
availableCommands,
|
|
197
|
+
};
|
|
198
|
+
await this.sendUpdate(update);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
// Log error but don't fail session creation
|
|
202
|
+
console.error('Error sending available commands update:', error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Requests permission from the client for a tool call.
|
|
207
|
+
* Used by SubAgentTracker for sub-agent approval requests.
|
|
208
|
+
*/
|
|
209
|
+
async requestPermission(params) {
|
|
210
|
+
return this.client.requestPermission(params);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Sets the approval mode for the current session.
|
|
214
|
+
* Maps ACP approval mode values to core ApprovalMode enum.
|
|
215
|
+
*/
|
|
216
|
+
async setMode(params) {
|
|
217
|
+
const modeMap = {
|
|
218
|
+
plan: ApprovalMode.PLAN,
|
|
219
|
+
default: ApprovalMode.DEFAULT,
|
|
220
|
+
'auto-edit': ApprovalMode.AUTO_EDIT,
|
|
221
|
+
yolo: ApprovalMode.YOLO,
|
|
222
|
+
};
|
|
223
|
+
const approvalMode = modeMap[params.modeId];
|
|
224
|
+
this.config.setApprovalMode(approvalMode);
|
|
225
|
+
return { modeId: params.modeId };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Sends a current_mode_update notification to the client.
|
|
229
|
+
* Called after the agent switches modes (e.g., from exit_plan_mode tool).
|
|
230
|
+
*/
|
|
231
|
+
async sendCurrentModeUpdateNotification(outcome) {
|
|
232
|
+
// Determine the new mode based on the approval outcome
|
|
233
|
+
// This mirrors the logic in ExitPlanModeTool.onConfirm
|
|
234
|
+
let newModeId;
|
|
235
|
+
switch (outcome) {
|
|
236
|
+
case ToolConfirmationOutcome.ProceedAlways:
|
|
237
|
+
newModeId = 'auto-edit';
|
|
238
|
+
break;
|
|
239
|
+
case ToolConfirmationOutcome.ProceedOnce:
|
|
240
|
+
default:
|
|
241
|
+
newModeId = 'default';
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
const update = {
|
|
245
|
+
sessionUpdate: 'current_mode_update',
|
|
246
|
+
modeId: newModeId,
|
|
247
|
+
};
|
|
248
|
+
await this.sendUpdate(update);
|
|
249
|
+
}
|
|
250
|
+
async runTool(abortSignal, promptId, fc) {
|
|
251
|
+
const callId = fc.id ?? `${fc.name}-${Date.now()}`;
|
|
252
|
+
const args = (fc.args ?? {});
|
|
253
|
+
const startTime = Date.now();
|
|
254
|
+
const errorResponse = (error) => {
|
|
255
|
+
const durationMs = Date.now() - startTime;
|
|
256
|
+
logToolCall(this.config, {
|
|
257
|
+
'event.name': 'tool_call',
|
|
258
|
+
'event.timestamp': new Date().toISOString(),
|
|
259
|
+
prompt_id: promptId,
|
|
260
|
+
function_name: fc.name ?? '',
|
|
261
|
+
function_args: args,
|
|
262
|
+
duration_ms: durationMs,
|
|
263
|
+
status: 'error',
|
|
264
|
+
success: false,
|
|
265
|
+
error: error.message,
|
|
266
|
+
tool_type: typeof tool !== 'undefined' && tool instanceof DiscoveredMCPTool
|
|
267
|
+
? 'mcp'
|
|
268
|
+
: 'native',
|
|
269
|
+
});
|
|
270
|
+
return [
|
|
271
|
+
{
|
|
272
|
+
functionResponse: {
|
|
273
|
+
id: callId,
|
|
274
|
+
name: fc.name ?? '',
|
|
275
|
+
response: { error: error.message },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
};
|
|
280
|
+
if (!fc.name) {
|
|
281
|
+
return errorResponse(new Error('Missing function name'));
|
|
282
|
+
}
|
|
283
|
+
const toolRegistry = this.config.getToolRegistry();
|
|
284
|
+
const tool = toolRegistry.getTool(fc.name);
|
|
285
|
+
if (!tool) {
|
|
286
|
+
return errorResponse(new Error(`Tool "${fc.name}" not found in registry.`));
|
|
287
|
+
}
|
|
288
|
+
// Detect TodoWriteTool early - route to plan updates instead of tool_call events
|
|
289
|
+
const isTodoWriteTool = tool.name === TodoWriteTool.Name;
|
|
290
|
+
const isTaskTool = tool.name === TaskTool.Name;
|
|
291
|
+
const isExitPlanModeTool = tool.name === ExitPlanModeTool.Name;
|
|
292
|
+
// Track cleanup functions for sub-agent event listeners
|
|
293
|
+
let subAgentCleanupFunctions = [];
|
|
294
|
+
try {
|
|
295
|
+
const invocation = tool.build(args);
|
|
296
|
+
if (isTaskTool && 'eventEmitter' in invocation) {
|
|
297
|
+
// Access eventEmitter from TaskTool invocation
|
|
298
|
+
const taskEventEmitter = invocation.eventEmitter;
|
|
299
|
+
// Create a SubAgentTracker for this tool execution
|
|
300
|
+
const subAgentTracker = new SubAgentTracker(this, this.client);
|
|
301
|
+
// Set up sub-agent tool tracking
|
|
302
|
+
subAgentCleanupFunctions = subAgentTracker.setup(taskEventEmitter, abortSignal);
|
|
303
|
+
}
|
|
304
|
+
const confirmationDetails = this.config.getApprovalMode() !== ApprovalMode.YOLO
|
|
305
|
+
? await invocation.shouldConfirmExecute(abortSignal)
|
|
306
|
+
: false;
|
|
307
|
+
if (confirmationDetails) {
|
|
308
|
+
const content = [];
|
|
309
|
+
if (confirmationDetails.type === 'edit') {
|
|
310
|
+
content.push({
|
|
311
|
+
type: 'diff',
|
|
312
|
+
path: confirmationDetails.fileName,
|
|
313
|
+
oldText: confirmationDetails.originalContent,
|
|
314
|
+
newText: confirmationDetails.newContent,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
// Add plan content for exit_plan_mode
|
|
318
|
+
if (confirmationDetails.type === 'plan') {
|
|
319
|
+
content.push({
|
|
320
|
+
type: 'content',
|
|
321
|
+
content: {
|
|
322
|
+
type: 'text',
|
|
323
|
+
text: confirmationDetails.plan,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// Map tool kind, using switch_mode for exit_plan_mode per ACP spec
|
|
328
|
+
const mappedKind = this.toolCallEmitter.mapToolKind(tool.kind, fc.name);
|
|
329
|
+
const params = {
|
|
330
|
+
sessionId: this.sessionId,
|
|
331
|
+
options: toPermissionOptions(confirmationDetails),
|
|
332
|
+
toolCall: {
|
|
333
|
+
toolCallId: callId,
|
|
334
|
+
status: 'pending',
|
|
335
|
+
title: invocation.getDescription(),
|
|
336
|
+
content,
|
|
337
|
+
locations: invocation.toolLocations(),
|
|
338
|
+
kind: mappedKind,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
const output = await this.client.requestPermission(params);
|
|
342
|
+
const outcome = output.outcome.outcome === 'cancelled'
|
|
343
|
+
? ToolConfirmationOutcome.Cancel
|
|
344
|
+
: z
|
|
345
|
+
.nativeEnum(ToolConfirmationOutcome)
|
|
346
|
+
.parse(output.outcome.optionId);
|
|
347
|
+
await confirmationDetails.onConfirm(outcome);
|
|
348
|
+
// After exit_plan_mode confirmation, send current_mode_update notification
|
|
349
|
+
if (isExitPlanModeTool && outcome !== ToolConfirmationOutcome.Cancel) {
|
|
350
|
+
await this.sendCurrentModeUpdateNotification(outcome);
|
|
351
|
+
}
|
|
352
|
+
switch (outcome) {
|
|
353
|
+
case ToolConfirmationOutcome.Cancel:
|
|
354
|
+
return errorResponse(new Error(`Tool "${fc.name}" was canceled by the user.`));
|
|
355
|
+
case ToolConfirmationOutcome.ProceedOnce:
|
|
356
|
+
case ToolConfirmationOutcome.ProceedAlways:
|
|
357
|
+
case ToolConfirmationOutcome.ProceedAlwaysServer:
|
|
358
|
+
case ToolConfirmationOutcome.ProceedAlwaysTool:
|
|
359
|
+
case ToolConfirmationOutcome.ModifyWithEditor:
|
|
360
|
+
break;
|
|
361
|
+
default: {
|
|
362
|
+
const resultOutcome = outcome;
|
|
363
|
+
throw new Error(`Unexpected: ${resultOutcome}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (!isTodoWriteTool) {
|
|
368
|
+
// Skip tool_call event for TodoWriteTool - use ToolCallEmitter
|
|
369
|
+
const startParams = {
|
|
370
|
+
callId,
|
|
371
|
+
toolName: fc.name,
|
|
372
|
+
args,
|
|
373
|
+
status: 'in_progress',
|
|
374
|
+
};
|
|
375
|
+
await this.toolCallEmitter.emitStart(startParams);
|
|
376
|
+
}
|
|
377
|
+
const toolResult = await invocation.execute(abortSignal);
|
|
378
|
+
// Clean up event listeners
|
|
379
|
+
subAgentCleanupFunctions.forEach((cleanup) => cleanup());
|
|
380
|
+
// Create response parts first (needed for emitResult and recordToolResult)
|
|
381
|
+
const responseParts = convertToFunctionResponse(fc.name, callId, toolResult.llmContent);
|
|
382
|
+
// Handle TodoWriteTool: extract todos and send plan update
|
|
383
|
+
if (isTodoWriteTool) {
|
|
384
|
+
const todos = this.planEmitter.extractTodos(toolResult.returnDisplay, args);
|
|
385
|
+
// Match original logic: emit plan if todos.length > 0 OR if args had todos
|
|
386
|
+
if ((todos && todos.length > 0) || Array.isArray(args['todos'])) {
|
|
387
|
+
await this.planEmitter.emitPlan(todos ?? []);
|
|
388
|
+
}
|
|
389
|
+
// Skip tool_call_update event for TodoWriteTool
|
|
390
|
+
// Still log and return function response for LLM
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// Normal tool handling: emit result using ToolCallEmitter
|
|
394
|
+
// Convert toolResult.error to Error type if present
|
|
395
|
+
const error = toolResult.error
|
|
396
|
+
? new Error(toolResult.error.message)
|
|
397
|
+
: undefined;
|
|
398
|
+
await this.toolCallEmitter.emitResult({
|
|
399
|
+
callId,
|
|
400
|
+
toolName: fc.name,
|
|
401
|
+
args,
|
|
402
|
+
message: responseParts,
|
|
403
|
+
resultDisplay: toolResult.returnDisplay,
|
|
404
|
+
error,
|
|
405
|
+
success: !toolResult.error,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const durationMs = Date.now() - startTime;
|
|
409
|
+
logToolCall(this.config, {
|
|
410
|
+
'event.name': 'tool_call',
|
|
411
|
+
'event.timestamp': new Date().toISOString(),
|
|
412
|
+
function_name: fc.name,
|
|
413
|
+
function_args: args,
|
|
414
|
+
duration_ms: durationMs,
|
|
415
|
+
status: 'success',
|
|
416
|
+
success: true,
|
|
417
|
+
prompt_id: promptId,
|
|
418
|
+
tool_type: typeof tool !== 'undefined' && tool instanceof DiscoveredMCPTool
|
|
419
|
+
? 'mcp'
|
|
420
|
+
: 'native',
|
|
421
|
+
});
|
|
422
|
+
// Record tool result for session management
|
|
423
|
+
this.config.getChatRecordingService()?.recordToolResult(responseParts, {
|
|
424
|
+
callId,
|
|
425
|
+
status: 'success',
|
|
426
|
+
resultDisplay: toolResult.returnDisplay,
|
|
427
|
+
error: undefined,
|
|
428
|
+
errorType: undefined,
|
|
429
|
+
});
|
|
430
|
+
return responseParts;
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
// Ensure cleanup on error
|
|
434
|
+
subAgentCleanupFunctions.forEach((cleanup) => cleanup());
|
|
435
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
436
|
+
// Use ToolCallEmitter for error handling
|
|
437
|
+
await this.toolCallEmitter.emitError(callId, error);
|
|
438
|
+
// Record tool error for session management
|
|
439
|
+
const errorParts = [
|
|
440
|
+
{
|
|
441
|
+
functionResponse: {
|
|
442
|
+
id: callId,
|
|
443
|
+
name: fc.name ?? '',
|
|
444
|
+
response: { error: error.message },
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
];
|
|
448
|
+
this.config.getChatRecordingService()?.recordToolResult(errorParts, {
|
|
449
|
+
callId,
|
|
450
|
+
status: 'error',
|
|
451
|
+
resultDisplay: undefined,
|
|
452
|
+
error,
|
|
453
|
+
errorType: undefined,
|
|
454
|
+
});
|
|
455
|
+
return errorResponse(error);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async #resolvePrompt(message, abortSignal) {
|
|
459
|
+
const FILE_URI_SCHEME = 'file://';
|
|
460
|
+
const embeddedContext = [];
|
|
461
|
+
const parts = message.map((part) => {
|
|
462
|
+
switch (part.type) {
|
|
463
|
+
case 'text':
|
|
464
|
+
return { text: part.text };
|
|
465
|
+
case 'image':
|
|
466
|
+
case 'audio':
|
|
467
|
+
return {
|
|
468
|
+
inlineData: {
|
|
469
|
+
mimeType: part.mimeType,
|
|
470
|
+
data: part.data,
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
case 'resource_link': {
|
|
474
|
+
if (part.uri.startsWith(FILE_URI_SCHEME)) {
|
|
475
|
+
return {
|
|
476
|
+
fileData: {
|
|
477
|
+
mimeData: part.mimeType,
|
|
478
|
+
name: part.name,
|
|
479
|
+
fileUri: part.uri.slice(FILE_URI_SCHEME.length),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
return { text: `@${part.uri}` };
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
case 'resource': {
|
|
488
|
+
embeddedContext.push(part.resource);
|
|
489
|
+
return { text: `@${part.resource.uri}` };
|
|
490
|
+
}
|
|
491
|
+
default: {
|
|
492
|
+
const unreachable = part;
|
|
493
|
+
throw new Error(`Unexpected chunk type: '${unreachable}'`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
const atPathCommandParts = parts.filter((part) => 'fileData' in part);
|
|
498
|
+
if (atPathCommandParts.length === 0 && embeddedContext.length === 0) {
|
|
499
|
+
return parts;
|
|
500
|
+
}
|
|
501
|
+
const atPathToResolvedSpecMap = new Map();
|
|
502
|
+
// Get centralized file discovery service
|
|
503
|
+
const fileDiscovery = this.config.getFileService();
|
|
504
|
+
const respectGitIgnore = this.config.getFileFilteringRespectGitIgnore();
|
|
505
|
+
const pathSpecsToRead = [];
|
|
506
|
+
const contentLabelsForDisplay = [];
|
|
507
|
+
const ignoredPaths = [];
|
|
508
|
+
const toolRegistry = this.config.getToolRegistry();
|
|
509
|
+
const readManyFilesTool = toolRegistry.getTool('read_many_files');
|
|
510
|
+
const globTool = toolRegistry.getTool('glob');
|
|
511
|
+
if (!readManyFilesTool) {
|
|
512
|
+
throw new Error('Error: read_many_files tool not found.');
|
|
513
|
+
}
|
|
514
|
+
for (const atPathPart of atPathCommandParts) {
|
|
515
|
+
const pathName = atPathPart.fileData.fileUri;
|
|
516
|
+
// Check if path should be ignored by git
|
|
517
|
+
if (fileDiscovery.shouldGitIgnoreFile(pathName)) {
|
|
518
|
+
ignoredPaths.push(pathName);
|
|
519
|
+
const reason = respectGitIgnore
|
|
520
|
+
? 'git-ignored and will be skipped'
|
|
521
|
+
: 'ignored by custom patterns';
|
|
522
|
+
console.warn(`Path ${pathName} is ${reason}.`);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
let currentPathSpec = pathName;
|
|
526
|
+
let resolvedSuccessfully = false;
|
|
527
|
+
try {
|
|
528
|
+
const absolutePath = path.resolve(this.config.getTargetDir(), pathName);
|
|
529
|
+
if (isWithinRoot(absolutePath, this.config.getTargetDir())) {
|
|
530
|
+
const stats = await fs.stat(absolutePath);
|
|
531
|
+
if (stats.isDirectory()) {
|
|
532
|
+
currentPathSpec = pathName.endsWith('/')
|
|
533
|
+
? `${pathName}**`
|
|
534
|
+
: `${pathName}/**`;
|
|
535
|
+
this.debug(`Path ${pathName} resolved to directory, using glob: ${currentPathSpec}`);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
this.debug(`Path ${pathName} resolved to file: ${currentPathSpec}`);
|
|
539
|
+
}
|
|
540
|
+
resolvedSuccessfully = true;
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
this.debug(`Path ${pathName} is outside the project directory. Skipping.`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
if (isNodeError(error) && error.code === 'ENOENT') {
|
|
548
|
+
if (this.config.getEnableRecursiveFileSearch() && globTool) {
|
|
549
|
+
this.debug(`Path ${pathName} not found directly, attempting glob search.`);
|
|
550
|
+
try {
|
|
551
|
+
const globResult = await globTool.buildAndExecute({
|
|
552
|
+
pattern: `**/*${pathName}*`,
|
|
553
|
+
path: this.config.getTargetDir(),
|
|
554
|
+
}, abortSignal);
|
|
555
|
+
if (globResult.llmContent &&
|
|
556
|
+
typeof globResult.llmContent === 'string' &&
|
|
557
|
+
!globResult.llmContent.startsWith('No files found') &&
|
|
558
|
+
!globResult.llmContent.startsWith('Error:')) {
|
|
559
|
+
const lines = globResult.llmContent.split('\n');
|
|
560
|
+
if (lines.length > 1 && lines[1]) {
|
|
561
|
+
const firstMatchAbsolute = lines[1].trim();
|
|
562
|
+
currentPathSpec = path.relative(this.config.getTargetDir(), firstMatchAbsolute);
|
|
563
|
+
this.debug(`Glob search for ${pathName} found ${firstMatchAbsolute}, using relative path: ${currentPathSpec}`);
|
|
564
|
+
resolvedSuccessfully = true;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
this.debug(`Glob search for '**/*${pathName}*' did not return a usable path. Path ${pathName} will be skipped.`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
this.debug(`Glob search for '**/*${pathName}*' found no files or an error. Path ${pathName} will be skipped.`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
catch (globError) {
|
|
575
|
+
console.error(`Error during glob search for ${pathName}: ${getErrorMessage(globError)}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
this.debug(`Glob tool not found. Path ${pathName} will be skipped.`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
console.error(`Error stating path ${pathName}. Path ${pathName} will be skipped.`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (resolvedSuccessfully) {
|
|
587
|
+
pathSpecsToRead.push(currentPathSpec);
|
|
588
|
+
atPathToResolvedSpecMap.set(pathName, currentPathSpec);
|
|
589
|
+
contentLabelsForDisplay.push(pathName);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Construct the initial part of the query for the LLM
|
|
593
|
+
let initialQueryText = '';
|
|
594
|
+
for (let i = 0; i < parts.length; i++) {
|
|
595
|
+
const chunk = parts[i];
|
|
596
|
+
if ('text' in chunk) {
|
|
597
|
+
initialQueryText += chunk.text;
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
// type === 'atPath'
|
|
601
|
+
const resolvedSpec = chunk.fileData && atPathToResolvedSpecMap.get(chunk.fileData.fileUri);
|
|
602
|
+
if (i > 0 &&
|
|
603
|
+
initialQueryText.length > 0 &&
|
|
604
|
+
!initialQueryText.endsWith(' ') &&
|
|
605
|
+
resolvedSpec) {
|
|
606
|
+
// Add space if previous part was text and didn't end with space, or if previous was @path
|
|
607
|
+
const prevPart = parts[i - 1];
|
|
608
|
+
if ('text' in prevPart ||
|
|
609
|
+
('fileData' in prevPart &&
|
|
610
|
+
atPathToResolvedSpecMap.has(prevPart.fileData.fileUri))) {
|
|
611
|
+
initialQueryText += ' ';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
// Append the resolved path spec for display purposes
|
|
615
|
+
if (resolvedSpec) {
|
|
616
|
+
initialQueryText += `@${resolvedSpec}`;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Handle ignored paths message
|
|
621
|
+
let ignoredPathsMessage = '';
|
|
622
|
+
if (ignoredPaths.length > 0) {
|
|
623
|
+
const pathList = ignoredPaths.map((p) => `- ${p}`).join('\n');
|
|
624
|
+
ignoredPathsMessage = `Note: The following paths were skipped because they are ignored:\n${pathList}\n\n`;
|
|
625
|
+
}
|
|
626
|
+
const processedQueryParts = [];
|
|
627
|
+
// Read files using read_many_files tool
|
|
628
|
+
if (pathSpecsToRead.length > 0) {
|
|
629
|
+
const readResult = await readManyFilesTool.buildAndExecute({
|
|
630
|
+
paths_with_line_ranges: pathSpecsToRead,
|
|
631
|
+
}, abortSignal);
|
|
632
|
+
const contentForLlm = typeof readResult.llmContent === 'string'
|
|
633
|
+
? readResult.llmContent
|
|
634
|
+
: JSON.stringify(readResult.llmContent);
|
|
635
|
+
// Combine content label, ignored paths message, file content, and user query
|
|
636
|
+
const combinedText = `${ignoredPathsMessage}${contentForLlm}`.trim();
|
|
637
|
+
processedQueryParts.push({ text: combinedText });
|
|
638
|
+
processedQueryParts.push({ text: initialQueryText });
|
|
639
|
+
}
|
|
640
|
+
else if (embeddedContext.length > 0) {
|
|
641
|
+
// No @path files to read, but we have embedded context
|
|
642
|
+
processedQueryParts.push({
|
|
643
|
+
text: `${ignoredPathsMessage}${initialQueryText}`.trim(),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
// No @path files found or resolved
|
|
648
|
+
processedQueryParts.push({
|
|
649
|
+
text: `${ignoredPathsMessage}${initialQueryText}`.trim(),
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
// Process embedded context from resource blocks
|
|
653
|
+
for (const contextPart of embeddedContext) {
|
|
654
|
+
// Type guard for text resources
|
|
655
|
+
if ('text' in contextPart && contextPart.text) {
|
|
656
|
+
processedQueryParts.push({
|
|
657
|
+
text: `File: ${contextPart.uri}\n${contextPart.text}`,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
// Type guard for blob resources
|
|
661
|
+
if ('blob' in contextPart && contextPart.blob) {
|
|
662
|
+
processedQueryParts.push({
|
|
663
|
+
inlineData: {
|
|
664
|
+
mimeType: contextPart.mimeType ?? 'application/octet-stream',
|
|
665
|
+
data: contextPart.blob,
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return processedQueryParts;
|
|
671
|
+
}
|
|
672
|
+
debug(msg) {
|
|
673
|
+
if (this.config.getDebugMode()) {
|
|
674
|
+
console.warn(msg);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// ============================================================================
|
|
679
|
+
// Helper functions
|
|
680
|
+
// ============================================================================
|
|
681
|
+
const basicPermissionOptions = [
|
|
682
|
+
{
|
|
683
|
+
optionId: ToolConfirmationOutcome.ProceedOnce,
|
|
684
|
+
name: 'Allow',
|
|
685
|
+
kind: 'allow_once',
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
optionId: ToolConfirmationOutcome.Cancel,
|
|
689
|
+
name: 'Reject',
|
|
690
|
+
kind: 'reject_once',
|
|
691
|
+
},
|
|
692
|
+
];
|
|
693
|
+
function toPermissionOptions(confirmation) {
|
|
694
|
+
switch (confirmation.type) {
|
|
695
|
+
case 'edit':
|
|
696
|
+
return [
|
|
697
|
+
{
|
|
698
|
+
optionId: ToolConfirmationOutcome.ProceedAlways,
|
|
699
|
+
name: 'Allow All Edits',
|
|
700
|
+
kind: 'allow_always',
|
|
701
|
+
},
|
|
702
|
+
...basicPermissionOptions,
|
|
703
|
+
];
|
|
704
|
+
case 'exec':
|
|
705
|
+
return [
|
|
706
|
+
{
|
|
707
|
+
optionId: ToolConfirmationOutcome.ProceedAlways,
|
|
708
|
+
name: `Always Allow ${confirmation.rootCommand}`,
|
|
709
|
+
kind: 'allow_always',
|
|
710
|
+
},
|
|
711
|
+
...basicPermissionOptions,
|
|
712
|
+
];
|
|
713
|
+
case 'mcp':
|
|
714
|
+
return [
|
|
715
|
+
{
|
|
716
|
+
optionId: ToolConfirmationOutcome.ProceedAlwaysServer,
|
|
717
|
+
name: `Always Allow ${confirmation.serverName}`,
|
|
718
|
+
kind: 'allow_always',
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
optionId: ToolConfirmationOutcome.ProceedAlwaysTool,
|
|
722
|
+
name: `Always Allow ${confirmation.toolName}`,
|
|
723
|
+
kind: 'allow_always',
|
|
724
|
+
},
|
|
725
|
+
...basicPermissionOptions,
|
|
726
|
+
];
|
|
727
|
+
case 'info':
|
|
728
|
+
return [
|
|
729
|
+
{
|
|
730
|
+
optionId: ToolConfirmationOutcome.ProceedAlways,
|
|
731
|
+
name: `Always Allow`,
|
|
732
|
+
kind: 'allow_always',
|
|
733
|
+
},
|
|
734
|
+
...basicPermissionOptions,
|
|
735
|
+
];
|
|
736
|
+
case 'plan':
|
|
737
|
+
return [
|
|
738
|
+
{
|
|
739
|
+
optionId: ToolConfirmationOutcome.ProceedAlways,
|
|
740
|
+
name: `Yes, and auto-accept edits`,
|
|
741
|
+
kind: 'allow_always',
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
optionId: ToolConfirmationOutcome.ProceedOnce,
|
|
745
|
+
name: `Yes, and manually approve edits`,
|
|
746
|
+
kind: 'allow_once',
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
optionId: ToolConfirmationOutcome.Cancel,
|
|
750
|
+
name: `No, keep planning (esc)`,
|
|
751
|
+
kind: 'reject_once',
|
|
752
|
+
},
|
|
753
|
+
];
|
|
754
|
+
default: {
|
|
755
|
+
const unreachable = confirmation;
|
|
756
|
+
throw new Error(`Unexpected: ${unreachable}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
//# sourceMappingURL=Session.js.map
|