From 8a7bdb7f93d42af3aafab40e115705014caae781 Mon Sep 17 00:00:00 2001 From: emikeliu Date: Sun, 2 Aug 2026 18:18:24 +0800 Subject: [PATCH 1/4] fix(config): Agent Type System should be reloaded at the settings.json changed --- src/extension.ts | 21 +++++++++++++++++++++ src/registry/agentTypeRegistry.ts | 10 ++++++---- src/registry/toolSetRegistry.ts | 10 ++++++---- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 91dbd69..88f97b0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -92,6 +92,27 @@ export async function activate( ); debugLogger.log("[Extension] AgentTypeRegistry initialized"); + // Agent Type System 应在 settings.json 内容变化时重新加载 + // 配置项为 mutsumi.agentConfig + // 复用 loadMutsumiConfig() 以保持与启动时一致的 merge + validate 行为 + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => { + if (e.affectsConfiguration("mutsumi.agentConfig")) { + try { + const mutsumiConfig = loadMutsumiConfig(); + const toolSetRegistry = ToolSetRegistry.getInstance(); + toolSetRegistry.initialize(mutsumiConfig.toolSets); + const agentTypeRegistry = AgentTypeRegistry.getInstance(); + agentTypeRegistry.initialize( + mutsumiConfig.agentTypes, + Object.keys(mutsumiConfig.toolSets), + ); + debugLogger.log("[Extension] Agent Type System reloaded after config change"); + } catch (err) { + debugLogger.log(`[Extension] Failed to reload Agent Type System: ${err}`); + } + } + })) + // Initialize SkillManager const skillManager = SkillManager.getInstance(); await skillManager.initialize(context); diff --git a/src/registry/agentTypeRegistry.ts b/src/registry/agentTypeRegistry.ts index dad086c..584f18c 100644 --- a/src/registry/agentTypeRegistry.ts +++ b/src/registry/agentTypeRegistry.ts @@ -44,9 +44,10 @@ export class AgentTypeRegistry { * @throws {Error} If validation fails */ initialize(config: AgentTypeConfigMap, toolSetNames: string[]): void { - if (this.initialized) { - return; - } + // 禁用只能 initialize 一次的特性 + // if (this.initialized) { + // return; + // } // Clear existing types this.agentTypes.clear(); @@ -60,7 +61,8 @@ export class AgentTypeRegistry { this.validateAgentType(name, typeConfig, allTypeNames); this.agentTypes.set(name, { ...typeConfig }); // Clone } - + + // 鉴于兼容性,依然需要保存初始化状态 this.initialized = true; } diff --git a/src/registry/toolSetRegistry.ts b/src/registry/toolSetRegistry.ts index 52b5ed9..c23c1f4 100644 --- a/src/registry/toolSetRegistry.ts +++ b/src/registry/toolSetRegistry.ts @@ -62,9 +62,10 @@ export class ToolSetRegistry { * @throws {Error} If a tool set references a non-existent tool */ initialize(config: ToolSetsConfig): void { - if (this.initialized) { - return; - } + // 禁用只能 initialize 一次的特性 + // if (this.initialized) { + // return; + // } // Clear existing tool sets this.toolSets.clear(); @@ -91,7 +92,8 @@ export class ToolSetRegistry { this.validateToolNames(processedToolNames, name); this.toolSets.set(name, processedToolNames); } - + + // 鉴于兼容性,依然需要保存初始化状态 this.initialized = true; } From 92ff7a284bdc96cd4cf433c4cc4c1b1404574b21 Mon Sep 17 00:00:00 2001 From: emikeliu Date: Sun, 2 Aug 2026 18:45:15 +0800 Subject: [PATCH 2/4] refactor(config): simplify the agent type system --- src/extension.ts | 55 +++++++++++++++++-------------- src/registry/agentTypeRegistry.ts | 51 ++++++++++++++-------------- src/registry/toolSetRegistry.ts | 44 ++++++++++++------------- 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 88f97b0..8f520d7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -52,6 +52,33 @@ async function fileExists(uri: vscode.Uri): Promise { } } +/** + * Initializes the Agent Type System by loading the Mutsumi configuration and + * populating both the ToolSetRegistry and AgentTypeRegistry. + * + * Single source of truth for (re)initializing the Agent Type System, shared + * between extension activation and the onDidChangeConfiguration handler so + * that behavior stays consistent. + */ +function initializeAgentTypeSystem(): void { + // 1. Load Mutsumi configuration (merged with built-in defaults + validated) + const mutsumiConfig = loadMutsumiConfig(); + debugLogger.log("[Extension] Mutsumi config loaded successfully"); + + // 2. Initialize ToolSetRegistry with configured tool sets + const toolSetRegistry = ToolSetRegistry.getInstance(); + toolSetRegistry.initialize(mutsumiConfig.toolSets); + debugLogger.log("[Extension] ToolSetRegistry initialized"); + + // 3. Initialize AgentTypeRegistry with configured agent types + const agentTypeRegistry = AgentTypeRegistry.getInstance(); + agentTypeRegistry.initialize( + mutsumiConfig.agentTypes, + Object.keys(mutsumiConfig.toolSets), + ); + debugLogger.log("[Extension] AgentTypeRegistry initialized"); +} + /** * Activates the Mutsumi extension. * @description Registers all extension components including notebook serializer, @@ -75,37 +102,15 @@ export async function activate( ToolRegistry.initialize(); // Initialize Agent Type System (Config + Registries) - // 1. Load Mutsumi configuration from VSCode Settings (merged with built-in defaults) - const mutsumiConfig = loadMutsumiConfig(); - debugLogger.log("[Extension] Mutsumi config loaded successfully"); - - // 2. Initialize ToolSetRegistry with configured tool sets - const toolSetRegistry = ToolSetRegistry.getInstance(); - toolSetRegistry.initialize(mutsumiConfig.toolSets); - debugLogger.log("[Extension] ToolSetRegistry initialized"); - - // 3. Initialize AgentTypeRegistry with configured agent types - const agentTypeRegistry = AgentTypeRegistry.getInstance(); - agentTypeRegistry.initialize( - mutsumiConfig.agentTypes, - Object.keys(mutsumiConfig.toolSets), - ); - debugLogger.log("[Extension] AgentTypeRegistry initialized"); + initializeAgentTypeSystem(); // Agent Type System 应在 settings.json 内容变化时重新加载 // 配置项为 mutsumi.agentConfig - // 复用 loadMutsumiConfig() 以保持与启动时一致的 merge + validate 行为 + // 复用 initializeAgentTypeSystem() 以保持与启动时一致的 load + validate + init 行为 context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => { if (e.affectsConfiguration("mutsumi.agentConfig")) { try { - const mutsumiConfig = loadMutsumiConfig(); - const toolSetRegistry = ToolSetRegistry.getInstance(); - toolSetRegistry.initialize(mutsumiConfig.toolSets); - const agentTypeRegistry = AgentTypeRegistry.getInstance(); - agentTypeRegistry.initialize( - mutsumiConfig.agentTypes, - Object.keys(mutsumiConfig.toolSets), - ); + initializeAgentTypeSystem(); debugLogger.log("[Extension] Agent Type System reloaded after config change"); } catch (err) { debugLogger.log(`[Extension] Failed to reload Agent Type System: ${err}`); diff --git a/src/registry/agentTypeRegistry.ts b/src/registry/agentTypeRegistry.ts index 584f18c..0ae2b96 100644 --- a/src/registry/agentTypeRegistry.ts +++ b/src/registry/agentTypeRegistry.ts @@ -8,13 +8,17 @@ import { DEFAULT_MUTSUMI_CONFIG } from "../config/types"; /** * Stores agent type configurations, validates toolSets/child types, provides entry type queries. - * Initialize during extension activation with config from the `mutsumi.agentConfig` VSCode Setting. + * + * Initialized during extension activation with config from the `mutsumi.agentConfig` + * VSCode Setting. Unlike a one-shot init, `initialize()` may be called again (e.g. + * when the setting changes) to reload the configuration in place. The `ready` flag + * only guards against reads before the first successful init. */ export class AgentTypeRegistry { private static instance: AgentTypeRegistry | null = null; private agentTypes: Map = new Map(); private toolSetNames: Set = new Set(); - private initialized = false; + private ready = false; /** * Gets the singleton instance. @@ -33,10 +37,11 @@ export class AgentTypeRegistry { private constructor() {} /** - * Initializes the registry with agent type configurations. + * Loads (or reloads) the registry with agent type configurations. * - * This should be called once during extension activation after - * the configuration has been loaded. It validates all references + * May be called multiple times: each call fully replaces the previous + * configuration. This is used during activation and again whenever the + * `mutsumi.agentConfig` setting changes. It validates all references * to ensure consistency. * * @param {AgentTypeConfigMap} config - Agent type configuration @@ -44,12 +49,7 @@ export class AgentTypeRegistry { * @throws {Error} If validation fails */ initialize(config: AgentTypeConfigMap, toolSetNames: string[]): void { - // 禁用只能 initialize 一次的特性 - // if (this.initialized) { - // return; - // } - - // Clear existing types + // Clear existing types so a reload fully replaces prior state this.agentTypes.clear(); this.toolSetNames = new Set(toolSetNames); @@ -61,9 +61,9 @@ export class AgentTypeRegistry { this.validateAgentType(name, typeConfig, allTypeNames); this.agentTypes.set(name, { ...typeConfig }); // Clone } - - // 鉴于兼容性,依然需要保存初始化状态 - this.initialized = true; + + // Mark the registry as ready for queries + this.ready = true; } /** @@ -107,7 +107,7 @@ export class AgentTypeRegistry { * @returns {AgentTypeConfig | undefined} The configuration or undefined if not found */ getAgentType(name: string): AgentTypeConfig | undefined { - this.ensureInitialized(); + this.ensureReady(); return this.agentTypes.get(name); } @@ -116,7 +116,7 @@ export class AgentTypeRegistry { * @returns {string[]} Array of entry type names */ listEntryTypes(): string[] { - this.ensureInitialized(); + this.ensureReady(); const entries: string[] = []; for (const [name, config] of this.agentTypes) { if (config.isEntry) { @@ -133,7 +133,7 @@ export class AgentTypeRegistry { * @returns {boolean} True if the child type is allowed */ isValidChildType(parentType: string, childType: string): boolean { - this.ensureInitialized(); + this.ensureReady(); const parent = this.agentTypes.get(parentType); if (!parent) { return false; @@ -146,7 +146,7 @@ export class AgentTypeRegistry { * @returns {string[]} Array of all agent type names */ getAllTypes(): string[] { - this.ensureInitialized(); + this.ensureReady(); return Array.from(this.agentTypes.keys()); } @@ -156,7 +156,7 @@ export class AgentTypeRegistry { * @returns {boolean} True if the type exists */ hasAgentType(name: string): boolean { - this.ensureInitialized(); + this.ensureReady(); return this.agentTypes.has(name); } @@ -166,17 +166,18 @@ export class AgentTypeRegistry { * @returns {string[] | undefined} The tool set names or undefined */ getToolSetNames(name: string): string[] | undefined { - this.ensureInitialized(); + this.ensureReady(); return this.agentTypes.get(name)?.toolSets; } /** - * Ensures the registry has been initialized. + * Guards against reads before the registry has been populated. + * * @private - * @throws {Error} If not initialized + * @throws {Error} If `initialize()` has never been called successfully */ - private ensureInitialized(): void { - if (!this.initialized) { + private ensureReady(): void { + if (!this.ready) { throw new Error( "AgentTypeRegistry not initialized. Call initialize() first.", ); @@ -189,6 +190,6 @@ export class AgentTypeRegistry { reset(): void { this.agentTypes.clear(); this.toolSetNames.clear(); - this.initialized = false; + this.ready = false; } } diff --git a/src/registry/toolSetRegistry.ts b/src/registry/toolSetRegistry.ts index c23c1f4..66766bb 100644 --- a/src/registry/toolSetRegistry.ts +++ b/src/registry/toolSetRegistry.ts @@ -20,6 +20,10 @@ import { DEFAULT_MUTSUMI_CONFIG } from "../config/types"; * It is a singleton that should be initialized during extension activation * with configuration loaded from the `mutsumi.agentConfig` VSCode Setting. * + * Unlike a one-shot init, `initialize()` may be called again (e.g. when the + * `mutsumi.agentConfig` setting changes) to reload the configuration in place. + * The `ready` flag only guards against reads before the first successful init. + * * @example * ```typescript * const registry = ToolSetRegistry.getInstance(); @@ -30,7 +34,7 @@ import { DEFAULT_MUTSUMI_CONFIG } from "../config/types"; export class ToolSetRegistry { private static instance: ToolSetRegistry | null = null; private toolSets: Map = new Map(); - private initialized = false; + private ready = false; /** * Gets the singleton instance of ToolSetRegistry. @@ -49,10 +53,11 @@ export class ToolSetRegistry { private constructor() {} /** - * Initializes the registry with tool set configurations. + * Loads (or reloads) the registry with tool set configurations. * - * This should be called once during extension activation after - * the configuration has been loaded. + * May be called multiple times: each call fully replaces the previous + * configuration. This is used during activation and again whenever the + * `mutsumi.agentConfig` setting changes. * * The RAG tool 'query_codebase' is handled specially: * - If embedding endpoint is NOT configured, it's removed from all tool sets @@ -62,12 +67,7 @@ export class ToolSetRegistry { * @throws {Error} If a tool set references a non-existent tool */ initialize(config: ToolSetsConfig): void { - // 禁用只能 initialize 一次的特性 - // if (this.initialized) { - // return; - // } - - // Clear existing tool sets + // Clear existing tool sets so a reload fully replaces prior state this.toolSets.clear(); // Check if RAG is enabled (embedding endpoint configured) @@ -92,9 +92,9 @@ export class ToolSetRegistry { this.validateToolNames(processedToolNames, name); this.toolSets.set(name, processedToolNames); } - - // 鉴于兼容性,依然需要保存初始化状态 - this.initialized = true; + + // Mark the registry as ready for queries + this.ready = true; } /** @@ -131,7 +131,7 @@ export class ToolSetRegistry { * @throws {Error} If the tool set does not exist */ getToolSet(name: string): ITool[] { - this.ensureInitialized(); + this.ensureReady(); const toolNames = this.toolSets.get(name); if (!toolNames) { @@ -150,7 +150,7 @@ export class ToolSetRegistry { * @throws {Error} If any tool set does not exist */ getCombinedToolSet(names: string[]): ITool[] { - this.ensureInitialized(); + this.ensureReady(); const seenTools = new Map(); @@ -172,7 +172,7 @@ export class ToolSetRegistry { * @returns {boolean} True if the tool set exists */ hasToolSet(name: string): boolean { - this.ensureInitialized(); + this.ensureReady(); return this.toolSets.has(name); } @@ -182,7 +182,7 @@ export class ToolSetRegistry { * @returns {string[]} Array of all tool set names */ getAllToolSetNames(): string[] { - this.ensureInitialized(); + this.ensureReady(); return Array.from(this.toolSets.keys()); } @@ -215,13 +215,13 @@ export class ToolSetRegistry { } /** - * Ensures the registry has been initialized. + * Guards against reads before the registry has been populated. * * @private - * @throws {Error} If not initialized + * @throws {Error} If `initialize()` has never been called successfully */ - private ensureInitialized(): void { - if (!this.initialized) { + private ensureReady(): void { + if (!this.ready) { throw new Error( "ToolSetRegistry not initialized. Call initialize() first.", ); @@ -233,6 +233,6 @@ export class ToolSetRegistry { */ reset(): void { this.toolSets.clear(); - this.initialized = false; + this.ready = false; } } From e5ae46f6c5e522553f0cd10a3f8289cf7a803284 Mon Sep 17 00:00:00 2001 From: emikeliu Date: Sun, 2 Aug 2026 19:46:58 +0800 Subject: [PATCH 3/4] feat(ui): init l10n --- l10n/bundle.l10n.json | 144 ++++++++++++++++++ l10n/bundle.l10n.zh-cn.json | 143 +++++++++++++++++ package.json | 105 ++++++------- package.nls.json | 57 +++++++ package.nls.zh-cn.json | 57 +++++++ src/agent/agentRunner.ts | 8 +- src/controller.ts | 14 +- src/debugLogger.ts | 3 +- src/extension.ts | 49 +++--- src/i18n.ts | 36 +++++ src/notebook/commands/compressConversation.ts | 21 +-- src/notebook/commands/debugContext.ts | 11 +- src/notebook/commands/pruneGhostBlocks.ts | 9 +- src/notebook/commands/renameSession.ts | 15 +- src/notebook/commands/selectModel.ts | 31 ++-- src/notebook/commands/testRagSearch.ts | 13 +- src/notebook/commands/toggleAutoApprove.ts | 13 +- src/notebook/completionProvider.ts | 11 +- src/notebook/serializer.ts | 5 +- src/notifications.ts | 5 +- src/sidebar/agentTreeItem.ts | 11 +- src/sidebar/approvalTreeItem.ts | 17 ++- src/sidebar/contextTreeItem.ts | 31 ++-- src/sidebar/shellTaskTreeItem.ts | 27 ++-- src/tools.d/edit_file.ts | 3 +- src/tools.d/permission.ts | 9 +- src/tools.d/toolsLogger.ts | 3 +- 27 files changed, 661 insertions(+), 190 deletions(-) create mode 100644 l10n/bundle.l10n.json create mode 100644 l10n/bundle.l10n.zh-cn.json create mode 100644 package.nls.json create mode 100644 package.nls.zh-cn.json create mode 100644 src/i18n.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json new file mode 100644 index 0000000..4482cc0 --- /dev/null +++ b/l10n/bundle.l10n.json @@ -0,0 +1,144 @@ +{ + "Mutsumi Debug": "Mutsumi Debug", + "Mutsumi Tools": "Mutsumi Tools", + "context.readError": "Error reading: {0}", + "renderer.parseError": "Error: Failed to parse render data\n reason: {0}\n", + + "httpServer.emptyPassword.warning": "Mutsumi HTTP Server is enabled but no password is set (mutsumi.httpServer.password). The server refuses to start.", + "httpServer.emptyPassword.openSettings": "Open Settings", + "httpServer.emptyPassword.generate": "Generate Random Password", + "httpServer.passwordGenerated": "Generated a random HTTP Server password, saved it to your user settings, and copied it to the clipboard.", + + "newAgent.noWorkspace": "Please open a workspace folder first.", + "newAgent.noEntryTypes": "No entry agent types available. Please check your configuration.", + "newAgent.quickPickPlaceHolder": "Select an agent type to create", + "newAgent.quickPickTitle": "Mutsumi: New Agent", + "newAgent.detail": "Model: {0} | Rules: {1} | Skills: {2}", + "newAgent.created": "Created {0} agent with {1} rules and {2} skills", + + "copyReference.noFile": "No file selected or active.", + "copyReference.notInWorkspace": "File is not in the workspace.", + "copyReference.statusBar": "Copied reference: {0}", + + "clearToolCache.done": "Tool result cache cleared.", + + "controller.copyDetails": "Copy Details", + "controller.mutsumiError": "Mutsumi Error: {0}", + + "agentRunner.llmError": "Mutsumi LLM Error: {0}", + + "status.running": "Running", + "status.pending": "Pending", + "status.finished": "Finished", + "status.standby": "Standby", + + "approval.pending": "⏳ Pending", + "approval.approved": "✅ Approved", + "approval.rejected": "❌ Rejected", + "approval.target": "📁 Target: `{0}`", + "approval.customActionAvailable": "🔍 **Custom Action Available**: {0}", + "approval.details": "📝 Details:\n```\n{0}\n```\n\n", + "approval.time": "🕐 Time: {0}", + "approval.status": "Status: {0}", + + "shellTask.bg": "bg {0}s", + "shellTask.fg": "fg {0}s", + "shellTask.stopped": "stopped", + "shellTask.exit": "exit {0}{1}", + "shellTask.cwd": "📁 CWD: `{0}`", + "shellTask.task": "🆔 Task: `{0}`", + "shellTask.agentSession": "🖥️ Agent Session: `{0}`", + "shellTask.mode": "Mode: {0}\n\n", + "shellTask.modeBackground": "background", + "shellTask.modeForeground": "foreground", + "shellTask.statusStopped": "Status: 🛑 stopped\n\n", + "shellTask.statusRunning": "Status: ▶️ running\n\n", + "shellTask.statusExit": "Status: ✅ exit {0}{1}\n\n", + "shellTask.signal": " (signal: {0})", + "shellTask.output": "Output:\n```\n{0}\n```", + + "context.category.rules": "Rules: Active context rules for Agents", + "context.category.skills": "Skills: Active context skills for Agents", + "context.category.macros": "Macros: Reusable text snippets", + "context.category.files": "Files: Referenced context files", + "context.category.default": "Category", + "context.active": " (Active)", + "context.inactive": " (Inactive)", + "context.refreshed": "Context tree refreshed", + "context.ruleActivated": "Rule \"{0}\" activated", + "context.ruleDeactivated": "Rule \"{0}\" deactivated", + "context.skillActivated": "Skill \"{0}\" activated", + "context.skillDeactivated": "Skill \"{0}\" deactivated", + "context.noOlderVersions": "No older versions of \"{0}\" to prune", + "context.fileNotFound": "File not found: {0}", + + "notebook.noEditor": "No active notebook editor.", + "notebook.onlyMutsumi": "This command only works with Mutsumi notebooks.", + "notebook.noCodeCell": "No code cell found in notebook.", + + "selectModel.noModels": "No models configured in settings.", + "selectModel.placeHolder": "Select model or reasoning effort (switching model resets effort to default; current: {0} / {1})", + "selectModel.modelChanged": "Model changed to: {0}; reasoning effort reset to default.", + "selectModel.effortChanged": "Reasoning effort changed to: {0}", + "selectModel.separatorModels": "Models", + "selectModel.separatorReasoning": "Reasoning efforts", + "selectModel.effortDefault": "Default", + "selectModel.effort.none": "Disable reasoning", + "selectModel.effort.minimal": "Minimal reasoning", + "selectModel.effort.low": "Low reasoning", + "selectModel.effort.medium": "Medium reasoning", + "selectModel.effort.high": "High reasoning", + "selectModel.effort.xhigh": "Extra-high reasoning", + "selectModel.effort.max": "Maximum reasoning", + + "renameSession.prompt": "Enter new session title (leave empty to auto-generate)", + "renameSession.renamed": "Session renamed: {0}", + "renameSession.failed": "Failed to rename session: {0}", + "renameSession.regenerated": "Title regenerated: {0}", + "renameSession.regenerateFailed": "Failed to regenerate title: {0}", + + "debugContext.displayed": "Debug context displayed. Total messages: {0}", + "debugContext.failed": "Failed to debug context: {0}", + + "pruneGhostBlocks.alreadyLatest": "All file references are already at their latest versions.", + "pruneGhostBlocks.done": "Pruned old ghost versions from {0} cell(s).", + + "toggleAutoApprove.on": "Auto-approve mode is now ON. Tools will be executed without confirmation.", + "toggleAutoApprove.off": "Auto-approve mode is now OFF. Tools will require confirmation.", + "toggleAutoApprove.failed": "Failed to toggle auto-approve: {0}", + + "testRag.prompt": "Enter natural language query for RAG search", + "testRag.placeHolder": "e.g., \"how to handle file operations\"", + "testRag.cancelled": "Search cancelled or empty query.", + "testRag.noWorkspaces": "No workspace folders open.", + "testRag.completed": "RAG search completed across {0} workspace(s).", + "testRag.failed": "RAG search failed: {0}", + + "compress.progress": "Compressing conversation...", + "compress.noModel": "Please configure mutsumi.compressModel or mutsumi.defaultModel in settings.", + "compress.failed": "Compression failed: {0}", + "compress.notEnough": "Not enough conversation content to compress.", + "compress.done": "Conversation compressed and saved to: {0}", + "compress.failedOverall": "Failed to compress conversation: {0}", + "compress.nameSuffix": "{0} (Compressed)", + + "completion.fileReference": "File Reference", + "completion.directoryReference": "Directory Reference", + "completion.tool": "Tool", + "completion.toolCall": "Tool Call", + "completion.referenceDoc": "Reference content of `{0}`", + + "permission.rejectPrompt": "Reason for rejecting {0}:", + "permission.rejectPlaceHolder": "Enter reason (ESC to abort generation)", + "permission.rejected": "[Rejected] The {0} operation was rejected by user.", + "permission.rejectedWithReason": "[Rejected with Reason] The {0} operation was rejected by user. Reason: {1}", + + "agentFile.openFailed": "Failed to open agent file: {0}", + + "editFile.reopenFailed": "Failed to reopen editor: {0}", + + "serializer.newAgent": "New Agent", + + "notify.approvalNeededSubtitle": "Approval needed", + "notify.approvalNeededMessage": "{0} — review in the sidebar" +} diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json new file mode 100644 index 0000000..3507408 --- /dev/null +++ b/l10n/bundle.l10n.zh-cn.json @@ -0,0 +1,143 @@ +{ + "Mutsumi Debug": "Mutsumi 调试", + "Mutsumi Tools": "Mutsumi 工具", + "context.readError": "读取错误: {0}", + + "httpServer.emptyPassword.warning": "Mutsumi HTTP Server 已启用但未设置密码(mutsumi.httpServer.password)。服务器拒绝启动。", + "httpServer.emptyPassword.openSettings": "打开设置", + "httpServer.emptyPassword.generate": "生成随机密码", + "httpServer.passwordGenerated": "已生成随机 HTTP Server 密码,保存到用户设置并复制到剪贴板。", + + "newAgent.noWorkspace": "请先打开一个工作区文件夹。", + "newAgent.noEntryTypes": "没有可用的入口 Agent 类型。请检查配置。", + "newAgent.quickPickPlaceHolder": "选择要创建的 Agent 类型", + "newAgent.quickPickTitle": "Mutsumi: 新建 Agent", + "newAgent.detail": "模型: {0} | 规则: {1} | 技能: {2}", + "newAgent.created": "已创建 {0} Agent,包含 {1} 条规则和 {2} 个技能", + + "copyReference.noFile": "未选中或未打开文件。", + "copyReference.notInWorkspace": "文件不在工作区中。", + "copyReference.statusBar": "已复制引用: {0}", + + "clearToolCache.done": "工具结果缓存已清除。", + + "controller.copyDetails": "复制详情", + "controller.mutsumiError": "Mutsumi 错误: {0}", + + "agentRunner.llmError": "Mutsumi LLM 错误: {0}", + + "status.running": "运行中", + "status.pending": "等待中", + "status.finished": "已完成", + "status.standby": "待命", + + "approval.pending": "⏳ 等待中", + "approval.approved": "✅ 已批准", + "approval.rejected": "❌ 已拒绝", + "approval.target": "📁 目标: `{0}`", + "approval.customActionAvailable": "🔍 **自定义操作可用**: {0}", + "approval.details": "📝 详情:\n```\n{0}\n```\n\n", + "approval.time": "🕐 时间: {0}", + "approval.status": "状态: {0}", + + "shellTask.bg": "后台 {0}秒", + "shellTask.fg": "前台 {0}秒", + "shellTask.stopped": "已停止", + "shellTask.exit": "退出 {0}{1}", + "shellTask.cwd": "📁 工作目录: `{0}`", + "shellTask.task": "🆔 任务: `{0}`", + "shellTask.agentSession": "🖥️ Agent 会话: `{0}`", + "shellTask.mode": "模式: {0}\n\n", + "shellTask.modeBackground": "后台", + "shellTask.modeForeground": "前台", + "shellTask.statusStopped": "状态: 🛑 已停止\n\n", + "shellTask.statusRunning": "状态: ▶️ 运行中\n\n", + "shellTask.statusExit": "状态: ✅ 退出 {0}{1}\n\n", + "shellTask.signal": " (信号: {0})", + "shellTask.output": "输出:\n```\n{0}\n```", + + "context.category.rules": "规则: Agent 的活动上下文规则", + "context.category.skills": "技能: Agent 的活动上下文技能", + "context.category.macros": "宏: 可复用的文本片段", + "context.category.files": "文件: 引用的上下文文件", + "context.category.default": "类别", + "context.active": " (已启用)", + "context.inactive": " (已禁用)", + "context.refreshed": "上下文树已刷新", + "context.ruleActivated": "规则 \"{0}\" 已启用", + "context.ruleDeactivated": "规则 \"{0}\" 已禁用", + "context.skillActivated": "技能 \"{0}\" 已启用", + "context.skillDeactivated": "技能 \"{0}\" 已禁用", + "context.noOlderVersions": "\"{0}\" 没有可裁剪的旧版本", + "context.fileNotFound": "未找到文件: {0}", + + "notebook.noEditor": "没有活动的笔记本编辑器。", + "notebook.onlyMutsumi": "此命令仅适用于 Mutsumi 笔记本。", + + "selectModel.noModels": "设置中未配置任何模型。", + "selectModel.placeHolder": "选择模型或推理强度(切换模型会将强度重置为默认;当前: {0} / {1})", + "selectModel.modelChanged": "模型已切换为: {0};推理强度已重置为默认。", + "selectModel.effortChanged": "推理强度已切换为: {0}", + "selectModel.separatorModels": "模型", + "selectModel.separatorReasoning": "推理强度", + "selectModel.effortDefault": "默认", + "selectModel.effort.none": "禁用推理", + "selectModel.effort.minimal": "最小推理", + "selectModel.effort.low": "低推理", + "selectModel.effort.medium": "中等推理", + "selectModel.effort.high": "高推理", + "selectModel.effort.xhigh": "超高推理", + "selectModel.effort.max": "最大推理", + + "renameSession.prompt": "输入新的会话标题(留空则自动生成)", + "renameSession.renamed": "会话已重命名: {0}", + "renameSession.failed": "重命名会话失败: {0}", + "renameSession.regenerated": "标题已重新生成: {0}", + "renameSession.regenerateFailed": "重新生成标题失败: {0}", + + "debugContext.displayed": "调试上下文已显示。消息总数: {0}", + "debugContext.failed": "调试上下文失败: {0}", + + "pruneGhostBlocks.alreadyLatest": "所有文件引用均已为最新版本。", + "pruneGhostBlocks.done": "已从 {0} 个 Cell 中裁剪旧 Ghost 版本。", + + "toggleAutoApprove.on": "自动批准模式已开启。工具将在无需确认的情况下执行。", + "toggleAutoApprove.off": "自动批准模式已关闭。工具将需要确认。", + "toggleAutoApprove.failed": "切换自动批准失败: {0}", + + "testRag.prompt": "输入用于 RAG 搜索的自然语言查询", + "testRag.placeHolder": "例如:\"如何处理文件操作\"", + "testRag.cancelled": "搜索已取消或查询为空。", + "testRag.noWorkspaces": "没有打开的工作区文件夹。", + "testRag.completed": "RAG 搜索已完成,涵盖 {0} 个工作区。", + "testRag.failed": "RAG 搜索失败: {0}", + + "compress.progress": "正在压缩对话...", + "compress.noModel": "请在设置中配置 mutsumi.compressModel 或 mutsumi.defaultModel。", + "compress.failed": "压缩失败: {0}", + "compress.notEnough": "对话内容不足,无法压缩。", + "compress.done": "对话已压缩并保存到: {0}", + "compress.failedOverall": "压缩对话失败: {0}", + "compress.nameSuffix": "{0} (已压缩)", + + "completion.fileReference": "文件引用", + "completion.directoryReference": "目录引用", + "completion.tool": "工具", + "completion.toolCall": "工具调用", + "completion.referenceDoc": "引用 `{0}` 的内容", + + "permission.rejectPrompt": "拒绝 {0} 的原因:", + "permission.rejectPlaceHolder": "输入原因(ESC 中止生成)", + "permission.rejected": "[已拒绝] {0} 操作已被用户拒绝。", + "permission.rejectedWithReason": "[已拒绝并附带原因] {0} 操作已被用户拒绝。原因: {1}", + + "agentFile.openFailed": "打开 Agent 文件失败: {0}", + + "editFile.reopenFailed": "重新打开编辑器失败: {0}", + + "serializer.newAgent": "新 Agent", + "renderer.parseError": "错误: 解析渲染数据失败\n 原因: {0}\n", + + "notify.approvalNeededSubtitle": "需要审批", + "notify.approvalNeededMessage": "{0} — 请在侧边栏查看" +} diff --git a/package.json b/package.json index b7c549e..bec12f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "mutsumi", - "displayName": "Mutsumi", - "description": "Multi-Agent Notebook Environment", + "displayName": "%displayName%", + "description": "%description%", + "l10n": "./l10n", "author": { "name": "Malachite", "email": "malachiten@outlook.com", @@ -27,7 +28,7 @@ "activitybar": [ { "id": "mutsumi-sidebar", - "title": "Mutsumi Agents", + "title": "%viewContainer.title%", "icon": "assets/sidebar-icon.png" } ] @@ -36,25 +37,25 @@ "mutsumi-sidebar": [ { "id": "mutsumi.agentSidebar", - "name": "Agents", + "name": "%view.agents.name%", "when": "true", "icon": "assets/sidebar-icon.png" }, { "id": "mutsumi.approvalSidebar", - "name": "Pending Approvals", + "name": "%view.pendingApprovals.name%", "when": "true", "icon": "assets/sidebar-icon.png" }, { "id": "mutsumi.contextSidebar", - "name": "Context Items", + "name": "%view.contextItems.name%", "when": "true", "icon": "assets/sidebar-icon.png" }, { "id": "mutsumi.shellTaskSidebar", - "name": "Shell Tasks", + "name": "%view.shellTasks.name%", "when": "true", "icon": "assets/sidebar-icon.png" } @@ -63,7 +64,7 @@ "notebooks": [ { "type": "mutsumi-notebook", - "displayName": "Mutsumi Agent Session", + "displayName": "%notebook.displayName%", "selector": [ { "filenamePattern": "*.mtm" @@ -74,7 +75,7 @@ "notebookRenderer": [ { "id": "mutsumi-agent-renderer", - "displayName": "Mutsumi Agent Chat Renderer", + "displayName": "%notebookRenderer.displayName%", "entrypoint": "./dist/notebookRenderer.js", "mimeTypes": [ "application/vnd.mutsumi.agent-chat" @@ -84,148 +85,148 @@ "commands": [ { "command": "mutsumi.newAgent", - "title": "Mutsumi: New Agent" + "title": "%command.newAgent.title%" }, { "command": "mutsumi.openAgentFile", - "title": "Open Agent Notebook", + "title": "%command.openAgentFile.title%", "category": "Mutsumi" }, { "command": "mutsumi.copyReference", - "title": "Copy Mutsumi Reference", + "title": "%command.copyReference.title%", "category": "Mutsumi" }, { "command": "mutsumi.approveRequest", - "title": "Approve", + "title": "%command.approveRequest.title%", "icon": "$(check)" }, { "command": "mutsumi.rejectRequest", - "title": "Reject", + "title": "%command.rejectRequest.title%", "icon": "$(x)" }, { "command": "mutsumi.customRequestAction", - "title": "Inspect", + "title": "%command.customRequestAction.title%", "icon": "$(search)" }, { "command": "mutsumi.selectModel", - "title": "Select Model", + "title": "%command.selectModel.title%", "category": "Mutsumi", "icon": "$(server-environment)" }, { "command": "mutsumi.renameSession", - "title": "Rename Session", + "title": "%command.renameSession.title%", "category": "Mutsumi", "icon": "$(symbol-keyword)" }, { "command": "mutsumi.debugContext", - "title": "Debug Context", + "title": "%command.debugContext.title%", "category": "Mutsumi", "icon": "$(debug-console)" }, { "command": "mutsumi.pruneGhostBlocks", - "title": "Prune Old Ghost Versions", + "title": "%command.pruneGhostBlocks.title%", "category": "Mutsumi", "icon": "$(history)" }, { "command": "mutsumi.toggleAutoApprove", - "title": "Enter Auto-Approve", + "title": "%command.toggleAutoApprove.title%", "category": "Mutsumi", "icon": "$(run-all)" }, { "command": "mutsumi.toggleAutoApproveOn", - "title": "Exit Auto-Approve", + "title": "%command.toggleAutoApproveOn.title%", "category": "Mutsumi", "icon": "$(debug-disconnect)" }, { "command": "mutsumi.testRagSearch", - "title": "Test RAG Search", + "title": "%command.testRagSearch.title%", "category": "Mutsumi", "icon": "$(search)" }, { "command": "mutsumi.compressConversation", - "title": "Compress Conversation", + "title": "%command.compressConversation.title%", "category": "Mutsumi", "icon": "$(fold)" }, { "command": "mutsumi.viewContextItem", - "title": "View Context Item", + "title": "%command.viewContextItem.title%", "category": "Mutsumi", "icon": "$(open-preview)" }, { "command": "mutsumi.toggleRule", - "title": "Toggle Rule", + "title": "%command.toggleRule.title%", "category": "Mutsumi", "icon": "$(check)" }, { "command": "mutsumi.toggleSkill", - "title": "Toggle Skill", + "title": "%command.toggleSkill.title%", "category": "Mutsumi", "icon": "$(check)" }, { "command": "mutsumi.removeMacro", - "title": "Remove Macro", + "title": "%command.removeMacro.title%", "category": "Mutsumi", "icon": "$(close)" }, { "command": "mutsumi.removeFile", - "title": "Remove File", + "title": "%command.removeFile.title%", "category": "Mutsumi", "icon": "$(close)" }, { "command": "mutsumi.pruneFileVersions", - "title": "Prune Old Versions", + "title": "%command.pruneFileVersions.title%", "category": "Mutsumi", "icon": "$(history)" }, { "command": "mutsumi.refreshContextTree", - "title": "Refresh Context Tree", + "title": "%command.refreshContextTree.title%", "category": "Mutsumi", "icon": "$(refresh)" }, { "command": "mutsumi.clearToolCache", - "title": "Clear Tool Cache", + "title": "%command.clearToolCache.title%", "category": "Mutsumi", "icon": "$(clear-all)" }, { "command": "mutsumi.generateHttpServerPassword", - "title": "Generate HTTP Server Password", + "title": "%command.generateHttpServerPassword.title%", "category": "Mutsumi", "icon": "$(key)" }, { "command": "mutsumi.detachShellTask", - "title": "Move to Background", + "title": "%command.detachShellTask.title%", "icon": "$(debug-disconnect)" }, { "command": "mutsumi.killShellTask", - "title": "Stop", + "title": "%command.killShellTask.title%", "icon": "$(debug-stop)" }, { "command": "mutsumi.removeShellTask", - "title": "Remove", + "title": "%command.removeShellTask.title%", "icon": "$(close)" } ], @@ -373,26 +374,26 @@ ] }, "configuration": { - "title": "Mutsumi", + "title": "%configuration.title%", "properties": { "mutsumi.providers": { "type": "array", - "markdownDescription": "Configured LLM API providers\n\nExample:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", + "markdownDescription": "%configuration.providers.markdownDescription%", "items": { "type": "object", "properties": { "name": { "type": "string", - "markdownDescription": "LLM API Provider Name (e.g., OpenAI, Anthropic, llama.cpp)" + "markdownDescription": "%configuration.providers.items.name.markdownDescription%" }, "baseurl": { "type": "string", - "markdownDescription": "LLM API BaseURL (e.g., https://api.openai.com/v1, http://localhost:8080/v1)" + "markdownDescription": "%configuration.providers.items.baseurl.markdownDescription%" }, "api_key": { "type": "string", "default": "", - "markdownDescription": "LLM API Key (required, use dummy value for local deployments without authentication)" + "markdownDescription": "%configuration.providers.items.api_key.markdownDescription%" } }, "required": [ @@ -409,65 +410,65 @@ "type": "string" }, "default": {}, - "markdownDescription": "Model to provider name mapping. Key is model identifier, value is the provider name from mutsumi.providers\n\nExample:\n```json\n\"mutsumi.models\": {\n \"kimi-for-coding\": \"kimi-for-coding\"\n}\n```" + "markdownDescription": "%configuration.models.markdownDescription%" }, "mutsumi.defaultModel": { "type": "string", "default": "kimi-for-coding", - "markdownDescription": "Default model to use (must be a key in mutsumi.models)" + "markdownDescription": "%configuration.defaultModel.markdownDescription%" }, "mutsumi.titleGeneratorModel": { "type": "string", "default": "kimi-for-coding", - "markdownDescription": "Model used for generating agent titles (must be a key in mutsumi.models)" + "markdownDescription": "%configuration.titleGeneratorModel.markdownDescription%" }, "mutsumi.autoApproveEnabled": { "type": "boolean", "default": false, - "description": "When enabled, all tool calls will be automatically approved without user confirmation (use with caution)" + "description": "%configuration.autoApproveEnabled.description%" }, "mutsumi.compressModel": { "type": "string", "default": "kimi-for-coding", - "markdownDescription": "Model used for compressing conversations (must be a key in mutsumi.models). If empty, falls back to titleGeneratorModel then defaultModel" + "markdownDescription": "%configuration.compressModel.markdownDescription%" }, "mutsumi.embeddingEndpoint": { "type": "string", "default": "", - "description": "OpenAI-compatible embedding endpoint URL (e.g., http://localhost:1234/v1/embeddings). If empty, RAG features will be disabled." + "description": "%configuration.embeddingEndpoint.description%" }, "mutsumi.shellSyncTimeout": { "type": "number", "default": 60, "minimum": 0, - "description": "Seconds a synchronous (non-background) shell call may run before being auto-detached to background. Set 0 to disable auto-detach." + "description": "%configuration.shellSyncTimeout.description%" }, "mutsumi.httpServer.enabled": { "type": "boolean", "default": false, - "description": "Enable the Mutsumi HTTP Server (disabled by default). All endpoints require Bearer token authentication when enabled." + "description": "%configuration.httpServer.enabled.description%" }, "mutsumi.httpServer.password": { "type": "string", "default": "", - "description": "Bearer token password for the Mutsumi HTTP Server. Stored in PLAINTEXT in settings. If empty while the server is enabled, the server refuses to start. Use the 'Mutsumi: Generate HTTP Server Password' command to create one." + "description": "%configuration.httpServer.password.description%" }, "mutsumi.httpServer.host": { "type": "string", "default": "127.0.0.1", - "description": "Address the HTTP Server binds to. WARNING: changing this to 0.0.0.0 exposes the server to your LAN or the public internet — set a strong password (mutsumi.httpServer.password) first." + "description": "%configuration.httpServer.host.description%" }, "mutsumi.httpServer.port": { "type": "number", "default": 3000, "minimum": 1, "maximum": 65535, - "description": "Starting port for the HTTP Server. If occupied, subsequent ports are scanned (up to +100)." + "description": "%configuration.httpServer.port.description%" }, "mutsumi.agentConfig": { "type": "object", "default": {}, - "markdownDescription": "Agent type & tool set configuration. Overrides built-in defaults. Leave empty `{}` to use built-in defaults.\n\nShape:\n```json\n\"mutsumi.agentConfig\": {\n \"toolSets\": {\n \"read\": [\"read\", \"glob\"],\n \"deliver\": [\"shell\", \"write\"]\n },\n \"agentTypes\": {\n \"chat\": {\n \"toolSets\": [],\n \"defaultModel\": \"kimi-for-coding\",\n \"defaultRules\": [\"default/chat.md\"],\n \"defaultSkills\": [],\n \"allowedChildTypes\": [],\n \"isEntry\": true\n }\n }\n}\n```\n\n`toolSets`: each key maps to an array of tool names. Given names fully replace the built-in tool set. New names add new tool sets.\n\n`agentTypes`: for a type that exists in built-in defaults, fields you provide override the built-in fields (fields you omit are kept from defaults). For a new type, provide all required fields.\n\nNote: `version` is not configurable here; it is fixed to 1 internally.", + "markdownDescription": "%configuration.agentConfig.markdownDescription%", "properties": { "toolSets": { "type": "object", diff --git a/package.nls.json b/package.nls.json new file mode 100644 index 0000000..2b2dcf2 --- /dev/null +++ b/package.nls.json @@ -0,0 +1,57 @@ +{ + "displayName": "Mutsumi", + "description": "Multi-Agent Notebook Environment", + "viewContainer.title": "Mutsumi Agents", + "view.agents.name": "Agents", + "view.pendingApprovals.name": "Pending Approvals", + "view.contextItems.name": "Context Items", + "view.shellTasks.name": "Shell Tasks", + "notebook.displayName": "Mutsumi Agent Session", + "notebookRenderer.displayName": "Mutsumi Agent Chat Renderer", + "notebookController.displayName": "Mutsumi Agent", + + "command.newAgent.title": "Mutsumi: New Agent", + "command.openAgentFile.title": "Open Agent Notebook", + "command.copyReference.title": "Copy Mutsumi Reference", + "command.approveRequest.title": "Approve", + "command.rejectRequest.title": "Reject", + "command.customRequestAction.title": "Inspect", + "command.selectModel.title": "Select Model", + "command.renameSession.title": "Rename Session", + "command.debugContext.title": "Debug Context", + "command.pruneGhostBlocks.title": "Prune Old Ghost Versions", + "command.toggleAutoApprove.title": "Enter Auto-Approve", + "command.toggleAutoApproveOn.title": "Exit Auto-Approve", + "command.testRagSearch.title": "Test RAG Search", + "command.compressConversation.title": "Compress Conversation", + "command.viewContextItem.title": "View Context Item", + "command.toggleRule.title": "Toggle Rule", + "command.toggleSkill.title": "Toggle Skill", + "command.removeMacro.title": "Remove Macro", + "command.removeFile.title": "Remove File", + "command.pruneFileVersions.title": "Prune Old Versions", + "command.refreshContextTree.title": "Refresh Context Tree", + "command.clearToolCache.title": "Clear Tool Cache", + "command.generateHttpServerPassword.title": "Generate HTTP Server Password", + "command.detachShellTask.title": "Move to Background", + "command.killShellTask.title": "Stop", + "command.removeShellTask.title": "Remove", + + "configuration.title": "Mutsumi", + "configuration.providers.markdownDescription": "Configured LLM API providers\n\nExample:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", + "configuration.providers.items.name.markdownDescription": "LLM API Provider Name (e.g., OpenAI, Anthropic, llama.cpp)", + "configuration.providers.items.baseurl.markdownDescription": "LLM API BaseURL (e.g., https://api.openai.com/v1, http://localhost:8080/v1)", + "configuration.providers.items.api_key.markdownDescription": "LLM API Key (required, use dummy value for local deployments without authentication)", + "configuration.models.markdownDescription": "Model to provider name mapping. Key is model identifier, value is the provider name from mutsumi.providers\n\nExample:\n```json\n\"mutsumi.models\": {\n \"kimi-for-coding\": \"kimi-for-coding\"\n}\n```", + "configuration.defaultModel.markdownDescription": "Default model to use (must be a key in mutsumi.models)", + "configuration.titleGeneratorModel.markdownDescription": "Model used for generating agent titles (must be a key in mutsumi.models)", + "configuration.autoApproveEnabled.description": "When enabled, all tool calls will be automatically approved without user confirmation (use with caution)", + "configuration.compressModel.markdownDescription": "Model used for compressing conversations (must be a key in mutsumi.models). If empty, falls back to titleGeneratorModel then defaultModel", + "configuration.embeddingEndpoint.description": "OpenAI-compatible embedding endpoint URL (e.g., http://localhost:1234/v1/embeddings). If empty, RAG features will be disabled.", + "configuration.shellSyncTimeout.description": "Seconds a synchronous (non-background) shell call may run before being auto-detached to background. Set 0 to disable auto-detach.", + "configuration.httpServer.enabled.description": "Enable the Mutsumi HTTP Server (disabled by default). All endpoints require Bearer token authentication when enabled.", + "configuration.httpServer.password.description": "Bearer token password for the Mutsumi HTTP Server. Stored in PLAINTEXT in settings. If empty while the server is enabled, the server refuses to start. Use the 'Mutsumi: Generate HTTP Server Password' command to create one.", + "configuration.httpServer.host.description": "Address the HTTP Server binds to. WARNING: changing this to 0.0.0.0 exposes the server to your LAN or the public internet — set a strong password (mutsumi.httpServer.password) first.", + "configuration.httpServer.port.description": "Starting port for the HTTP Server. If occupied, subsequent ports are scanned (up to +100).", + "configuration.agentConfig.markdownDescription": "Agent type & tool set configuration. Overrides built-in defaults. Leave empty `{}` to use built-in defaults.\n\nShape:\n```json\n\"mutsumi.agentConfig\": {\n \"toolSets\": {\n \"read\": [\"read\", \"glob\"],\n \"deliver\": [\"shell\", \"write\"]\n },\n \"agentTypes\": {\n \"chat\": {\n \"toolSets\": [],\n \"defaultModel\": \"kimi-for-coding\",\n \"defaultRules\": [\"default/chat.md\"],\n \"defaultSkills\": [],\n \"allowedChildTypes\": [],\n \"isEntry\": true\n }\n }\n}\n```\n\n`toolSets`: each key maps to an array of tool names. Given names fully replace the built-in tool set. New names add new tool sets.\n\n`agentTypes`: for a type that exists in built-in defaults, fields you provide override the built-in fields (fields you omit are kept from defaults). For a new type, provide all required fields.\n\nNote: `version` is not configurable here; it is fixed to 1 internally." +} diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json new file mode 100644 index 0000000..95176e5 --- /dev/null +++ b/package.nls.zh-cn.json @@ -0,0 +1,57 @@ +{ + "displayName": "Mutsumi", + "description": "多 Agent 笔记本环境", + "viewContainer.title": "Mutsumi Agents", + "view.agents.name": "Agent", + "view.pendingApprovals.name": "待审批", + "view.contextItems.name": "上下文项", + "view.shellTasks.name": "Shell 任务", + "notebook.displayName": "Mutsumi Agent 会话", + "notebookRenderer.displayName": "Mutsumi Agent 聊天渲染器", + "notebookController.displayName": "Mutsumi Agent", + + "command.newAgent.title": "Mutsumi: 新建 Agent", + "command.openAgentFile.title": "打开 Agent 笔记本", + "command.copyReference.title": "复制 Mutsumi 引用", + "command.approveRequest.title": "批准", + "command.rejectRequest.title": "拒绝", + "command.customRequestAction.title": "检查", + "command.selectModel.title": "选择模型", + "command.renameSession.title": "重命名会话", + "command.debugContext.title": "调试上下文", + "command.pruneGhostBlocks.title": "裁剪旧 Ghost 版本", + "command.toggleAutoApprove.title": "进入自动批准", + "command.toggleAutoApproveOn.title": "退出自动批准", + "command.testRagSearch.title": "测试 RAG 搜索", + "command.compressConversation.title": "压缩对话", + "command.viewContextItem.title": "查看上下文项", + "command.toggleRule.title": "切换规则", + "command.toggleSkill.title": "切换技能", + "command.removeMacro.title": "移除宏", + "command.removeFile.title": "移除文件", + "command.pruneFileVersions.title": "裁剪旧版本", + "command.refreshContextTree.title": "刷新上下文树", + "command.clearToolCache.title": "清除工具缓存", + "command.generateHttpServerPassword.title": "生成 HTTP Server 密码", + "command.detachShellTask.title": "移至后台", + "command.killShellTask.title": "停止", + "command.removeShellTask.title": "移除", + + "configuration.title": "Mutsumi", + "configuration.providers.markdownDescription": "已配置的 LLM API 提供商\n\n示例:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", + "configuration.providers.items.name.markdownDescription": "LLM API 提供商名称(例如 OpenAI、Anthropic、llama.cpp)", + "configuration.providers.items.baseurl.markdownDescription": "LLM API BaseURL(例如 https://api.openai.com/v1、http://localhost:8080/v1)", + "configuration.providers.items.api_key.markdownDescription": "LLM API 密钥(必填,本地无鉴权部署可使用占位值)", + "configuration.models.markdownDescription": "模型到提供商名称的映射。键为模型标识符,值为 mutsumi.providers 中的提供商名称\n\n示例:\n```json\n\"mutsumi.models\": {\n \"kimi-for-coding\": \"kimi-for-coding\"\n}\n```", + "configuration.defaultModel.markdownDescription": "默认使用的模型(必须是 mutsumi.models 中的键)", + "configuration.titleGeneratorModel.markdownDescription": "用于生成 Agent 标题的模型(必须是 mutsumi.models 中的键)", + "configuration.autoApproveEnabled.description": "启用后,所有工具调用将自动批准,无需用户确认(请谨慎使用)", + "configuration.compressModel.markdownDescription": "用于压缩对话的模型(必须是 mutsumi.models 中的键)。若为空,则依次回退到 titleGeneratorModel、defaultModel", + "configuration.embeddingEndpoint.description": "OpenAI 兼容的嵌入端点 URL(例如 http://localhost:1234/v1/embeddings)。若为空,RAG 功能将被禁用。", + "configuration.shellSyncTimeout.description": "同步(非后台)Shell 调用在自动转入后台前可运行的秒数。设为 0 可禁用自动转入。", + "configuration.httpServer.enabled.description": "启用 Mutsumi HTTP Server(默认禁用)。启用后所有端点均需 Bearer 令牌鉴权。", + "configuration.httpServer.password.description": "Mutsumi HTTP Server 的 Bearer 令牌密码。以明文形式存储在设置中。若启用服务器但密码为空,服务器将拒绝启动。可使用“Mutsumi: 生成 HTTP Server 密码”命令创建密码。", + "configuration.httpServer.host.description": "HTTP Server 绑定的地址。警告:将其改为 0.0.0.0 会将服务器暴露给局域网或公网——请先设置强密码(mutsumi.httpServer.password)。", + "configuration.httpServer.port.description": "HTTP Server 的起始端口。若被占用,将扫描后续端口(最多 +100)。", + "configuration.agentConfig.markdownDescription": "Agent 类型与工具集配置。覆盖内置默认值。留空 `{}` 则使用内置默认值。\n\n结构:\n```json\n\"mutsumi.agentConfig\": {\n \"toolSets\": {\n \"read\": [\"read\", \"glob\"],\n \"deliver\": [\"shell\", \"write\"]\n },\n \"agentTypes\": {\n \"chat\": {\n \"toolSets\": [],\n \"defaultModel\": \"kimi-for-coding\",\n \"defaultRules\": [\"default/chat.md\"],\n \"defaultSkills\": [],\n \"allowedChildTypes\": [],\n \"isEntry\": true\n }\n }\n}\n```\n\n`toolSets`:每个键映射到工具名数组。给定的名称将完全替换内置工具集。新名称将添加新的工具集。\n\n`agentTypes`:对于内置默认值中已存在的类型,提供的字段会覆盖内置字段(省略的字段保留默认值)。对于新类型,请提供所有必填字段。\n\n注意:`version` 在此处不可配置,内部固定为 1。" +} diff --git a/src/agent/agentRunner.ts b/src/agent/agentRunner.ts index 09c1a91..a1e8f8e 100644 --- a/src/agent/agentRunner.ts +++ b/src/agent/agentRunner.ts @@ -17,6 +17,7 @@ import { LiteAgentSession } from '../adapters/liteAdapter'; import { debugLogger } from '../debugLogger'; import { getModelCredentials } from '../utils'; import { AgentRunOptions } from './types'; +import { t } from '../i18n'; export { AgentRunOptions } from './types'; @@ -160,11 +161,12 @@ export class AgentRunner { console.error('LLM Stream Error:', error); // Show error as VSCode notification (non-modal) + const copyDetailsBtn = t('controller.copyDetails'); vscode.window.showErrorMessage( - `Mutsumi LLM Error: ${errorMessage}`, - 'Copy Details' + t('agentRunner.llmError', errorMessage), + copyDetailsBtn ).then(selection => { - if (selection === 'Copy Details') { + if (selection === copyDetailsBtn) { vscode.env.clipboard.writeText(error.stack || errorMessage); } }); diff --git a/src/controller.ts b/src/controller.ts index c73c1c3..9626bf6 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -12,6 +12,7 @@ import { buildInteractionHistory } from './contextManagement/history'; import { AgentMetadata } from './types'; import { getModelCredentials } from './utils'; import { normalizeReasoningEffort } from './agent/types'; +import { t } from './i18n'; /** * Controls the execution of agent notebooks. @@ -174,14 +175,15 @@ export class AgentController { // Only show notification here as a fallback for unhandled errors const errorMessage = err.message || String(err); console.error('Agent execution error:', err); - + + const copyDetailsBtn = t('controller.copyDetails'); vscode.window.showErrorMessage( - `Mutsumi Error: ${errorMessage}`, - 'Copy Details' + t('controller.mutsumiError', errorMessage), + copyDetailsBtn ).then(selection => { - if (selection === 'Copy Details') { - vscode.env.clipboard.writeText(err.stack || errorMessage); - } + if (selection === copyDetailsBtn) { + vscode.env.clipboard.writeText(err.stack || errorMessage); + } }); // Do NOT replace output - preserve any streamed content that was displayed diff --git a/src/debugLogger.ts b/src/debugLogger.ts index c8b37a6..c1a7e6c 100644 --- a/src/debugLogger.ts +++ b/src/debugLogger.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { t } from './i18n'; /** * Shared debug logger for all Mutsumi modules. @@ -13,7 +14,7 @@ class DebugLogger { */ public initialize(context: vscode.ExtensionContext): void { if (!this.outputChannel) { - this.outputChannel = vscode.window.createOutputChannel('Mutsumi Debug'); + this.outputChannel = vscode.window.createOutputChannel(t('Mutsumi Debug')); context.subscriptions.push(this.outputChannel); } } diff --git a/src/extension.ts b/src/extension.ts index 8f520d7..c1f66f7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import { import { ImagePasteProvider } from "./contextManagement/imagePasteProvider"; import { SkillManager } from "./contextManagement/skillManager"; import { sanitizeFileName } from "./utils"; +import { t } from "./i18n"; import { registerToolbarCommands } from "./notebook/toolbar"; import { HeadlessAdapter } from "./adapters/headlessAdapter"; import { ToolRegistry } from "./tools.d/toolManager"; @@ -196,11 +197,11 @@ export async function activate( }; const warnHttpServerEmptyPassword = () => { - const OPEN_SETTINGS = "Open Settings"; - const GENERATE_PASSWORD = "Generate Random Password"; + const OPEN_SETTINGS = t("httpServer.emptyPassword.openSettings"); + const GENERATE_PASSWORD = t("httpServer.emptyPassword.generate"); vscode.window .showWarningMessage( - "Mutsumi HTTP Server is enabled but no password is set (mutsumi.httpServer.password). The server refuses to start.", + t("httpServer.emptyPassword.warning"), OPEN_SETTINGS, GENERATE_PASSWORD, ) @@ -295,7 +296,7 @@ export async function activate( ); await vscode.env.clipboard.writeText(password); vscode.window.showInformationMessage( - "Generated a random HTTP Server password, saved it to your user settings, and copied it to the clipboard.", + t("httpServer.passwordGenerated"), ); const cfg = getHttpServerConfig(); if (cfg.enabled && !httpServer) { @@ -324,7 +325,7 @@ export async function activate( const controller = vscode.notebooks.createNotebookController( "mutsumi-agent", "mutsumi-notebook", - "Mutsumi Agent", + t("notebookController.displayName"), ); controller.supportedLanguages = ["markdown"]; controller.supportsExecutionOrder = true; @@ -487,32 +488,37 @@ function registerCommands(context: vscode.ExtensionContext): void { vscode.commands.registerCommand("mutsumi.newAgent", async () => { const wsFolders = vscode.workspace.workspaceFolders; if (!wsFolders) { - vscode.window.showErrorMessage("Please open a workspace folder first."); + vscode.window.showErrorMessage(t("newAgent.noWorkspace")); return; } - + // AgentType Step 1: Show QuickPick for Agent Type Selection const entryTypes = getEntryAgentTypes(); - + if (entryTypes.length === 0) { vscode.window.showErrorMessage( - "No entry agent types available. Please check your configuration.", + t("newAgent.noEntryTypes"), ); return; } - + // Build QuickPick items with descriptions const typeItems = entryTypes.map(({ name, config }) => ({ label: name, description: `${config.toolSets.join("+")}`, - detail: `Model: ${config.defaultModel} | Rules: ${config.defaultRules.length} | Skills: ${config.defaultSkills.length}`, + detail: t( + "newAgent.detail", + config.defaultModel, + config.defaultRules.length, + config.defaultSkills.length, + ), typeName: name, })); - + // Show QuickPick for agent type selection const selectedType = await vscode.window.showQuickPick(typeItems, { - placeHolder: "Select an agent type to create", - title: "Mutsumi: New Agent", + placeHolder: t("newAgent.quickPickPlaceHolder"), + title: t("newAgent.quickPickTitle"), }); if (!selectedType) { @@ -573,7 +579,12 @@ function registerCommands(context: vscode.ExtensionContext): void { // Show confirmation message with agent type info vscode.window.showInformationMessage( - `Created ${selectedAgentType} agent with ${defaults.rules.length} rules and ${defaults.skills.length} skills`, + t( + "newAgent.created", + selectedAgentType, + defaults.rules.length, + defaults.skills.length, + ), ); }), ); @@ -592,7 +603,7 @@ function registerCommands(context: vscode.ExtensionContext): void { targetUri = editor.document.uri; selection = editor.selection; } else { - vscode.window.showErrorMessage("No file selected or active."); + vscode.window.showErrorMessage(t("copyReference.noFile")); return; } } else { @@ -610,7 +621,7 @@ function registerCommands(context: vscode.ExtensionContext): void { const workspaceFolder = vscode.workspace.getWorkspaceFolder(targetUri); if (!workspaceFolder) { - vscode.window.showErrorMessage("File is not in the workspace."); + vscode.window.showErrorMessage(t("copyReference.notInWorkspace")); return; } @@ -656,7 +667,7 @@ function registerCommands(context: vscode.ExtensionContext): void { await vscode.env.clipboard.writeText(refString); vscode.window.setStatusBarMessage( - `Copied reference: ${refString}`, + t("copyReference.statusBar", refString), 3000, ); }, @@ -667,7 +678,7 @@ function registerCommands(context: vscode.ExtensionContext): void { context.subscriptions.push( vscode.commands.registerCommand("mutsumi.clearToolCache", () => { clearToolCache(); - vscode.window.showInformationMessage("Tool result cache cleared."); + vscode.window.showInformationMessage(t("clearToolCache.done")); }), ); diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..b47b3ef --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,36 @@ +/** + * @fileoverview Localization helper wrapping `vscode.l10n`. + * + * This re-exports `vscode.l10n.t` so call sites can use a short `t(...)` + * import while keeping the bundle keys centralized in `l10n/`. + * + * Usage: + * ```ts + * import { t } from "./i18n"; + * vscode.window.showInformationMessage(t("clearToolCache.done")); + * vscode.window.showErrorMessage(t("controller.mutsumiError", errorMessage)); + * ``` + * + * @module i18n + */ + +import * as vscode from "vscode"; + +/** + * Translate a message string using the active VS Code locale. + * + * Pass the bundle key as the first argument. If the key is not found, the key + * itself is returned (which for this project is always a readable English + * sentence). Additional positional arguments are substituted into `{0}`, + * `{1}`, ... placeholders inside the translated string. + * + * @param message The bundle key (or literal message) to translate. + * @param args Values to substitute into `{n}` placeholders. + * @returns The localized string. + */ +export function t( + message: string, + ...args: Array +): string { + return vscode.l10n.t(message, ...args); +} diff --git a/src/notebook/commands/compressConversation.ts b/src/notebook/commands/compressConversation.ts index ab9ed32..679b7b8 100644 --- a/src/notebook/commands/compressConversation.ts +++ b/src/notebook/commands/compressConversation.ts @@ -12,6 +12,7 @@ import type { AgentRunOptions } from '../../agent/types'; import { MutsumiSerializer } from '../serializer'; import { formatMessagesToString, createDebugSessionFromNotebook } from './utils'; import { getModelCredentials } from '../../utils'; +import { t } from '../../i18n'; /** * Register the compress conversation command. @@ -22,12 +23,12 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon vscode.commands.registerCommand('mutsumi.compressConversation', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -41,7 +42,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon } if (lastCodeCellIndex === -1) { - vscode.window.showWarningMessage('No code cell found in notebook.'); + vscode.window.showWarningMessage(t('notebook.noCodeCell')); return; } @@ -51,7 +52,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const compressModel = config.get('compressModel') || config.get('titleGeneratorModel') || config.get('defaultModel'); if (!compressModel) { - vscode.window.showErrorMessage('Please configure mutsumi.compressModel or mutsumi.defaultModel in settings.'); + vscode.window.showErrorMessage(t('compress.noModel')); return; } @@ -59,7 +60,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon try { credentials = getModelCredentials(compressModel); } catch (err: any) { - vscode.window.showErrorMessage(`Compression failed: ${err.message}`); + vscode.window.showErrorMessage(t('compress.failed', err.message)); return; } const { apiKey, baseUrl } = credentials; @@ -69,14 +70,14 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const { messages } = await buildInteractionHistory(session); if (messages.length <= 1) { - vscode.window.showWarningMessage('Not enough conversation content to compress.'); + vscode.window.showWarningMessage(t('compress.notEnough')); return; } // Show progress await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: 'Compressing conversation...', + title: t('compress.progress'), cancellable: false }, async () => { // Dynamically import AgentRunner to avoid circular dependency @@ -162,7 +163,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const compressedMetadata: AgentMetadata = { ...originalMetadata, uuid: uuidv4(), - name: `${originalMetadata?.name || 'Compressed'} (Compressed)`, + name: t('compress.nameSuffix', originalMetadata?.name || 'Compressed'), created_at: new Date().toISOString(), parent_agent_id: null }; @@ -193,11 +194,11 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const doc = await vscode.workspace.openNotebookDocument(newUri); await vscode.window.showNotebookDocument(doc); - vscode.window.showInformationMessage(`Conversation compressed and saved to: ${newFileName}`); + vscode.window.showInformationMessage(t('compress.done', newFileName)); }); } catch (error: any) { console.error('Failed to compress conversation:', error); - vscode.window.showErrorMessage(`Failed to compress conversation: ${error.message}`); + vscode.window.showErrorMessage(t('compress.failedOverall', error.message)); } }) ); diff --git a/src/notebook/commands/debugContext.ts b/src/notebook/commands/debugContext.ts index 0232976..0464439 100644 --- a/src/notebook/commands/debugContext.ts +++ b/src/notebook/commands/debugContext.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { buildInteractionHistory } from '../../contextManagement/history'; import { formatMessagesToString, createDebugSessionFromNotebook } from './utils'; +import { t } from '../../i18n'; /** * Register the debug context command. @@ -16,12 +17,12 @@ export function registerDebugContextCommand(context: vscode.ExtensionContext): v vscode.commands.registerCommand('mutsumi.debugContext', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -35,7 +36,7 @@ export function registerDebugContextCommand(context: vscode.ExtensionContext): v } if (lastCodeCellIndex === -1) { - vscode.window.showWarningMessage('No code cell found in notebook.'); + vscode.window.showWarningMessage(t('notebook.noCodeCell')); return; } @@ -57,10 +58,10 @@ export function registerDebugContextCommand(context: vscode.ExtensionContext): v }); await vscode.window.showTextDocument(doc, { preview: true }); - vscode.window.showInformationMessage(`Debug context displayed. Total messages: ${messages.length}`); + vscode.window.showInformationMessage(t('debugContext.displayed', messages.length)); } catch (error) { console.error('Failed to debug context:', error); - vscode.window.showErrorMessage(`Failed to debug context: ${error}`); + vscode.window.showErrorMessage(t('debugContext.failed', String(error))); } }) ); diff --git a/src/notebook/commands/pruneGhostBlocks.ts b/src/notebook/commands/pruneGhostBlocks.ts index d7395b1..5435098 100644 --- a/src/notebook/commands/pruneGhostBlocks.ts +++ b/src/notebook/commands/pruneGhostBlocks.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { AgentMetadata } from '../../types'; import { decodeGhostBlock } from '../../contextManagement/ghostBlocks'; import { buildGhostStripEdits } from './utils'; +import { t } from '../../i18n'; /** * Register the prune ghost blocks command. @@ -23,12 +24,12 @@ export function registerPruneGhostBlocksCommand(context: vscode.ExtensionContext vscode.commands.registerCommand('mutsumi.pruneGhostBlocks', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -59,14 +60,14 @@ export function registerPruneGhostBlocksCommand(context: vscode.ExtensionContext ); if (edits.length === 0) { - vscode.window.showInformationMessage('All file references are already at their latest versions.'); + vscode.window.showInformationMessage(t('pruneGhostBlocks.alreadyLatest')); return; } const edit = new vscode.WorkspaceEdit(); edit.set(notebook.uri, edits); await vscode.workspace.applyEdit(edit); - vscode.window.showInformationMessage(`Pruned old ghost versions from ${edits.length} cell(s).`); + vscode.window.showInformationMessage(t('pruneGhostBlocks.done', edits.length)); }) ); } diff --git a/src/notebook/commands/renameSession.ts b/src/notebook/commands/renameSession.ts index cc4aefc..97eca3f 100644 --- a/src/notebook/commands/renameSession.ts +++ b/src/notebook/commands/renameSession.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { regenerateTitleForSession, extractMessagesFromNotebook, getTitleGeneratorConfig, sanitizeFileName, updateNotebookMetadataWithSync } from '../../agent/titleGenerator'; import { LiteAdapter, LiteAgentSessionConfig } from '../../adapters/liteAdapter'; import { AgentMetadata } from '../../types'; +import { t } from '../../i18n'; /** * Register the regenerate title command. @@ -17,19 +18,19 @@ export function registerRenameSessionCommand(context: vscode.ExtensionContext): vscode.commands.registerCommand('mutsumi.renameSession', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } const currentTitle = editor.notebook.metadata?.name || ''; const titleInput = await vscode.window.showInputBox({ value: currentTitle, - prompt: 'Enter new session title (leave empty to auto-generate)' + prompt: t('renameSession.prompt') }); if (titleInput === undefined) { @@ -40,9 +41,9 @@ export function registerRenameSessionCommand(context: vscode.ExtensionContext): try { const sanitized = sanitizeFileName(titleInput); await updateNotebookMetadataWithSync(editor.notebook, sanitized); - vscode.window.showInformationMessage(`Session renamed: ${sanitized}`); + vscode.window.showInformationMessage(t('renameSession.renamed', sanitized)); } catch (error: any) { - vscode.window.showErrorMessage(`Failed to rename session: ${error.message}`); + vscode.window.showErrorMessage(t('renameSession.failed', error.message)); } return; } @@ -65,10 +66,10 @@ export function registerRenameSessionCommand(context: vscode.ExtensionContext): }); const title = await regenerateTitleForSession(session, messages, config, editor.notebook); - vscode.window.showInformationMessage(`Title regenerated: ${title}`); + vscode.window.showInformationMessage(t('renameSession.regenerated', title)); } catch (error: any) { console.error('Failed to regenerate title:', error); - vscode.window.showErrorMessage(`Failed to regenerate title: ${error.message}`); + vscode.window.showErrorMessage(t('renameSession.regenerateFailed', error.message)); } }) ); diff --git a/src/notebook/commands/selectModel.ts b/src/notebook/commands/selectModel.ts index 76fde2b..6c23d11 100644 --- a/src/notebook/commands/selectModel.ts +++ b/src/notebook/commands/selectModel.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { normalizeReasoningEffort, REASONING_EFFORT_SETTING_VALUES } from '../../agent/types'; import type { ReasoningEffortSetting } from '../../agent/types'; import { getModelsConfig } from '../../utils'; +import { t } from '../../i18n'; /** QuickPick item representing a configured model. */ interface ModelQuickPickItem extends vscode.QuickPickItem { @@ -30,13 +31,13 @@ type SelectModelQuickPickItem = ModelQuickPickItem | ReasoningEffortQuickPickIte /** Human-readable descriptions for concrete reasoning effort levels. */ const reasoningEffortDescriptions: Readonly, string>> = { - none: 'Disable reasoning', - minimal: 'Minimal reasoning', - low: 'Low reasoning', - medium: 'Medium reasoning', - high: 'High reasoning', - xhigh: 'Extra-high reasoning', - max: 'Maximum reasoning' + none: t('selectModel.effort.none'), + minimal: t('selectModel.effort.minimal'), + low: t('selectModel.effort.low'), + medium: t('selectModel.effort.medium'), + high: t('selectModel.effort.high'), + xhigh: t('selectModel.effort.xhigh'), + max: t('selectModel.effort.max') }; /** @@ -48,12 +49,12 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo vscode.commands.registerCommand('mutsumi.selectModel', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -61,7 +62,7 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo const modelNames = Object.keys(modelsConfig); if (modelNames.length === 0) { - vscode.window.showErrorMessage('No models configured in settings.'); + vscode.window.showErrorMessage(t('selectModel.noModels')); return; } @@ -96,14 +97,14 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo })); const items: SelectModelQuickPickItem[] = [ - { itemType: 'separator', label: 'Models', kind: vscode.QuickPickItemKind.Separator }, + { itemType: 'separator', label: t('selectModel.separatorModels'), kind: vscode.QuickPickItemKind.Separator }, ...modelItems, - { itemType: 'separator', label: 'Reasoning efforts', kind: vscode.QuickPickItemKind.Separator }, + { itemType: 'separator', label: t('selectModel.separatorReasoning'), kind: vscode.QuickPickItemKind.Separator }, ...effortItems ]; const selected = await vscode.window.showQuickPick(items, { - placeHolder: `Select model or reasoning effort (switching model resets effort to default; current: ${currentModel || 'default'} / ${effectiveReasoningEffort})` + placeHolder: t('selectModel.placeHolder', currentModel || 'default', effectiveReasoningEffort) }); if (!selected || selected.itemType === 'separator') { @@ -128,10 +129,10 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo if (selected.itemType === 'model') { vscode.window.showInformationMessage( - `Model changed to: ${selected.value}; reasoning effort reset to default.` + t('selectModel.modelChanged', selected.value) ); } else { - vscode.window.showInformationMessage(`Reasoning effort changed to: ${selected.value}`); + vscode.window.showInformationMessage(t('selectModel.effortChanged', selected.value)); } }) ); diff --git a/src/notebook/commands/testRagSearch.ts b/src/notebook/commands/testRagSearch.ts index 38124ca..be49f92 100644 --- a/src/notebook/commands/testRagSearch.ts +++ b/src/notebook/commands/testRagSearch.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { RagService } from '../../codebase/rag/service'; +import { t } from '../../i18n'; /** * Register the test RAG search command. @@ -15,13 +16,13 @@ export function registerTestRagSearchCommand(context: vscode.ExtensionContext): vscode.commands.registerCommand('mutsumi.testRagSearch', async () => { // 获取查询输入 const query = await vscode.window.showInputBox({ - prompt: 'Enter natural language query for RAG search', - placeHolder: 'e.g., "how to handle file operations"', + prompt: t('testRag.prompt'), + placeHolder: t('testRag.placeHolder'), ignoreFocusOut: true }); if (!query || !query.trim()) { - vscode.window.showInformationMessage('Search cancelled or empty query.'); + vscode.window.showInformationMessage(t('testRag.cancelled')); return; } @@ -30,7 +31,7 @@ export function registerTestRagSearchCommand(context: vscode.ExtensionContext): const workspaces = vscode.workspace.workspaceFolders; if (!workspaces || workspaces.length === 0) { - vscode.window.showWarningMessage('No workspace folders open.'); + vscode.window.showWarningMessage(t('testRag.noWorkspaces')); return; } @@ -79,10 +80,10 @@ export function registerTestRagSearchCommand(context: vscode.ExtensionContext): }); await vscode.window.showTextDocument(doc, { preview: true }); - vscode.window.showInformationMessage(`RAG search completed across ${workspaces.length} workspace(s).`); + vscode.window.showInformationMessage(t('testRag.completed', workspaces.length)); } catch (error: any) { console.error('RAG search failed:', error); - vscode.window.showErrorMessage(`RAG search failed: ${error.message}`); + vscode.window.showErrorMessage(t('testRag.failed', error.message)); } }) ); diff --git a/src/notebook/commands/toggleAutoApprove.ts b/src/notebook/commands/toggleAutoApprove.ts index a53380a..1280d08 100644 --- a/src/notebook/commands/toggleAutoApprove.ts +++ b/src/notebook/commands/toggleAutoApprove.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { toggleAutoApprove, isAutoApproveEnabled } from '../../tools.d/permission'; +import { t } from '../../i18n'; /** * Register the toggle auto approve commands. @@ -20,13 +21,13 @@ export function registerToggleAutoApproveCommands(context: vscode.ExtensionConte await vscode.commands.executeCommand('setContext', 'mutsumi:autoApproveEnabled', newState); if (newState) { - vscode.window.showWarningMessage('Auto-approve mode is now ON. Tools will be executed without confirmation.'); + vscode.window.showWarningMessage(t('toggleAutoApprove.on')); } else { - vscode.window.showInformationMessage('Auto-approve mode is now OFF. Tools will require confirmation.'); + vscode.window.showInformationMessage(t('toggleAutoApprove.off')); } } catch (error) { console.error('Failed to toggle auto-approve:', error); - vscode.window.showErrorMessage(`Failed to toggle auto-approve: ${error}`); + vscode.window.showErrorMessage(t('toggleAutoApprove.failed', String(error))); } }) ); @@ -40,13 +41,13 @@ export function registerToggleAutoApproveCommands(context: vscode.ExtensionConte await vscode.commands.executeCommand('setContext', 'mutsumi:autoApproveEnabled', newState); if (newState) { - vscode.window.showWarningMessage('Auto-approve mode is now ON. Tools will be executed without confirmation.'); + vscode.window.showWarningMessage(t('toggleAutoApprove.on')); } else { - vscode.window.showInformationMessage('Auto-approve mode is now OFF. Tools will require confirmation.'); + vscode.window.showInformationMessage(t('toggleAutoApprove.off')); } } catch (error) { console.error('Failed to toggle auto-approve:', error); - vscode.window.showErrorMessage(`Failed to toggle auto-approve: ${error}`); + vscode.window.showErrorMessage(t('toggleAutoApprove.failed', String(error))); } }) ); diff --git a/src/notebook/completionProvider.ts b/src/notebook/completionProvider.ts index b472f96..4bf4a84 100644 --- a/src/notebook/completionProvider.ts +++ b/src/notebook/completionProvider.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import { isCommonIgnored } from '../tools.d/utils'; import { ToolManager } from '../tools.d/toolManager'; +import { t } from '../i18n'; /** * Recursively append a JSON-shaped snippet placeholder for a tool parameter schema. @@ -116,8 +117,8 @@ export class ReferenceCompletionProvider implements vscode.CompletionItemProvide const item = new vscode.CompletionItem(relPath, vscode.CompletionItemKind.File); item.insertText = `[${relPath}]`; - item.detail = 'File Reference'; - item.documentation = new vscode.MarkdownString(`Reference content of \`${relPath}\``); + item.detail = t('completion.fileReference'); + item.documentation = new vscode.MarkdownString(t('completion.referenceDoc', relPath)); item.sortText = '000_' + relPath; items.push(item); } @@ -137,7 +138,7 @@ export class ReferenceCompletionProvider implements vscode.CompletionItemProvide const item = new vscode.CompletionItem(displayLabel, vscode.CompletionItemKind.Folder); item.insertText = `[${displayLabel}]`; - item.detail = 'Directory Reference'; + item.detail = t('completion.directoryReference'); item.sortText = '001_' + displayLabel; items.push(item); } @@ -155,11 +156,11 @@ export class ReferenceCompletionProvider implements vscode.CompletionItemProvide for (const tool of tools) { const fn = (tool as any).function; const name = fn.name; - const desc = fn.description || 'Tool'; + const desc = fn.description || t('completion.tool'); const parameters = fn.parameters || {}; const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function); - item.detail = 'Tool Call'; + item.detail = t('completion.toolCall'); const properties = parameters.properties || {}; const required = parameters.required || []; diff --git a/src/notebook/serializer.ts b/src/notebook/serializer.ts index ab9c22a..7dbaf38 100644 --- a/src/notebook/serializer.ts +++ b/src/notebook/serializer.ts @@ -9,6 +9,7 @@ import { resolveAgentDefaults } from '../config/resolver'; import { RenderBlock, RenderData } from './renderTypes'; import { GhostBlock } from '../contextManagement/interfaces'; import { decodeGhostBlock } from '../contextManagement/ghostBlocks'; +import { t } from '../i18n'; // ============================================================================ // Core Data Structures (VSCode-agnostic) @@ -396,7 +397,7 @@ export class MutsumiSerializer implements vscode.NotebookSerializer { raw = { metadata: { uuid: uuidv4(), - name: 'New Agent', + name: t('serializer.newAgent'), created_at: new Date().toISOString(), parent_agent_id: null, allowed_uris: ['/'], @@ -488,7 +489,7 @@ export class MutsumiSerializer implements vscode.NotebookSerializer { const raw: AgentContext = { metadata: { uuid: uuid ?? uuidv4(), - name: 'New Agent', + name: t('serializer.newAgent'), created_at: new Date().toISOString(), parent_agent_id: null, allowed_uris: allowedUris, diff --git a/src/notifications.ts b/src/notifications.ts index b0dbfa0..d743cf2 100644 --- a/src/notifications.ts +++ b/src/notifications.ts @@ -1,4 +1,5 @@ import { NotificationCenter, WindowsToaster } from "node-notifier"; +import { t } from "./i18n"; type MacNotifier = InstanceType; type WinNotifier = InstanceType; @@ -59,7 +60,7 @@ export function notifyWaitingForApproval( export function notifyApprovalNeeded(actionDescription: string): void { notifyWaitingForApproval( "Mutsumi", - `${actionDescription} — review in the sidebar`, - "Approval needed", + t("notify.approvalNeededMessage", actionDescription), + t("notify.approvalNeededSubtitle"), ); } diff --git a/src/sidebar/agentTreeItem.ts b/src/sidebar/agentTreeItem.ts index 3358825..01abfa2 100644 --- a/src/sidebar/agentTreeItem.ts +++ b/src/sidebar/agentTreeItem.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { AgentRuntimeStatus } from '../types'; +import { t } from '../i18n'; /** * @description Agent node data interface, defining the basic information of Agent tree items @@ -66,10 +67,10 @@ export class AgentTreeItem extends vscode.TreeItem { */ private getStatusLabel(status: AgentRuntimeStatus): string { switch (status) { - case 'running': return 'Running'; - case 'pending': return 'Pending'; - case 'finished': return 'Finished'; - case 'standby': return 'Standby'; + case 'running': return t('status.running'); + case 'pending': return t('status.pending'); + case 'finished': return t('status.finished'); + case 'standby': return t('status.standby'); default: return ''; } } @@ -114,7 +115,7 @@ export function registerAgentCommands(context: vscode.ExtensionContext): void { preview: false }); } catch (e) { - vscode.window.showErrorMessage(`Failed to open agent file: ${e}`); + vscode.window.showErrorMessage(t('agentFile.openFailed', String(e))); } } }) diff --git a/src/sidebar/approvalTreeItem.ts b/src/sidebar/approvalTreeItem.ts index 94de84b..ca84e08 100644 --- a/src/sidebar/approvalTreeItem.ts +++ b/src/sidebar/approvalTreeItem.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { ApprovalRequest, approvalManager } from '../tools.d/permission'; +import { t } from '../i18n'; /** * @description Approval request tree node item for displaying tool call approval requests in the sidebar @@ -48,17 +49,17 @@ export class ApprovalTreeItem extends vscode.TreeItem { private buildTooltip(): vscode.MarkdownString { const md = new vscode.MarkdownString(); md.appendMarkdown(`**${this.request.actionDescription}**\n\n`); - md.appendMarkdown(`📁 Target: \`${this.request.targetUri}\`\n\n`); + md.appendMarkdown(t('approval.target', this.request.targetUri) + `\n\n`); if (this.request.customAction) { - md.appendMarkdown(`🔍 **Custom Action Available**: ${this.request.customAction.label}\n\n`); + md.appendMarkdown(t('approval.customActionAvailable', this.request.customAction.label) + `\n\n`); } if (this.request.details) { - md.appendMarkdown(`📝 Details:\n\`\`\`\n${this.request.details}\n\`\`\`\n\n`); + md.appendMarkdown(t('approval.details', this.request.details)); } - md.appendMarkdown(`🕐 Time: ${this.request.timestamp.toLocaleString()}\n\n`); - md.appendMarkdown(`Status: ${this.getStatusText()}`); + md.appendMarkdown(t('approval.time', this.request.timestamp.toLocaleString()) + `\n\n`); + md.appendMarkdown(t('approval.status', this.getStatusText())); return md; } @@ -68,9 +69,9 @@ export class ApprovalTreeItem extends vscode.TreeItem { */ private getStatusText(): string { switch (this.request.status) { - case 'pending': return '⏳ Pending'; - case 'approved': return '✅ Approved'; - case 'rejected': return '❌ Rejected'; + case 'pending': return t('approval.pending'); + case 'approved': return t('approval.approved'); + case 'rejected': return t('approval.rejected'); } } diff --git a/src/sidebar/contextTreeItem.ts b/src/sidebar/contextTreeItem.ts index 0bd8342..e9a41dc 100644 --- a/src/sidebar/contextTreeItem.ts +++ b/src/sidebar/contextTreeItem.ts @@ -3,6 +3,7 @@ import { TemplateEngine } from '../contextManagement/templateEngine'; import { AgentMetadata } from '../types'; import { ContextTreeDataProvider } from './contextTreeProvider'; import { buildGhostStripEdits } from '../notebook/commands/utils'; +import { t } from '../i18n'; /** * @description Context item type definition @@ -180,15 +181,15 @@ export class ContextTreeItem extends vscode.TreeItem { if (type === 'category') { switch (category) { case 'rules': - return 'Rules: Active context rules for Agents'; + return t('context.category.rules'); case 'skills': - return 'Skills: Active context skills for Agents'; + return t('context.category.skills'); case 'macros': - return 'Macros: Reusable text snippets'; + return t('context.category.macros'); case 'files': - return 'Files: Referenced context files'; + return t('context.category.files'); default: - return 'Category'; + return t('context.category.default'); } } @@ -197,7 +198,7 @@ export class ContextTreeItem extends vscode.TreeItem { // Type label let typeLabel = type.charAt(0).toUpperCase() + type.slice(1); if ((type === 'rule' || type === 'skill') && isActive !== undefined) { - typeLabel += isActive ? ' (Active)' : ' (Inactive)'; + typeLabel += isActive ? t('context.active') : t('context.inactive'); } md.appendMarkdown(`**${typeLabel}**: \`${this.data.key}\`\n\n`); @@ -231,7 +232,7 @@ export function registerContextCommands( context.subscriptions.push( vscode.commands.registerCommand('mutsumi.refreshContextTree', async () => { await contextTreeDataProvider.refreshAll(); - vscode.window.showInformationMessage('Context tree refreshed'); + vscode.window.showInformationMessage(t('context.refreshed')); }) ); // Register view context item command @@ -282,7 +283,7 @@ export function registerContextCommands( displayContent = renderedText; } } catch (error) { - displayContent = `Error reading rule: ${error}`; + displayContent = t('context.readError', String(error)); } } else if (args.type === 'skill') { // Skills: read skill file and display as markdown (no TemplateEngine expansion) @@ -304,7 +305,7 @@ export function registerContextCommands( const skillText = new TextDecoder().decode(skillContent); displayContent = skillText; } catch (innerError) { - displayContent = `Error reading skill: ${error}`; + displayContent = t('context.readError', String(error)); } } } else if (args.type === 'file') { @@ -333,7 +334,7 @@ export function registerContextCommands( displayContent = fileItem.content; } } else { - displayContent = `File not found: ${args.key}`; + displayContent = t('context.fileNotFound', args.key); } } @@ -374,11 +375,11 @@ export function registerContextCommands( if (index === -1) { // Add to active rules activeRules.push(ruleName); - vscode.window.showInformationMessage(`Rule "${ruleName}" activated`); + vscode.window.showInformationMessage(t('context.ruleActivated', ruleName)); } else { // Remove from active rules activeRules.splice(index, 1); - vscode.window.showInformationMessage(`Rule "${ruleName}" deactivated`); + vscode.window.showInformationMessage(t('context.ruleDeactivated', ruleName)); } // Update notebook metadata @@ -417,11 +418,11 @@ export function registerContextCommands( if (index === -1) { // Add to active skills activeSkills.push(skillName); - vscode.window.showInformationMessage(`Skill "${item.data.key}" activated`); + vscode.window.showInformationMessage(t('context.skillActivated', item.data.key)); } else { // Remove from active skills activeSkills.splice(index, 1); - vscode.window.showInformationMessage(`Skill "${item.data.key}" deactivated`); + vscode.window.showInformationMessage(t('context.skillDeactivated', item.data.key)); } // Update notebook metadata @@ -537,7 +538,7 @@ export function registerContextCommands( file => file.key === key && file.version !== latestVersion ); if (edits.length === 0) { - vscode.window.showInformationMessage(`No older versions of "${key}" to prune`); + vscode.window.showInformationMessage(t('context.noOlderVersions', key)); return; } diff --git a/src/sidebar/shellTaskTreeItem.ts b/src/sidebar/shellTaskTreeItem.ts index a545bfa..b43db81 100644 --- a/src/sidebar/shellTaskTreeItem.ts +++ b/src/sidebar/shellTaskTreeItem.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { type ShellTask, formatShellOutput } from "../tools.d/shell/shellTask"; import { shellTaskRegistry } from "../tools.d/shell/registry"; +import { t } from "../i18n"; /** * Shell task tree node for the Shell Tasks sidebar view. @@ -20,35 +21,35 @@ export class ShellTaskTreeItem extends vscode.TreeItem { private formatDescription(): string { const elapsed = Math.floor((Date.now() - this.task.createdAt) / 1000); if (this.task.isRunning) { - return this.task.background ? `bg ${elapsed}s` : `fg ${elapsed}s`; + return this.task.background ? t("shellTask.bg", elapsed) : t("shellTask.fg", elapsed); } const snap = this.task.snapshot(); - if (snap.aborted) return "stopped"; - const sig = snap.signal ? ` (${snap.signal})` : ""; - return `exit ${snap.exitCode}${sig}`; + if (snap.aborted) return t("shellTask.stopped"); + const sig = snap.signal ? t("shellTask.signal", snap.signal) : ""; + return t("shellTask.exit", String(snap.exitCode), sig); } private buildTooltip(): vscode.MarkdownString { const md = new vscode.MarkdownString(); md.appendMarkdown(`**${this.task.cmd}**\n\n`); - md.appendMarkdown(`📁 CWD: \`${this.task.cwd}\`\n\n`); - md.appendMarkdown(`🆔 Task: \`${this.task.id}\`\n\n`); - md.appendMarkdown(`🖥️ Agent Session: \`${this.task.agentSessionId}\`\n\n`); + md.appendMarkdown(t("shellTask.cwd", this.task.cwd) + `\n\n`); + md.appendMarkdown(t("shellTask.task", this.task.id) + `\n\n`); + md.appendMarkdown(t("shellTask.agentSession", this.task.agentSessionId) + `\n\n`); md.appendMarkdown( - `Mode: ${this.task.background ? "background" : "foreground"}\n\n`, + t("shellTask.mode", this.task.background ? t("shellTask.modeBackground") : t("shellTask.modeForeground")), ); const snap = this.task.snapshot(); if (snap.aborted) { - md.appendMarkdown(`Status: 🛑 stopped\n\n`); + md.appendMarkdown(t("shellTask.statusStopped")); } else if (this.task.isRunning) { - md.appendMarkdown(`Status: ▶️ running\n\n`); + md.appendMarkdown(t("shellTask.statusRunning")); } else { - const sig = snap.signal ? ` (signal: ${snap.signal})` : ""; - md.appendMarkdown(`Status: ✅ exit ${snap.exitCode}${sig}\n\n`); + const sig = snap.signal ? t("shellTask.signal", snap.signal) : ""; + md.appendMarkdown(t("shellTask.statusExit", String(snap.exitCode), sig)); } const out = formatShellOutput(snap, { showExit: true }); if (out && out !== "(no output)") { - md.appendMarkdown(`Output:\n\`\`\`\n${out}\n\`\`\``); + md.appendMarkdown(t("shellTask.output", out)); } return md; } diff --git a/src/tools.d/edit_file.ts b/src/tools.d/edit_file.ts index 949f6a2..e337de0 100644 --- a/src/tools.d/edit_file.ts +++ b/src/tools.d/edit_file.ts @@ -11,6 +11,7 @@ import { handleRejectionFlow, } from "./permission"; import { notifyApprovalNeeded } from "../notifications"; +import { t } from "../i18n"; // ============================================================================ // Types and Interfaces @@ -468,7 +469,7 @@ class EditService { ); } catch (e: any) { vscode.window.showErrorMessage( - `Failed to reopen editor: ${e.message}`, + t("editFile.reopenFailed", e.message), ); } }, diff --git a/src/tools.d/permission.ts b/src/tools.d/permission.ts index 55adc1b..197c3bf 100644 --- a/src/tools.d/permission.ts +++ b/src/tools.d/permission.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import type { ToolContext } from "./interface"; import { v4 as uuidv4 } from "uuid"; import { notifyApprovalNeeded } from "../notifications"; +import { t } from "../i18n"; // ====== Auto Approval Configuration ====== @@ -333,15 +334,15 @@ export async function handleRejectionFlow( signalTermination: (isTaskComplete?: boolean) => void, ): Promise { const reason = await vscode.window.showInputBox({ - prompt: `Reason for rejecting ${toolName}:`, - placeHolder: "Enter reason (ESC to abort generation)", + prompt: t("permission.rejectPrompt", toolName), + placeHolder: t("permission.rejectPlaceHolder"), }); if (reason === undefined || reason.trim() === "") { signalTermination(false); - return `[Rejected] The ${toolName} operation was rejected by user.`; + return t("permission.rejected", toolName); } else { - return `[Rejected with Reason] The ${toolName} operation was rejected by user. Reason: ${reason}`; + return t("permission.rejectedWithReason", toolName, reason); } } diff --git a/src/tools.d/toolsLogger.ts b/src/tools.d/toolsLogger.ts index 413e86a..62baa52 100644 --- a/src/tools.d/toolsLogger.ts +++ b/src/tools.d/toolsLogger.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { t } from '../i18n'; /** * Shared tools logger for real-time tool output streaming. @@ -13,7 +14,7 @@ class ToolsLogger { */ public initialize(context: vscode.ExtensionContext): void { if (!this.outputChannel) { - this.outputChannel = vscode.window.createOutputChannel('Mutsumi Tools'); + this.outputChannel = vscode.window.createOutputChannel(t('Mutsumi Tools')); context.subscriptions.push(this.outputChannel); } } From ab31c4900b62b9b981966a3908cca0afadafcb77 Mon Sep 17 00:00:00 2001 From: emikeliu Date: Sun, 2 Aug 2026 21:40:24 +0800 Subject: [PATCH 4/4] feat(agent): support inquiry agent mode when notepad created --- l10n/bundle.l10n.json | 4 ++++ l10n/bundle.l10n.zh-cn.json | 3 +++ package.json | 11 ++++++++++ package.nls.json | 1 + package.nls.zh-cn.json | 1 + src/notebook/commands/modeDisplay.ts | 32 ++++++++++++++++++++++++++++ src/notebook/toolbar.ts | 2 ++ 7 files changed, 54 insertions(+) create mode 100644 src/notebook/commands/modeDisplay.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 4482cc0..816ff99 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -75,6 +75,9 @@ "notebook.noEditor": "No active notebook editor.", "notebook.onlyMutsumi": "This command only works with Mutsumi notebooks.", "notebook.noCodeCell": "No code cell found in notebook.", + + "modeDisplay.currentMode": "Current mode: {0}", + "modeDisplay.unknown": "Unknown", "selectModel.noModels": "No models configured in settings.", "selectModel.placeHolder": "Select model or reasoning effort (switching model resets effort to default; current: {0} / {1})", @@ -141,4 +144,5 @@ "notify.approvalNeededSubtitle": "Approval needed", "notify.approvalNeededMessage": "{0} — review in the sidebar" + } diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json index 3507408..ba55d12 100644 --- a/l10n/bundle.l10n.zh-cn.json +++ b/l10n/bundle.l10n.zh-cn.json @@ -74,6 +74,9 @@ "notebook.noEditor": "没有活动的笔记本编辑器。", "notebook.onlyMutsumi": "此命令仅适用于 Mutsumi 笔记本。", + "modeDisplay.currentMode": "当前模式: {0}", + "modeDisplay.unknown": "元数据异常", + "selectModel.noModels": "设置中未配置任何模型。", "selectModel.placeHolder": "选择模型或推理强度(切换模型会将强度重置为默认;当前: {0} / {1})", "selectModel.modelChanged": "模型已切换为: {0};推理强度已重置为默认。", diff --git a/package.json b/package.json index bec12f4..1dea8c7 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,12 @@ "category": "Mutsumi", "icon": "$(history)" }, + { + "command": "mutsumi.displayMode", + "title": "%command.displayMode.title%", + "category": "Mutsumi", + "icon": "$(lightbulb)" + }, { "command": "mutsumi.toggleAutoApprove", "title": "%command.toggleAutoApprove.title%", @@ -331,6 +337,11 @@ } ], "notebook/toolbar": [ + { + "command": "mutsumi.displayMode", + "group": "navigation@0", + "when": "notebookType == mutsumi-notebook" + }, { "command": "mutsumi.selectModel", "group": "navigation@1", diff --git a/package.nls.json b/package.nls.json index 2b2dcf2..4357694 100644 --- a/package.nls.json +++ b/package.nls.json @@ -36,6 +36,7 @@ "command.detachShellTask.title": "Move to Background", "command.killShellTask.title": "Stop", "command.removeShellTask.title": "Remove", + "command.displayMode.title": "Display Current Mode", "configuration.title": "Mutsumi", "configuration.providers.markdownDescription": "Configured LLM API providers\n\nExample:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 95176e5..84b4e37 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -36,6 +36,7 @@ "command.detachShellTask.title": "移至后台", "command.killShellTask.title": "停止", "command.removeShellTask.title": "移除", + "command.displayMode.title": "显示当前模式", "configuration.title": "Mutsumi", "configuration.providers.markdownDescription": "已配置的 LLM API 提供商\n\n示例:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", diff --git a/src/notebook/commands/modeDisplay.ts b/src/notebook/commands/modeDisplay.ts new file mode 100644 index 0000000..e813c0e --- /dev/null +++ b/src/notebook/commands/modeDisplay.ts @@ -0,0 +1,32 @@ +/** + * @fileoverview Model selection command for Mutsumi notebook. + * @module notebook/commands/selectModel + */ + +import * as vscode from 'vscode'; +import { t } from '../../i18n'; + +/** + * Register the select model command. + * @param {vscode.ExtensionContext} context - Extension context for registering disposables + */ +export function registerModeDisplayCommand(context: vscode.ExtensionContext): void { + context.subscriptions.push( + vscode.commands.registerCommand('mutsumi.displayMode', async () => { + const editor = vscode.window.activeNotebookEditor; + if (!editor) { + vscode.window.showWarningMessage(t('notebook.noEditor')); + return; + } + + if (editor.notebook.notebookType !== 'mutsumi-notebook') { + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); + return; + } + + const notebook = editor.notebook; + const metadata = notebook.metadata as { contextItems?: { content?: string }[] } | undefined; + + vscode.window.showInformationMessage(t('modeDisplay.currentMode', metadata?.contextItems?.[0]?.content || t('modeDisplay.unknown')),{ modal: true } ); + })); +} diff --git a/src/notebook/toolbar.ts b/src/notebook/toolbar.ts index 58e8cf7..903bc0b 100644 --- a/src/notebook/toolbar.ts +++ b/src/notebook/toolbar.ts @@ -13,12 +13,14 @@ import { registerCompressConversationCommand, registerPruneGhostBlocksCommand } from './commands'; +import { registerModeDisplayCommand } from './commands/modeDisplay'; /** * Registers all toolbar-related commands for Mutsumi notebooks. * @param {vscode.ExtensionContext} context - Extension context for registering disposables */ export function registerToolbarCommands(context: vscode.ExtensionContext): void { + registerModeDisplayCommand(context); registerSelectModelCommand(context); registerRenameSessionCommand(context); registerDebugContextCommand(context);