Skip to content

Comments

[MI][AI][Copilot] Add agent mode to MI Copilot#1452

Merged
ChinthakaJ98 merged 109 commits intowso2:mainfrom
IsuruMaduranga:mi-agent-mode
Feb 20, 2026
Merged

[MI][AI][Copilot] Add agent mode to MI Copilot#1452
ChinthakaJ98 merged 109 commits intowso2:mainfrom
IsuruMaduranga:mi-agent-mode

Conversation

@IsuruMaduranga
Copy link
Contributor

@IsuruMaduranga IsuruMaduranga commented Feb 16, 2026

Purpose

This PR adds Claude Code/Cursor-like autonomous agentic end-to-end integration development abilities to MI Copilot. It also migrates the extension to a common SSO login through WSO2 Devant and a unified AI proxy shared between MI and BI.

Fixes:


Key Features

Autonomous Agentic Development

  • 22 Specialized Tools: The agent can autonomously develop Synapse integrations using 22 tools across 8 categories, including file operations, connector management, shell execution, and LSP integration.
  • Mode System (Ask/Edit/Plan):
    • Ask mode: Read-only exploration with code blocks that can be applied to the project.
    • Edit mode: Full tool access for autonomous implementation.
    • Plan mode: A dedicated phase for architecture and planning, utilizing user-visible plan files and a blocking approval workflow.
  • Specialized Subagents: Includes an Explore agent for codebase search, a Data Mapper agent for TypeScript mappings, and a Compact agent for conversation summarization.

Advanced Developer UX

  • Undo & Checkpoint System: Captures file states using SHA-256 hashes and calculates LCS-based line diffs before agent modifications, allowing users to undo changes with conflict detection.
  • Claude Code-style UI: A new compact input bar with a mode switcher pill, @mentions for file/folder search, and rich content segments for thinking blocks and bash output.
  • Session Management: Full multi-session support with JSONL history persistence, time-grouped session lists, and automatic title generation from the first user message.
  • Extended Thinking: Supports Claude's reasoning mode with collapsible streaming UI segments.

Infrastructure & Performance

  • Unified Auth & Proxy: Migration to a common AI Proxy and SSO (MI_INTEL) for a consolidated WSO2 AI infrastructure.
  • Prompt Caching: Strategic cache breakpoints deliver up to a 90% reduction in token costs.
  • Tool Result Persistence: Large tool outputs (>30KB) are persisted to disk to manage the context window effectively.

Technical Changes

Component Description
Agent Service Implemented using Vercel AI SDK with multi-step tool calling (stopWhen: stepCountIs()).
Undo Manager New AgentUndoCheckpointManager handles stack-based checkpoints (max 25) and SHA-256 hashing.
Plan Mode Implementation of ask_user_question and exit_plan_mode tools that block execution for user input.
Shell Tools Integrated background shell execution with output polling and tree-kill process cleanup.
LSP Integration The validate_code tool utilizes the LemMinx XML LSP for real-time diagnostics.
Screen.Recording.2026-02-16.at.17.44.43.mov

Copilot AI review requested due to automatic review settings February 16, 2026 08:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds MI Copilot Agent mode and migrates ai-panel into ai-features: new agent-mode RPC/types/handlers, full agent execution pipeline with many agents and tools (file, bash, web, connectors, data-mapper, subagents, plan-mode), session/history/undo management, unified Copilot auth and Anthropic provider utilities, and large import/entry-point reorganization.

Changes

Cohort / File(s) Summary
Core RPC & Types
workspaces/mi/mi-core/src/index.ts, workspaces/mi/mi-core/src/interfaces/mi-copilot.ts, workspaces/mi/mi-core/src/state-machine-types.ts, workspaces/mi/mi-core/src/rpc-types/agent-mode/*, workspaces/mi/mi-core/src/rpc-types/ai-features/*, workspaces/mi/mi-core/src/rpc-types/mi-diagram/*
Add and re-export agent-mode and ai-features RPC types; change usage/token shapes and small diagram request/response type tweaks.
Agent Core & Agents
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/...
Add main agent engine and subagents (main, compact, data-mapper, explore), prompts/system templates, Langfuse hooks, and public agent APIs.
Tools & Tooling Surface
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/*, .../tools/types.ts
Introduce a comprehensive tool suite (file, grep, glob, bash background tasks, subagents, web_search/fetch, connectors, data-mapper, runtime/build, plan-mode, validation, result persistence) and centralized tool factories/types.
Session, History & Undo
workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts, .../undo/checkpoint-manager.ts, .../storage-paths.ts
Add ChatHistoryManager (JSONL sessions, metadata), AgentUndoCheckpointManager (capture/commit/undo/conflict detection), and per-project/session storage helpers.
RPC Wiring & Managers
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts, .../rpc-handler.ts, .../event-handler.ts, workspaces/mi/mi-rpc-client/src/RpcClient.ts
Introduce MIAgentPanelRpcManager implementing MIAgentPanelAPI, handler registration, event forwarding, RpcClient agent client getter and agent event subscription.
Auth, Connection & aiMachine
workspaces/mi/mi-extension/src/ai-features/auth.ts, .../connection.ts, .../ai-features/aiMachine.ts
Add unified Copilot auth (platform STS/Devant exchange), token exchange/refresh/storage utilities, provider-based Anthropic client, and platform login listener wiring.
Reorg: ai-panel → ai-features & Imports
many workspaces/mi/mi-extension/src/*, workspaces/mi/mi-core/*
Replace ai-panel with ai-features import paths, remove legacy ai-panel auth, add agent-mode registration and barrels; update many module resolutions.
Attachment, Utils & Context
workspaces/mi/mi-extension/src/ai-features/agent-mode/attachment-utils.ts, .../context/*, .../tool-action-mapper.ts, .../utils/file-utils.ts
Add attachment validation/composition, Synapse/connectors guidance text, tool-action mapping, and project file scanning/tree-format utilities.
Webview, Language Client & Commands
workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts, workspaces/mi/mi-rpc-client/src/RpcClient.ts, workspaces/mi/mi-extension/src/constants/index.ts
Add getCodeActions, agent RPC client getter and onAgentEvent subscription, and new OPEN_AGENT_PANEL command constant.
Dependencies & Minor Types
workspaces/mi/mi-extension/package.json, workspaces/ballerina/overview-view/src/Overview.tsx
Dependency bumps/additions (Anthropic SDK, tokenizer, Langfuse OTEL, pdf-lib, glob, etc.) and small Module/type expansion in Overview.tsx.
Removed / Deprecated
workspaces/mi/mi-extension/src/ai-panel/auth.ts, workspaces/mi/mi-extension/src/uri-handler.ts
Remove legacy ai-panel auth implementation and OAuth callback handling; signin route made a no-op log.

Sequence Diagram(s)

sequenceDiagram
    participant Webview as Client/Webview
    participant RPC as MIAgentPanelRpcManager
    participant History as ChatHistoryManager
    participant Agent as AgentExecutor
    participant Tools as Tool Executors
    participant LLM as Anthropic
    participant FS as File System

    rect rgba(100,150,240,0.5)
    Webview->>RPC: sendAgentMessage(request)
    RPC->>History: saveMessage(user)
    RPC->>Agent: executeAgent(request)
    end

    rect rgba(120,200,150,0.5)
    Agent->>LLM: stream/generate(messages + system)
    LLM-->>Agent: streaming assistant/tool steps
    Agent->>Tools: invoke tool (e.g., file_read / bash / web_fetch)
    Tools->>FS: read/write files
    Tools-->>Agent: tool result
    Agent->>History: saveMessage(assistant/tool_result)
    Agent->>RPC: emit agentEvent
    RPC->>Webview: notify agentEvent
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Extension/MI

Suggested reviewers

  • hevayo
  • gigara

Poem

🐰
I nibble code and stitch a plan,
Sessions saved by floppy span,
Tools queued up and agents hum,
Checkpoints jump and summaries come,
A hopping patch — hooray, well done!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive The PR contains extensive refactoring beyond linked issue scope: reorganizing ai-panel to ai-features, updating numerous import paths across copilot modules, adding cache utilities, and refactoring auth modules. While some structural reorganization supports the agent-mode implementation, the breadth of path changes and component reorganization exceeds the stated objectives. Clarify whether the ai-panel to ai-features reorganization is intentional scope (component restructuring) or could be separated into a distinct PR to isolate agent-mode implementation from architectural refactoring.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR substantially implements both linked issues: #1367 (MI Copilot Agent Mode with agents, tools, session management, and plan mode) and #1389 (Devant auth, token exchange, and Copilot LLM API proxy integration) with extensive new modules, RPC handlers, and supporting infrastructure.
Title check ✅ Passed The title '[MI][AI][Copilot] Add agent mode to MI Copilot' directly and accurately summarizes the primary change—adding agent mode functionality to MI Copilot. It is concise, clear, and highlights the main feature addition.
Description check ✅ Passed The PR description provides comprehensive purpose, goals, and approach details for implementing agent mode and authentication migration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds “MI agent mode” alongside the existing AI features, addressing linked issues by expanding RPC/webview support, improving auth integration via platform extension, and enriching agent tooling (sessions, undo checkpoints, web tools, LSP validation, etc.).

Changes:

  • Introduces agent-mode RPCs, event streaming, session management, undo checkpointing, and a large set of agent tools.
  • Refactors “ai-panel” paths to “ai-features” and unifies auth/token/usage handling.
  • Adds LSP code-action support and extends diagram “open” to support line navigation.

Reviewed changes

Copilot reviewed 87 out of 149 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
workspaces/mi/mi-rpc-client/src/RpcClient.ts Adds agent panel RPC client + agent event subscription support.
workspaces/mi/mi-extension/src/visualizer/activate.ts Updates AI panel import path to ai-features.
workspaces/mi/mi-extension/src/uri-handler.ts Removes legacy /signin OAuth exchange route behavior (now platform-based).
workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts Refactors AI/auth usage, improves open-file navigation (line support), adjusts formatting/edit flow.
workspaces/mi/mi-extension/src/rpc-managers/mi-data-mapper/rpc-manager.ts Updates mapper import path to ai-features.
workspaces/mi/mi-extension/src/rpc-managers/ai-features/utils.ts Switches to unified auth storage APIs and login-method-aware token logic.
workspaces/mi/mi-extension/src/rpc-managers/ai-features/rpc-manager.ts Updates imports, usage fetch parsing, and auth base URL usage.
workspaces/mi/mi-extension/src/rpc-managers/ai-features/event-handler.ts Updates imports to ai-features paths.
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-handler.ts Adds RPC handler registration for agent mode requests (incl. sessions/compact/search).
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/event-handler.ts Adds agent event forwarding from extension to the webview.
workspaces/mi/mi-extension/src/lang-client/ExtendedLanguageClient.ts Adds textDocument/codeAction request helper.
workspaces/mi/mi-extension/src/extension.ts Activates AI panel via ai-features and updates state machine import path.
workspaces/mi/mi-extension/src/constants/index.ts Adds OPEN_AGENT_PANEL command constant.
workspaces/mi/mi-extension/src/ai-panel/auth.ts Deletes legacy AI-panel auth implementation (moved/unified).
workspaces/mi/mi-extension/src/ai-features/utils/file-utils.ts Adds file tree scanning/formatting utilities for prompts.
workspaces/mi/mi-extension/src/ai-features/copilot/unit-tests/unit_test_generate.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/unit-tests/unit_test_case_generate.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/suggestions/suggestions.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/message-utils.ts Tightens image attachment validation and improves warning messaging.
workspaces/mi/mi-extension/src/ai-features/copilot/idp/idp.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/idp/fill_schema.ts Updates AI SDK option API (maxOutputTokens) and connection import path.
workspaces/mi/mi-extension/src/ai-features/copilot/generation/generations.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/dmc_to_ts/dmc_to_ts.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/diagnostics/diagnostics.ts Updates AI SDK option API (maxOutputTokens) and connection import path.
workspaces/mi/mi-extension/src/ai-features/copilot/data-mapper/mapper.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/connectors/connectors.ts Fixes connection import path under ai-features.
workspaces/mi/mi-extension/src/ai-features/copilot/auto-fill/fill.ts Updates AI SDK option API (maxOutputTokens) and connection import path.
workspaces/mi/mi-extension/src/ai-features/connection.ts Updates auth flow, headers, provider accessors, and token refresh behavior.
workspaces/mi/mi-extension/src/ai-features/cache-utils.ts Adds prompt-caching helper for Anthropic models.
workspaces/mi/mi-extension/src/ai-features/aiMachine.ts Integrates Devant/platform extension auth and adds login listener setup.
workspaces/mi/mi-extension/src/ai-features/agent-mode/undo/checkpoint-manager.ts Adds undo-checkpoint stack storage and conflict detection.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/web_tools.ts Adds web_search/web_fetch tools with approval flow.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/validation-utils.ts Adds shared XML validation utilities + optional code actions.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/types.ts Adds common tool types/constants/errors for agent toolset.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/tool-result-persistence.ts Adds persistence for oversized tool outputs with cleanup policies.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/skill_tools.ts Adds skill context loader tool for specialized guidance.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/lsp_tools.ts Adds validate_code tool built atop shared LSP validation utils.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/index.ts Exports tools and provides createFileTools factory.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_tools.ts Adds connector definition tool integrating store cache + docs.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_store_cache.ts Adds connector store caching keyed by runtime version + TTL.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tool-action-mapper.ts Adds UI-friendly action text mapping per tool name.
workspaces/mi/mi-extension/src/ai-features/agent-mode/storage-paths.ts Adds stable on-disk storage keying for projects/sessions.
workspaces/mi/mi-extension/src/ai-features/agent-mode/langfuse-setup.ts Adds Langfuse OpenTelemetry initialization/shutdown helpers.
workspaces/mi/mi-extension/src/ai-features/agent-mode/index.ts Exports agent mode core, prompts, and tools.
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_expression_guide.ts Adds Synapse expression guidance content for prompts.
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_expression_examples.ts Adds Synapse expression examples for prompts.
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/connectors_guide.ts Adds connector usage guidance + AI connector documentation content.
workspaces/mi/mi-extension/src/ai-features/agent-mode/attachment-utils.ts Adds attachment validation + AI SDK message-content builder for agent mode.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/subagents/index.ts Adds subagent exports (Explore subagent).
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/subagents/explore/system.ts Adds Explore subagent system prompt.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/subagents/explore/agent.ts Adds Explore subagent implementation using read-only tools.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/prompt.ts Adds main agent user-prompt builder including project structure + env.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/mode.ts Adds ask/edit/plan mode policy text and plan workflow guidance.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/system.ts Adds data-mapper agent system prompt emphasizing dmUtils usage.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/prompt.ts Adds data-mapper agent user prompt template.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/agent.ts Adds data-mapper sub-agent that writes mapping code into TS file.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/tools.ts Adds compact agent tools scaffold that blocks execution.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/prompt.ts Adds compact summarization system-reminder prompt.
workspaces/mi/mi-extension/src/ai-features/activate.ts Adds Langfuse setup + adjusts AI panel command registration.
workspaces/mi/mi-extension/src/RPCLayer.ts Registers agent RPC handlers and exposes env config to webviews.
workspaces/mi/mi-extension/package.json Upgrades AI SDK deps and adds agent-mode related libraries.
workspaces/mi/mi-core/src/state-machine-types.ts Adjusts token/usage types for unified auth + usage percentage.
workspaces/mi/mi-core/src/rpc-types/mi-diagram/types.ts Adds optional line to open diagram request.
workspaces/mi/mi-core/src/rpc-types/ai-features/rpc-type.ts Updates fetchUsage response typing for new usage shape.
workspaces/mi/mi-core/src/rpc-types/ai-features/index.ts Updates AI panel API typing for new usage shape.
workspaces/mi/mi-core/src/rpc-types/agent-mode/rpc-type.ts Adds agent-mode RPC types and agent event notification type.
workspaces/mi/mi-core/src/rpc-types/agent-mode/index.ts Exports agent-mode RPC types and request/response types.
workspaces/mi/mi-core/src/interfaces/mi-copilot.ts Extends chat entries to optionally include model/tool messages.
workspaces/mi/mi-core/src/index.ts Re-exports ai-features and agent-mode RPC types from mi-core.
workspaces/ballerina/overview-view/src/Overview.tsx Reorders interfaces (non-functional change) to satisfy TS usage/order.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
workspaces/mi/mi-extension/package.json (1)

1060-1060: ⚠️ Potential issue | 🟡 Minor

glob is listed in both devDependencies (v11.0.2) and dependencies (v11.1.0) with mismatched versions.

This can lead to unpredictable resolution behavior depending on the package manager. If glob is needed at runtime, keep it only in dependencies and remove the devDependencies entry (or vice versa). Also align on a single version.

Proposed fix

Remove glob from devDependencies (line 1060) if it's needed at runtime, or from dependencies (line 1104) if it's only needed at build time. Pick one version.

Also applies to: 1104-1104

workspaces/mi/mi-extension/src/ai-features/connection.ts (1)

180-211: ⚠️ Potential issue | 🟠 Major

Stale provider cache when API key changes under the same login method.

cachedAnthropic is invalidated only when loginMethod changes (line 184). For ANTHROPIC_KEY users, the API key is embedded in the provider at creation time (line 199). If the user updates their API key without changing login methods, the cached provider retains the old key, causing persistent authentication failures until the extension is reloaded.

Consider additionally comparing the current access token (or a hash of it) to detect key changes:

Proposed fix sketch
 let cachedAnthropic: ReturnType<typeof createAnthropic> | null = null;
 let cachedAuthMethod: LoginMethod | null = null;
+let cachedApiKey: string | null = null;

 export const getAnthropicProvider = async (): Promise<ReturnType<typeof createAnthropic>> => {
     const loginMethod = await getLoginMethod();
+    const currentKey = loginMethod === LoginMethod.ANTHROPIC_KEY ? await getAccessToken() : null;

     // Recreate client if login method has changed or no cached instance
-    if (!cachedAnthropic || cachedAuthMethod !== loginMethod) {
+    if (!cachedAnthropic || cachedAuthMethod !== loginMethod || (loginMethod === LoginMethod.ANTHROPIC_KEY && currentKey !== cachedApiKey)) {
         if (loginMethod === LoginMethod.MI_INTEL) {
             // ... existing MI_INTEL setup ...
         } else if (loginMethod === LoginMethod.ANTHROPIC_KEY) {
-            const apiKey = await getAccessToken();
+            const apiKey = currentKey;
             // ... existing ANTHROPIC_KEY setup ...
         }
         cachedAuthMethod = loginMethod;
+        cachedApiKey = currentKey;
     }
workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (1)

2661-2680: ⚠️ Potential issue | 🟠 Major

Address incomplete multi-file saves in ApplyEditsRequest handler.

When params.edits contains entries with different documentUri values (line 2650 shows editRequest.documentUri ?? params.documentUri), only params.documentUri is saved (lines 2662–2665). Other edited files remain dirty. Additionally, the non-null assertion on editRequest.documentUri at line 2671 assumes the parameter is always provided, but ExtendedTextEdit.documentUri is optional.

Collect all edited URIs upfront and save all modified documents. Guard against missing URIs in formatEdits:

Suggested fix (multi-file save and URI guard)
             await workspace.applyEdit(edit);
-            const file = Uri.file(params.documentUri);
-            let document = workspace.textDocuments.find(doc => doc.uri.fsPath === params.documentUri) 
-                            || await workspace.openTextDocument(file);
-            await document.save();
+            const editedUris = new Set<string>();
+            if ('text' in params) {
+                editedUris.add(params.documentUri);
+            } else if ('edits' in params) {
+                params.edits.forEach(editRequest => {
+                    const targetUri = editRequest.documentUri ?? params.documentUri;
+                    if (targetUri) {
+                        editedUris.add(targetUri);
+                    }
+                });
+            }
+            await Promise.all([...editedUris].map(async (uri) => {
+                const file = Uri.file(uri);
+                const document = workspace.textDocuments.find(doc => doc.uri.fsPath === uri)
+                    || await workspace.openTextDocument(file);
+                await document.save();
+            }));
-                    return this.rangeFormat({ uri: editRequest.documentUri!, range: formatRange });
+                    const targetUri = editRequest.documentUri ?? params.documentUri;
+                    if (!targetUri) {
+                        return Promise.resolve({ status: true });
+                    }
+                    return this.rangeFormat({ uri: targetUri, range: formatRange });
🤖 Fix all issues with AI agents
In `@workspaces/mi/mi-extension/src/ai-features/activate.ts`:
- Around line 44-47: The registered command sets extension.initialPrompt but
immediately calls openAIWebview() which accepts an optional initialPrompt and
reassigns extension.initialPrompt, overwriting the value; fix by passing the
command parameter through to openAIWebview so the value is preserved—update the
COMMANDS.OPEN_AI_PANEL handler to call openAIWebview(initialPrompt) (referencing
extension.initialPrompt, openAIWebview, and the PromptObject parameter) so the
initial prompt is not lost.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/prompt.ts`:
- Around line 54-59: The template references {{payloads}} but the template
context only sets userPreconfigured: params.payloads, so payloads is never
present and renders empty; update the context-building code (where the template
context object is constructed) to include payloads: params.payloads (or
otherwise assign payloads = params.payloads) so both userPreconfigured and
payloads are available to the template rendering; ensure the context change is
made in the same function that currently sets userPreconfigured (the template
context creation code).
- Around line 176-192: getCurrentlyOpenedFile currently returns a full sentence
which duplicates text in the template; change getCurrentlyOpenedFile to return
only the relative file path string (or null) instead of a complete sentence
(keep the existing fs.existsSync check and path.relative logic), and ensure the
template that injects {{currentlyOpenedFile}} continues to add the surrounding
sentence so consumers use the value as a bare path; update any code that
depended on the helper producing a full sentence to handle a plain path or null
accordingly.

In `@workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts`:
- Around line 4193-4195: The code assigns llmBaseUrl to both openaiUrl and
anthropicUrl causing OpenAI calls to be routed to the Claude endpoint; change
the assignments so openaiUrl is sourced from MI_COPILOT_OPENAI_PROXY_URL only
(e.g., openaiUrl = process.env.MI_COPILOT_OPENAI_PROXY_URL as string) and
anthropicUrl uses llmBaseUrl as its primary fallback (e.g., anthropicUrl =
llmBaseUrl || process.env.MI_COPILOT_ANTHROPIC_PROXY_URL as string); also remove
the unused anthropicUrl variable if it's truly never referenced (or
document/export it if intended for future use). Ensure you update usages
referenced in IdpConnectorSchemaGenerateForm.tsx to rely on the corrected
openaiUrl.
🟠 Major comments (21)
workspaces/mi/mi-extension/package.json-1072-1072 (1)

1072-1072: 🛠️ Refactor suggestion | 🟠 Major

Migrate generateObject calls to v6 pattern using generateText with output: Output.object(...).

The core v5→v6 migration is largely complete (no v5 patterns like CoreMessage or convertToCoreMessages remain), but generateObject is deprecated in AI SDK v6 and still used across multiple files:

  • diagnostics.ts:149
  • fill.ts:215
  • connectors.ts:177
  • fill_schema.ts:143

All of these should be migrated to use generateText with output: Output.object(...) to align with v6's recommended API.

workspaces/mi/mi-extension/src/ai-features/activate.ts-27-27 (1)

27-27: ⚠️ Potential issue | 🟠 Major

ENABLE_LANGFUSE is hardcoded to true — this will activate Langfuse tracing for all users.

This flag is described as a "dev flag" but is unconditionally enabled. If the LANGFUSE_PUBLIC_KEY/LANGFUSE_SECRET_KEY env vars happen to be set (e.g., in a CI or dev environment), traces will be sent to https://cloud.langfuse.com. Consider defaulting to false, or gating on process.env.ENABLE_LANGFUSE === 'true' so it doesn't accidentally activate in production builds.

Proposed fix
-// Dev flag - set to true to enable Langfuse observability
-const ENABLE_LANGFUSE = true;
+// Dev flag - enable Langfuse observability via environment variable
+const ENABLE_LANGFUSE = process.env.ENABLE_LANGFUSE === 'true';
workspaces/mi/mi-extension/src/ai-features/agent-mode/attachment-utils.ts-19-176 (1)

19-176: 🛠️ Refactor suggestion | 🟠 Major

Extract common attachment validation and filtering logic into a shared utility module.

This file duplicates nearly all of its code from workspaces/mi/mi-extension/src/ai-features/copilot/message-utils.ts: the constants TEXT_MIMETYPES and IMAGE_MIMETYPES, the validation functions isValidBase64 and isValidImageDataUri, and the core exports filterFiles, buildMessageContent, and validateAttachments are identical between both files.

The only functional difference is in text file formatting: attachment-utils.ts uses a plain string template while message-utils.ts uses a Handlebars template. This duplication should be consolidated by extracting shared validation and filtering logic into a common utility module, with only the formatting differences remaining module-specific.

workspaces/mi/mi-extension/src/ai-features/agent-mode/storage-paths.ts-59-61 (1)

59-61: ⚠️ Potential issue | 🟠 Major

sessionId parameter from user input is used directly in path construction without validation — potential directory traversal.

The sessionId parameter in SwitchSessionRequest flows directly from user RPC calls through switchSession()ChatHistoryManagergetCopilotSessionDir() without sanitization. A caller could provide a value like ../../etc/passwd to construct paths outside the intended storage directory. Validate that sessionId contains only safe characters (alphanumeric, hyphens, underscores) or use path.basename() to strip directory components before using it in path operations.

workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts-231-246 (1)

231-246: ⚠️ Potential issue | 🟠 Major

writeStream is not nullified after close(), allowing stale writes.

After close() resolves, this.writeStream still holds the reference to the ended stream. A subsequent call to saveMessage() would attempt writeStream.write() on a closed stream (instead of hitting the "Write stream not initialized" guard at line 254), leading to an error or silent data loss.

Proposed fix
     async close(): Promise<void> {
         if (this.writeStream) {
             await this.writeSessionEnd();
+            const stream = this.writeStream;
+            this.writeStream = null;
             return new Promise((resolve, reject) => {
-                this.writeStream!.end((err: Error | null | undefined) => {
+                stream.end((err: Error | null | undefined) => {
                     if (err) {
                         logError('[ChatHistory] Failed to close stream', err);
                         reject(err);
                     } else {
                         logInfo('[ChatHistory] Session closed');
                         resolve();
                     }
                 });
             });
         }
     }
workspaces/mi/mi-extension/src/ai-features/utils/file-utils.ts-226-244 (1)

226-244: ⚠️ Potential issue | 🟠 Major

Cross-platform bug: path.sep split after forward-slash normalization.

shouldIgnoreFile (Line 132) normalizes paths to /, but formatFileTree splits on path.sep (Line 230). On Windows, path.sep is \, so the split produces a single-element array for /-separated paths, breaking the tree structure entirely.

Use a consistent separator for splitting:

Proposed fix
     for (const file of filteredFiles) {
-        const parts = file.split(path.sep);
+        const parts = file.replace(/\\/g, '/').split('/');
         let current = tree;
workspaces/mi/mi-extension/src/ai-features/agent-mode/undo/checkpoint-manager.ts-97-120 (1)

97-120: ⚠️ Potential issue | 🟠 Major

LCS diff has O(n×m) memory — risky for large files.

calculateLineChanges allocates a full (rows+1) × (cols+1) 2D array. For a file with 10,000 lines, this is ~100 million entries (~800 MB for 64-bit numbers). Even moderate files (2,000 lines) would allocate ~16 million entries.

Consider using a space-optimized LCS (only two rows needed) or switching to a well-tested diff library (e.g., diff npm package) that handles large inputs efficiently.

Space-optimized alternative (O(min(n,m)) memory)
 function calculateLineChanges(beforeContent: string, afterContent: string): { addedLines: number; deletedLines: number } {
     const beforeLines = beforeContent.split('\n');
     const afterLines = afterContent.split('\n');
 
     const rows = beforeLines.length;
     const cols = afterLines.length;
-    const dp: number[][] = Array.from({ length: rows + 1 }, () => Array(cols + 1).fill(0));
+    // Space-optimized: only keep two rows
+    let prev = new Array(cols + 1).fill(0);
+    let curr = new Array(cols + 1).fill(0);
 
     for (let i = 1; i <= rows; i++) {
+        curr[0] = 0;
         for (let j = 1; j <= cols; j++) {
             if (beforeLines[i - 1] === afterLines[j - 1]) {
-                dp[i][j] = dp[i - 1][j - 1] + 1;
+                curr[j] = prev[j - 1] + 1;
             } else {
-                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
+                curr[j] = Math.max(prev[j], curr[j - 1]);
             }
         }
+        [prev, curr] = [curr, prev];
     }
 
-    const lcs = dp[rows][cols];
+    const lcs = prev[cols];
     return {
         deletedLines: rows - lcs,
         addedLines: cols - lcs,
     };
 }
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide.ts-444-476 (1)

444-476: ⚠️ Potential issue | 🟠 Major

Incorrect path: src/main/java/wso2mi/resources should be src/main/wso2mi/resources.

Lines 445, 463, and 476 reference src/main/java/wso2mi/resources, but the standard MI project structure used throughout the rest of the prompts (e.g., system.ts line 185, and line 395 of this file) is src/main/wso2mi/resources. This inconsistency will cause the AI to generate incorrect resource paths.

Proposed fix
-When creating supportive resources that are needed for the Integration inside src/main/java/wso2mi/resources, an entry should be added to the src/main/java/wso2mi/resources/artifact.xml. If an artifacts.xml doesn't exist, then create one and add the entry. The format should be as follows:
+When creating supportive resources that are needed for the Integration inside src/main/wso2mi/resources, an entry should be added to the src/main/wso2mi/resources/artifact.xml. If an artifacts.xml doesn't exist, then create one and add the entry. The format should be as follows:
-For an example if an XSLT file is added inside src/main/java/wso2mi/resources/xslt/conversion.xslt, then the artifact entry can be as follows:
+For an example if an XSLT file is added inside src/main/wso2mi/resources/xslt/conversion.xslt, then the artifact entry can be as follows:
-Content under api-definitions, conf, connectors and metadata are not added as registry resources and hence do not require an entry in the artifact.xml. Only supportive resources that are needed for the integration and are added inside src/main/java/wso2mi/resources need to be added as registry resources and require an entry in the artifact.xml.
+Content under api-definitions, conf, connectors and metadata are not added as registry resources and hence do not require an entry in the artifact.xml. Only supportive resources that are needed for the integration and are added inside src/main/wso2mi/resources need to be added as registry resources and require an entry in the artifact.xml.
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/event-handler.ts-65-80 (1)

65-80: 🛠️ Refactor suggestion | 🟠 Major

Use RPCLayer.getMessenger() instead of casting to any to access private _messengers.

RPCLayer already exposes a public static getMessenger(projectUri: string) method (see RPCLayer.ts line 83–85). The (RPCLayer as any)._messengers.get(...) cast breaks encapsulation unnecessarily and will silently break if the internal field is renamed or refactored.

Proposed fix
     private sendEventToVisualizer(event: AgentEvent): void {
         try {
-            const messenger = (RPCLayer as any)._messengers.get(this.projectUri);
+            const messenger = RPCLayer.getMessenger(this.projectUri);
             if (messenger) {
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/web_tools.ts-149-152 (1)

149-152: ⚠️ Potential issue | 🟠 Major

Mixed injected vs module-level Anthropic provider creates inconsistency.

createWebSearchExecute accepts getAnthropicClient as a parameter (line 123) and uses it for model creation (line 165), but calls the module-level getAnthropicProvider() (line 151) for tool factory discovery. If a caller provides a differently-configured client, the tool factory will still originate from the global cached provider, potentially using different auth or base URL.

Either use the injected dependency consistently (derive the provider from it or accept the provider as a parameter too) or document why the global provider is intentionally used for tool factory access.

The same issue applies to createWebFetchExecute at line 251.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_store_cache.ts-145-159 (1)

145-159: ⚠️ Potential issue | 🟠 Major

No timeout on the HTTP fetch — could block the agent indefinitely.

fetch(storeUrl) at line 147 has no AbortSignal or timeout. If the connector store backend is slow or unresponsive, this call will hang for the default TCP timeout (potentially minutes), blocking the agent flow.

🛡️ Proposed fix: add a timeout using AbortSignal
 async function fetchStoreItems(urlTemplate: string, runtimeVersion: string | null): Promise<any[]> {
     const storeUrl = resolveStoreUrl(urlTemplate, runtimeVersion);
-    const response = await fetch(storeUrl);
+    const response = await fetch(storeUrl, {
+        signal: AbortSignal.timeout(15_000), // 15 second timeout
+    });
 
     if (!response.ok) {
         throw new Error(`HTTP ${response.status} ${response.statusText}`);
     }
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts-234-242 (1)

234-242: ⚠️ Potential issue | 🟠 Major

Zod schema includes 'XSD' but the TypeScript type does not.

input_type and output_type in the Zod schema (lines 237, 239) accept 'JSON' | 'XML' | 'XSD' | 'CSV', but the corresponding CreateDataMapperExecuteFn type in types.ts only allows 'JSON' | 'XML' | 'CSV'. Since XSD is supported elsewhere in the data mapper system (as evidenced by test suites and the component itself), add 'XSD' to the type definition in types.ts to match the Zod schema.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-976-976 (1)

976-976: ⚠️ Potential issue | 🟠 Major

Default value mismatch between execute function and tool description.

The execute function defaults output_mode to 'content' (line 976), but the tool description at line 1315 states it "Defaults to files_with_matches". This inconsistency will confuse the AI model, which may omit the parameter expecting one default while the code applies a different one.

Pick one default and align both locations.

Also applies to: 1315-1315

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-980-983 (1)

980-983: ⚠️ Potential issue | 🟠 Major

ReDoS risk: user-supplied pattern passed directly to RegExp constructor.

The pattern argument is provided by the AI agent and forwarded to new RegExp(...) without any validation or timeout protection. A maliciously crafted or accidentally pathological regex (e.g., (a+)+$) could hang the extension host.

Consider wrapping the regex construction in a try/catch (to handle invalid patterns gracefully) and optionally adding a complexity check or execution timeout.

🛡️ Proposed fix
-            const regex = new RegExp(pattern, caseInsensitive ? 'gi' : 'g');
+            let regex: RegExp;
+            try {
+                regex = new RegExp(pattern, caseInsensitive ? 'gi' : 'g');
+            } catch (regexError) {
+                return {
+                    success: false,
+                    message: `Invalid regex pattern '${pattern}': ${regexError instanceof Error ? regexError.message : String(regexError)}`,
+                    error: 'Error: Invalid regex pattern'
+                };
+            }
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-984-986 (1)

984-986: ⚠️ Potential issue | 🟠 Major

searchPath is not validated for path traversal.

searchPath is joined with projectPath via path.join without any traversal check. A value like ../../etc would resolve outside the project directory. The same issue applies to createGlobExecute at line 1172. Unlike write/edit/read operations, grep and glob skip validateFilePathSecurity.

🛡️ Proposed fix
             // Resolve the search path (always relative to projectPath for security)
             const fullSearchPath = path.join(projectPath, searchPath);
+
+            // Security: verify resolved path is within project
+            if (!isPathWithin(projectPath, path.resolve(fullSearchPath))) {
+                return {
+                    success: false,
+                    message: 'Search path must be within the project directory.',
+                    error: 'Error: Path outside project'
+                };
+            }
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/project_tools.ts-305-311 (1)

305-311: ⚠️ Potential issue | 🟠 Major

Missing validation for item.version?.tagName — dependency could be created with undefined version.

If a connector store entry has no version or the version object has no tagName, the dependency will be constructed with version: undefined. This could cause downstream failures when updating pom.xml or produce malformed XML.

🛡️ Proposed fix
+        const itemVersion = item.version?.tagName;
+        if (!itemVersion) {
+            return {
+                name: itemName,
+                type: itemType,
+                success: false,
+                error: `${itemType === 'connector' ? 'Connector' : 'Inbound endpoint'} '${itemName}' has no version information in the store`
+            };
+        }
+
         // Prepare dependency details
         const dependencies: DependencyDetails[] = [{
             groupId: item.mavenGroupId,
             artifact: item.mavenArtifactId,
-            version: item.version?.tagName,
+            version: itemVersion,
             type: "zip"
         }];
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-604-620 (1)

604-620: 🛠️ Refactor suggestion | 🟠 Major

captureBeforeChange is called before the file-exists-with-content guard.

Line 606 captures an undo checkpoint before line 609–620 checks whether the file already has content and returns an error. This means an undo snapshot is recorded even when the tool performs no mutation. Same pattern occurs in createEditExecute at line 866 (before the existence check at 869).

Move captureBeforeChange after the existence/content guard so checkpoints are only created when a write will actually occur.

♻️ Proposed fix (write)
         const fullPath = resolveFullPath(projectPath, file_path);
-        await undoCheckpointManager?.captureBeforeChange(file_path);

         // Check if file exists with non-empty content
         const fileExists = fs.existsSync(fullPath);
         if (fileExists) {
             const existingContent = fs.readFileSync(fullPath, 'utf-8');
             if (existingContent.trim().length > 0) {
                 ...
                 return { ... };
             }
         }

+        await undoCheckpointManager?.captureBeforeChange(file_path);
+
         // Create parent directories if they don't exist
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/bash_tools.ts-149-155 (1)

149-155: ⚠️ Potential issue | 🟠 Major

Unbounded output accumulation for background shells.

shell.output is a string that grows via += for every data chunk from stdout and stderr. A long-running or verbose background command can accumulate tens or hundreds of megabytes, leading to memory pressure in the extension host process.

Consider capping shell.output to a reasonable size (e.g., keep only the last N bytes), or switching to a ring-buffer / file-backed approach.

Example: cap output size
+const MAX_SHELL_OUTPUT_SIZE = 1024 * 1024; // 1 MB
+
 proc.stdout?.on('data', (data) => {
-    shell.output += data.toString();
+    shell.output += data.toString();
+    if (shell.output.length > MAX_SHELL_OUTPUT_SIZE) {
+        shell.output = '...[truncated]\n' + shell.output.slice(-MAX_SHELL_OUTPUT_SIZE);
+    }
 });

 proc.stderr?.on('data', (data) => {
-    shell.output += data.toString();
+    shell.output += data.toString();
+    if (shell.output.length > MAX_SHELL_OUTPUT_SIZE) {
+        shell.output = '...[truncated]\n' + shell.output.slice(-MAX_SHELL_OUTPUT_SIZE);
+    }
 });
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts-652-653 (1)

652-653: 🛠️ Refactor suggestion | 🟠 Major

Unsafe type widening to extract checkpointId.

Line 652 uses an intersection cast (request as UndoLastCheckpointRequest & { checkpointId?: string }).checkpointId to access a field not on the declared type. If this field is genuinely needed, it should be added to the UndoLastCheckpointRequest interface in @wso2/mi-core. Otherwise, this is a type-safety escape hatch that can mask breaking changes if the request shape evolves.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/plan_mode_tools.ts-251-261 (1)

251-261: 🛠️ Refactor suggestion | 🟠 Major

Multiple as any casts on event handler calls suggest AgentEvent type is too narrow.

Event emissions on lines 251, 339, 471, 557, 649, and 741 all cast to any to bypass type checking. This defeats the purpose of having a typed event system. Consider extending the AgentEvent union type in @wso2/mi-core to include plan_approval_requested, ask_user, plan_mode_entered, plan_mode_exited, and todo_updated event shapes, then remove the casts.

Also applies to: 339-339, 471-471

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/runtime_tools.ts-602-605 (1)

602-605: ⚠️ Potential issue | 🟠 Major

Stop command may fail: environment is missing process.env.

The env for the stop process (line 603-604) is set to only setJavaHomeInEnvironmentAndPath(projectPath), unlike the run command (lines 450-453) which merges ...process.env. This means the stop process lacks PATH and other environment variables, which will likely cause the stop command to fail or not find required executables.

🐛 Proposed fix
-                    const env = setJavaHomeInEnvironmentAndPath(projectPath);
-                    const stopProcess = childProcess.spawn(stopCommand, [], { shell: true, env });
+                    const env = {
+                        ...process.env,
+                        ...setJavaHomeInEnvironmentAndPath(projectPath),
+                    };
+                    const stopProcess = childProcess.spawn(stopCommand, [], { shell: true, env });
🟡 Minor comments (28)
workspaces/mi/mi-extension/src/ai-features/copilot/connectors/inbound_db.ts-36-36 (1)

36-36: ⚠️ Potential issue | 🟡 Minor

Copy-paste error: every init operation says "Initialize Kafka Inbound Endpoint".

The description field for the init operation is "Initialize Kafka Inbound Endpoint" across all connectors (SQS on Line 36, CDC on Line 185, ISO8583 on Line 453, Pulsar on Line 1229, SMPP on Line 1786), not just the Kafka one. Since this metadata is fed to the AI agent, incorrect descriptions may confuse the LLM when reasoning about connector types.

Example fix for Amazon SQS (Line 36)
-                    "description": "Initialize Kafka Inbound Endpoint",
+                    "description": "Initialize Amazon SQS Inbound Endpoint",

Apply similar fixes for CDC, ISO8583, Pulsar, and SMPP entries.

Also applies to: 185-185, 453-453, 1229-1229, 1786-1786

workspaces/mi/mi-extension/src/ai-features/copilot/connectors/inbound_db.ts-670-675 (1)

670-675: ⚠️ Potential issue | 🟡 Minor

Incorrect description for contentType parameter in Kafka connector.

The contentType parameter's description reads "Consumer group ID to use when consuming messages." — this is the description for group.id, not for contentType.

Proposed fix
                        {
                            "name": "contentType",
                            "type": "string",
                            "required": true,
-                            "description": "Consumer group ID to use when consuming messages.",
+                            "description": "The content type of the messages (e.g., application/json, text/xml).",
                            "defaultValue": "application/json"
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/connectors_guide.ts-118-118 (1)

118-118: ⚠️ Potential issue | 🟡 Minor

maxTokens value 4069 looks like a typo for 4096.

This appears in both the chat example (line 118) and the RAG chat example (line 164). The common token limit for GPT-4o is typically 4096 or 16384.

workspaces/mi/mi-extension/src/ai-features/agent-mode/context/connectors_guide.ts-27-27 (1)

27-27: ⚠️ Potential issue | 🟡 Minor

Typo: "synaose" should be "synapse".

-   - You must initialize the connection via the init operation everytime you use a connector operation in the synaose seqence itself.
+   - You must initialize the connection via the init operation every time you use a connector operation in the synapse sequence itself.
workspaces/mi/mi-extension/src/ai-features/cache-utils.ts-73-84 (1)

73-84: ⚠️ Potential issue | 🟡 Minor

Shallow merge may overwrite existing anthropic provider options on the last message.

The spread { ...message.providerOptions, ...providerOptions } replaces top-level keys entirely. If the last message already has providerOptions.anthropic with other settings, they'll be lost.

Suggested deep-merge for the anthropic key
         if (index === messages.length - 1) {
+            const existingAnthropicOpts = (message.providerOptions?.anthropic ?? {}) as Record<string, JSONValue>;
+            const newAnthropicOpts = (providerOptions.anthropic ?? {}) as Record<string, JSONValue>;
             return {
                 ...message,
                 providerOptions: {
                     ...message.providerOptions,
-                    ...providerOptions,
+                    anthropic: {
+                        ...existingAnthropicOpts,
+                        ...newAnthropicOpts,
+                    },
                 },
             };
         }
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/mode.ts-113-113 (1)

113-113: ⚠️ Potential issue | 🟡 Minor

Typo: "extreamly" → "extremely".

This text is part of the LLM prompt, so the typo could subtly affect model interpretation.

Fix
-3. **Then present extreamly brief summary of the plan to the user in the chat** - System will attach full plan as a collapsable markdown block in the chat window for the user to review if needed.
+3. **Then present an extremely brief summary of the plan to the user in the chat** - System will attach the full plan as a collapsible markdown block in the chat window for the user to review if needed.
workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts-1088-1191 (1)

1088-1191: ⚠️ Potential issue | 🟡 Minor

Switch case declarations leak across branches (Biome: noSwitchDeclarations).

Variables declared with let/const inside case 'user': (e.g., userContent, files, images, queryMatch) are technically accessible from other case branches due to missing block scoping. While the break statements prevent accidental fall-through at runtime, this is a correctness hazard if future changes remove a break.

Wrap the case 'user': body in a block:

Proposed fix (wrap in block)
                 case 'user':
+                {
                     // Skip synthetic compact summary messages ...
                     if (msg._compactSynthetic) {
                         continue;
                     }
                     // ... existing user case body ...
                     events.push({ ... });
                     break;
+                }

Apply the same pattern for other cases flagged by Biome.

workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts-36-75 (1)

36-75: ⚠️ Potential issue | 🟡 Minor

Remove duplicate type declarations; import from @wso2/mi-core instead.

The types SessionMetadata, SessionSummary, and GroupedSessions are already exported from the public API of @wso2/mi-core and can be imported directly. Maintaining local copies creates a maintenance risk — the types could drift from the canonical definitions in mi-core. Since the file already imports other types from @wso2/mi-core (line 26), add these three types to that import statement and remove the local declarations (lines 36–75).

workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_expression_examples.ts-60-62 (1)

60-62: ⚠️ Potential issue | 🟡 Minor

Inconsistent example: <log> missing category attribute.

The synapse_guide.ts instructs the AI to use category instead of the deprecated level, and all other log examples in this file include category="INFO". This example omits it, which could lead the AI to generate logs without category.

Suggested fix
-            <log>
+            <log category="INFO">
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide.ts-332-332 (1)

332-332: ⚠️ Potential issue | 🟡 Minor

Typo: "fault sequencs" → "fault sequences".

workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_expression_guide.ts-40-92 (1)

40-92: ⚠️ Potential issue | 🟡 Minor

Inconsistent list formatting for the 6 global variables.

The first three variables (payload, vars, params) use unordered bullet syntax (-), while the last three (headers, properties, configs) switch to numbered list syntax (4., 5., 6.). This may render confusingly in markdown.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/system.ts-112-112 (1)

112-112: ⚠️ Potential issue | 🟡 Minor

Typo: "backtickets" → "backticks".

Proposed fix
-Unless explicitly asked for by the user, DO NOT USE backtickets \` or HTML tags like code for file references - always use markdown [text](link) format.
+Unless explicitly asked for by the user, DO NOT USE backticks \` or HTML tags like code for file references - always use markdown [text](link) format.
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/system.ts-53-53 (1)

53-53: ⚠️ Potential issue | 🟡 Minor

Typo: "unncessory" → "unnecessary".

Proposed fix
-- NEVER create any file unncessory for WSO2 synapse project files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.
+- NEVER create any file unnecessary for WSO2 synapse project files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide.ts-366-388 (1)

366-388: ⚠️ Potential issue | 🟡 Minor

Example uses deprecated <log level="full"/> which contradicts the guide's own rule.

Line 371 uses <log level="full"/> in the throwError example, but line 126-140 of this same guide explicitly states that level is deprecated and category should be used instead. The AI may learn contradictory patterns from this.

Proposed fix
                    <then>
-                        <log level="full"/>
+                        <log category="INFO">
+                            <message>${payload}</message>
+                        </log>
                         <respond/>
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/system.ts-106-106 (1)

106-106: ⚠️ Potential issue | 🟡 Minor

Typo: extra closing parenthesis in the prompt example.

Line 106 has a double closing paren in input.items.find(...)). Since this is part of the system prompt sent to the AI model, it could confuse the model or be reproduced in generated code.

Proposed fix
-  \`input.items[0]\` (first element) or \`input.items.find(...))\` (conditional)
+  \`input.items[0]\` (first element) or \`input.items.find(...)\` (conditional)
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/web_tools.ts-78-116 (1)

78-116: ⚠️ Potential issue | 🟡 Minor

Approval promise has no timeout or abort-signal integration — can hang forever.

requestWebApproval creates a promise (line 100) that resolves only when the user responds via pendingApprovals. If the user abandons the session or the parent agent is aborted, this promise is never settled, leaking resources.

Consider wiring the abortSignal (available from the parent agent) through to this function, or adding a configurable timeout that auto-rejects after a reasonable period.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_store_cache.ts-206-222 (1)

206-222: ⚠️ Potential issue | 🟡 Minor

Inconsistent env var access pattern for store URLs.

Line 210 uses the centralized APIS.MI_CONNECTOR_STORE_BACKEND constant, but line 218 reads process.env.MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS directly. The APIS object (from ../../../constants) doesn't include MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS. If this env var is expected to exist, it should be added to the APIS constant for consistency and to avoid silent undefined when the env var name changes.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/subagents/explore/agent.ts-140-154 (1)

140-154: ⚠️ Potential issue | 🟡 Minor

Remove unnecessary type cast when accessing step.response?.messages.

The pattern (step as any).response?.messages bypasses type safety. The response property with messages is part of the official Vercel AI SDK's StepResult type—not internal structure. Use step.response?.messages directly (matching the main agent's pattern) to maintain type safety without the any cast.

if (result.steps && result.steps.length > 0) {
    for (const step of result.steps) {
        if (step.response?.messages) {
            fullMessages.push(...step.response.messages);
        }
    }
    logDebug(`[ExploreSubagent] Extracted messages from ${result.steps.length} steps`);
}
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-1282-1284 (1)

1282-1284: ⚠️ Potential issue | 🟡 Minor

Typo in comment.

"Creates the file_edit to" → "Creates the file_edit tool"

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/prompt.ts-87-89 (1)

87-89: ⚠️ Potential issue | 🟡 Minor

Naming mismatch: template uses system_remainder while the section is <system_reminder>.

The Handlebars variable is system_remainder (line 88 and context line 283), but it's placed inside a <system_reminder> XML tag (line 87). This appears to be a typo — "remainder" should likely be "reminder" for consistency and clarity.

Also applies to: 283-288

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-1310-1318 (1)

1310-1318: ⚠️ Potential issue | 🟡 Minor

type field in grep input schema is unused.

The type property is declared in grepInputSchema (line 1314) but createGrepExecute never destructures or uses it. This dead schema field misleads the AI model into providing a parameter that has no effect.

Either implement type-based filtering in the execute function or remove it from the schema.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/agent.ts-325-328 (1)

325-328: ⚠️ Potential issue | 🟡 Minor

Hardcoded summary prefix doesn't match user-triggered compaction.

The prefix "This session is being continued from a previous conversation that ran out of context" is inaccurate when trigger === 'user' (user explicitly ran /compact). The context didn't "run out" — the user chose to compact.

Consider varying the prefix based on request.trigger.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/agent.ts-636-641 (1)

636-641: ⚠️ Potential issue | 🟡 Minor

Overly broad abort detection may cause false positives.

errorMsg.toLowerCase().includes('cancel') will match any error message that happens to contain the substring "cancel" in a non-abort context (e.g., "The operation was cancelled by the server due to rate limiting"). This could suppress genuine errors by misclassifying them as user-initiated aborts.

Consider removing the 'cancel' substring check or narrowing it to match known error formats more precisely.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/agent.ts-255-264 (1)

255-264: ⚠️ Potential issue | 🟡 Minor

process.chdir() is process-global and not concurrency-safe.

Even though this is guarded by ENABLE_DEVTOOLS = false, when enabled during development, process.chdir() affects the entire Node.js process. If multiple agent executions run concurrently (e.g., different projects), they would race on the working directory. The await import(...) at line 258 yields control, giving other async tasks a window to observe the changed cwd.

Consider documenting that DevTools mode must only be used with a single project, or use an alternative that doesn't mutate global state.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/project_tools.ts-133-142 (1)

133-142: ⚠️ Potential issue | 🟡 Minor

Add MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS to the APIS constants module for consistency.

The codebase uses inconsistent approaches to access connector store URLs: connectors use APIS.MI_CONNECTOR_STORE_BACKEND (e.g., project_tools.ts:91, connector_store_cache.ts:210), while inbound endpoints access the URL directly via process.env.MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS (project_tools.ts:133, connector_store_cache.ts:218). The APIS constant defined in constants/index.ts lacks the inbound endpoints property, forcing direct environment variable access. Adding this property to the APIS constants module would ensure both connector and inbound endpoint operations follow the same pattern.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/bash_tools.ts-107-107 (1)

107-107: ⚠️ Potential issue | 🟡 Minor

Logging the raw command may expose secrets.

If the AI model emits a command containing tokens, passwords, or other sensitive values (e.g., curl -H "Authorization: ..." or export SECRET=...), this logInfo call will persist them in the output channel. Consider redacting or truncating the logged command.

workspaces/mi/mi-extension/src/ai-features/connection.ts-76-100 (1)

76-100: ⚠️ Potential issue | 🟡 Minor

Variable shadowing and costly debug body parsing.

Two concerns in this debug block:

  1. Line 79: const headers shadows the outer headers declared on line 56. Rename to e.g. requestHeaders to avoid confusion in later maintenance.

  2. Lines 84-96: JSON.parse(options.body as string) is called on every /messages request solely for debug logging. For large payloads (e.g., messages with file contents), this re-parse is expensive. Guard it behind a debug-level check or remove it before production.

Proposed fix for shadowing
-            const headers = options.headers as Record<string, string>;
-            const betaHeader = headers['anthropic-beta'] || headers['Anthropic-Beta'] || 'none';
+            const reqHeaders = options.headers as Record<string, string>;
+            const betaHeader = reqHeaders['anthropic-beta'] || reqHeaders['Anthropic-Beta'] || 'none';
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/runtime_tools.ts-134-167 (1)

134-167: ⚠️ Potential issue | 🟡 Minor

Build process has no timeout — a hanging build will block indefinitely.

The buildProcess spawned on line 138 has no timeout. If the Maven build hangs (e.g., waiting for user input or a network resource), the tool will block forever. Consider adding a configurable timeout (e.g., similar to the server startup timeout pattern at line 518-521).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
workspaces/mi/mi-extension/src/ai-features/connection.ts (1)

180-211: ⚠️ Potential issue | 🟠 Major

Cache not invalidated when credentials are updated within the same login method.

The Anthropic client cache is keyed solely on loginMethod. If a user updates their credentials (e.g., corrects an API key or re-authenticates), the cache is not cleared since loginMethod remains the same. The stale client with the old credential continues to be used. Additionally, the logout() function clears stored credentials but does not clear cachedAnthropic or cachedAuthMethod, risking stale client reuse after re-login.

Add cache invalidation when credentials are updated (e.g., in storeAuthCredentials) or when the user logs out, and clear both cachedAnthropic and cachedAuthMethod in the logout() function.

🤖 Fix all issues with AI agents
In `@workspaces/mi/mi-extension/src/ai-features/auth.ts`:
- Around line 331-367: checkToken currently calls getAccessToken() and
getLoginMethod() in Promise.all without error handling, so when getAccessToken()
throws (e.g., TOKEN_NOT_AVAILABLE_ERROR_MESSAGE) the error bubbles up and the
machine goes to Disabled; wrap the parallel fetch in a try-catch inside
checkToken (around the Promise.all) and on error treat token and/or loginMethod
as undefined (i.e., fall through to the STS bootstrap path), but rethrow only
unexpected errors if needed; reference the Promise.all call with getAccessToken
and getLoginMethod in the checkToken function to locate where to add the
try-catch.
- Around line 391-447: The rate-limit detection in validateApiKey is unreliable
because it checks error.message for "rate_limit"; update the catch to detect 429
by testing the error's statusCode property (e.g., check 'statusCode' in error
and === 429) and throw the same "Too many requests..." message when true; also
replace the hardcoded model string used in generateText (currently
"claude-3-5-haiku-20241022") with a shared model constant (e.g.,
DEFAULT_ANTHROPIC_MODEL) so the model can be changed centrally.

In `@workspaces/mi/mi-extension/src/ai-features/connection.ts`:
- Around line 142-163: Handle 401 for ANTHROPIC_KEY like MI_INTEL: when
response.status === 401 and loginMethod === LoginMethod.ANTHROPIC_KEY, call
StateMachineAI.sendEvent(AI_EVENT_TYPE.SILENT_LOGOUT) and surface an error
(throw) so the caller can prompt the user to re-enter their key (mirrors the
getRefreshedAccessToken branch for MI_INTEL); update the conditional block
around response.status === 401 to include a branch for LoginMethod.ANTHROPIC_KEY
that triggers SILENT_LOGOUT and returns/throws an appropriate authentication
error.
- Around line 38-42: Update the error message and return value in the
getCopilotLlmApiBaseUrl caller: when proxyUrl is falsy, throw an Error that
references the correct config (e.g., COPILOT_ROOT_URL or instruct to configure
via getCopilotLlmApiBaseUrl) instead of the stale
MI_COPILOT_ANTHROPIC_PROXY_URL, and change the return from the redundant
template literal `\`${proxyUrl}\`` to simply `proxyUrl`; locate the logic around
the variable proxyUrl and function getCopilotLlmApiBaseUrl to make these edits.

In `@workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts`:
- Around line 3051-3054: The POM uses (artifactID ?? name).toLowerCase() which
can diverge from user-specified artifactId and other references; change the
logic so you either stop forcing lowercase (remove .toLowerCase()) or compute a
single normalized variable (e.g., artifactIdNormalized = artifactID ?? name or
artifactIdNormalized = (artifactID ?? name).toLowerCase()) and then use that
single symbol consistently across rootPomXmlContent, folder names (tempName) and
any other places that reference the artifactId so all project files stay in
sync.
🧹 Nitpick comments (13)
workspaces/mi/mi-extension/src/uri-handler.ts (1)

29-32: Consider user-visible feedback instead of console.log for the legacy route.

If a user or an external redirect still hits /signin, the silent console.log provides no indication of what happened. A window.showWarningMessage(...) would surface the situation to the user, helping them understand they need to use the Devant platform extension for authentication.

💡 Suggested change
             case '/signin':
-                    // Legacy OAuth callback route - no longer used for MI Copilot auth.
-                    console.log("Legacy /signin route called - MI Copilot authentication now uses Devant platform extension.");
+                    // Legacy OAuth callback route - no longer used for MI Copilot auth.
+                    window.showWarningMessage("MI Copilot authentication now uses the Devant platform extension.");
workspaces/ballerina/overview-view/src/Overview.tsx (2)

26-47: Consider replacing any[] with concrete types for better type safety.

All fields in Module are typed as any[], which disables type checking on the items being filtered and rendered downstream (e.g., accessing .name and .filePath in the filter blocks). Even a minimal shared shape would catch typos and misuse at compile time:

interface ModuleItem {
    name: string;
    filePath: string;
}

Then each field could be ModuleItem[] (or more specific sub-types where needed).

Also, the // Create an interface for … comments are noise — the declarations are self-explanatory.


104-154: Extract the duplicated filter predicate into a helper.

All ten filter blocks (services, types, functions, records, objects, classes, constants, enums, listeners, moduleVariables) are identical aside from the field name. This makes the code harder to maintain — any fix to the filtering logic must be replicated in ten places.

♻️ Proposed refactor

Define a helper above the map call and apply it to each field:

+    const matchesFilter = (item: { name: string; filePath: string }) => {
+        if (
+            selectedFile === SELECT_ALL_FILES ||
+            item.filePath === selectedFile.replace(workspaceInfo?.workspaceRoot ?? '', '').substring(1)
+        ) {
+            return item.name.toLowerCase().includes(query.toLowerCase());
+        }
+        return false;
+    };
+
     const filteredComponents = components?.packages.map(pkg => {
         const modules = pkg.modules.map(module => {
-            const services = module.services.filter(service => {
-                if (selectedFile === SELECT_ALL_FILES || service.filePath === selectedFile.replace(workspaceInfo?.workspaceRoot, '').substring(1)) {
-                    return service.name.toLowerCase().includes(query.toLowerCase());
-                }
-            });
-            // ... 9 more near-identical blocks ...
+            return {
+                ...module,
+                services: module.services.filter(matchesFilter),
+                types: module.types.filter(matchesFilter),
+                functions: module.functions.filter(matchesFilter),
+                records: module.records.filter(matchesFilter),
+                objects: module.objects.filter(matchesFilter),
+                classes: module.classes.filter(matchesFilter),
+                constants: module.constants.filter(matchesFilter),
+                enums: module.enums.filter(matchesFilter),
+                listeners: module.listeners.filter(matchesFilter),
+                moduleVariables: module.moduleVariables.filter(matchesFilter),
+            };
         });

Note the explicit return false — the current code implicitly returns undefined when the if condition is false, which works but is less clear.

workspaces/mi/mi-core/src/state-machine-types.ts (1)

292-297: Naming convention inconsistency in AIUserTokens confirmed.

The field remainingUsagePercentage uses camelCase while max_usage, remaining_tokens, and time_to_reset use snake_case. This pattern is intentional and repeated consistently across multiple type definitions (state-machine-types.ts, rpc-type.ts, rpc-client.ts, index.ts), indicating the snake_case fields align with an external API or RPC response shape.

To resolve this inconsistency, either:

  1. Convert all fields to camelCase (maxUsage, remainingTokens, timeToReset) and update all consuming code across multiple files (utils.ts, AIMapButton.tsx, rpc-manager.ts, etc.), or
  2. Add a clarifying comment explaining that snake_case fields reflect the upstream API contract while camelCase is used for calculated/derived fields.
workspaces/mi/mi-extension/src/ai-features/connection.ts (2)

56-74: Custom headers unconditionally override caller-provided headers.

The spread order ...options.headers, ...headers means the hardcoded headers (e.g., Content-Type) always override whatever the @ai-sdk/anthropic SDK sets in options.headers. This is fragile — if the SDK ever sends a content type other than application/json, or if it sets any of the same header keys, those will be silently overwritten.

Consider flipping the spread order so SDK headers take precedence, or selectively merge only the headers that are truly additive (like x-product, x-metadata, etc.).

Proposed fix — let SDK headers take precedence for shared keys
         options.headers = {
-            ...options.headers,
             ...headers,
+            ...options.headers,
         };

76-100: Variable shadowing: headers on line 79 shadows the outer headers declared on line 56.

const headers at line 79 shadows the outer headers record. It works because the outer headers was already merged into options.headers, but this makes the code confusing to read and maintain. Rename to e.g. mergedHeaders or requestHeaders.

workspaces/mi/mi-extension/src/rpc-managers/ai-features/utils.ts (3)

71-82: Redundant double fetch of credentials from secret storage.

getAccessToken() and getLoginMethod() both call getAuthCredentials() internally, resulting in two secret store reads. For MI_INTEL, getAccessToken() already validates the login method and handles token refresh. You could simplify by calling getAuthCredentials() once.

♻️ Suggested simplification
-export async function getUserAccessToken(): Promise<string> {
-    const [token, loginMethod] = await Promise.all([
-        getAccessToken(),
-        getLoginMethod(),
-    ]);
-
-    if (!token || loginMethod !== LoginMethod.MI_INTEL) {
-        throw new Error('User access token not available');
-    }
-
-    return token;
-}
+export async function getUserAccessToken(): Promise<string> {
+    const credentials = await getAuthCredentials();
+    if (!credentials || credentials.loginMethod !== LoginMethod.MI_INTEL) {
+        throw new Error('User access token not available');
+    }
+
+    const token = await getAccessToken();
+    if (!token) {
+        throw new Error('User access token not available');
+    }
+
+    return token;
+}

98-104: Unreachable null check — getRefreshedAccessToken always throws on failure.

Looking at auth.ts (lines 288-303), getRefreshedAccessToken either returns a non-empty string or throws. The if (!newToken) guard on line 100 is dead code.

♻️ Simplified version
 export async function refreshUserAccessToken(): Promise<string> {
-    const newToken = await getRefreshedAccessToken();
-    if (!newToken) {
-        throw new Error('Failed to refresh access token');
-    }
-    return newToken;
+    return await getRefreshedAccessToken();
 }

87-93: Function name hasAnthropicApiKey is misleading — it returns the key, not a boolean.

The name suggests a boolean check, but the return type is string | undefined. Consider renaming to getAnthropicApiKey for clarity, or keeping the name and returning a boolean if that's the intended contract.

workspaces/mi/mi-extension/src/ai-features/auth.ts (2)

164-197: validateStatus: () => true suppresses all HTTP errors — ensure this is intentional.

With validateStatus: () => true, axios never throws on HTTP status codes, so e.g. 500 or network timeout responses are handled solely by the manual status check on line 180. This is fine for custom error handling, but note there's no request timeout configured — a hanging token exchange endpoint would block indefinitely.

⏱️ Add a timeout
             {
                 headers: { 'Content-Type': 'application/json' },
-                validateStatus: () => true
+                validateStatus: () => true,
+                timeout: 15_000
             }

453-455: Unused parameter _isUserLogout — consider removing it.

The logout function accepts _isUserLogout but ignores it, always just clearing credentials. If the distinction between user-initiated and silent logout isn't needed here (Devant session managed separately per the comment), consider dropping the parameter to avoid confusion.

workspaces/mi/mi-extension/src/ai-features/aiMachine.ts (1)

406-422: Duplicated STS exchange + credential store pattern in three places.

The sequence getPlatformStsToken → exchangeStsToCopilotToken → storeAuthCredentials is repeated in:

  1. openLogin (here, lines 410-419)
  2. setupPlatformExtensionListener callback (lines 504-513)
  3. auth.tscheckToken (lines 352-361)

Consider extracting a shared helper in auth.ts (e.g., bootstrapMiIntelFromPlatform) to consolidate this logic and reduce the maintenance burden.

♻️ Example helper in auth.ts
// In auth.ts
export const bootstrapMiIntelFromPlatform = async (): Promise<{ accessToken: string }> => {
    const stsToken = await getPlatformStsToken();
    if (!stsToken) {
        throw new Error('Failed to get STS token from platform extension');
    }
    const secrets = await exchangeStsToCopilotToken(stsToken);
    await storeAuthCredentials({ loginMethod: LoginMethod.MI_INTEL, secrets });
    return secrets;
};

Then use it in all three call sites.

workspaces/mi/mi-extension/src/rpc-managers/mi-diagram/rpc-manager.ts (1)

5406-5410: Avoid passing an empty string as projectPath

If projectPath is optional, passing undefined is clearer and avoids accidental path handling downstream.

♻️ Suggested adjustment
- response = await langClient.swaggerFromAPI({ apiPath: params.apiPath, port: DebuggerConfig.getServerPort(), projectPath: versionedUrl ? this.projectUri : "", ...(fs.existsSync(swaggerPath) && { swaggerPath: swaggerPath }) });
+ const projectPath = versionedUrl ? this.projectUri : undefined;
+ response = await langClient.swaggerFromAPI({
+     apiPath: params.apiPath,
+     port: DebuggerConfig.getServerPort(),
+     ...(projectPath && { projectPath }),
+     ...(fs.existsSync(swaggerPath) && { swaggerPath: swaggerPath })
+ });

@ChinthakaJ98
Copy link
Contributor

Would it be possible to include a screen recording in the PR description to better illustrate the user experience of this feature?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/prompt.ts`:
- Around line 34-54: The prompt template in prompt.ts currently forces the model
to emit an <analysis> block (lines instructing "wrap your analysis in <analysis>
tags"); remove that requirement and instead change the instruction to ask the
model to "think step-by-step internally" and only emit a safe external tag like
<summary>...</summary>; update the prompt text (the prompt template string
exported from prompt.ts) so any sentence requiring an <analysis> block is
replaced with a single sentence directing internal reasoning and explicit output
of only a <summary> tag, leaving surrounding guidance intact.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/agent.ts`:
- Around line 40-47: Request object field tsFilePath in DataMapperAgentRequest
must be validated to ensure it resides inside the provided projectPath before
any read/write; update the agent code that handles request.tsFilePath (the logic
around the file read/write operations and any helper that loads/saves mapping
files) to resolve the absolute path (use path.resolve(projectPath,
request.tsFilePath) or resolve both and normalize) and then enforce a prefix
containment check against the resolved projectPath (or use path.relative and
ensure it does not start with '..'); if the check fails, throw or return a safe
error and do not perform any file I/O. Ensure the same guard is applied wherever
tsFilePath is used in the agent flow (all code paths that read/write the file).
- Around line 106-111: The code in DataMapperAgent is using blocking
fs.existsSync and fs.readFileSync on request.tsFilePath; change these to
non-blocking async APIs (e.g., use fs/promises or vscode.workspace.fs) so the
extension host isn't blocked: replace existsSync + readFileSync with an awaited
fs.promises.access or a try/catch around an awaited fs.promises.readFile (or use
vscode.workspace.fs.readFile with URI), update the containing function to be
async and propagate/await callers, and keep the same error handling and the
logDebug call (logDebug(`[DataMapperAgent] Read file with ${content.length}
characters`)) after the awaited read; reference request.tsFilePath, logDebug,
and the DataMapperAgent function when making these changes.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/prompt.ts`:
- Around line 27-53: The prompt (DATA_MAPPER_PROMPT) currently asks to "Return
ONLY the mapFunction code" but doesn't require a fenced TypeScript block, which
breaks extractTypeScriptCode(); update the template string so the final
instruction requires the mapFunction wrapped in a fenced TypeScript code block
(e.g., start with ```typescript and end with ```), and consider allowing both
```typescript and ```ts to match the extractor; ensure the wording still
enforces "ONLY the mapFunction code" and preserves existing placeholders like
{{`#if` instructions}} and {{ts_file}}.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/system.ts`:
- Around line 65-68: The comment flags a policy conflict between the rule in the
system prompt that forbids exposing internal tool names and the injected
<system_reminder> content which embeds template variables like
${EXIT_PLAN_MODE_TOOL_NAME} and ${TODO_WRITE_TOOL_NAME}; update the code that
processes <system_reminder> (the reminder rendering path and any functions that
assemble mode reminders) to either (a) mark those reminders as internal-only and
ensure they are never rendered to end users, or (b) sanitize/replace embedded
tool-name templates with user-safe aliases before display; specifically, locate
the code that injects or renders <system_reminder> tags and the logic enforcing
"do not mention internal tool names" and implement a deterministic
replacement/suppression step so tool-name templates are not leaked to users
while keeping internal reminders intact for agent use.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide.ts`:
- Around line 23-43: The SYNAPSE_GUIDE constant contains conflicting guidance
about the call and send mediators: they are listed as supported in the mediators
list (items including "call" and "send") but later text forbids them; update
SYNAPSE_GUIDE to remove the contradiction by either deleting "call" and "send"
from the supported mediators list or, preferably, rephrasing the later absolute
ban to a conditional guidance such as "avoid using call or send mediators unless
required for legacy compatibility or specific use-cases" so the list and the
later rule are consistent.
- Around line 59-63: The XML example in synapse_guide.ts has an invalid unquoted
attribute (the snippet under the uri-template example uses
value=your_api_key_here); update that example so the API key attribute is
properly quoted (e.g., change value=your_api_key_here to
value="your_api_key_here") so the example in the documentation is valid XML and
will pass validation when copied.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/storage-paths.ts`:
- Around line 59-61: getCopilotSessionDir currently joins sessionId directly
which allows path traversal; sanitize sessionId before joining (e.g., compute a
safeSessionId via path.basename(sessionId) or validate against a UUID regex) and
use that sanitized value when calling getCopilotProjectStorageDir(projectPath)
in getCopilotSessionDir so the returned path cannot escape the project storage
directory.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tool-action-mapper.ts`:
- Around line 41-198: The switch in getToolAction declares case-local variables
that violate noSwitchDeclarations; wrap each affected case in an explicit block
({ ... }) so the const/let bindings are scoped to that case and cannot leak to
others. Specifically add braces around the bodies for FILE_WRITE_TOOL_NAME,
MANAGE_CONNECTOR_TOOL_NAME, SUBAGENT_TOOL_NAME, TODO_WRITE_TOOL_NAME,
BASH_TOOL_NAME, KILL_TASK_TOOL_NAME, and TASK_OUTPUT_TOOL_NAME, keeping the
existing logic and return statements inside the new blocks so behavior is
unchanged.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_tools.ts`:
- Around line 165-176: The code assumes def.version exists when building
messages from connectorDefinitions and inbound endpoint definitions, which can
cause a TypeError; update the loop that iterates
Object.entries(connectorDefinitions) (and the inbound endpoint section that
similarly accesses def.version) to defensively check for def.version before
reading tagName or operations (e.g., treat missing version as "unknown" and skip
operations mapping), and guard accesses to def.version.operations with optional
checks (or use short-circuiting) so you never dereference undefined while still
including a sensible fallback in the message.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`:
- Around line 301-327: The code currently constructs fullPath by joining
projectPath and dm_config_path (used in tsFilePath resolution) without
validating that the resolved path remains inside the project directory; to fix,
resolve and normalize the candidate path (use path.resolve or fs.realpathSync)
for both absolute and relative dm_config_path values, compute the canonical
project root (e.g., const root = path.resolve(projectPath)), then ensure the
resolved full path starts with the project root (or path.relative(root,
resolvedPath) does not begin with '..'); if the check fails, return the same
error shape rejecting the request; apply this validation before any fs.statSync
or exists checks and reuse the validated resolved path for tsFilePath
construction to prevent path traversal.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`:
- Around line 976-1054: The code constructs RegExp objects directly from
untrusted inputs (pattern and glob) in the Grep tool (see the regex creation at
"const regex = new RegExp(pattern, ...)" and the globRegex creation) which risks
ReDoS; add input validation in the grep handler and in grepInputSchema to
reject/limit overly long patterns (e.g., max length), attempt RegExp compilation
inside try/catch to return a clear error on invalid regex, and cap / sanitize
the glob before converting to a RegExp (also limit length and escape risky
constructs or reject suspicious patterns); ensure both the main regex and the
globRegex are only constructed after validation and that any RegExp construction
failures return success: false with an error message instead of blocking the
host.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/plan_mode_tools.ts`:
- Around line 288-391: The AskUser tool is logging full question/option content
and the entire event JSON (in createAskUserExecute) which can leak sensitive
user/project data, and it leaves entries in pendingQuestions indefinitely if the
session ends. Replace verbose content logs with minimal metadata (number of
questions, per-question option counts, and mask/hide option labels/paths) and
stop logging the full event JSON; then when you add the pendingQuestions entry
in createAskUserExecute include a createdAt timestamp and a per-question timeout
(e.g., 5 minutes) that automatically removes the entry from pendingQuestions and
resolves it with the USER_CANCELLED result when expired; also wire a cleanup
hook (or mention to call from session disposal code) to iterate pendingQuestions
and reject/resolve and delete entries for that session to avoid leaks.
- Around line 236-277: The call in requestPlanApproval currently casts the
payload to any which hides a type mismatch with the AgentEvent interface;
replace the cast by formalizing the payload type: add a
PlanApprovalRequestedEvent (or extend AgentEvent) in the mi-core RPC types that
includes approvalId, planFilePath?, content, approvalKind, approvalTitle,
approveLabel, rejectLabel, and allowFeedback, then import and use that new type
for the eventHandler call in requestPlanApproval (remove the "as any" cast) so
the object passed to eventHandler is statically typed and matches the mi-core
contract; keep pendingApprovals and the Promise logic unchanged.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/runtime_tools.ts`:
- Around line 41-54: The four duplicated exports (BUILD_PROJECT_TOOL_NAME,
SERVER_MANAGEMENT_TOOL_NAME, BuildProjectExecuteFn, ServerManagementExecuteFn)
should be removed and imported from the central definitions in types.ts and then
re-exported to avoid divergence; replace the local type/constant declarations
with a single import line like import { BUILD_PROJECT_TOOL_NAME,
SERVER_MANAGEMENT_TOOL_NAME, BuildProjectExecuteFn, ServerManagementExecuteFn }
from './types' and export them (or export directly from the import), and update
any local references in runtime_tools.ts to use these imported symbols so there
are no duplicate definitions.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-handler.ts`:
- Around line 51-59: The current singleton rpcManagerInstance causes
cross-project routing; replace it with a Map keyed by projectUri (e.g.,
rpcManagerMap: Map<string, MIAgentPanelRpcManager>) and update
registerMIAgentPanelRpcHandlers to get or create the manager via
rpcManagerMap.get(projectUri) / rpcManagerMap.set(projectUri, new
MIAgentPanelRpcManager(projectUri)); ensure all handlers registered inside
registerMIAgentPanelRpcHandlers close over the local manager variable (not a
global) so each MessengerAPI instance uses its own MIAgentPanelRpcManager;
optionally add cleanup logic to remove the map entry when a project is closed.
🟡 Minor comments (21)
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide_old.ts-60-64 (1)

60-64: ⚠️ Potential issue | 🟡 Minor

Invalid XML in example: value attribute is missing quotes.

The example shows value=your_api_key_here but XML requires quoted attribute values. Since this guide instructs the AI agent on correct Synapse syntax, the example should be valid XML to avoid the agent reproducing the mistake.

Proposed fix
-    <variable name="username" value=your_api_key_here type="STRING"/>
+    <variable name="username" value="your_api_key_here" type="STRING"/>
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide_old.ts-120-120 (1)

120-120: ⚠️ Potential issue | 🟡 Minor

Typo: "sequencs" → "sequences".

Proposed fix
-    - You can define fault sequencs for each API resource or each sequence.
+    - You can define fault sequences for each API resource or each sequence.
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/inbound_db.ts-36-36 (1)

36-36: ⚠️ Potential issue | 🟡 Minor

Copy-paste error: operation descriptions say "Initialize Kafka Inbound Endpoint" for non-Kafka connectors.

The description field for the init operation is "Initialize Kafka Inbound Endpoint" across Amazon SQS (Line 36), CDC (Line 185), ISO8583 (Line 453), Pulsar (Line 1229), and SMPP (Line 1786). Since this data is used as AI agent context, incorrect descriptions will mislead the model.

Example fix for Amazon SQS (Line 36)
-                    "description": "Initialize Kafka Inbound Endpoint",
+                    "description": "Initialize Amazon SQS Inbound Endpoint",

Apply the same pattern for CDC, ISO8583, Pulsar, and SMPP entries.

Also applies to: 185-185, 453-453, 1229-1229, 1786-1786

workspaces/mi/mi-extension/src/ai-features/agent-mode/context/inbound_db.ts-670-675 (1)

670-675: ⚠️ Potential issue | 🟡 Minor

Incorrect description for contentType parameter in Kafka connector.

The description reads "Consumer group ID to use when consuming messages." which is the description for the group.id parameter. It should describe the content type of consumed messages.

Proposed fix
                        {
                            "name": "contentType",
                            "type": "string",
                            "required": true,
-                            "description": "Consumer group ID to use when consuming messages.",
+                            "description": "The content type of the messages consumed from Kafka.",
                            "defaultValue": "application/json"
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts-976-977 (1)

976-977: ⚠️ Potential issue | 🟡 Minor

grep schema advertises options that aren’t implemented.
type is accepted but ignored, and the schema says output_mode defaults to files_with_matches while the implementation defaults to content. Align schema/docs with behavior or implement the options.

📌 Possible alignment (remove unsupported `type`, fix default text)
-    type: z.string().optional().describe(`File type to search (rg --type). Common types: js, py, rust, go, java, etc. More efficient than include for standard file types.`),
-    output_mode: z.enum(['content', 'files_with_matches']).optional().describe(`Output mode: "content" shows matching lines (supports -A/-B/-C context, -n line numbers, head_limit), "files_with_matches" shows only file paths (supports head_limit). Defaults to "files_with_matches".`),
+    output_mode: z.enum(['content', 'files_with_matches']).optional().describe(`Output mode: "content" shows matching lines (supports -A/-B/-C context, -n line numbers, head_limit), "files_with_matches" shows only file paths (supports head_limit). Defaults to "content".`),

Also applies to: 1310-1317

workspaces/mi/mi-extension/src/ai-features/auth.ts-71-82 (1)

71-82: ⚠️ Potential issue | 🟡 Minor

Align LLM base‑URL JSDoc with actual fallback behavior.
The comment claims fallback to legacy env vars, but the function returns undefined when COPILOT_ROOT_URL is missing.

📝 Suggested doc fix
- * Prefers COPILOT_ROOT_URL-derived endpoint and falls back to legacy proxy env vars.
+ * Uses COPILOT_ROOT_URL-derived endpoint; returns undefined if not configured.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tool-action-mapper.ts-154-163 (1)

154-163: ⚠️ Potential issue | 🟡 Minor

Avoid “undefined” action text when the shell command is missing.
cmdPreview becomes the string "undefined" if toolInput.command is absent, which leaks into UI labels.

🧩 Suggested fix
-            const bashDesc = toolInput?.description;
-            const cmdPreview = toolInput?.command?.substring(0, 50) + (toolInput?.command?.length > 50 ? '...' : '');
-            const displayText = bashDesc || cmdPreview || 'command';
+            const bashDesc = toolInput?.description;
+            const cmd = toolInput?.command;
+            const cmdPreview = cmd ? `${cmd.substring(0, 50)}${cmd.length > 50 ? '...' : ''}` : '';
+            const displayText = bashDesc || cmdPreview || 'command';
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/bash_tools.ts-107-107 (1)

107-107: ⚠️ Potential issue | 🟡 Minor

User-supplied commands are logged verbatim.

logInfo will record the full shell command. If the LLM-generated command contains secrets (env vars, tokens passed inline), they end up in logs. Consider truncating or redacting sensitive patterns.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/runtime_tools.ts-462-470 (1)

462-470: ⚠️ Potential issue | 🟡 Minor

Consider quoting vmArgs when passing to spawn() with shell: true.

When shell: true, the command and args are passed to the shell as a concatenated string. If any vmArgs entry contains spaces or special characters (e.g., -Dsome.key="value with spaces"), they may not be properly escaped by the shell. To avoid potential parsing issues, either quote each arg in the vmArgs array before passing them, or use shell: false if the executable can be invoked directly without a shell.

This same pattern appears in debugHelper.ts (line 340) and should be reviewed there as well.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/web_tools.ts-152-152 (1)

152-152: ⚠️ Potential issue | 🟡 Minor

Add fallback versions to line 152 for consistency with line 252.

Line 152 specifies only ['webSearch_20250305'], whereas line 252 already implements a fallback pattern with ['webFetch_20250910', 'webFetch_20250305']. Update line 152 to include fallback versions (e.g., ['webSearch_20250305', 'webSearch_20250305'] if older versions exist, or document why fallback is not needed) to match the resilience strategy already applied to webFetch.

workspaces/mi/mi-extension/src/ai-features/agent-mode/context/synapse_guide.ts-45-58 (1)

45-58: ⚠️ Potential issue | 🟡 Minor

Minor text polish in a behavior-driving guide (typos + clarity).

A few typos reduce trust (“fault sequencs”, etc.) and some statements are absolute but context-dependent (e.g., DB credentials always inside mediators vs deployment.toml).

Also applies to: 444-477

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/prompt.ts-19-28 (1)

19-28: ⚠️ Potential issue | 🟡 Minor

Make reminder tag spelling consistent (comment vs implementation).

Line 22 says <system-reminder> but the actual wrappers use <system_reminder>. Aligning terminology prevents future misuse/copy-paste bugs.

Also applies to: 100-112

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/system.ts-56-57 (1)

56-57: ⚠️ Potential issue | 🟡 Minor

Typos in “system-of-record” prompt text (small but worth fixing).

Line 56: “unncessory” → “unnecessary”
Line 115: “backtickets” → “backticks”
This text will be repeatedly seen/used by the model; small errors can propagate.

Also applies to: 115-116

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_store_cache.ts-154-168 (1)

154-168: ⚠️ Potential issue | 🟡 Minor

No timeout on fetch() — a slow/unresponsive store API could block the agent indefinitely.

fetchStoreItems calls fetch(storeUrl) without a timeout. If the connector store is down or extremely slow, this will hang until the OS-level TCP timeout (potentially minutes). Consider adding an AbortSignal.timeout():

🛡️ Proposed fix
 async function fetchStoreItems(urlTemplate: string, runtimeVersion: string | null): Promise<any[]> {
     const storeUrl = resolveStoreUrl(urlTemplate, runtimeVersion);
-    const response = await fetch(storeUrl);
+    const response = await fetch(storeUrl, {
+        signal: AbortSignal.timeout(15_000), // 15 second timeout
+    });
 
     if (!response.ok) {
         throw new Error(`HTTP ${response.status} ${response.statusText}`);
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/agent.ts-639-646 (1)

639-646: ⚠️ Potential issue | 🟡 Minor

Fragile abort detection via string matching could produce false positives.

Checking errorMsg.toLowerCase().includes('abort') or includes('cancel') will match any error whose message incidentally contains these words (e.g., "Failed to cancel subscription" or "Transaction aborted by database"). The first three checks (error?.name === 'AbortError', request.abortSignal?.aborted, error?.code === 'ABORT_ERR') are already robust. Consider dropping the string-matching heuristics or at least tightening them.

♻️ Suggested tightening
         const isAborted = 
             error?.name === 'AbortError' || 
             request.abortSignal?.aborted ||
-            errorMsg.toLowerCase().includes('abort') ||
-            errorMsg.toLowerCase().includes('cancel') ||
             error?.code === 'ABORT_ERR';
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/agent.ts-255-269 (1)

255-269: ⚠️ Potential issue | 🟡 Minor

getSystemPrompt() called without runtimeVersion — compact agent may use wrong system prompt for older runtimes.

getSystemPrompt (from main/system.ts, line 234) selects between SYSTEM_PROMPT and SYSTEM_PROMPT_OLD based on runtime version. Here it's called without a version, so it always returns the newer prompt. If the project targets an older MI runtime (< 4.4.0), the summary will be based on the wrong system prompt, potentially producing inaccurate compaction.

Consider passing the runtime version through CompactAgentRequest and forwarding it here.

🐛 Proposed fix
 export interface CompactAgentRequest {
     messages: any[];
     trigger: 'user' | 'auto';
     projectPath: string;
+    runtimeVersion?: string | null;
 }
-        const systemPrompt = getSystemPrompt();
+        const systemPrompt = getSystemPrompt(request.runtimeVersion);
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/agent.ts-282-289 (1)

282-289: ⚠️ Potential issue | 🟡 Minor

Consider making the thinking budgetTokens configurable rather than hardcoded.

The 1024 value is Anthropic's recommended starting point and minimum for extended thinking. While Anthropic suggests increasing the budget for complex reasoning tasks, the current hardcoded value allows toggling thinking on/off but not tuning the budget per request. Since request.thinking already enables per-request control, extending this to include a configurable budgetTokens would allow better optimization based on task complexity without hardcoding a single value across all use cases.

workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts-1063-1064 (1)

1063-1064: ⚠️ Potential issue | 🟡 Minor

convertToEventFormat uses current time instead of stored timestamps.

Line 1064 creates new Date().toISOString() for each message, so every UI event gets the "replay time" rather than the original message timestamp. The JournalEntry has a timestamp field — if the raw entry were available (it is for compact_summary and undo_checkpoint types, where msg.timestamp is used), it should be used for all message types too.

Since model messages are unwrapped from their JournalEntry before being passed here, the original timestamp is lost. Consider preserving the entry timestamp on the unwrapped message (e.g., _timestamp) in getMessages() so convertToEventFormat() can use it.

workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts-341-356 (1)

341-356: ⚠️ Potential issue | 🟡 Minor

loadMetadata silently upgrades missing sessionVersion to current version.

When loading metadata for a session that predates versioning (no sessionVersion field), loadMetadata() returns the metadata with sessionVersion set to SESSION_STORAGE_VERSION. However, this patched value is only in memory — subsequent updateMetadata() calls will persist it. This effectively upgrades legacy sessions to the current version on first touch, which prevents future version-incompatibility detection for those sessions.

If this is intentional (i.e., legacy sessions are always compatible), the behavior is fine — but it should be documented in a comment.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts-85-91 (1)

85-91: ⚠️ Potential issue | 🟡 Minor

isFilePath heuristic can produce false positives on inline content.

A string like "data.json" (meant as inline content) or any input ending with .json, .xml, .xsd, or .csv will be misidentified as a file path. Conversely, a relative path like resources/input.txt won't match because .txt isn't in the extension list and it doesn't start with ./, /, or src/.

Consider tightening the heuristic — e.g., require the extension check only when the string also contains a path separator, or check for the presence of whitespace/newlines to distinguish inline content from paths.

workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts-1088-1191 (1)

1088-1191: ⚠️ Potential issue | 🟡 Minor

Switch case declarations leak across cases (Biome lint error).

Variables userContent, files, images, attachmentMetadata, and queryMatch declared in the case 'user' block are accessible from case 'assistant' and case 'tool' cases because there are no block scopes. Biome flagged this as noSwitchDeclarations.

Wrap the case 'user' body in braces to create a block scope.

🔧 Proposed fix
             switch (msg.role) {
-                case 'user':
+                case 'user': {
                     // Skip synthetic compact summary messages ...
                     if (msg._compactSynthetic) {
                         continue;
                     }
                     // ... existing user case body ...
                     break;
+                }

-                case 'assistant':
+                case 'assistant': {
                     // ... existing assistant case body ...
                     break;
+                }

-                case 'tool':
+                case 'tool': {
                     // ... existing tool case body ...
                     break;
+                }
             }
🧹 Nitpick comments (33)
workspaces/mi/mi-extension/src/ai-features/agent-mode/context/inbound_db.ts (1)

19-1968: Consolidate duplicate INBOUND_DB exports to a single source of truth.

Both workspaces/mi/mi-extension/src/ai-features/agent-mode/context/inbound_db.ts and workspaces/mi/mi-extension/src/ai-features/copilot/connectors/inbound_db.ts export the same INBOUND_DB constant. While the files are similar (both 1968 lines), they have different checksums, indicating content divergence. To prevent maintenance issues and drift, export INBOUND_DB from a shared location and import it in both modules.

workspaces/mi/mi-extension/src/ai-features/copilot/message-utils.ts (1)

83-92: Consider centralizing IMAGE_MIMETYPES to avoid drift.
The same whitelist now exists in attachment-utils; a shared constant would keep Copilot and agent-mode validators in sync.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/bash_tools.ts (2)

195-200: treeKill on timeout lacks an error callback.

If treeKill fails (e.g., the process already exited between the check and the kill), the error is silently swallowed. Consider adding an error callback to at least log the failure.

Suggested fix
                 const timeoutHandle = setTimeout(() => {
                     timedOut = true;
                     if (proc.pid) {
-                        treeKill(proc.pid, 'SIGKILL');
+                        treeKill(proc.pid, 'SIGKILL', (err) => {
+                            if (err) {
+                                logError(`[ShellTool] Failed to kill timed-out process: ${err.message}`);
+                            }
+                        });
                     }
                 }, effectiveTimeout);

436-439: Complex ternary on line 439 is hard to follow.

The unified task view construction packs a lot of conditional logic into a single expression. Consider extracting a small helper function for readability.

workspaces/mi/mi-extension/src/ai-features/connection.ts (1)

76-80: Variable headers on line 79 shadows the outer headers from line 56.

Both are in scope simultaneously, which is confusing for readers. Rename the inner variable to avoid shadowing.

Suggested fix
-            const headers = options.headers as Record<string, string>;
-            const betaHeader = headers['anthropic-beta'] || headers['Anthropic-Beta'] || 'none';
+            const mergedHeaders = options.headers as Record<string, string>;
+            const betaHeader = mergedHeaders['anthropic-beta'] || mergedHeaders['Anthropic-Beta'] || 'none';
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/types.ts (1)

55-80: TODO comment flags incomplete file extension list.

The comment on line 55 notes this list might be incomplete. Consider tracking this as a follow-up issue to audit project files against the allowlist.

Would you like me to open an issue to track auditing VALID_FILE_EXTENSIONS against actual MI project file types?

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/validation-utils.ts (1)

60-64: Synchronous readFileSync used inside an async function.

Since validateXmlFile is already async and the language client call on line 61 is awaited, the file read on line 63 could use fs.promises.readFile for consistency and to avoid blocking the event loop on large XML files.

Suggested fix
+        import { promises as fsPromises } from 'fs';
+        // ... or at top of file
         const diagnosticsResponse = await langClient.getCodeDiagnostics({
             fileName: absolutePath,
-            code: fs.readFileSync(absolutePath, 'utf8')
+            code: await fsPromises.readFile(absolutePath, 'utf8')
         });
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/web_tools.ts (1)

122-127: getAnthropicClient parameter is accepted but getAnthropicProvider is imported and used directly.

createWebSearchExecute accepts getAnthropicClient as a parameter (line 123), used only for model instantiation (line 165). However, tool factory discovery on line 151 uses the directly imported getAnthropicProvider. This creates a hidden dependency that bypasses the injected parameter, making the function harder to test and reason about.

Consider either injecting the provider as a parameter too, or documenting why the split exists.

Also applies to: 149-152

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/subagent_tool.ts (2)

58-81: loadSubagentHistory silently re-throws non-ENOENT errors.

On line 79, any file-system error other than ENOENT (e.g., permission denied, corrupt JSON) is re-thrown without additional context. Consider wrapping with a descriptive message.

Suggested fix
     } catch (error: any) {
         if (error.code === 'ENOENT') {
             throw new Error(`Subagent with ID ${subagentId} not found. Cannot resume.`);
         }
-        throw error;
+        throw new Error(`Failed to load subagent history for ${subagentId}: ${error.message}`);
     }

67-69: No validation of JSON parse results in history loading.

Each line is parsed with JSON.parse(line) but there's no validation that the parsed object is a valid message. Corrupt or truncated JSONL could produce unexpected values in the messages array. Given this is internal persistence, the risk is low but worth noting.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/plan_mode_tools.ts (3)

98-108: Pick a deterministic “existing plan file” when multiple .md files exist.

Line 101-108 picks planFiles[0] from readdir() order, which is not guaranteed stable across platforms/filesystems. If multiple plan files exist (e.g., leftover files), this can select a surprising file.

Example: prefer newest mtime or a fixed filename
-        const planFiles = files.filter(f => f.endsWith('.md'));
+        const planFiles = files.filter(f => f.endsWith('.md')).sort();

(Or: stat each and pick the newest mtime if that’s the intended behavior.)


623-630: mtime-based “plan updated” check is fragile on fast edits / coarse filesystem timestamps.

Line 624-630 requires planMtimeMs > baselineMtimeMs + 1. On some systems/edit flows, edits can land within the same timestamp tick, causing false negatives (“not updated” even though content changed).

Suggestion: track a baseline content hash (sha256 of trimmed content) rather than (or in addition to) mtime.


730-784: Validate todos input (empty list, multiple in_progress) to match the tool contract.

The tool description says “Only ONE task should be in_progress at a time” (Line 779-780) but the zod schema doesn’t enforce it, and execute doesn’t validate it. This is easy to harden and avoids UI inconsistencies.

workspaces/mi/mi-extension/src/ai-features/agent-mode/storage-paths.ts (1)

30-33: Compute project basename before separator normalization (portability).

Line 31-33 normalizes to forward slashes, then Line 50 calls path.basename(normalizedPath). Node usually handles / on Windows, but it’s safer/clearer to derive basename from path.resolve(projectPath) before doing string replacements for hashing.

Also applies to: 48-53

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/data-mapper/agent.ts (1)

63-82: Make extraction stricter: fail if response includes non-code or doesn’t define mapFunction.

Right now, if the model returns prose + code without fences, extractTypeScriptCode() returns full text and removeMapFunctionEntry() throws. That’s okay, but error messages could be more actionable, and you can reduce failures by requiring fenced output (prompt-side) and validating the extracted content starts with a function declaration.

Also applies to: 140-146

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/agent.ts (2)

293-303: process.chdir() mutates global state — races possible if multiple agents run concurrently.

Although guarded by ENABLE_DEVTOOLS = false, the pattern of changing process.cwd() and restoring it is not safe under concurrency. If an exception were thrown between the chdir calls, the CWD would remain changed. Consider using a try/finally at minimum:

🛡️ Safer pattern
         if (ENABLE_DEVTOOLS) {
             const originalCwd = process.cwd();
-            process.chdir(request.projectPath);
-            const { devToolsMiddleware } = await import('@ai-sdk/devtools');
-            model = wrapLanguageModel({
-                model,
-                middleware: devToolsMiddleware() as any,
-            });
-            process.chdir(originalCwd);
+            try {
+                process.chdir(request.projectPath);
+                const { devToolsMiddleware } = await import('@ai-sdk/devtools');
+                model = wrapLanguageModel({
+                    model,
+                    middleware: devToolsMiddleware() as any,
+                });
+            } finally {
+                process.chdir(originalCwd);
+            }
         }

325-328: Minor formatting inconsistency in the summary prefix.

The template string has \n ${summary} with a trailing space after the newline. This may be intentional for readability, but consider using \n\n for a cleaner paragraph break:

-            summary: `This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation. \n ${summary}` 
+            summary: `This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.\n\n${summary}` 
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/compact/tools.ts (1)

97-122: Consider importing tool name constants from the barrel instead of re-exporting.

These constants are already exported from ../../tools/types (and re-exported via ../../tools/index.ts). The re-export here creates a third path for the same constants. If a new tool is added, this file must also be updated. Consumers of the compact module could import directly from ../../tools/types.

That said, this is a minor concern — the module boundary encapsulation may be intentional.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/agent.ts (2)

260-270: Same process.chdir() issue as in compact agent — wrap in try/finally.

If the dynamic import or wrapLanguageModel throws, the working directory won't be restored. This is the same pattern flagged in the compact agent.

🛡️ Proposed fix
         if (ENABLE_DEVTOOLS) {
             const originalCwd = process.cwd();
-            process.chdir(request.projectPath);
-            const { devToolsMiddleware } = await import('@ai-sdk/devtools');
-            model = wrapLanguageModel({
-                model,
-                middleware: devToolsMiddleware() as any,
-            });
-            process.chdir(originalCwd);
+            try {
+                process.chdir(request.projectPath);
+                const { devToolsMiddleware } = await import('@ai-sdk/devtools');
+                model = wrapLanguageModel({
+                    model,
+                    middleware: devToolsMiddleware() as any,
+                });
+            } finally {
+                process.chdir(originalCwd);
+            }
         }

424-516: Large if-else chain for tool display inputs could benefit from a registry pattern.

This ~90-line block maps each tool name to its display-relevant input fields. While functional and readable, it will grow linearly with each new tool. A registry object or a getToolDisplayInput(toolName, toolInput) utility would centralize this and reduce the per-tool boilerplate.

Not urgent — the current structure works and each tool has unique field selections. But consider extracting this if the tool count keeps growing.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/tool-result-persistence.ts (1)

59-61: getDisplayPath is a no-op identity function.

If this is a placeholder for future path transformation (e.g., making paths relative or clickable), consider adding a brief TODO comment. Otherwise, callers could use filePath directly.

workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/prompt.ts (1)

85-89: Typo in template variable: system_remainder should likely be system_reminder.

The variable is named system_remainder (line 88) but represents a system reminder containing mode context. This naming inconsistency could confuse future maintainers.

Additionally, the value at line 285 starts with a stray period (.) before the <mode> tag:

system_remainder: `.
        <mode>

This period will appear in the rendered prompt. Was this intentional?

♻️ Suggested cleanup

In the template (line 88):

-{{system_remainder}}
+{{system_reminder}}

In the context (line 285):

-        system_remainder: `.
-        <mode>
-        ${mode.toUpperCase()}
-        </mode>
-        ${modeReminder}`
+        system_reminder: `<mode>
+${mode.toUpperCase()}
+</mode>
+${modeReminder}`

Also applies to: 285-290

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/project_tools.ts (1)

88-127: Store fetching duplicates logic from connector_store_cache.ts without caching.

This file fetches directly from the connector/inbound store APIs on every tool invocation, duplicating the URL resolution (replace('${version}', ...)) and response validation logic that already exists in connector_store_cache.ts (fetchStoreItems, resolveStoreUrl). Additionally, these fetches have no timeout protection.

Consider either:

  1. Extracting a shared fetchStoreItems utility that both modules use, or
  2. Using the cache layer from connector_store_cache.ts (though the full store data with Maven coordinates may differ from the cached catalog).

At minimum, the duplicate process.env.MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS access (line 133) and the APIS.MI_CONNECTOR_STORE_BACKEND.replace(...) pattern (line 91) should be consolidated.

Also applies to: 131-177

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_store_cache.ts (1)

221-238: Add MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS to the APIS constant for consistency.

This file uses APIS.MI_CONNECTOR_STORE_BACKEND on line 225 but accesses process.env.MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS directly on line 233. While the current code works, adding the inbound endpoints URL to the APIS constant in constants/index.ts would maintain consistent access patterns for API endpoint configuration.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/index.ts (1)

189-219: Remove unnecessary dynamic require() calls in favor of static imports already available.

The static re-exports at the top of this file (lines 20–36 for createFileTools and related functions; line 20 for types via export * from './types') already make these symbols available with full type safety. The dynamic require() calls inside createFileTools re-import the same symbols and serve no purpose—there is no circular dependency (verified: neither file_tools.ts nor types.ts import from index.ts). Use the statically exported symbols directly instead:

♻️ Proposed refactor
 export function createFileTools(projectPath: string, modifiedFiles?: string[]) {
-    // Import here to avoid circular dependencies
-    const {
-        createWriteExecute,
-        createReadExecute,
-        createEditExecute,
-        createGrepExecute,
-        createGlobExecute,
-        createWriteTool,
-        createReadTool,
-        createEditTool,
-        createGrepTool,
-        createGlobTool,
-    } = require('./file_tools');
-
-    const {
-        FILE_WRITE_TOOL_NAME,
-        FILE_READ_TOOL_NAME,
-        FILE_EDIT_TOOL_NAME,
-        FILE_GREP_TOOL_NAME,
-        FILE_GLOB_TOOL_NAME,
-    } = require('./types');
-
     return {
         [FILE_WRITE_TOOL_NAME]: createWriteTool(createWriteExecute(projectPath, modifiedFiles)),
         [FILE_READ_TOOL_NAME]: createReadTool(createReadExecute(projectPath), projectPath),
         [FILE_EDIT_TOOL_NAME]: createEditTool(createEditExecute(projectPath, modifiedFiles)),
         [FILE_GREP_TOOL_NAME]: createGrepTool(createGrepExecute(projectPath)),
         [FILE_GLOB_TOOL_NAME]: createGlobTool(createGlobExecute(projectPath)),
     };
 }
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/connector_tools.ts (2)

35-62: Duplicated lookup logic between getConnectorDefinitions and getInboundEndpointDefinitions.

Both functions are structurally identical — they iterate a list, match by connectorName, and build a Record. Consider extracting a single generic helper, e.g. getDefinitionsByName(names, items), to reduce duplication.

♻️ Example unified helper
-function getConnectorDefinitions(connectorNames: string[], connectors: any[]): Record<string, any> {
-    const definitions: Record<string, any> = {};
-    for (const name of connectorNames) {
-        const connector = connectors.find(c => c.connectorName === name);
-        if (connector) {
-            definitions[name] = connector;
-        }
-    }
-    return definitions;
-}
-
-function getInboundEndpointDefinitions(inboundNames: string[], inbounds: any[]): Record<string, any> {
-    const definitions: Record<string, any> = {};
-    for (const name of inboundNames) {
-        const inbound = inbounds.find(i => i.connectorName === name);
-        if (inbound) {
-            definitions[name] = inbound;
-        }
-    }
-    return definitions;
-}
+function getDefinitionsByName(names: string[], items: any[]): Record<string, any> {
+    const definitions: Record<string, any> = {};
+    for (const name of names) {
+        const item = items.find(i => i.connectorName === name);
+        if (item) {
+            definitions[name] = item;
+        }
+    }
+    return definitions;
+}

237-249: (tool as any) bypasses type safety for the Vercel AI SDK tool function.

The comment mentions this is to avoid "TypeScript deep instantiation issues with Zod". This is an acceptable workaround, but worth tracking. If/when the upstream ai SDK or Zod version fixes the deep instantiation issue, this cast should be removed to restore full type-checking.

workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts (1)

171-215: Duplicated input/output schema processing blocks.

Lines 171–192 (input) and 194–215 (output) follow an identical pattern: check isFilePath, read file or use inline content, call generateSchemaFromContent, then updateTsFileIoTypes. Consider extracting a helper such as processSchema(projectPath, schema, type, tsFilePath, dmName) to eliminate the duplication.

♻️ Sketch of extracted helper
async function resolveAndApplySchema(
    projectPath: string,
    schemaInput: string,
    ioType: IOType,
    schemaType: string,
    dmName: string,
    tsFilePath: string
): Promise<ToolResult | null> {
    let content: string;
    if (isFilePath(schemaInput)) {
        const fullPath = path.join(projectPath, schemaInput);
        if (!fs.existsSync(fullPath)) {
            return {
                success: false,
                message: `${ioType} schema file not found: ${schemaInput}`,
                error: 'Error: File not found',
            };
        }
        content = fs.readFileSync(fullPath, 'utf8');
    } else {
        content = schemaInput;
    }
    const jsonSchema = await generateSchemaFromContent(projectPath, ioType, content, schemaType);
    await updateTsFileIoTypes(dmName, tsFilePath, jsonSchema, ioType);
    return null;
}
workspaces/mi/mi-extension/src/ai-features/agent-mode/agents/main/tools.ts (2)

266-294: Shell command blocklist is inherently bypassable.

The blocklist approach in getPlanModeShellRestrictionReason can be circumvented via interpreters (python -c "import os; os.remove('f')", node -e "...", perl -e "..."), shell built-ins (eval, exec), encoded/obfuscated commands, or tools not in the list (e.g., rsync --remove-source-files).

This is understandable as a best-effort guard for an LLM agent running locally, but consider adding a comment acknowledging the limitation so future maintainers don't assume it's a strong security boundary.


149-149: Import statement after the re-export block.

AgentEventHandler is imported at Line 149, after the re-export block (Lines 124-148). This breaks the conventional import grouping at the top of the file.

♻️ Move import to top
 import * as path from 'path';
 import { getCopilotSessionDir } from '../../storage-paths';
+import { AgentEventHandler } from './agent';

 // Re-export tool name constants for use in agent.ts
 export {
     ...
 };
-import { AgentEventHandler } from './agent';
workspaces/mi/mi-extension/src/ai-features/agent-mode/chat-history-manager.ts (3)

384-389: Per-message metadata I/O in batch saves is inefficient.

saveMessages calls saveMessage in a loop, and each saveMessage invocation calls incrementMessageCount(), which does a loadMetadata()updateMetadata()saveMetadata() round-trip (3 file I/O operations). For a batch of N messages, this results in ~3N metadata file operations.

Consider batching the metadata update — increment the count once after all messages are written.

♻️ Sketch of batched metadata update
 async saveMessages(messages: any[], options?: { totalInputTokens?: number; chatId?: number }): Promise<void> {
     if (messages.length === 0) {
         return;
     }

     for (let i = 0; i < messages.length; i++) {
         const isLast = i === messages.length - 1;
-        await this.saveMessage(messages[i], isLast ? options : options?.chatId !== undefined ? { chatId: options.chatId } : undefined);
+        // Write entry + handle title, but skip per-message metadata increment
+        await this.writeMessageEntry(messages[i], isLast ? options : options?.chatId !== undefined ? { chatId: options.chatId } : undefined);
     }
+    // Batch metadata update
+    const metadata = await this.loadMetadata();
+    if (metadata) {
+        await this.updateMetadata({ messageCount: metadata.messageCount + messages.length });
+    }
 }

Also applies to: 499-508


654-697: Full file reads for getLatestMode and getLastUsage may be slow for large sessions.

Both methods read the entire JSONL file into memory and parse every line. For long-running sessions with thousands of entries, this could cause noticeable latency. Consider caching the latest mode and usage in the metadata file, which is already read for other purposes, or reading the file from the end (e.g., reading the last N bytes).


849-882: Double metadata read per session in listing flow.

listSessions() calls isSessionCompatible() for each session (reads metadata.json), then listSessionsWithMetadata() calls getSessionSummary() for each session (reads metadata.json again). This means metadata.json is read twice per session during listing.

Consider combining compatibility check and summary extraction into a single pass.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Fix all issues with AI agents
In `@workspaces/mi/mi-core/src/rpc-types/agent-mode/types.ts`:
- Around line 482-487: SearchMentionablePathsRequest currently declares query as
mandatory but the RPC manager handlers (references: rpc-manager.ts where
(request.query || '').trim() is used and the duplicate declaration with query?:
string) treat it as optional; change the interface SearchMentionablePathsRequest
to make query optional (query?: string) and adjust its JSDoc to note that an
empty or omitted query should return root items so the type matches the runtime
behavior used by the rpc-manager handlers.

In `@workspaces/mi/mi-core/src/rpc-types/ai-features/index.ts`:
- Around line 71-74: The RPC client's fetchUsage return type is using legacy
field names (max_usage, remaining_tokens, time_to_reset) and must be aligned to
the canonical core type: update the fetchUsage declaration in the RPC client
(the fetchUsage method) so its Promise resolves to { remainingUsagePercentage?:
number; resetsIn?: number } | undefined, removing the old fields and ensuring
the shape and optionality match the core definition.

In `@workspaces/mi/mi-core/src/state-machine-types.ts`:
- Around line 292-295: AIMapButton.tsx and rpc-client.ts still reference old
AIUserTokens fields; update AIMapButton.tsx usages of
machineView.userTokens.max_usage and machineView.userTokens.remaining_tokens to
machineView.userTokens.remainingUsagePercentage and
machineView.userTokens.resetsIn (adjust any percentage calculations accordingly
to use remainingUsagePercentage), and change the fetchUsage() return type in
rpc-client.ts to match the AIUserTokens shape (use remainingUsagePercentage?:
number; resetsIn?: number) so the types and runtime fields align with the
AIUserTokens interface.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tool-action-mapper.ts`:
- Around line 158-168: The current BASH_TOOL_NAME branch builds cmdPreview by
concatenating possibly undefined (toolInput?.command) which coerces to the
string "undefined" and prevents the displayText fallback; update the logic in
tool-action-mapper.ts so cmdPreview is only constructed when a real command
exists (e.g., read toolInput?.command into a local variable like command, check
if command is truthy before calling substring/length), then set displayText =
toolInput?.description || cmdPreview || 'command' so the 'command' fallback is
reachable when command is missing.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`:
- Around line 201-239: The code currently uses path.join(projectPath,
input_schema/output_schema) to read files which allows escaping the project
root; replace those joins with resolveProjectBoundPath(projectPath,
input_schema) and resolveProjectBoundPath(projectPath, output_schema), check for
undefined and return the existing { success: false, message: ..., error: ... }
immediately if undefined, then use the returned safe path for
fs.existsSync/readFileSync before passing content into generateSchemaFromContent
and updateTsFileIoTypes (refer to isFilePath, generateSchemaFromContent,
updateTsFileIoTypes, IOType.Input and IOType.Output to locate the relevant
blocks).

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`:
- Around line 1404-1406: The schema description for the output_mode option in
file_tools.ts is inconsistent with the runtime default used by createGrepExecute
(which defaults to "content"); update the z.enum description for output_mode to
state that the default is "content" (or alternatively change createGrepExecute
to default to "files_with_matches" if you prefer that behavior) so both the
schema and runtime agree; modify the output_mode description text around the
output_mode enum and verify createGrepExecute's default value remains correct
after the change.
- Around line 1080-1088: The search path is constructed with
path.join(projectPath, searchPath) into fullSearchPath (and similarly near the
second occurrence) without validating that the result stays inside the project
or allowed Copilot roots; update the code that computes fullSearchPath to call
the existing validateFilePathSecurity (or isPathWithin) to ensure the resolved
path is within projectPath (or allowed roots) before any fs access, and return
the existing error response if validation fails so grep/glob cannot escape via
absolute or ../ inputs.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts`:
- Around line 172-179: The rejectPendingInteractions function currently rejects
all promises but leaves stale entries in the maps; update
rejectPendingInteractions to both reject each pending (pending.reject(reason))
and then remove those entries (e.g., call this.pendingQuestions.clear() and
this.pendingApprovals.clear() or delete inside the loops) so that subsequent
lookups like respondToQuestion won't find already-rejected entries; ensure you
operate on the same maps (pendingQuestions, pendingApprovals) and that clearing
happens after all rejects.
- Around line 490-492: The current log in sendAgentMessage logs user-provided
content (request.message.substring...) at INFO level which may expose PII;
change this to either redact the content or lower the log level to DEBUG: update
the log call in sendAgentMessage to not emit raw user text (e.g., log a
sanitized summary like "<redacted>" or only message length/metadata) or move the
existing substring output to a debug-only log (use logDebug or equivalent) while
keeping the INFO log safe and non-sensitive (include request.id or other non-PII
identifiers if needed).
- Around line 83-144: The file re-declares types that already live in
`@wso2/mi-core` (ListSessionsRequest, ListSessionsResponse, SwitchSessionRequest,
SwitchSessionResponse, CreateNewSessionRequest, CreateNewSessionResponse,
DeleteSessionRequest, DeleteSessionResponse, MentionablePathType,
MentionablePathItem, SearchMentionablePathsRequest,
SearchMentionablePathsResponse); remove the local declarations and add those
names to the existing import from '@wso2/mi-core' (the current import block at
the top), and if you for some reason must keep the empty request shapes locally
convert ListSessionsRequest and CreateNewSessionRequest to type aliases instead
of empty interfaces. Ensure all references in this file use the imported types
and then delete the entire duplicate block.
🧹 Nitpick comments (6)
workspaces/mi/mi-extension/.env.example (1)

15-15: Verify naming convention and consider placement.

The new COPILOT_ROOT_URL variable lacks the MI_ prefix that most other variables in this file use (e.g., MI_COPILOT_OPENAI_PROXY_URL, MI_COPILOT_ANTHROPIC_PROXY_URL). If this is a shared/common Copilot URL used across multiple products (consistent with the PR's goal of migrating to a "common AI proxy"), the naming is appropriate. Otherwise, consider renaming it to MI_COPILOT_ROOT_URL for consistency.

Additionally, the variable is placed at the end of the file. For better organization, consider moving it near the other Copilot-related variables (lines 3-6) to maintain logical grouping.

♻️ Suggested placement improvement
 MI_AUTH_ORG=
 MI_AUTH_CLIENT_ID=
+COPILOT_ROOT_URL=https://7eff1239-64bb-4663-b256-30a00d187a5c-prod.e1-us-east-azure.choreoapis.dev/copilot
 MI_COPILOT_FEEDBACK=https://e95488c8-8511-4882-967f-ec3ae2a0f86f-prod.e1-us-east-azure.choreoapis.dev/miaideployments/copilot-analytics/v1.0
 MI_COPILOT_OPENAI_PROXY_URL=https://e95488c8-8511-4882-967f-ec3ae2a0f86f-prod.e1-us-east-azure.choreoapis.dev/miaideployments/ai-proxy/v1.0
 MI_COPILOT_ANTHROPIC_PROXY_URL=https://e95488c8-8511-4882-967f-ec3ae2a0f86f-prod.e1-us-east-azure.choreoapis.dev/miaideployments/ai-proxy/v1.0
 MI_AUTH_REDIRECT_URL=https://mi-copilot.wso2.com/vscode-auth
 MI_UPDATE_VERSION_CHECK_URL=https://mi-distribution.wso2.com/versions.json
 MI_SAMPLE_ICONS_GITHUB_URL=https://raw.githubusercontent.com/wso2/integration-studio/main/SamplesForVSCode/icons/
 MI_CONNECTOR_STORE=https://apis.wso2.com/connector-store/connector-details
 MI_CONNECTOR_STORE_BACKEND=https://apis.wso2.com/qgpf/connector-store-backend/endpoint-9090-803/v1.0/connectors/details?limit=100&offset=0&product=MI&type=Connector&runtimeVersion=${version}
 MI_CONNECTOR_STORE_BACKEND_SEARCH=https://apis.wso2.com/qgpf/connector-store-backend/endpoint-9090-803/v1.0/connectors/details?limit=10&offset=0&searchQuery=${searchValue}&type=Connector&product=MI&runtimeVersion=${version}
 MI_CONNECTOR_STORE_BACKEND_INBOUND_ENDPOINTS=https://apis.wso2.com/qgpf/connector-store-backend/endpoint-9090-803/v1.0/connectors/details?offset=0&product=MI&type=inbound&runtimeVersion=${version}
 MI_CONNECTOR_STORE_BACKEND_GETBYVERSION=https://apis.wso2.com/qgpf/connector-store-backend/endpoint-9090-803/v1.0/connectors/${repoName}/versions/${versionId}?runtimeVersion=4.3.0&product=MI
 ADOPTIUM_API_BASE_URL=https://api.adoptium.net/v3/assets/feature_releases
-COPILOT_ROOT_URL=https://7eff1239-64bb-4663-b256-30a00d187a5c-prod.e1-us-east-azure.choreoapis.dev/copilot
workspaces/mi/mi-extension/src/ai-features/agent-mode/tool-action-mapper.ts (1)

47-54: Redundant re-assignment in the 'created' branch.

Lines 48–50 re-assign completedAction and failedAction to the same values they were initialized with on lines 45–46. You can simplify by only handling the 'updated' case:

♻️ Suggested simplification
             if (toolResult?.message) {
-                if (toolResult.message.includes('created')) {
-                    completedAction = 'created';
-                    failedAction = 'failed to create';
-                } else if (toolResult.message.includes('updated')) {
+                if (toolResult.message.includes('updated')) {
                     completedAction = 'updated';
                     failedAction = 'failed to update';
                 }
             }
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts (3)

660-662: Unnecessary type cast — checkpointId already exists on UndoLastCheckpointRequest.

UndoLastCheckpointRequest in types.ts (line 78–81) already declares checkpointId?: string. The intersection cast (request as UndoLastCheckpointRequest & { checkpointId?: string }) is redundant.

Proposed fix
-const requestedCheckpointId = (request as UndoLastCheckpointRequest & { checkpointId?: string }).checkpointId;
+const requestedCheckpointId = request.checkpointId;

940-966: Duplicated session-load logic when switching to the current session.

Lines 942–966 (the "already on requested session" branch) duplicate the same message-loading, event-conversion, checkpoint-normalization, and usage-fetching logic found in loadChatHistory (lines 834–886) and the main switchSession path (lines 968–1001). Consider extracting a shared helper.


462-471: File restore via createFile + full-range replace may silently fail on empty files.

When restoring a file that should exist but is empty, createFile with overwrite: true writes a zero-byte file, then replace uses a range spanning (0,0) to (MAX_SAFE_INTEGER, MAX_SAFE_INTEGER). On an empty document this range resolves to an empty range, so the replace should insert correctly. However, the two-step approach (create then replace) means a crash between them leaves a corrupted file. Consider using workspaceEdit.createFile with contents (available via vscode.workspace.fs.writeFile) as a single atomic operation, or at minimum document this ordering dependency.

workspaces/mi/mi-core/src/rpc-types/agent-mode/types.ts (1)

177-235: AgentEvent is a large bag-of-optional-fields — consider a discriminated union.

AgentEvent carries ~25 optional fields for very different event kinds (content streaming, tool calls, plan approvals, shell output, usage, etc.). A discriminated union on type would provide compile-time exhaustiveness checks and prevent impossible field combinations (e.g., bashCommand on a usage event). This is a larger refactor best deferred, but worth tracking.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (12)
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts (4)

681-696: Undo checkpoint captured before validation completes.

captureBeforeChange at Line 682 runs before the file-existence check at Line 685. If the file already has content, the write is rejected but a checkpoint was already recorded. Move the capture to after all pre-write validation passes (after Line 696).

Similarly, a readFileSync at Line 687 can throw on permission errors with no surrounding try-catch.

♻️ Proposed reorder
-        const fullPath = resolveFullPath(projectPath, file_path);
-        await undoCheckpointManager?.captureBeforeChange(file_path);
-
-        // Check if file exists with non-empty content
-        const fileExists = fs.existsSync(fullPath);
-        if (fileExists) {
-            const existingContent = fs.readFileSync(fullPath, 'utf-8');
-            if (existingContent.trim().length > 0) {
+        const fullPath = resolveFullPath(projectPath, file_path);
+
+        // Check if file exists with non-empty content
+        let fileExists = false;
+        try {
+            fileExists = fs.existsSync(fullPath);
+            if (fileExists) {
+                const existingContent = fs.readFileSync(fullPath, 'utf-8');
+                if (existingContent.trim().length > 0) {
+                    console.error(`[FileWriteTool] File already exists with content: ${file_path}`);
+                    return {
+                        success: false,
+                        message: `File '${file_path}' already exists with content. Use ${FILE_EDIT_TOOL_NAME} to modify it instead.`,
+                        error: `Error: ${ErrorMessages.FILE_ALREADY_EXISTS}`
+                    };
+                }
+            }
+        } catch (error) {
+            logError(`[FileWriteTool] Error checking existing file: ${file_path}`, error);
+            return {
+                success: false,
+                message: `Cannot access file '${file_path}': ${error instanceof Error ? error.message : String(error)}`,
+                error: `Error: ${ErrorMessages.FILE_WRITE_FAILED}`
+            };
+        }
+
+        await undoCheckpointManager?.captureBeforeChange(file_path);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`
around lines 681 - 696, The undo checkpoint is captured too early—move the call
to undoCheckpointManager?.captureBeforeChange(file_path) so it executes only
after pre-write validation (i.e., after checking existence and non-empty
content) and before the actual write; wrap the fs.readFileSync(existing) usage
inside a try-catch to handle permission/IO errors and return an appropriate
error message instead of throwing; reference resolveFullPath,
undoCheckpointManager.captureBeforeChange, FILE_EDIT_TOOL_NAME, and
ErrorMessages.FILE_ALREADY_EXISTS when making these changes so the validation
happens first, IO errors are caught, and checkpoints are only recorded when the
write is allowed.

941-952: Same checkpoint-before-validation issue as in createWriteExecute.

captureBeforeChange at Line 942 fires before the existence check at Line 945. If the file doesn't exist, the edit is rejected but a checkpoint was already captured. Consider moving it after the existence check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`
around lines 941 - 952, The checkpoint is being captured before validating file
existence: move the call to undoCheckpointManager.captureBeforeChange(file_path)
so it occurs after the existence check (after fs.existsSync(fullPath) succeeds)
to avoid creating checkpoints for rejected edits; locate the call in
file_tools.ts near resolveFullPath/undoCheckpointManager and adjust so
captureBeforeChange runs only when the file exists (preserve the same parameters
file_path and behavior for subsequent edit logic).

1099-1159: Recursive directory walk has no depth limit.

searchInDirectory recurses without bounding depth. A project with deeply nested symlink loops (or just very deep trees) could overflow the call stack. Consider adding a maxDepth guard.

♻️ Sketch
-            const searchInDirectory = (dirPath: string) => {
+            const MAX_SEARCH_DEPTH = 20;
+            const searchInDirectory = (dirPath: string, depth: number = 0) => {
+                if (depth > MAX_SEARCH_DEPTH) return;
                 if (output_mode === 'content' && results.length >= head_limit) return;
                 ...
-                        searchInDirectory(fullPath);
+                        searchInDirectory(fullPath, depth + 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`
around lines 1099 - 1159, searchInDirectory currently recurses unbounded and can
blow the stack; modify it to accept a currentDepth parameter and enforce a
maxDepth constant (e.g., maxDepth) before recursing, incrementing currentDepth
when calling searchInDirectory(fullPath); additionally, when iterating entries
use fs.lstatSync (or fs.promises.lstat) to detect and skip symbolic links
(entry.isSymbolicLink()) to avoid symlink loops; update any initial call site to
start with currentDepth = 0 and ensure the depth-check short-circuits both
directory recursion and further processing when exceeded.

269-275: ~ check rejects any path containing a tilde, not just home-dir shorthand.

Line 270 uses normalizedPath.includes('~'), which will reject legitimate filenames like backup~1.xml. If this is intentional, a comment clarifying the trade-off would help; otherwise, consider narrowing to paths starting with ~/ or ~.

♻️ Narrower check
-    if (normalizedPath.includes('~') || (!path.isAbsolute(normalizedPath) && normalizedPath.includes('..'))) {
+    if (normalizedPath.startsWith('~') || (!path.isAbsolute(normalizedPath) && normalizedPath.includes('..'))) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`
around lines 269 - 275, The current security check uses
normalizedPath.includes('~') which erroneously rejects filenames containing a
tilde anywhere (e.g., "backup~1.xml"); change the check to only detect home-dir
shorthand by testing for a leading tilde (e.g., normalizedPath === '~' or
normalizedPath.startsWith('~' + path.sep) or using a regex like
/^~(?:[\/\\]|$)/) and keep the existing relative-traversal check using
path.isAbsolute(normalizedPath) && normalizedPath.includes('..') logic; update
the conditional around normalizedPath and the returned error/comment to reflect
that only leading-tilde home shorthand is disallowed.
workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts (4)

115-121: isFilePath heuristic could false-positive on inline content ending with .json, .xml, etc.

For example, inline XML or JSON content that happens to end with a string matching /\.(json|xml|xsd|csv)$/i would be misclassified as a file path. This is unlikely given LLM-generated args but worth a brief comment in the code to document the heuristic's limitations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`
around lines 115 - 121, The isFilePath function's extension-based check
(function isFilePath) can false-positive when inline content ends with ".json",
".xml", ".xsd", or ".csv"; add a brief comment directly above isFilePath
documenting this limitation and why the heuristic is used (simple/fast for LLM
args), note the potential false-positive case (inline content that happens to
end with those extensions) and that this is an accepted trade-off unless
stricter validation is later required.

377-388: Appending .ts to a path that already has an extension is unlikely to match a real file.

Line 383 falls back to ${fullPath}.ts (e.g., foo.dmc.ts) when the extension-replacement attempt fails. This path pattern is improbable for data mapper files. Consider removing this fallback to reduce confusion, or adding a comment explaining when this case would apply.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`
around lines 377 - 388, The fallback that appends `.ts` to a path with an
existing extension (the `${fullPath}.ts` branch using appendedTsPath) is
misleading and should be removed or documented; update the block in
data_mapper_tools.ts where fullPath is handled (the logic using
resolveProjectBoundPath and tsFilePath) to stop trying `${fullPath}.ts` when
fullPath already has an extension — instead only attempt the
extension-replacement (`fullPath.replace(/\.[^/.]+$/, '.ts')`) and assign
tsFilePath if that resolves and exists; if you prefer to keep the appended case,
add a clear comment explaining the rare scenario where `foo.dmc.ts` would be
valid and ensure you only assign tsFilePath after verifying
fs.existsSync(appendedTsPath).

314-323: (tool as any) casts suppress type safety.

Both createCreateDataMapperTool and createGenerateDataMappingTool use (tool as any)(...), which disables compile-time validation of the schema-to-execute type alignment. This is what allows the 'XSD' mismatch above to go undetected. Consider typing these properly if the ai SDK's tool function supports it in the version you're using.

Also applies to: 462-471

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`
around lines 314 - 323, The code is suppressing type-safety by using (tool as
any)(...), so update createCreateDataMapperTool and
createGenerateDataMappingTool to call tool with the correct generic/type
parameters instead of casting to any: remove "(tool as any)" and supply the
tool's generic type that ties inputSchema (createDataMapperInputSchema /
createGenerateDataMappingInputSchema) to the corresponding execute function type
(CreateDataMapperExecuteFn / CreateGenerateDataMappingExecuteFn or equivalent),
or adapt the execute types to match the tool signature; make the change in both
createCreateDataMapperTool and the other function at lines ~462-471 so the
schema-to-execute alignment is checked by the compiler.

174-199: Undo checkpoint only captures the main .ts file, not all files created by createDMFiles.

captureBeforeChange is called only for relativeTsPath (Line 178), but dmRpcManager.createDMFiles (Line 194) generates additional files (e.g., dm-utils.ts, registry resources). If the user triggers undo, only the main TS file state is restored while the other scaffolding files persist.

Consider capturing before-state for all files that createDMFiles will generate, or documenting that undo for data mapper creation is partial.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`
around lines 174 - 199, The undo checkpoint only captures the single main TS
file (relativeTsPath) but createDMFiles (MiDataMapperRpcManager.createDMFiles)
creates additional files (e.g., dm-utils.ts and registry resources), so update
the flow to capture before-state for all files that will be generated: derive
the full list of target files (the main `${name}.ts` at tsFilePath, the dm-utils
file in dmFolder, and any registry/resource files that createDMFiles will
create) using getDataMapperFolder/dmFolder and the name, then call
undoCheckpointManager.captureBeforeChange for each file's path (use
path.relative(projectPath, ...)) before invoking dmRpcManager.createDMFiles;
ensure you guard for undefined undoCheckpointManager as currently done.
workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts (3)

612-621: Redundant type assertion — checkpointId is already on UndoLastCheckpointRequest.

UndoLastCheckpointRequest (types.ts, Line 80) already declares checkpointId?: string. The intersection cast on Line 614 is unnecessary.

Proposed fix
-            const requestedCheckpointId = (request as UndoLastCheckpointRequest & { checkpointId?: string }).checkpointId;
+            const requestedCheckpointId = request.checkpointId;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts` around
lines 612 - 621, The code uses an unnecessary intersection cast when extracting
checkpointId; remove the redundant " & { checkpointId?: string }" and simply
read the optional property from the request as defined by
UndoLastCheckpointRequest (i.e., set requestedCheckpointId from
request.checkpointId or from (request as
UndoLastCheckpointRequest).checkpointId), keeping the rest of the logic that
compares requestedCheckpointId to checkpoint.summary.checkpointId unchanged.

1179-1265: buildMentionablePathCache recursively walks with no depth or item count limit.

For large Maven multi-module projects, the unbounded walk could traverse deeply nested trees and produce a large cache. Consider adding a max-depth parameter or a cap on the total number of items to keep the cache-build fast and memory-bounded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts` around
lines 1179 - 1265, buildMentionablePathCache's inner async function walk
currently recurses without limits and can exhaust memory on large repos; modify
buildMentionablePathCache to enforce traversal bounds by adding
parameters/guards (e.g., maxDepth and maxItems) and stop conditions: update walk
to accept currentDepth and return early when currentDepth >= maxDepth or a
shared counter tracking total items (incrementing when addMentionable is called)
reaches maxItems, and ensure addMentionable and the final mentionablePathCache
assignment respect the same counter; keep function names referenced
(buildMentionablePathCache, walk, addMentionable, addMentionable calls that push
to mentionables, mentionablePathCache, mentionableRootPathSet) so the traversal
halts deterministically and the cache remains memory-bounded.

879-964: switchSession duplicates the load-and-normalize-events logic in three places.

The same pattern — getMessagesconvertToEventFormatgetUndoCheckpointManagerapplyLatestUndoAvailabilityToEventsgetLastUsagegetLatestMode — appears in loadChatHistory (Lines 804-820), switchSession same-session branch (Lines 898-911), and switchSession new-session branch (Lines 931-944). Extracting this into a private helper would reduce duplication and bug surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts` around
lines 879 - 964, switchSession duplicates the same "load + normalize events"
sequence found in loadChatHistory and two branches of switchSession; extract
that sequence into a single private helper (e.g., loadAndNormalizeSessionEvents)
on the same class that performs: getMessages (from either
getChatHistoryManager() or this.chatHistoryManager), convert with
ChatHistoryManager.convertToEventFormat, fetch undo checkpoint via
getUndoCheckpointManager().getLatestCheckpoint(), call
applyLatestUndoAvailabilityToEvents with the checkpoint id, fetch last usage via
getLastUsage, and fetch latest mode via getLatestMode; have the helper return
the normalized events, lastTotalInputTokens, and mode, then replace the
duplicated blocks in loadChatHistory and both switchSession branches to call
this helper and assign this.currentMode from the returned mode.
workspaces/mi/mi-core/src/rpc-types/agent-mode/types.ts (1)

174-235: AgentEvent is a wide union-style interface — consider documenting which fields apply to which event types.

AgentEvent has 30+ optional fields for different event types (thinking, tool, plan, usage, shell). The PlanApprovalRequestedEvent sub-interface (Lines 240-250) is a good pattern for narrowing. Consider adding similar discriminated sub-interfaces or at least a field-to-event-type mapping comment for maintainability as this surface grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@workspaces/mi/mi-core/src/rpc-types/agent-mode/types.ts` around lines 174 -
235, AgentEvent is a large catch‑all interface; split it into discriminated
sub-interfaces (e.g., ThinkingEvent, ToolCallEvent, ToolResultEvent, PlanEvent,
PlanApprovalRequestedEvent, UsageEvent, ShellEvent) keyed by the existing type
field (AgentEventType) and export a union alias AgentEvent = ThinkingEvent |
ToolCallEvent | ... so each variant declares only the fields it needs;
alternatively add a clear field-to-event mapping comment next to AgentEventType
and the AgentEvent declaration if refactoring into sub-interfaces is too
invasive. Use the existing PlanApprovalRequestedEvent name and AgentEventType
discriminant to guide naming and ensure tsconfig strict type checking benefits
from narrowing via switch/case on event.type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@workspaces/mi/mi-data-mapper/src/components/DataMapper/Header/AIMapButton.tsx`:
- Line 76: The bug: when setRemainingTokenPercentage assigns the string "Not
Available" to remainingTokenPercentage, later formatting naively appends "%"
resulting in "Not Available%". Fix: change how remainingTokenPercentage is
represented and checked—use a nullable number (e.g., number | null) or keep the
sentinel string but update the formatter to check the type/value before
appending "%" (inspect remainingTokenPercentage via typeof or Number.isFinite)
in the render/formatting code that currently does
`${remainingTokenPercentage}%`; update the setter (setRemainingTokenPercentage)
usage so it sets null or NaN-safe numbers and update the display logic in
AIMapButton (referencing remainingTokenPercentage and
setRemainingTokenPercentage) to conditionally append "%" only for valid numeric
percentages, otherwise render "Not Available" (no trailing %).

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`:
- Around line 304-312: The zod schema createDataMapperInputSchema includes 'XSD'
for input_type and output_type but the TypeScript type CreateDataMapperExecuteFn
in types.ts restricts those fields to 'JSON' | 'XML' | 'CSV', causing a mismatch
and a cast workaround; update the type definition(s) in types.ts that define the
input_type/output_type union (the CreateDataMapperExecuteFn type and any related
Input/Output format type aliases) to include 'XSD' so the TS types match the zod
schema and existing schemaBuilder handling of XSD.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`:
- Around line 1416-1424: The schema defines a type field (grepInputSchema) but
the grep executor (createGrepExecute) never uses it; update createGrepExecute to
destructure the type property from the input and incorporate it into the ripgrep
args (e.g., add ['-t', type] or ['--type', type] when present), ensuring you
validate/escape the value as needed or map it to allowed types, or alternatively
remove the type entry from grepInputSchema if you choose not to support
file-type filtering.
- Around line 1388-1390: Fix the typo in the JSDoc/comment that currently reads
"Creates the file_edit to" to "Creates the file_edit tool"; update the comment
text where the file_edit tool is defined (the comment immediately above the
file_edit tool creation/registration function in file_tools.ts) so it accurately
describes the purpose of the file_edit tool.
- Around line 1288-1292: The glob pattern can escape the validated searchPath
via path traversal, so after computing globPattern and calling glob.sync (the
matches array), validate and filter results to ensure each resolved match stays
inside fullSearchPath: for each match produced by glob.sync, resolve/normalize
it (path.resolve / path.normalize) and then check via
path.relative(fullSearchPath, resolved) that it does not start with '..' (or
ensure resolved.startsWith(fullSearchPath + path.sep) or equals fullSearchPath)
before accepting it; reject any matches outside the boundary and log or ignore
them. This change should be applied around the code using fullSearchPath,
pattern, globPattern and matches so the post-filter enforces the project
boundary regardless of the pattern content.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-handler.ts`:
- Around line 51-53: disposeMIAgentPanelRpcManager currently just deletes the
rpcManagerMap entry and must first abort any in-flight operations: find the
manager by projectUri in rpcManagerMap, call its abortAgentGeneration() method
to trigger the AbortController and reject pending interaction promises, then
remove the map entry; also ensure the disposeMIAgentPanelRpcManager function is
invoked from the workspace folder removal handler in extension.ts so managers
are cleaned up when folders are closed.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts`:
- Around line 303-379: The resolveLegacyCodeSegmentPath function extracts
artifactName from XML and then uses it in path.join without sanitization,
allowing path traversal; sanitize and validate artifactName (the variable parsed
at nameMatch[2]) before any use in path.join: reject or return null if it
contains path separators, "..", or is an absolute path, or alternatively
normalize to a safe basename/whitelisted pattern (e.g., only alphanumerics,
dots, dashes, underscores); apply this check for all branches that use
artifactName (including the apis branch that also reads versionMatch) and ensure
any invalid artifactName returns null instead of constructing a path.
- Around line 494-536: The handler runAgentOnce sets this.currentAbortController
to a single controller (via createAgentAbortController), so concurrent
sendAgentMessage calls overwrite the controller and earlier runs become
unabortable; change the logic to track controllers per run (e.g., maintain a Map
or Set of AbortControllers) or guard against concurrent runs by
rejecting/awaiting when a run is in progress. Specifically, update
sendAgentMessage/runAgentOnce to push the new controller into a controllers
collection (and remove it in the finally block), or prevent a new run while
this.currentAbortController (or an inProgress flag) exists, and modify
abortAgentGeneration to abort all controllers in the collection (or clear the
flag) so abortAgentGeneration correctly aborts the intended run(s); ensure you
still clear controllers in the finally branch of runAgentOnce.

---

Duplicate comments:
In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`:
- Around line 180-196: The current compileGrepPattern has ReDoS mitigations
(length check + try/catch) but the same protections must be applied
consistently: update compileGlobPattern to enforce the same
MAX_GREP_PATTERN_LENGTH guard, use the same try/catch error handling and
return-shape ({ regex?: RegExp; error?: string }), and make the regex flag logic
symmetric to compileGrepPattern (use 'gi' when caseInsensitive, otherwise 'g');
ensure error messages consistently include the Error.message when available.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts`:
- Around line 440-446: The log in sendAgentMessage should avoid logging PII by
not including the actual message content; keep only metadata (chatId, mode,
messageLength) in the logInfo call and remove any references to request.message
from INFO-level logging. Update the logInfo invocation in the sendAgentMessage
function (rpc-manager.ts) to only interpolate request.chatId, request.mode (or
this.currentMode/DEFAULT_AGENT_MODE) and the computed messageLength, and ensure
no other INFO logs in this function output request.message; if deeper debugging
of message content is needed, move that to a DEBUG/TRACE-level log gated by a
config flag.
- Around line 120-129: The method rejectPendingInteractions correctly rejects
and clears both pendingQuestions and pendingApprovals, but to harden it ensure
each pending.reject call is protected so one thrown error doesn’t stop the rest:
in rejectPendingInteractions wrap pending.reject(...) calls for both
this.pendingQuestions and this.pendingApprovals in try/catch (or use
Promise.resolve().catch) and still clear the maps afterward so all entries are
removed even if individual rejects throw; reference the
rejectPendingInteractions function and the this.pendingQuestions /
this.pendingApprovals maps when making the change.

---

Nitpick comments:
In `@workspaces/mi/mi-core/src/rpc-types/agent-mode/types.ts`:
- Around line 174-235: AgentEvent is a large catch‑all interface; split it into
discriminated sub-interfaces (e.g., ThinkingEvent, ToolCallEvent,
ToolResultEvent, PlanEvent, PlanApprovalRequestedEvent, UsageEvent, ShellEvent)
keyed by the existing type field (AgentEventType) and export a union alias
AgentEvent = ThinkingEvent | ToolCallEvent | ... so each variant declares only
the fields it needs; alternatively add a clear field-to-event mapping comment
next to AgentEventType and the AgentEvent declaration if refactoring into
sub-interfaces is too invasive. Use the existing PlanApprovalRequestedEvent name
and AgentEventType discriminant to guide naming and ensure tsconfig strict type
checking benefits from narrowing via switch/case on event.type.

In
`@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/data_mapper_tools.ts`:
- Around line 115-121: The isFilePath function's extension-based check (function
isFilePath) can false-positive when inline content ends with ".json", ".xml",
".xsd", or ".csv"; add a brief comment directly above isFilePath documenting
this limitation and why the heuristic is used (simple/fast for LLM args), note
the potential false-positive case (inline content that happens to end with those
extensions) and that this is an accepted trade-off unless stricter validation is
later required.
- Around line 377-388: The fallback that appends `.ts` to a path with an
existing extension (the `${fullPath}.ts` branch using appendedTsPath) is
misleading and should be removed or documented; update the block in
data_mapper_tools.ts where fullPath is handled (the logic using
resolveProjectBoundPath and tsFilePath) to stop trying `${fullPath}.ts` when
fullPath already has an extension — instead only attempt the
extension-replacement (`fullPath.replace(/\.[^/.]+$/, '.ts')`) and assign
tsFilePath if that resolves and exists; if you prefer to keep the appended case,
add a clear comment explaining the rare scenario where `foo.dmc.ts` would be
valid and ensure you only assign tsFilePath after verifying
fs.existsSync(appendedTsPath).
- Around line 314-323: The code is suppressing type-safety by using (tool as
any)(...), so update createCreateDataMapperTool and
createGenerateDataMappingTool to call tool with the correct generic/type
parameters instead of casting to any: remove "(tool as any)" and supply the
tool's generic type that ties inputSchema (createDataMapperInputSchema /
createGenerateDataMappingInputSchema) to the corresponding execute function type
(CreateDataMapperExecuteFn / CreateGenerateDataMappingExecuteFn or equivalent),
or adapt the execute types to match the tool signature; make the change in both
createCreateDataMapperTool and the other function at lines ~462-471 so the
schema-to-execute alignment is checked by the compiler.
- Around line 174-199: The undo checkpoint only captures the single main TS file
(relativeTsPath) but createDMFiles (MiDataMapperRpcManager.createDMFiles)
creates additional files (e.g., dm-utils.ts and registry resources), so update
the flow to capture before-state for all files that will be generated: derive
the full list of target files (the main `${name}.ts` at tsFilePath, the dm-utils
file in dmFolder, and any registry/resource files that createDMFiles will
create) using getDataMapperFolder/dmFolder and the name, then call
undoCheckpointManager.captureBeforeChange for each file's path (use
path.relative(projectPath, ...)) before invoking dmRpcManager.createDMFiles;
ensure you guard for undefined undoCheckpointManager as currently done.

In `@workspaces/mi/mi-extension/src/ai-features/agent-mode/tools/file_tools.ts`:
- Around line 681-696: The undo checkpoint is captured too early—move the call
to undoCheckpointManager?.captureBeforeChange(file_path) so it executes only
after pre-write validation (i.e., after checking existence and non-empty
content) and before the actual write; wrap the fs.readFileSync(existing) usage
inside a try-catch to handle permission/IO errors and return an appropriate
error message instead of throwing; reference resolveFullPath,
undoCheckpointManager.captureBeforeChange, FILE_EDIT_TOOL_NAME, and
ErrorMessages.FILE_ALREADY_EXISTS when making these changes so the validation
happens first, IO errors are caught, and checkpoints are only recorded when the
write is allowed.
- Around line 941-952: The checkpoint is being captured before validating file
existence: move the call to undoCheckpointManager.captureBeforeChange(file_path)
so it occurs after the existence check (after fs.existsSync(fullPath) succeeds)
to avoid creating checkpoints for rejected edits; locate the call in
file_tools.ts near resolveFullPath/undoCheckpointManager and adjust so
captureBeforeChange runs only when the file exists (preserve the same parameters
file_path and behavior for subsequent edit logic).
- Around line 1099-1159: searchInDirectory currently recurses unbounded and can
blow the stack; modify it to accept a currentDepth parameter and enforce a
maxDepth constant (e.g., maxDepth) before recursing, incrementing currentDepth
when calling searchInDirectory(fullPath); additionally, when iterating entries
use fs.lstatSync (or fs.promises.lstat) to detect and skip symbolic links
(entry.isSymbolicLink()) to avoid symlink loops; update any initial call site to
start with currentDepth = 0 and ensure the depth-check short-circuits both
directory recursion and further processing when exceeded.
- Around line 269-275: The current security check uses
normalizedPath.includes('~') which erroneously rejects filenames containing a
tilde anywhere (e.g., "backup~1.xml"); change the check to only detect home-dir
shorthand by testing for a leading tilde (e.g., normalizedPath === '~' or
normalizedPath.startsWith('~' + path.sep) or using a regex like
/^~(?:[\/\\]|$)/) and keep the existing relative-traversal check using
path.isAbsolute(normalizedPath) && normalizedPath.includes('..') logic; update
the conditional around normalizedPath and the returned error/comment to reflect
that only leading-tilde home shorthand is disallowed.

In `@workspaces/mi/mi-extension/src/rpc-managers/agent-mode/rpc-manager.ts`:
- Around line 612-621: The code uses an unnecessary intersection cast when
extracting checkpointId; remove the redundant " & { checkpointId?: string }" and
simply read the optional property from the request as defined by
UndoLastCheckpointRequest (i.e., set requestedCheckpointId from
request.checkpointId or from (request as
UndoLastCheckpointRequest).checkpointId), keeping the rest of the logic that
compares requestedCheckpointId to checkpoint.summary.checkpointId unchanged.
- Around line 1179-1265: buildMentionablePathCache's inner async function walk
currently recurses without limits and can exhaust memory on large repos; modify
buildMentionablePathCache to enforce traversal bounds by adding
parameters/guards (e.g., maxDepth and maxItems) and stop conditions: update walk
to accept currentDepth and return early when currentDepth >= maxDepth or a
shared counter tracking total items (incrementing when addMentionable is called)
reaches maxItems, and ensure addMentionable and the final mentionablePathCache
assignment respect the same counter; keep function names referenced
(buildMentionablePathCache, walk, addMentionable, addMentionable calls that push
to mentionables, mentionablePathCache, mentionableRootPathSet) so the traversal
halts deterministically and the cache remains memory-bounded.
- Around line 879-964: switchSession duplicates the same "load + normalize
events" sequence found in loadChatHistory and two branches of switchSession;
extract that sequence into a single private helper (e.g.,
loadAndNormalizeSessionEvents) on the same class that performs: getMessages
(from either getChatHistoryManager() or this.chatHistoryManager), convert with
ChatHistoryManager.convertToEventFormat, fetch undo checkpoint via
getUndoCheckpointManager().getLatestCheckpoint(), call
applyLatestUndoAvailabilityToEvents with the checkpoint id, fetch last usage via
getLastUsage, and fetch latest mode via getLatestMode; have the helper return
the normalized events, lastTotalInputTokens, and mode, then replace the
duplicated blocks in loadChatHistory and both switchSession branches to call
this helper and assign this.currentMode from the returned mode.

xlight05
xlight05 previously approved these changes Feb 17, 2026
Copy link
Contributor

@xlight05 xlight05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. MI/BI UI unification and deviations, we'll address separately. Lets go ahead with this for alpha.

@IsuruMaduranga shall we take a review from MI side too?

@IsuruMaduranga
Copy link
Contributor Author

Overall LGTM. MI/BI UI unification and deviations, we'll address separately. Lets go ahead with this for alpha.

@IsuruMaduranga shall we take a review from MI side too?

Yeah. @ChinthakaJ98? Looks good?

@ChinthakaJ98
Copy link
Contributor

There is a build failure. Can you please check that?

@IsuruMaduranga IsuruMaduranga changed the title MI agent mode [MI][AI][Copilot] Add agent mode to MI Copilot Feb 18, 2026
@IsuruMaduranga IsuruMaduranga force-pushed the mi-agent-mode branch 2 times, most recently from 1a7c8df to 76d5167 Compare February 20, 2026 08:19
@ChinthakaJ98 ChinthakaJ98 removed the Checks/Run MI UI Tests Force run MI UI tests label Feb 20, 2026
@ChinthakaJ98 ChinthakaJ98 merged commit fd8fc7f into wso2:main Feb 20, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants