Skip to content

✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator + chat panel UI#1389

Open
ibolton336 wants to merge 17 commits intokonveyor:mainfrom
ibolton336:feature/pluggable-agent-backend
Open

✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator + chat panel UI#1389
ibolton336 wants to merge 17 commits intokonveyor:mainfrom
ibolton336:feature/pluggable-agent-backend

Conversation

@ibolton336
Copy link
Copy Markdown
Member

@ibolton336 ibolton336 commented May 1, 2026

Summary

Complete pluggable agent backend infrastructure with matching chat panel UI — enables the extension to drive external coding agents (Goose, OpenCode, Claude, Codex) for migration tasks. Replaces the monolithic solutionWorkflowOrchestrator.ts with a modular agent architecture.

No merge-order dependencies

This PR is self-contained — all required code is included. The following standalone PRs were extracted from this branch to keep individual diffs focused, but their content is already present here:

When those PRs merge to main first, rebasing this PR will be a clean no-op for the overlapping files.

Related standalone PRs (independent, can merge in any order):


What's included

Shared — New messaging pattern (shared/src/types/):

  • messages.ts — Consolidated StateChangeData pattern replacing individual granular message types
  • agent.tsAgentBackend, AgentState, AgentConfig, AgentCapability types
  • agentActions.ts — Agent action contracts for extension↔webview communication
  • agentMessages.ts — Agent message types for state sync

Agentic — Type additions (types.ts):

  • Added originalContent?: string to KaiModifiedFile
  • Added result?: string to KaiToolCall

Agent clients (vscode/core/src/client/):

  • gooseClient.ts — Goose CLI integration with JSON-RPC/stdio streaming
  • opencodeClient.ts — OpenCode SDK integration with SSE streaming
  • agentClient.ts — abstract client interface
  • directLLMClient.ts — fallback LLM client

Agent orchestrator (vscode/core/src/features/agent/):

  • agentOrchestrator.ts — coordinates agent start/stop, message routing, state
  • fileTracker.ts / fileChangeRouter.ts — monitor agent file modifications
  • batchReviewHandlers.ts — multi-file review workflows
  • toolPermissionHandler.ts / toolPermissionPolicy.ts — policy-based tool access control
  • promptBuilder.ts — agent interaction prompts

MCP bridge server (vscode/core/src/api/):

Chat panel UI (webview-ui/src/components/ChatPage/):

  • ChatPage.tsx — Main agent chat interface with streaming message display
  • AgentFileReview.tsx — File review UI for agent-modified files
  • AgentSettings.tsx — Agent backend configuration panel
  • CompactBatchReview.tsx — Multi-file batch review interface
  • PermissionReviewMessage.tsx — Tool permission review UI
  • ThinkingIndicator.tsx — Agent thinking state indicator

Webview hooks & utilities:

  • useContainerWidth.ts — ResizeObserver-based container width hook
  • useScrollManagement.ts — Updated scroll management with agent message support
  • useVSCodeMessageHandler.ts — Updated for new StateChangeData messaging pattern
  • ToolMessage.tsx — Refactored with CollapsibleToolGroup and AgentToolGroup
  • actions.ts — Agent mode toggle and new action types
  • store.ts — Updated store shape for agent state
  • App.tsx — Agent mode routing

Extension wiring:

  • extensionStore.ts / syncBridges.ts — new store and state sync layer
  • featureRegistry.ts — pluggable feature registration
  • Updated extension.ts, commands.ts, webviewMessageHandler.ts, extensionState.ts

Changelog:

  • 1351-pluggable-agent-backend.yaml — Feature entry

Review guide

~2.5k lines are CSS (ChatPage.css, AgentFileReview.css, CompactBatchReview.css, PermissionReviewMessage.css) — safe to skim.

Recommended review order:

  1. Shared typesshared/src/types/ (messaging contracts)
  2. Agent clientsvscode/core/src/client/ (Goose + OpenCode integration)
  3. Agent orchestratorvscode/core/src/features/agent/ (core logic)
  4. Extension wiringextension.ts, webviewMessageHandler.ts, extensionStore.ts
  5. Chat UIwebview-ui/src/components/ChatPage/ (rendering layer)

Testing

  • toolPermissionHandler.test.ts — unit tests for tool permission handler
  • batchedAnalysisTrigger.test.ts — updated for state changes
  • Build verified locally ✅

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

This pull request implements a pluggable agent backend system enabling the VS Code extension to support multiple interchangeable AI backends (Goose, OpenCode, Claude, Codex). The changes introduce new type definitions for agent operations, implement two agent client variants communicating via JSON-RPC (Goose) and SSE (OpenCode), refactor state management to a centralized Zustand store replacing prior Immer-based mutations, establish a feature module system for experimental functionality, and deliver a complete chat UI with file review, settings, and permission handling workflows. Configuration management, file tracking, and batch review flows are added to support agent-produced code changes with optional review gates.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Rationale: The diff spans 9000+ lines across ~80 files with intricate, high-density logic including: (1) two distinct backend client implementations using JSON-RPC framing and SSE streaming protocols; (2) substantial state architecture refactoring (Immer→Zustand store with slice subscriptions); (3) new feature bootstrapping layer with per-module initialization; (4) complex orchestration flows for agent message handling, file change routing, and permission workflows; (5) comprehensive webview chat UI components with diff rendering and batch operations; (6) multi-pattern changes (store refactoring, message routing, client lifecycle). The heterogeneity—diverse backend protocols, structural architectural shifts, new UI system—demands reasoning for each section rather than applying a single pattern uniformly.

Suggested reviewers

  • pranavgaikwad
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@ibolton336 ibolton336 force-pushed the feature/pluggable-agent-backend branch from f973bb1 to 0f5b07f Compare May 1, 2026 16:26
@ibolton336 ibolton336 changed the title ✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator [2/3] ✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator [2/3] May 1, 2026
@ibolton336 ibolton336 force-pushed the feature/pluggable-agent-backend branch from 0f5b07f to 1e9b620 Compare May 1, 2026 16:38
@ibolton336 ibolton336 changed the title ✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator [2/3] ✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator [1/2] May 1, 2026
@ibolton336 ibolton336 changed the title ✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator [1/2] ✨ pluggable agent backend — Goose, OpenCode, MCP server, orchestrator + chat panel UI May 1, 2026
Copy link
Copy Markdown
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: 15

Note

Due to the large number of review comments, Critical, Major 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 (2)
vscode/core/src/paths.ts (1)

444-452: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-add the out-of-workspace guard before ignore matching.

relative() returns ../... for files outside workspaceRepo and can return an absolute path on Windows cross-drive paths. Without the old boundary check, isIgnoredBy(f) can treat external files as analyzable, so save events outside the workspace can leak into partial analysis.

Patch sketch
-import { relative, dirname, join } from "node:path";
+import { relative, dirname, join, isAbsolute } from "node:path";
...
   const f = relative(fsPaths().workspaceRepo, uri.fsPath);
   _logger?.debug(`isUriIgnored: ${f}`);
+
+  if (f.startsWith("..") || isAbsolute(f)) {
+    return true;
+  }
 
   // Always ignore .konveyor directory
   if (f.startsWith(".konveyor/") || f === ".konveyor") {
     return true;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/paths.ts` around lines 444 - 452, Reintroduce the
out-of-workspace guard before calling isIgnoredBy: after computing f =
relative(fsPaths().workspaceRepo, uri.fsPath) (and before the ".konveyor" check
or the isIgnoredBy(f) call), detect paths that start with ".." or are absolute
(Windows cross-drive results) and return true (ignore) for those external files
so that files outside the workspaceRepo cannot be treated as analyzable; update
the logic around the existing variables f, uri.fsPath, and the isIgnoredBy(f)
invocation to short-circuit external paths.
vscode/core/src/analysis/batchedAnalysisTrigger.ts (1)

24-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not enqueue ignored paths.

With the early ignore guard gone, ignored files now stay in both notifyFileChangesQueue and analysisFileChangesQueue forever because the later isUriIgnored(...) filters never remove them. In repos with lots of generated or vendor files, that turns into unbounded queue growth plus repeated re-filtering work on every schedule.

Suggested fix
 async notifyFileChanges(change: FileChange) {
+  if (isUriIgnored(change.path)) {
+    return;
+  }
+
   if (this.enableHotRerun) {
     this.extensionState.mutate((draft) => {
       draft.isAnalysisScheduled = true;
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/analysis/batchedAnalysisTrigger.ts` around lines 24 - 33, The
notifyFileChanges handler currently enqueues every change into
notifyFileChangesQueue and analysisFileChangesQueue which causes unbounded
growth for ignored paths; fix this by checking the ignore predicate before
mutating state or adding to queues: call the existing isUriIgnored (or
isUriIgnoredSync) with change.path and return early if it matches so you never
call this.notifyFileChangesQueue.set(...) or
this.analysisFileChangesQueue.add(...). Keep the existing behavior around
extensionState.mutate, this.enableHotRerun, scheduleNotifyFileChanges and
schedulePartialAnalysis but only after confirming the path is not ignored.
🟡 Minor comments (11)
docs/goose-acp-messaging-guide.md-24-24 (1)

24-24: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

These fences are missing language tags (MD040). Adding explicit tags (text, json, typescript, etc.) will satisfy lint and improve readability.

Also applies to: 57-57, 457-457, 471-471, 591-591, 643-643, 672-672

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

In `@docs/goose-acp-messaging-guide.md` at line 24, Several fenced code blocks in
the document are missing language identifiers (triple backticks with no tag);
update each fenced code block to include an appropriate language tag (e.g.,
text, json, typescript, bash) so the Markdown linter (MD040) passes and syntax
highlighting improves; search for occurrences of bare ``` fences and replace
them with ```text, ```json, ```typescript, etc., matching the contained snippet
content.
docs/goose-acp-messaging-guide.md-13-13 (1)

13-13: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

TOC fragment target appears invalid.

The anchor in the Table of Contents for “Webview <-> Extension Messaging” does not resolve cleanly under markdownlint (MD051). Please update the fragment to match the generated heading ID.

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

In `@docs/goose-acp-messaging-guide.md` at line 13, The TOC anchor "[Webview <->
Extension Messaging](`#3-webview--extension-messaging`)" doesn't match the
generated heading ID; update the fragment to the actual slug for the heading
"Webview <-> Extension Messaging" (lowercase, spaces and special chars
normalized) so the link resolves under markdownlint MD051—replace
"#3-webview--extension-messaging" with the generated heading ID that your
Markdown renderer produces for "Webview <-> Extension Messaging".
webview-ui/src/components/ChatPage/ResourceBlock.tsx-11-11 (1)

11-11: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty text content is misclassified as binary.

Using || converts valid empty strings to "(binary content)". Use nullish coalescing so only null/undefined falls back.

Suggested fix
-  const content = block.text || "(binary content)";
+  const content = block.text ?? "(binary content)";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ChatPage/ResourceBlock.tsx` at line 11, The
assignment to content uses the || operator which treats empty string as falsy
and replaces it with "(binary content)"; change the fallback to use the nullish
coalescing operator so only null/undefined fall back. Update the line that sets
content (using block.text) to use ?? instead of || so empty strings remain valid
while null/undefined produce "(binary content)". Ensure references to block.text
and the content variable are updated accordingly.
webview-ui/src/components/ChatPage/ChatPage.css-710-710 (1)

710-710: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace deprecated word-break: break-word value.

The break-word value for word-break is deprecated. Use overflow-wrap: break-word instead for the same behavior.

🐛 Proposed fix
 .agent-chat-msg--user .agent-chat-msg__content {
   max-width: 85%;
   padding: 6px 10px;
   border-radius: 10px 10px 2px 10px;
   background: var(--vscode-button-background, `#0e639c`);
   color: var(--vscode-button-foreground, `#fff`);
   font-size: 12px;
   line-height: 1.5;
   white-space: pre-wrap;
-  word-break: break-word;
+  overflow-wrap: break-word;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ChatPage/ChatPage.css` at line 710, Replace the
deprecated CSS declaration "word-break: break-word;" in ChatPage.css with
"overflow-wrap: break-word;" (and optionally keep "word-break: normal;" for
explicit behavior) so the element uses the supported overflow-wrap property
instead of the deprecated value; locate the occurrence of the "word-break:
break-word;" declaration and update it accordingly.
scripts/test-goose-streaming.sh-27-37 (1)

27-37: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the session/prompt waits deterministic.

This currently mixes fixed sleeps with a non-failing timeout, so a slow session/new can produce a false failure and a missing stopReason can still exit 0 with misleading stats. Poll for sessionId the same way you poll for completion, and fail hard when the completion deadline is hit.

Suggested fix
 echo '{"jsonrpc":"2.0","id":2,"method":"session/new","params":{}}' >&3
-sleep 1
-
-# Extract session ID from the response
-SESSION_ID=$(grep -o '"sessionId":"[^"]*"' "$OUTFILE" | head -1 | cut -d'"' -f4)
+# Extract session ID from the response
+SESSION_ID=""
+for _ in $(seq 1 20); do
+  SESSION_ID=$(grep -o '"sessionId":"[^"]*"' "$OUTFILE" | head -1 | cut -d'"' -f4)
+  [ -n "$SESSION_ID" ] && break
+  sleep 0.5
+done
 if [ -z "$SESSION_ID" ]; then
   echo "ERROR: Could not extract session ID from goose output."
   echo "Raw output:"
   cat "$OUTFILE"
@@
 echo "Waiting for response..."
-for i in $(seq 1 60); do
+GOT_STOP_REASON=0
+for _ in $(seq 1 60); do
   if grep -q '"stopReason"' "$OUTFILE" 2>/dev/null; then
+    GOT_STOP_REASON=1
     break
   fi
   sleep 0.5
 done
+if [ "$GOT_STOP_REASON" -ne 1 ]; then
+  echo "ERROR: Timed out waiting for stopReason."
+  echo "Raw output:"
+  cat "$OUTFILE"
+  exit 1
+fi

Also applies to: 43-50

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

In `@scripts/test-goose-streaming.sh` around lines 27 - 37, The script currently
uses fixed sleeps and a non-failing timeout which can cause false negatives;
replace the one-shot sleep+grep that sets SESSION_ID by implementing a poll loop
that repeatedly greps "$OUTFILE" for '"sessionId":"[^"]*"' (same pattern
currently used) up to the same completion deadline used elsewhere, assign
SESSION_ID when matched, and exit non-zero with an error message if the deadline
is hit; do the same deterministic polling and hard-fail behavior for the later
block that checks for stopReason so both session/new and completion waits are
polled rather than relying on fixed sleeps.
webview-ui/src/components/ChatPage/AgentFileReview.tsx-338-343 (1)

338-343: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Incorrect useEffect dependency causes missing re-runs.

The dependency [pendingFiles.length > 0] evaluates to a boolean at definition time, not a tracked dependency. This means the effect won't re-run when pendingFiles changes from empty to non-empty after the initial render. Use pendingFiles.length or pendingFiles as the dependency.

🐛 Fix useEffect dependency
   // Auto-expand the first file when the review appears
   React.useEffect(() => {
     if (pendingFiles.length > 0 && expandedTokens.size === 0) {
       setExpandedTokens(new Set([pendingFiles[0].messageToken]));
     }
-  }, [pendingFiles.length > 0]); // eslint-disable-line react-hooks/exhaustive-deps
+  }, [pendingFiles]);

Alternatively, if you want to avoid re-running when files change but the list remains non-empty, use a ref to track if expansion has occurred.

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

In `@webview-ui/src/components/ChatPage/AgentFileReview.tsx` around lines 338 -
343, The useEffect currently uses a boolean literal dependency
`[pendingFiles.length > 0]` which doesn't track changes to the array; update the
dependency to either `pendingFiles.length` (to re-run when the number of files
changes) or `pendingFiles` (to re-run on any change) so the effect that
auto-expands the first file (uses React.useEffect, pendingFiles, expandedTokens,
setExpandedTokens, and pendingFiles[0].messageToken) will run when files are
added; if you need to avoid re-running for content changes while preserving a
single expansion, consider using a ref to track whether expansion has already
occurred and check that inside the effect.
vscode/core/src/features/agent/fileChangeRouter.ts-41-58 (1)

41-58: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Inconsistency between docstring and implementation for batch review mode.

The docstring states "When isBatchReviewMode is enabled (or forceReview is true)", suggesting both conditions should be checked. However, line 48 simply assigns forceReview to isBatchReviewMode, ignoring state.data.isBatchReviewMode entirely.

If the intent is that forceReview overrides all behavior, the docstring should be updated. If both conditions should enable batch review mode, the logic needs adjustment.

🔧 Suggested fix if both conditions should enable batch review
 export async function routeFileChange(
   state: ExtensionState,
   filePath: string,
   content: string,
   originalContent?: string,
   forceReview = false,
 ): Promise<void> {
-  const isBatchReviewMode = forceReview;
+  const isBatchReviewMode = forceReview || state.data.isBatchReviewMode;
   const relativePath = normalizeFilePath(filePath, state.data.workspaceRoot);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/fileChangeRouter.ts` around lines 41 - 58, The
code assigns isBatchReviewMode = forceReview but the docstring says batch review
should be enabled when either forceReview is true or
state.data.isBatchReviewMode is true; update the logic in routeFileChange to
compute isBatchReviewMode as the OR of both conditions (e.g., use forceReview ||
state.data.isBatchReviewMode) so both sources enable batch review, and ensure
any docstring/comment matches this behavior; touch the symbols routeFileChange,
isBatchReviewMode, forceReview, and state.data.isBatchReviewMode when making the
change.
vscode/core/src/features/agent/batchReviewHandlers.ts-17-21 (1)

17-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Potential Windows path issue with file:// URL parsing.

On Windows, new URL("file:///C:/path").pathname returns /C:/path with a leading slash, which can cause path.join to produce incorrect paths like /C:/path/relative.

🛠️ Proposed fix
 function resolveAbsolutePath(filePath: string, state: ExtensionState): string {
   if (isAbsolute(filePath)) {
     return filePath;
   }
   let wsRoot = state.data.workspaceRoot;
   if (wsRoot.startsWith("file://")) {
-    wsRoot = new URL(wsRoot).pathname;
+    const url = new URL(wsRoot);
+    wsRoot = process.platform === "win32" 
+      ? url.pathname.slice(1) // Remove leading slash on Windows
+      : url.pathname;
   }
   return join(wsRoot, filePath);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/batchReviewHandlers.ts` around lines 17 - 21,
The code naively converts a file:// URL using new URL(wsRoot).pathname which
yields a leading slash on Windows (e.g. /C:/path) and breaks path.join; replace
that logic to use the Node utility that correctly converts file URLs to OS paths
(use fileURLToPath from 'url') so when wsRoot startsWith("file://") set wsRoot =
fileURLToPath(wsRoot) before calling join(wsRoot, filePath), ensuring correct
Windows drive-letter paths and preserving behavior on POSIX.
vscode/core/src/client/opencodeClient.ts-430-434 (1)

430-434: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

**Missing error handling and null check in createSession.**The createSession method accesses session.data.id directly without checking for errors or null, unlike the robust error handling in start() at lines 298-311. The SDK can throw errors that you can catch and handle.

🛡️ Proposed fix to add error handling
   async createSession(): Promise<string> {
     if (this.agentState !== "running" || !this.client) {
       throw new Error("OpencodeAgentClient: not running");
     }

     const session = await this.client.session.create({
       body: { title: "Konveyor Migration Assistant" },
     });

+    if (session.error) {
+      throw new Error(
+        `Session creation failed: ${typeof session.error === "string" ? session.error : JSON.stringify(session.error)}`,
+      );
+    }
+
-    this.sessionId = session.data.id;
+    const sessionData = session.data ?? session.response?.body;
+    const sessionId = sessionData?.id ?? sessionData?.ID;
+    if (!sessionId) {
+      throw new Error("Session response missing id");
+    }
+    this.sessionId = sessionId;
     this.logger.info(`OpencodeAgentClient: new session created ${this.sessionId}`);
     return this.sessionId!;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/client/opencodeClient.ts` around lines 430 - 434, The
createSession method currently uses session.data.id without any null checks or
error handling; wrap the SDK call that produces session in a try/catch, validate
session and session.data.id before assigning to this.sessionId, and on failure
log the full error via this.logger.error (include error details and context
"createSession") and either throw a descriptive error or return a
rejected/undefined result consistent with start(); update references to
this.sessionId only after the null check so you never set it from an invalid
response.
vscode/core/src/features/agent/handlers.ts-334-335 (1)

334-335: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fragile acceptance detection based on string matching.

Using optionId.includes("allow") to determine acceptance is brittle. If option IDs change or new kinds are added, this logic could break silently. Consider using the kind property from the PermissionOption if available, or document this naming convention requirement.

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

In `@vscode/core/src/features/agent/handlers.ts` around lines 334 - 335, The
current acceptance check uses a brittle string match (const accepted =
optionId.includes("allow")), so update the logic to use the explicit
PermissionOption.kind (or a similarly named enum/property) instead: locate where
optionId and the option object are available in the handler (symbols: optionId,
accepted, PermissionOption) and replace the includes-based test with a strict
check against option.kind (e.g., option.kind === "allow" or the corresponding
enum value); if kind is not present, add/read a clear property on the
PermissionOption shape and fall back to a documented, strict naming rule rather
than a substring search.
vscode/core/src/features/agent/handlers.ts-174-178 (1)

174-178: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Agent restart lacks graceful error handling.

If stop() throws, start() is never called, leaving the agent in a potentially inconsistent state. Consider handling failures in the restart cycle more gracefully.

🛡️ Suggested improvement
       const agentClient = getAgentClient(state);
       if (agentClient) {
-        await agentClient.stop();
-        await agentClient.start();
+        try {
+          await agentClient.stop();
+        } catch (stopErr) {
+          logger.warn("Failed to stop agent during config update, proceeding with start:", stopErr);
+        }
+        await agentClient.start();
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/handlers.ts` around lines 174 - 178, The
restart sequence for the agent (getAgentClient -> agentClient.stop() ->
agentClient.start()) needs robust error handling so a thrown error from stop()
doesn't prevent start() from running; wrap the stop() call in a try/catch (or
use Promise.catch) to log the error from agentClient.stop() and continue to call
agentClient.start(), and optionally handle/rethrow start() errors separately so
the agent is left in a consistent state and failures are observable via logging
from the same getAgentClient/agentClient context.
🧹 Nitpick comments (22)
webview-ui/src/components/ResolutionsPage/MessageWrapper.css (1)

11-13: 💤 Low value

Consider removing the redundant last-child rule.

The .message-wrapper:last-child rule sets margin-bottom: 4px, but this is already defined by the default .message-wrapper margin (margin: 4px 0). Since both specify the same bottom margin value, the last-child rule has no effect and can be removed for cleaner CSS.

♻️ Proposed cleanup
-.message-wrapper:last-child {
-  margin-bottom: 4px;
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ResolutionsPage/MessageWrapper.css` around lines 11
- 13, Remove the redundant CSS rule `.message-wrapper:last-child {
margin-bottom: 4px; }` because the base `.message-wrapper { margin: 4px 0; }`
already applies the same bottom margin; delete the `.message-wrapper:last-child`
selector from MessageWrapper.css to clean up duplicate styles and leave only the
default `.message-wrapper` rule.
webview-ui/src/components/ChatPage/ChatPage.css (1)

5-5: 💤 Low value

Container name should use kebab-case per Stylelint convention.

The container name chatPage should be chat-page to follow kebab-case naming conventions. This affects the definition here and all @container queries referencing it (lines 1185, 1200, 1264).

♻️ Proposed fix
-.chat-page-container {
-  container-name: chatPage;
+.chat-page-container {
+  container-name: chat-page;

And update all @container chatPage to @container chat-page.

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

In `@webview-ui/src/components/ChatPage/ChatPage.css` at line 5, Change the CSS
container name from camelCase to kebab-case: replace the container-name value
"chatPage" with "chat-page" in the ChatPage CSS (the declaration using
container-name: chatPage;) and update every corresponding `@container` rule that
references "chatPage" to use "@container chat-page" (there are multiple
`@container` queries that must be updated).
vscode/core/src/utilities/profiles/profileActions.ts (1)

41-55: ⚡ Quick win

Make the profile update and config revalidation atomic.

These two state.mutate(...) calls represent one logical change. Emitting them separately lets subscribers observe the new profile with stale configErrors and causes an extra state-sync pass.

♻️ Suggested consolidation
-  state.mutate((draft) => {
-    const target = draft.profiles.find((p) => p.id === updated.id);
-    if (target) {
-      Object.assign(target, updated);
-    }
-  });
-
-  // Re-validate config errors to clear "no custom rules" error if applicable
   state.mutate((draft) => {
+    const target = draft.profiles.find((p) => p.id === updated.id);
+    if (target) {
+      Object.assign(target, updated);
+    }
+
+    // Re-validate config errors to clear "no custom rules" error if applicable
     // Clear existing profile-related errors and re-validate using centralized logic
     draft.configErrors = draft.configErrors.filter(
       (error) => error.type !== "invalid-label-selector" && error.type !== "no-custom-rules",
     );
     updateConfigErrors(draft, "");
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/utilities/profiles/profileActions.ts` around lines 41 - 55,
The two separate state.mutate calls cause intermediate state exposure;
consolidate them into one atomic mutation: inside a single state.mutate
callback, find and Object.assign the matching profile in draft.profiles (same
logic as current), then clear profile-related errors from draft.configErrors
(filter out "invalid-label-selector" and "no-custom-rules") and call
updateConfigErrors(draft, "") so both the profile update and revalidation happen
in the same transaction.
webview-ui/src/components/ResolutionsPage/ModifiedFile/useModifiedFileData.ts (1)

27-41: 💤 Low value

Edge case: file:/// prefix stripping may leave leading slash on Windows.

When workspaceRoot is "file:///C:/project", stripping "file://" (7 chars) leaves "/C:/project". The normalizedPath (e.g., "C:/project/src/file.ts") won't start with "/C:/project", causing the fallback to extract just the filename instead of the relative path.

The fallback behavior is acceptable for display purposes, but if accurate relative paths on Windows are needed, consider stripping three slashes for the file:/// case:

Suggested adjustment
       if (root.startsWith("file:///")) {
-        root = root.slice("file://".length);
+        root = root.slice("file:///".length);
       } else if (root.startsWith("file:")) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@webview-ui/src/components/ResolutionsPage/ModifiedFile/useModifiedFileData.ts`
around lines 27 - 41, The current logic slices the wrong prefix for file URIs on
Windows (it uses "file://".length), which leaves a leading slash (e.g.,
"/C:/project") so normalizedPath ("C:/project/...") won't match; update the
handling in useModifiedFileData.ts: when detecting "file:///" slice the correct
length (or use "file:///" explicitly) and then remove a single leading "/" only
when it precedes a Windows drive letter (e.g., if root startsWith("/") and
root[1] matches /[A-Za-z]:/), so workspaceRoot/root and normalizedPath compare
correctly; keep the existing fallbacks for fileName if matching fails.
vscode/core/src/features/agent/toolPermissionPolicy.ts (1)

5-14: 💤 Low value

Consider whether undo_edit should be classified as read-only.

undo_edit modifies the file system by reverting changes, which isn't strictly "read-only" in the traditional sense. If the intent is to auto-approve safe operations that don't introduce new content, this is acceptable, but the comment/JSDoc could clarify the semantics (e.g., "safe to auto-approve" vs "read-only").

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

In `@vscode/core/src/features/agent/toolPermissionPolicy.ts` around lines 5 - 14,
The current code classifies "undo_edit" as read-only via READ_ONLY_COMMANDS and
isReadOnlyToolCall, but undo_edit actually mutates files; update the
code/comments to reflect intended semantics: either remove "undo_edit" from
READ_ONLY_COMMANDS if you want a strict read-only check, or change the JSDoc and
symbol names (e.g., rename READ_ONLY_COMMANDS to SAFE_AUTO_APPROVE_COMMANDS and
update the comment on isReadOnlyToolCall to describe "safe to
auto-approve/reversible" operations) and document why undo_edit is considered
safe; adjust any callers/tests that rely on isReadOnlyToolCall accordingly.
webview-ui/src/components/ChatPage/CompactModifiedFile.tsx (1)

7-17: 💤 Low value

Unused timestamp prop.

The timestamp prop is declared in CompactModifiedFileProps but is not destructured or used in the component implementation.

♻️ Remove unused prop or use it

If timestamp is intended for future use, consider removing it until needed:

 interface CompactModifiedFileProps {
   data: ModifiedFileMessageValue;
-  timestamp?: string;
 }

Or if it should be displayed, add it to the component.

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

In `@webview-ui/src/components/ChatPage/CompactModifiedFile.tsx` around lines 7 -
17, CompactModifiedFileProps declares a timestamp?: string that
CompactModifiedFile (React.memo component) does not use; either remove timestamp
from CompactModifiedFileProps if not needed, or destructure and render it inside
the CompactModifiedFile component (e.g., add timestamp to the props in the
CompactModifiedFile signature and display it where the filename/status are
shown) and update any callers accordingly to pass a timestamp when required.
webview-ui/src/components/ChatPage/AgentFileReview.tsx (1)

100-101: 💤 Low value

Redundant ternary expression.

Both branches return line.content, making the ternary unnecessary.

♻️ Simplify redundant ternary
             <span className="afr-diff__content">
-              {line.type === "hunk-header" ? line.content : line.content}
+              {line.content}
             </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ChatPage/AgentFileReview.tsx` around lines 100 -
101, The JSX contains a redundant ternary in the span with className
"afr-diff__content" where both branches return line.content; remove the ternary
and render the value directly (replace the expression {line.type ===
"hunk-header" ? line.content : line.content} with a single {line.content}) in
the AgentFileReview component to simplify the markup.
mcp-server/src/index.ts (1)

38-44: 💤 Low value

Consider handling non-JSON error responses gracefully.

If the bridge returns a non-OK response, you extract the text for the error message (line 40), which is good. However, if the response is OK but not valid JSON (e.g., empty body or malformed), response.json() will throw. This edge case may be unlikely given you control the bridge, but a defensive check would improve resilience.

🛡️ Optional defensive handling
   if (!response.ok) {
     const text = await response.text();
     throw new Error(`Bridge request failed: ${response.status} ${text}`);
   }

-  return response.json();
+  const text = await response.text();
+  if (!text) {
+    return {};
+  }
+  return JSON.parse(text);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcp-server/src/index.ts` around lines 38 - 44, The current code calls
response.json() without guarding against empty or malformed JSON; wrap the final
return in a defensive block: check response.headers.get('content-type') for
'application/json' and, if missing, call await response.text() and handle an
empty body (return null or throw a clear Error), otherwise attempt JSON.parse on
the text inside a try/catch and rethrow a descriptive error (including the raw
body) if parsing fails; update the code around response.json(), response.text(),
and the existing throw new Error(...) so callers get a clear, handled outcome
for non-JSON or empty responses.
webview-ui/src/components/ChatPage/AgentSettings.tsx (2)

266-273: 💤 Low value

Optimistic store update before server confirmation.

The experimental chat toggle updates the local store immediately before posting the message to VS Code. If the extension fails to persist this setting, the UI state will be out of sync with the actual configuration.

This is a common optimistic update pattern, but consider whether the setting should only update after confirmation from the extension (via a state change message).

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

In `@webview-ui/src/components/ChatPage/AgentSettings.tsx` around lines 266 - 273,
The toggle currently flips experimentalChatEnabled optimistically by calling
useExtensionStore.getState().setExperimentalChatEnabled(...) before sending
window.vscode.postMessage({ type: SET_EXPERIMENTAL_CHAT, ... }), which can leave
UI out-of-sync if the extension fails to persist the setting; change the flow so
the click posts the SET_EXPERIMENTAL_CHAT message first (using
window.vscode.postMessage) and only update the local store (via
useExtensionStore.getState().setExperimentalChatEnabled) when you receive the
extension's confirmation message/event (or revert the store on an error
response), and ensure the component listens for that confirmation to update
experimentalChatEnabled accordingly.

103-108: 💤 Low value

hasChanges may incorrectly return undefined instead of boolean.

The .some() on the last line can return undefined if agentConfig?.capabilities is undefined (since undefined?.some() is undefined). This could cause the truthy check to behave correctly in practice, but the type would not be a strict boolean.

💡 Suggested fix for type safety
   const hasChanges =
     selectedProvider !== (agentConfig?.provider ?? "") ||
     modelInput !== (agentConfig?.model ?? "") ||
     agentModeEnabled !== (agentConfig?.agentMode ?? true) ||
     Object.values(credentialInputs).some((v) => v.length > 0) ||
-    agentConfig?.capabilities.some((ext) => extensionStates[ext.id] !== ext.enabled);
+    (agentConfig?.capabilities.some((ext) => extensionStates[ext.id] !== ext.enabled) ?? false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ChatPage/AgentSettings.tsx` around lines 103 - 108,
The hasChanges expression can become undefined because
agentConfig?.capabilities?.some(...) may be undefined; update the last clause to
ensure a boolean by using a safe default or nullish coalescing so it evaluates
to false when capabilities is missing (e.g., replace
agentConfig?.capabilities.some(...) with agentConfig?.capabilities?.some(...) ??
false or default agentConfig?.capabilities to [] before calling some), keeping
references to hasChanges, agentConfig, extensionStates, and extension id checks
unchanged.
webview-ui/src/components/ChatPage/ChatPage.tsx (2)

185-194: 💤 Low value

Consider guarding against undefined messageToken.

When building the tool group key, toolBuffer[0].messageToken is used. While flushTools only runs when toolBuffer.length > 0, if a ChatMessage in the buffer has an undefined messageToken, the key would be undefined.

💡 Suggested improvement
     const flushTools = () => {
       if (toolBuffer.length > 0) {
         items.push({
           type: "tool-group",
           tools: toolBuffer,
-          key: toolBuffer[0].messageToken,
+          key: toolBuffer[0].messageToken ?? `tool-group-${items.length}`,
         });
         toolBuffer = [];
       }
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ChatPage/ChatPage.tsx` around lines 185 - 194,
flushTools currently uses toolBuffer[0].messageToken directly for the group key
which can be undefined; change the key construction in flushTools to guard
against undefined by selecting the first defined messageToken in toolBuffer
(e.g., find a message with a non-undefined messageToken) or falling back to a
stable alternative (such as a message id property, index-based token, or a
generated unique string) so the key is never undefined; update references to
toolBuffer, items, and the key generation inside the flushTools function
accordingly and ensure the chosen fallback yields a unique, repeatable key for
the group.

45-84: ⚖️ Poor tradeoff

Consider consolidating store subscriptions.

The component has 18 individual useExtensionStore calls. While Zustand optimizes re-renders per selector, consolidating related state into fewer selectors or a single selector returning an object could improve readability.

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

In `@webview-ui/src/components/ChatPage/ChatPage.tsx` around lines 45 - 84, The
ChatPage component makes many separate useExtensionStore calls (e.g.,
experimentalChatEnabled, agentState, agentError, agentConfig, agentMessages,
configErrors, modelSupportsTools, chatMessages, solutionScope,
isFetchingSolution, isAnalyzing, analysisProgressMessage, enhancedIncidents,
ruleSets, analysisProgress, isWaitingForUserInteraction), so consolidate related
subscriptions by replacing multiple calls with one or a few selectors that
return grouped objects (e.g., useExtensionStore(s => ({ experimentalChatEnabled:
s.experimentalChatEnabled, agentState: s.agentState, agentMessages:
s.agentMessages, chatMessages: s.chatMessages, ... }))) and use shallow
comparison (import shallow from 'zustand/shallow') when selecting objects to
avoid unnecessary re-renders; then update references in ChatPage (isProcessing,
hasWorkflowContent, hasPendingPermission, isAgentStreaming, isTriggeredByUser,
incidentCount, etc.) to use the properties off the grouped selector. Ensure you
keep derived values (like isProcessing = isFetchingSolution || isAnalyzing)
computed from the grouped result so behavior is unchanged.
vscode/core/src/client/directLLMClient.ts (2)

75-77: 💤 Low value

getState() always returns "running" regardless of actual state.

This could be misleading if callers expect accurate state reporting. Consider tracking the actual state based on lifecycle events.

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

In `@vscode/core/src/client/directLLMClient.ts` around lines 75 - 77, getState()
currently returns the hardcoded string "running" which misrepresents the real
lifecycle; add a real state field (e.g., a private this.state or this._state
typed as AgentState) in the DirectLLMClient class, set it to appropriate values
during lifecycle methods (start/stop/initialize/terminate or any methods that
change status), and make getState() return that field instead of the constant so
callers receive the actual state.

107-131: 💤 Low value

sendMessage ignores _content and _responseMessageId parameters.

The method signature accepts content and responseMessageId but neither is used. The workflow runs with the pre-configured workflowInput regardless of what message is sent. If this is intentional (the DirectLLMClient is a fire-once wrapper), consider documenting this behavior or removing the unused parameters.

📝 Suggested documentation
   // ─── Core: run the workflow ────────────────────────────────────────

+  /**
+   * Runs the pre-configured workflow. The content and responseMessageId
+   * parameters are ignored as DirectLLMClient is designed as a single-use
+   * wrapper that executes the workflow with its initial workflowInput.
+   */
   async sendMessage(_content: string, _responseMessageId: string): Promise<string> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/client/directLLMClient.ts` around lines 107 - 131,
sendMessage currently ignores its parameters _content and _responseMessageId and
always runs this.workflow with the preconfigured this.workflowInput; either wire
those parameters into the workflow input or remove/document them: before calling
this.workflow.run(this.workflowInput) assign the incoming values into the
appropriate fields on this.workflowInput (e.g., set this.workflowInput.message
or this.workflowInput.content = _content and
this.workflowInput.responseMessageId = _responseMessageId) so the runtime
message is used, or if the client is intentionally fire-once, remove the unused
parameters from the sendMessage signature (or add a clear JSDoc comment) and
update any call sites accordingly; keep promptActive handling and error logging
unchanged.
vscode/core/src/agentConfigReader.ts (2)

66-78: 💤 Low value

Consider logging config read failures for debugging.

The silent catch block swallows all errors, which could hide issues like permission errors or malformed YAML that users would want to know about.

💡 Suggested improvement
   try {
     const { fsPaths } = require("./paths");
     const content = fs.readFileSync(fsPaths().settingsYaml, "utf-8");
     const doc = parse(content) as Record<string, any> | undefined;
     const active = doc?.active;
     if (active) {
       const langchainName = typeof active.provider === "string" ? active.provider : "";
       provider = langchainProviderToUiId(langchainName, active.args) ?? langchainName;
       model = typeof active.args?.model === "string" ? active.args.model : "";
     }
-  } catch {
+  } catch (err) {
     // provider-settings.yaml missing or malformed — use defaults
+    // Optionally log for debugging: console.debug("OpenCode config read error:", err);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/agentConfigReader.ts` around lines 66 - 78, The try/catch in
agentConfigReader.ts silently swallows all errors when reading/parsing
fsPaths().settingsYaml, making diagnosis impossible; update the catch to log the
error (including the error object/message and context like settingsYaml path)
using the project's logging mechanism or console (e.g., reference
fsPaths().settingsYaml, the parse step and langchainProviderToUiId) so
permission issues or YAML parse errors are visible, while still falling back to
defaults for provider/model.

50-57: 💤 Low value

Consider handling undefined workspaceRoot more defensively.

When workspaceRoot is undefined, getAgentConfigPath returns "./opencode.json" which is relative to the current working directory, potentially not the intended location.

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

In `@vscode/core/src/agentConfigReader.ts` around lines 50 - 57,
getAgentConfigPath currently falls back to "./opencode.json" when workspaceRoot
is undefined, which can produce an unintended relative path; update
getAgentConfigPath (handling the "opencode" case) to defensively resolve a
concrete absolute directory instead of relying on "./" by using
path.resolve(workspaceRoot ?? process.cwd()) when building the opencode.json
path (or alternatively throw/return an explicit error if workspaceRoot must be
provided), so the returned path is deterministic and not relative to the current
working directory.
vscode/core/src/extension.ts (1)

1519-1523: ⚡ Quick win

Build the first-run globalState key from EXTENSION_NAME.

This hard-codes the branded prefix into persisted state, which makes downstream rebranding carry the old key forever. Please derive the key from EXTENSION_NAME instead of embedding "konveyor" here.

Based on learnings, extension names should be programmatic rather than hard-coded to support downstream rebranding efforts.

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

In `@vscode/core/src/extension.ts` around lines 1519 - 1523, The persisted
globalState key currently hard-codes "konveyor.hasShownChatView"; change both
uses of that literal to derive the key from EXTENSION_NAME (e.g., build a key
string using EXTENSION_NAME + ".hasShownChatView") so get and update call
context.globalState.get<boolean>(generatedKey) /
context.globalState.update(generatedKey, true) and keep the
vscode.commands.executeCommand call unchanged; update references near
hasShownChat, context.globalState.get, and context.globalState.update to use the
computed key.
vscode/core/src/features/agent/toolPermissionHandler.ts (2)

110-112: ⚡ Quick win

Consider using static import for the diff module.

Using require("diff") inside the function body means the module is loaded on each call. Since diff is already a dependency (see package.json line 541), a static import at the top of the file would be cleaner and more efficient.

♻️ Proposed refactor

At the top of the file:

 import { v4 as uuidv4 } from "uuid";
+import { createPatch } from "diff";
 import type { ExtensionData, ChatMessage, ToolMessageValue } from "@editor-extensions/shared";

Then replace the dynamic requires:

   if (typeof oldStr === "string" && typeof newStr === "string") {
-    const { createPatch } = require("diff");
     const displayPath = extractDisplayPath(input, workspaceRoot) ?? "file";
     const patch = createPatch(displayPath, oldStr, newStr, "", "", { context: 3 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/toolPermissionHandler.ts` around lines 110 -
112, The code dynamically requires the diff module inside the function
(createPatch via require("diff")), causing repeated loads; change this to a
static top-level import by adding an import { createPatch } from "diff" at the
top of the file and remove the require("diff") call in the body where
displayPath is computed (symbols to update: createPatch, extractDisplayPath,
displayPath, workspaceRoot). Ensure the import uses ES module/TypeScript syntax
consistent with the file and remove the inline require to avoid per-call module
resolution overhead.

226-231: 💤 Low value

Single replace() only substitutes the first occurrence.

If oldStr appears multiple times in originalContent, String.replace(oldStr, newStr) only replaces the first match. This may not reflect the actual file state after the tool executes.

💡 Consider using replaceAll or regex with global flag
     if (typeof oldStr === "string" && typeof newStr === "string" && originalContent) {
-      rawFileContent = originalContent.replace(oldStr, newStr);
+      rawFileContent = originalContent.replaceAll(oldStr, newStr);
     }

Alternatively, if the tool semantics are to replace only the first occurrence, this is correct as-is.

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

In `@vscode/core/src/features/agent/toolPermissionHandler.ts` around lines 226 -
231, The current replacement in toolPermissionHandler (when
oldStr/newStr/originalContent are strings) only replaces the first occurrence;
if you intend to replace all occurrences update the branch that sets
rawFileContent to use a global replace (either
originalContent.replaceAll(oldStr, newStr) or originalContent.replace(new
RegExp(escapeRegExp(oldStr), "g"), newStr)); add a small escapeRegExp helper to
safely escape special regex chars if using RegExp, and ensure rawFileContent is
set from that global-replace result instead of String.replace.
vscode/core/package.json (1)

381-396: 💤 Low value

Duplicate order value for configuration properties.

Both konveyor-core.genai.batchReviewMode (line 386) and konveyor-core.genai.excludedDiagnosticSources (line 395) have "order": 22. This may cause inconsistent ordering in the VS Code settings UI.

🔧 Proposed fix
         "konveyor-core.genai.batchReviewMode": {
           "type": "boolean",
           "default": false,
           "description": "When enabled, file changes from the agent are queued for review instead of being applied immediately. Allows accepting or rejecting changes in bulk.",
           "scope": "window",
-          "order": 22
+          "order": 23
         },
         "konveyor-core.genai.excludedDiagnosticSources": {
           "type": "array",
           "default": [
             "trunk"
           ],
           "description": "List of diagnostic sources to exclude from agentic workflow.",
           "scope": "window",
-          "order": 22
+          "order": 24
         },

Note: This would require updating subsequent order values as well.

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

In `@vscode/core/package.json` around lines 381 - 396, The two settings keys
konveyor-core.genai.batchReviewMode and
konveyor-core.genai.excludedDiagnosticSources both use the same "order": 22
which causes ambiguous ordering in the VS Code settings UI; update
konveyor-core.genai.excludedDiagnosticSources to a unique, incremented order
(e.g., 23) and then adjust any subsequent "order" values for later settings to
maintain a monotonically increasing sequence so the UI displays them
consistently.
shared/src/types/messages.ts (1)

125-141: 💤 Low value

Type guards use unsafe as any casts.

The type guards cast msg to any before accessing .type. Since WebviewMessage is a discriminated union, you can safely check the type property directly without casting.

♻️ Safer type guard pattern
 export function isStateChange(msg: WebviewMessage): msg is StateChangeMessage {
-  return (msg as any).type === MessageTypes.STATE_CHANGE;
+  return msg.type === MessageTypes.STATE_CHANGE;
 }

 export function isFocusViolation(msg: WebviewMessage): msg is FocusViolationMessage {
-  return (msg as any).type === MessageTypes.FOCUS_VIOLATION;
+  return msg.type === MessageTypes.FOCUS_VIOLATION;
 }

 // Chat
 export function isChatStateChange(msg: WebviewMessage): msg is ChatStateChangeMessage {
-  return (msg as any).type === MessageTypes.CHAT_STATE_CHANGE;
+  return msg.type === MessageTypes.CHAT_STATE_CHANGE;
 }

 export function isChatStreamingUpdate(msg: WebviewMessage): msg is ChatStreamingUpdateMessage {
-  return (msg as any).type === MessageTypes.CHAT_STREAMING_UPDATE;
+  return msg.type === MessageTypes.CHAT_STREAMING_UPDATE;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shared/src/types/messages.ts` around lines 125 - 141, The type guards
(isStateChange, isFocusViolation, isChatStateChange, isChatStreamingUpdate)
currently use unsafe "as any" casts; remove those casts and access the
discriminant directly (e.g., use msg.type === MessageTypes.STATE_CHANGE). To be
defensive, first ensure msg is non-null and an object (e.g., typeof msg ===
"object" && msg !== null) before checking msg.type, then compare against the
appropriate MessageTypes constant in each guard.
vscode/core/src/features/agent/handlers.ts (1)

307-309: 💤 Low value

Silent catch swallows all errors.

The empty catch block silently ignores any failures during webview initialization. While "best-effort" is intended, consider logging at debug level to aid troubleshooting.

♻️ Add debug logging
-    } catch {
-      // Best-effort — agent may not be configured yet
-    }
+    } catch (err) {
+      // Best-effort — agent may not be configured yet
+      logger.debug("AGENT_WEBVIEW_READY: best-effort config send failed:", err);
+    }

Note: This requires changing _logger parameter to logger on line 281.

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

In `@vscode/core/src/features/agent/handlers.ts` around lines 307 - 309, The empty
catch in the webview initialization swallows errors; update the function
signature to rename the parameter from _logger to logger (as noted) and change
the catch to capture the error (catch (err)) and call logger.debug with a
concise message and the error to preserve "best-effort" behavior while enabling
troubleshooting—locate the try/catch around the webview init in handlers.ts and
the function that declares _logger (near line 281) and replace the silent catch
with logger.debug("Failed to initialize agent webview (best-effort)", err).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6d1b0702-376c-4722-89d6-61e041af03f1

📥 Commits

Reviewing files that changed from the base of the PR and between 3d97606 and d25ed92.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (106)
  • .husky/pre-commit
  • agentic/src/cache.ts
  • agentic/src/nodes/analysisIssueFix.ts
  • agentic/src/nodes/base.ts
  • agentic/src/nodes/diagnosticsIssueFix.ts
  • agentic/src/types.ts
  • changes/unreleased/1351-pluggable-agent-backend.yaml
  • changes/unreleased/fix-duplicate-loading-indicators.yaml
  • docs/goose-acp-messaging-guide.md
  • mcp-server/package.json
  • mcp-server/src/index.ts
  • mcp-server/tsconfig.json
  • package.json
  • scripts/test-goose-streaming.sh
  • shared/src/types/actions.ts
  • shared/src/types/agent.ts
  • shared/src/types/agentActions.ts
  • shared/src/types/agentMessages.ts
  • shared/src/types/index.ts
  • shared/src/types/messages.ts
  • shared/src/types/types.ts
  • vscode/core/CONTRIBUTING-FEATURES.md
  • vscode/core/package.json
  • vscode/core/resources/sample-provider-settings.yaml
  • vscode/core/src/KonveyorGUIWebviewViewProvider.ts
  • vscode/core/src/__tests__/mocks.ts
  • vscode/core/src/agentConfigReader.ts
  • vscode/core/src/analysis/__tests__/batchedAnalysisTrigger.test.ts
  • vscode/core/src/analysis/batchedAnalysisTrigger.ts
  • vscode/core/src/analysis/runAnalysis.ts
  • vscode/core/src/api/mcpBridgeServer.ts
  • vscode/core/src/client/agentClient.ts
  • vscode/core/src/client/analyzerClient.ts
  • vscode/core/src/client/directLLMClient.ts
  • vscode/core/src/client/gooseClient.ts
  • vscode/core/src/client/opencodeClient.ts
  • vscode/core/src/clients/ProfileSyncClient.ts
  • vscode/core/src/commands.ts
  • vscode/core/src/data/loadResults.ts
  • vscode/core/src/diff/vertical/manager.ts
  • vscode/core/src/extension.ts
  • vscode/core/src/extensionState.ts
  • vscode/core/src/features/agent/__tests__/toolPermissionHandler.test.ts
  • vscode/core/src/features/agent/agentOrchestrator.ts
  • vscode/core/src/features/agent/batchReviewHandlers.ts
  • vscode/core/src/features/agent/fileChangeRouter.ts
  • vscode/core/src/features/agent/fileTracker.ts
  • vscode/core/src/features/agent/handlers.ts
  • vscode/core/src/features/agent/index.ts
  • vscode/core/src/features/agent/init.ts
  • vscode/core/src/features/agent/promptBuilder.ts
  • vscode/core/src/features/agent/toolPermissionHandler.ts
  • vscode/core/src/features/agent/toolPermissionPolicy.ts
  • vscode/core/src/features/featureRegistry.ts
  • vscode/core/src/features/index.ts
  • vscode/core/src/gooseConfig.ts
  • vscode/core/src/modelProvider/config.ts
  • vscode/core/src/modelProvider/providerConfigGenerator.ts
  • vscode/core/src/paths.ts
  • vscode/core/src/solutionWorkflowOrchestrator.ts
  • vscode/core/src/store/extensionStore.ts
  • vscode/core/src/store/syncBridges.ts
  • vscode/core/src/utilities/ModifiedFiles/handleFileResponse.ts
  • vscode/core/src/utilities/ModifiedFiles/handleModifiedFile.ts
  • vscode/core/src/utilities/ModifiedFiles/handleQuickResponse.ts
  • vscode/core/src/utilities/ModifiedFiles/processMessage.ts
  • vscode/core/src/utilities/ModifiedFiles/processModifiedFile.ts
  • vscode/core/src/utilities/ModifiedFiles/queueManager.ts
  • vscode/core/src/utilities/agentCredentialStorage.ts
  • vscode/core/src/utilities/configuration.ts
  • vscode/core/src/utilities/gooseCredentialStorage.ts
  • vscode/core/src/utilities/profiles/profileActions.ts
  • vscode/core/src/utilities/profiles/profileService.ts
  • vscode/core/src/webviewMessageHandler.ts
  • webview-ui/package.json
  • webview-ui/src/App.tsx
  • webview-ui/src/components/AnalysisPage/AnalysisPage.tsx
  • webview-ui/src/components/ChatPage/AgentFileReview.css
  • webview-ui/src/components/ChatPage/AgentFileReview.tsx
  • webview-ui/src/components/ChatPage/AgentSettings.tsx
  • webview-ui/src/components/ChatPage/ChatPage.css
  • webview-ui/src/components/ChatPage/ChatPage.tsx
  • webview-ui/src/components/ChatPage/CompactBatchReview.css
  • webview-ui/src/components/ChatPage/CompactBatchReview.tsx
  • webview-ui/src/components/ChatPage/CompactMigrationScope.tsx
  • webview-ui/src/components/ChatPage/CompactModifiedFile.css
  • webview-ui/src/components/ChatPage/CompactModifiedFile.tsx
  • webview-ui/src/components/ChatPage/PermissionReviewMessage.css
  • webview-ui/src/components/ChatPage/PermissionReviewMessage.tsx
  • webview-ui/src/components/ChatPage/ResourceBlock.tsx
  • webview-ui/src/components/ChatPage/ResourceLink.tsx
  • webview-ui/src/components/ChatPage/ThinkingIndicator.tsx
  • webview-ui/src/components/ChatPage/providerOptions.ts
  • webview-ui/src/components/ProfileManager/ProfileList.tsx
  • webview-ui/src/components/ResolutionsPage/MessageWrapper.css
  • webview-ui/src/components/ResolutionsPage/ModifiedFile/useModifiedFileData.ts
  • webview-ui/src/components/ResolutionsPage/ReceivedMessage.tsx
  • webview-ui/src/components/ResolutionsPage/ResolutionsPage.tsx
  • webview-ui/src/components/ResolutionsPage/ToolMessage.tsx
  • webview-ui/src/components/ResolutionsPage/toolMessage.css
  • webview-ui/src/components/ViolationIncidentsList.tsx
  • webview-ui/src/hooks/actions.ts
  • webview-ui/src/hooks/useContainerWidth.ts
  • webview-ui/src/hooks/useScrollManagement.ts
  • webview-ui/src/hooks/useVSCodeMessageHandler.ts
  • webview-ui/src/store/store.ts
💤 Files with no reviewable changes (5)
  • vscode/core/src/tests/mocks.ts
  • webview-ui/src/components/ResolutionsPage/ResolutionsPage.tsx
  • webview-ui/src/hooks/actions.ts
  • webview-ui/src/components/AnalysisPage/AnalysisPage.tsx
  • vscode/core/src/solutionWorkflowOrchestrator.ts

Comment thread agentic/src/nodes/base.ts Outdated
Comment thread shared/src/types/actions.ts
Comment thread vscode/core/src/api/mcpBridgeServer.ts
Comment thread vscode/core/src/api/mcpBridgeServer.ts
Comment thread vscode/core/src/client/opencodeClient.ts Outdated
Comment thread vscode/core/src/modelProvider/providerConfigGenerator.ts
Comment thread vscode/core/src/utilities/ModifiedFiles/handleFileResponse.ts
Comment on lines +7 to +12
export interface ProviderOption {
id: string;
name: string;
envVars: ProviderEnvVar[];
commonModels: string[];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This schema cannot produce a valid Azure configuration.

AgentSettings is driven by envVars and commonModels, but Azure also needs non-env required args like azureOpenAIApiDeploymentName and azureOpenAIApiVersion from the shipped sample config. With the current shape, selecting Azure in the settings UI can only persist an incomplete active block, so provider initialization fails later.

Patch sketch
 export interface ProviderOption {
   id: string;
   name: string;
   envVars: ProviderEnvVar[];
   commonModels: string[];
+  args?: Array<{
+    key: string;
+    label: string;
+    required?: boolean;
+  }>;
 }
 ...
   {
     id: "azure",
     name: "Azure OpenAI",
     envVars: [
       { key: "AZURE_OPENAI_API_KEY", label: "API Key", isSecret: true },
       { key: "AZURE_OPENAI_ENDPOINT", label: "Endpoint" },
     ],
     commonModels: [],
+    args: [
+      { key: "azureOpenAIApiDeploymentName", label: "Deployment name", required: true },
+      { key: "azureOpenAIApiVersion", label: "API version", required: true },
+    ],
   },

The form layer then needs to render and serialize args alongside envVars.

Also applies to: 50-56

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

In `@webview-ui/src/components/ChatPage/providerOptions.ts` around lines 7 - 12,
The ProviderOption schema lacks a place to persist non-environment configuration
required by some providers (e.g. Azure), causing incomplete active configs and
initialization failures; update the ProviderOption interface used by the form
(and any AgentSettings serialization) to include a new args: { [key: string]:
string } (or a typed args array) so the UI can render and serialize
provider-specific fields like azureOpenAIApiDeploymentName and
azureOpenAIApiVersion alongside envVars and commonModels, and ensure the form
layer that builds AgentSettings reads/writes this args field when
selecting/saving a provider.

Comment thread webview-ui/src/components/ChatPage/ResourceLink.tsx
Comment thread webview-ui/src/components/ResolutionsPage/ReceivedMessage.tsx Outdated
ibolton336 added 15 commits May 1, 2026 15:15
Agentic package improvements and LLM reliability fixes. No runtime
behavior changes to the VS Code extension UI — safe to merge independently.

Removed collect-assets, provider, husky, go/java extension, and build
changes that overlap with konveyor#1379.

### What's included
- **Agentic package** — refactored base node with 5-min LLM timeout,
  improved caching, updated types (originalContent, result fields)
- **LLM reliability** — warn-level logging when LLM returns no response,
  timeout protection against hanging requests
- **Changelog** — added new fragments for pluggable agent backend and
  duplicate loading indicator fix

### Stack order
This is **PR 1 of 3** breaking up the feature/goose branch:
1. **→ Agentic refactoring & LLM improvements** (this PR)
2. Pluggable agent backend → depends on this
3. Chat panel UI → depends on konveyor#2

Signed-off-by: ibolton336 <ibolton@redhat.com>
- husky pre-commit: respect existing NVM_DIR with parameter expansion fallback
- base.ts: gate error emission on emitResponseChunks to avoid surfacing
  internal "thinking" operation errors to users

Signed-off-by: ibolton336 <ibolton@redhat.com>
Adds the complete pluggable agent backend infrastructure:

- Agent client interface with Goose and OpenCode implementations
- MCP bridge server for exposing analysis tools to agents
- Agent orchestrator with file tracking and batch review
- State management refactoring (StateChangeData pattern, extension store, sync bridges)
- Tool permission handler with policy-based access control
- Agent configuration and credential storage
- Shared agent types, actions, and message contracts
- Updated extension wiring for agent lifecycle management
- Modified file handler improvements for agent workflows
- Documentation and contributing guide

Signed-off-by: ibolton336 <ibolton@redhat.com>
Integrates the agent chat panel, permission review UI, streaming
message handler, and updated store/hooks to match the new
StateChangeData messaging pattern from shared/.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
The webview-ui eslint config does not include the react-hooks plugin,
so the disable comment causes a 'Definition for rule not found' error.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
Signed-off-by: Ian Bolton <ibolton@redhat.com>
Signed-off-by: Ian Bolton <ibolton@redhat.com>
Keeps originalContent and result fields, reverts KaiWorkflow
trailing comma formatting and removes verbose JSDoc comment.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
fix-duplicate-loading-indicators.yaml describes a separate bugfix
not included in this PR.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
The silly→warn logging upgrade is now in its own PR (konveyor#1391).
Reverting to main versions to keep this PR focused.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
- Remove goose-acp-messaging-guide.md (reference doc, not needed for merge)
- Remove CONTRIBUTING-FEATURES.md (can land separately)
- Remove test-goose-streaming.sh (test script, not needed for merge)
- Revert base.ts to main (LLM timeout extracted to PR konveyor#1392)

Signed-off-by: Ian Bolton <ibolton@redhat.com>
Signed-off-by: Ian Bolton <ibolton@redhat.com>
…yor#1393, konveyor#1394)

These changes are now in standalone PRs that should merge first:
- konveyor#1393 — scroll management refactor + useContainerWidth
- konveyor#1394 — ToolMessage UI refactor

Signed-off-by: Ian Bolton <ibolton@redhat.com>
MCP server package and bridge are now in their own PR.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
- CRITICAL: Pass bridge bearer token to MCP sidecar via KONVEYOR_BRIDGE_TOKEN env
- Add missing action types to WebviewActionType union (FILE_RESPONSE, BATCH_APPLY_ALL, etc.)
- Track and clean up injected env vars in OpencodeAgentClient (prevent process.env pollution)
- Fix fire-and-forget race in fileTracker.cacheFileBeforeWrite (track inflight reads)
- Use fileURLToPath() for all file: URI conversions (fixes Windows paths + encoded chars)
- Use existing fileUriToPath utility in handleFileResponse.ts
- Validate parsed YAML doc before dereferencing in config.ts
- Use semantic <button> for clickable file links in ResourceLink.tsx (accessibility)
- Restore rehype-sanitize in ReceivedMessage.tsx
- Fix nullish coalescing in ResourceBlock.tsx (|| → ??)
- Fix deprecated CSS word-break: break-word → overflow-wrap: break-word
- Fix useEffect dependency in AgentFileReview.tsx
- Remove redundant ternary in AgentFileReview.tsx
- Remove unsafe 'as any' casts in message type guards
- Add error handling for agent restart (stop() failure doesn't block start())
- Add debug logging in AGENT_WEBVIEW_READY catch block

Signed-off-by: Ian Bolton <ibolton@redhat.com>
Signed-off-by: ibolton336 <ibolton@redhat.com>
@ibolton336 ibolton336 force-pushed the feature/pluggable-agent-backend branch from 425d3f2 to a7673cf Compare May 1, 2026 19:16
- CRITICAL: Pass bridge bearer token to MCP sidecar env (init.ts)
- Add missing action types to WebviewActionType union (actions.ts)
- Track and clean up injected env keys in opencodeClient (env pollution)
- Use workflow.removeListener instead of removeAllListeners (orchestrator)
- Use fileURLToPath for cross-platform URI conversion (fileTracker, batchReviewHandlers)
- Track inflight reads to prevent cache race condition (fileTracker)
- Validate parsed YAML document before dereferencing (config.ts)
- Use existing fileUriToPath utility (handleFileResponse)
- Re-add rehypeSanitize for HTML sanitization (ReceivedMessage)
- Use nullish coalescing for empty string handling (ResourceBlock)
- Replace deprecated word-break: break-word (ChatPage.css)
- Fix useEffect boolean dependency (AgentFileReview)
- Remove redundant ternary expression (AgentFileReview)
- Use semantic button element for accessibility (ResourceLink)
- Remove unsafe 'as any' casts in type guards (messages.ts)
- Add graceful error handling for agent restart (handlers.ts)
- Use deep comparison for provider disambiguation (providerConfigGenerator)

Signed-off-by: Ian Bolton <ibolton@redhat.com>
@ibolton336 ibolton336 force-pushed the feature/pluggable-agent-backend branch from a7673cf to ffd34be Compare May 1, 2026 19:17
ibolton336 added a commit to ibolton336/editor-extensions that referenced this pull request May 1, 2026
ExtensionStore is introduced in PR konveyor#1389. Use a minimal McpBridgeStore
interface so this PR builds independently.

Signed-off-by: Ian Bolton <ibolton@redhat.com>
Signed-off-by: ibolton336 <ibolton@redhat.com>
Copy link
Copy Markdown
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.

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)
vscode/core/src/paths.ts (2)

444-452: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ignore out-of-workspace files before consulting ignore patterns.

relative() returns ..-prefixed paths for files outside workspaceRepo. Feeding those into isIgnoredBy() can return false, so partial analysis-on-save starts treating external files as workspace files again. Restore the out-of-workspace guard before the .konveyor and ignore-file checks.

Suggested fix
-import { relative, dirname, join } from "node:path";
+import { relative, dirname, join, isAbsolute } from "node:path";
@@
   const f = relative(fsPaths().workspaceRepo, uri.fsPath);
   _logger?.debug(`isUriIgnored: ${f}`);
+
+  if (f.startsWith("..") || isAbsolute(f)) {
+    return true;
+  }
 
   // Always ignore .konveyor directory
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/paths.ts` around lines 444 - 452, The relative path f
(computed via relative(fsPaths().workspaceRepo, uri.fsPath)) can be a
".."-prefixed path for files outside the workspace and must be treated as
ignored before running ignore-file logic; update the isUriIgnored flow so you
first check for out-of-workspace (e.g. f starts with ".." or is exactly "..")
and return true for those cases, then apply the existing .konveyor guard
(f.startsWith(".konveyor/") || f === ".konveyor") and finally call
isIgnoredBy(f); reference variables/functions: relative,
fsPaths().workspaceRepo, f, .konveyor check, and isIgnoredBy.

186-188: ⚠️ Potential issue | 🟠 Major

Wrap both activation-time fetch calls in AbortController timeouts.

Both fetch() calls at lines 188 and 226 currently run indefinitely during extension startup. On blackholed networks or unresponsive proxies, this stalls activation even though a fallback mechanism exists (user-configured analyzer path via analyzerPath setting). Apply the timeout + AbortController pattern already established in HubConnectionManager to both downloads.

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

In `@vscode/core/src/paths.ts` around lines 186 - 188, Wrap both activation-time
fetch calls (the one calling fetch(sha256sumUrl) that assigns sha256Response and
the other activation download fetch) with an AbortController timeout pattern
like used in HubConnectionManager: create an AbortController, pass
controller.signal to fetch, start a setTimeout to call controller.abort() after
the same timeout value used by HubConnectionManager, and clear the timeout when
the fetch completes; ensure you handle the abort/AbortError path so the code
falls back to the user-configured analyzerPath instead of stalling activation.
Use the existing symbols sha256Response / fetch(sha256sumUrl) and the other
activation fetch call to locate the two sites to apply this pattern.
webview-ui/src/hooks/useVSCodeMessageHandler.ts (1)

59-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear pending streaming writes before applying a full chat replacement.

If a throttled CHAT_STREAMING_UPDATE is queued and a CHAT_STATE_CHANGE arrives before the timer fires, the delayed merge will run against the new array and can overwrite finalized content or tool-status changes with stale data. Reset the timer and pendingStreamingUpdateRef before setChatMessages(...) on the structural path.

Also applies to: 105-123

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

In `@webview-ui/src/hooks/useVSCodeMessageHandler.ts` around lines 59 - 100, The
throttled CHAT_STREAMING_UPDATE merge can overwrite a subsequent
CHAT_STATE_CHANGE full replacement; before applying any full chat replacement
inside useVSCodeMessageHandler (the CHAT_STATE_CHANGE handling that calls
setChatMessages) clear throttleTimerRef.current and
pendingStreamingUpdateRef.current (and clearTimeout if needed) so any queued
streaming update is discarded; conversely, inside the streaming timer callback
(the setTimeout created when handling CHAT_STREAMING_UPDATE) clear the
pendingStreamingUpdateRef and throttleTimerRef before calling
latestStore.setChatMessages(...) so you don’t apply a stale merge after a
structural replace—update both code paths (the streaming timer and the
CHAT_STATE_CHANGE path) to reset these refs/timeouts prior to modifying chat
state.
♻️ Duplicate comments (2)
vscode/core/src/features/agent/fileTracker.ts (1)

270-304: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

scanForMissedChanges() still races in-flight snapshots.

doScan() waits for inflightReads, but the post-run scan does not. If a pre-write snapshot is still loading when this runs, that file never gets a stable baseline and the “missed changes” pass can skip it entirely.

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

In `@vscode/core/src/features/agent/fileTracker.ts` around lines 270 - 304,
scanForMissedChanges() can run while pre-write snapshots are still loading and
miss files; ensure it waits for any in-flight snapshot reads before comparing
cached originals to disk. Before iterating this.originalContentCache (and
checking this.routedFiles), await the pending promises tracked in the
inflightReads collection (e.g., Promise.all([...this.inflightReads.values()]) or
equivalent) so all snapshot loads complete and produce stable baselines; then
proceed to read disk, compare, log, and return missed changes.
vscode/core/src/features/agent/toolPermissionHandler.ts (1)

197-217: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Permission previews can race the pre-write snapshot.

cacheFileBeforeWrite() kicks off the snapshot asynchronously, but getOriginalContent() is called immediately afterward. When that read has not finished yet, this falls back to git/undefined instead of the working-tree baseline, so the user can approve against the wrong diff. This call path needs an awaitable snapshot step.

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

In `@vscode/core/src/features/agent/toolPermissionHandler.ts` around lines 197 -
217, The async snapshot kicked off by fileTracker.cacheFileBeforeWrite can still
be in-flight when getOriginalContent is called, causing incorrect diffs; change
the call site to await an ensured snapshot before reading: make
cacheFileBeforeWrite return a Promise that resolves when the pre-write snapshot
is complete (or add an awaitable method like
fileTracker.waitForCachedSnapshot/cacheFileSnapshotComplete), then replace the
fire-and-forget call with await fileTracker.cacheFileBeforeWrite(data.title,
data.rawInput, workspaceRoot, data.toolCallId) (or await the new waitFor... API)
so getOriginalContent(absPath, wsRoot) only runs after the snapshot finishes.
Ensure the change references the existing symbols
fileTracker.cacheFileBeforeWrite and fileTracker.getOriginalContent so the
snapshot is completed before originalContent is fetched.
🟠 Major comments (22)
vscode/core/src/diff/vertical/manager.ts-26-45 (1)

26-45: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Closing a diffed document now leaves stale diff state behind.

After this refactor the manager only listens for edits. If the user closes the editor while a vertical diff is open, nothing clears fileUriToHandler, fileUriToStreamId, activeDecorators, or the diff-visible context until some later manual cleanup path runs. Reintroduce tab/document-close cleanup here.

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

In `@vscode/core/src/diff/vertical/manager.ts` around lines 26 - 45, Register a
cleanup listener in the constructor (assign to userChangeListener) that listens
for the editor/document/tab close event for this.fileEditor and calls a new
cleanup routine which removes this file's entries from fileUriToHandler and
fileUriToStreamId, clears activeDecorators for the file, and updates the
diff-visible context (via mutate or the existing context updater) so no stale
diff state remains; ensure the listener is disposed in any teardown/dispose path
and that the cleanup routine is referenced by name from the constructor so the
behavior is applied immediately when a diffed document is closed.
vscode/core/src/features/agent/promptBuilder.ts-24-35 (1)

24-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The tool-mode instructions contradict each other.

Do not skip unchanged files conflicts with Only call write_file for files that need modifications. That ambiguity is likely to make the agent either rewrite every listed file or ignore the "complete file" requirement. Rephrase the first sentence to say not to omit unchanged portions of a file when write_file is used.

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

In `@vscode/core/src/features/agent/promptBuilder.ts` around lines 24 - 35, In
promptBuilder.ts where the "instructions" array is built (the variable set by
input.enableAgentMode), fix the contradictory sentences in the non-agent-mode
branch: replace the pair "Do not skip unchanged files. Only call write_file for
files that need modifications." with a single clear sentence that instructs the
agent to include the complete file contents (including unchanged portions) when
using write_file and to only invoke write_file for files that actually require
changes; keep the rest of the non-agent-mode instructions unchanged.
vscode/core/src/features/agent/promptBuilder.ts-103-110 (1)

103-110: ⚠️ Potential issue | 🟠 Major

Use fileURLToPath() instead of new URL(uri).pathname for file:// URI conversion.

On Windows, new URL(uri).pathname produces paths like /C:/Users/... with a leading slash, which causes fs.readFile(absPath) to fail and produces incorrect results in path.relative(). The codebase already uses fileURLToPath() in fileTracker.ts (same directory) and other utilities for this exact pattern. Import from "node:url" and apply the same approach.

Suggested fix
 import * as fs from "fs/promises";
 import * as path from "path";
+import { fileURLToPath } from "node:url";
 
 function uriToAbsolute(uri: string, workspaceDir: string): string {
   if (uri.startsWith("file://")) {
-    return new URL(uri).pathname;
+    return fileURLToPath(uri);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/promptBuilder.ts` around lines 103 - 110, The
uriToAbsolute function uses new URL(uri).pathname which yields incorrect Windows
paths; replace that with fileURLToPath from "node:url" to convert file:// URIs
correctly. Import fileURLToPath at the top, update uriToAbsolute to call
fileURLToPath(uri) when uri.startsWith("file://"), and keep the existing
path.isAbsolute and path.join fallback logic unchanged; reference function name
uriToAbsolute and ensure the import mirrors how fileURLToPath is used elsewhere
in the codebase (e.g., fileTracker.ts).
webview-ui/src/components/ChatPage/ChatPage.css-4-5 (1)

4-5: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the Stylelint errors in this file before merge.

container-name: chatPage and the matching @container chatPage usages violate the configured naming rules, and Stylelint also flags the missing blank lines before declarations in this block. Renaming the container to a kebab-case identifier and fixing the reported spacing issues will unblock lint.

Also applies to: 1114-1119, 1185-1264

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

In `@webview-ui/src/components/ChatPage/ChatPage.css` around lines 4 - 5, The
container declaration uses an invalid camelCase name and has spacing issues;
rename the container-name value from chatPage to a kebab-case identifier (e.g.,
chat-page) and update every matching `@container` chatPage usage to `@container`
chat-page, and fix Stylelint spacing by adding the required blank line(s) before
the declarations in the container block(s) (check the container-name: and
container-type: blocks and their corresponding `@container` rules between the
mentioned ranges).
vscode/core/src/webviewMessageHandler.ts-602-607 (1)

602-607: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This action now carries secrets into the generic message logger.

UPDATE_MODEL_PROVIDER_CONFIG accepts raw credentials, and messageHandler() still debug-logs every webview message via JSON.stringify(message). That will write API keys to the extension logs unless this payload is redacted or excluded from the generic log path.

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

In `@vscode/core/src/webviewMessageHandler.ts` around lines 602 - 607, The generic
webview logger in messageHandler() currently JSON.stringify(message) and will
log secrets carried by the UPDATE_MODEL_PROVIDER_CONFIG action; modify
messageHandler() to detect the UPDATE_MODEL_PROVIDER_CONFIG action (or presence
of the payload.credentials field) and redact or omit credentials before logging
(e.g., replace payload.credentials with a masked value or remove it), ensuring
the destructuring of payload ({ provider, model, credentials, agentMode })
remains unchanged for processing but that credentials are excluded from any
generic logging path.
vscode/core/src/features/agent/index.ts-72-73 (1)

72-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize workspaceRoot before using it as workspaceDir.

In this PR, workspaceRoot is treated as a possible file:// URI in multiple other call sites, but here it is passed straight into Goose/OpenCode. If the store still holds the URI form, the backend gets a URI string instead of a filesystem working directory and path resolution will fail.

Suggested fix
-  const workspaceDir = ctx.store.getState().workspaceRoot;
+  const workspaceRoot = ctx.store.getState().workspaceRoot;
+  const workspaceDir = workspaceRoot.startsWith("file://")
+    ? vscode.Uri.parse(workspaceRoot).fsPath
+    : workspaceRoot;

Also applies to: 126-142

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

In `@vscode/core/src/features/agent/index.ts` around lines 72 - 73, Normalize the
workspaceRoot retrieved from ctx.store.getState().workspaceRoot before passing
it into the agent backend: detect and convert a file:// URI to a proper
filesystem path (e.g., strip the file:// scheme and decode) and then assign that
normalized path to workspaceDir so getConfigAgentBackend() / Goose/OpenCode
receive a valid filesystem directory; apply the same normalization wherever
workspaceRoot is read (including the other usages around the block referenced at
lines ~126-142) to ensure path resolution works correctly.
vscode/core/src/commands.ts-861-876 (1)

861-876: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't fall back to disk when originalContent is intentionally empty.

Here "" is a valid sentinel for “new file”, but the new if (!originalContent) branch treats it as cache-miss and rereads the already-modified file from disk. That makes new files look like existing files and breaks the “skip decorator view for new file” path.

Suggested fix
-        const cachedFileState = state.modifiedFiles.get(uri.fsPath);
-        let originalContent = cachedFileState?.originalContent ?? "";
-
-        if (!originalContent) {
+        const cachedFileState = state.modifiedFiles.get(uri.fsPath);
+        const hasCachedOriginalContent = cachedFileState?.originalContent !== undefined;
+        let originalContent = cachedFileState?.originalContent ?? "";
+
+        if (!hasCachedOriginalContent) {
           try {
             const doc = await workspace.openTextDocument(uri);
             originalContent = doc.getText();
           } catch {
             logger.debug(`File not found, treating as new file: ${filePath}`);
             originalContent = "";
           }
         }

Also applies to: 878-885

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

In `@vscode/core/src/commands.ts` around lines 861 - 876, The code treats an empty
string originalContent as a cache miss because it uses a falsy check; change the
logic to only fall back to disk when cachedFileState?.originalContent is
strictly undefined (or null) so that an intentional "" (new file sentinel) is
preserved. Concretely, initialize let originalContent =
cachedFileState?.originalContent; and replace the if (!originalContent) check
with if (originalContent === undefined) before calling
workspace.openTextDocument (same change for the repeated block around the
878-885 area), keeping the existing error handling and final assignment of "" on
file-not-found.
webview-ui/src/components/ChatPage/AgentFileReview.tsx-217-217 (1)

217-217: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disable batch actions while an individual file response is still processing.

handleAccept/handleReject add the token to processingTokens, but the header buttons only key off isBatchOperationInProgress. That leaves “Accept All” / “Reject All” clickable while a per-file response is already in flight, so the same file can be sent through both paths.

Suggested fix
-  const busy = isBatchOperationInProgress;
+  const busy = isBatchOperationInProgress || processingTokens.size > 0;

Also applies to: 244-267, 269-294, 366-370

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

In `@webview-ui/src/components/ChatPage/AgentFileReview.tsx` at line 217, Header
batch actions are only keyed off isBatchOperationInProgress, so per-file
operations (handleAccept/handleReject) that add tokens to processingTokens don't
disable the "Accept All"/"Reject All" header buttons; change the guarding
condition to also check processingTokens (e.g. treat busy =
isBatchOperationInProgress || processingTokens.size > 0 or
processingTokens.length > 0 depending on the collection) and update every place
that currently uses isBatchOperationInProgress (including the header button
disable logic and the other checks referenced around the
handleAccept/handleReject usages) to use this combined busy variable so batch
actions are disabled while any per-file token is in processingTokens.
vscode/core/src/webviewMessageHandler.ts-624-638 (1)

624-638: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist provider/model changes even when the user leaves credentials blank.

This only writes provider-settings.yaml when hasCredentials is true. That drops provider/model updates for providers that need no credentials and for the “Stored” flow where the UI explicitly tells the user to leave secrets blank to keep the current ones.

Suggested fix
-      if (provider && model && hasCredentials) {
+      if (provider && model) {
         const { paths } = await import("./paths");
-        const yamlContent = generateProviderSettingsYaml(provider, model, credentials);
+        const yamlContent = generateProviderSettingsYaml(provider, model, credentials ?? {});
         const encoder = new TextEncoder();
         await vscode.workspace.fs.writeFile(paths().settingsYaml, encoder.encode(yamlContent));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/webviewMessageHandler.ts` around lines 624 - 638, The current
guard uses hasCredentials so provider/model changes are only persisted when
credentials are non-empty; change the condition in webviewMessageHandler.ts from
"if (provider && model && hasCredentials)" to simply "if (provider && model)" so
generateProviderSettingsYaml(provider, model, credentials) is always written to
paths().settingsYaml (via vscode.workspace.fs.writeFile) even when credentials
are blank, ensuring providers that don't require secrets and the "Stored" flow
aren’t dropped; keep the rest of the logic (logger.info and
vscode.window.showInformationMessage) unchanged.
vscode/core/src/webviewMessageHandler.ts-471-479 (1)

471-479: ⚠️ Potential issue | 🟠 Major

Use vscode.Uri.parse().fsPath for Windows path compatibility.

The new URL(wsRoot).pathname approach produces /C:/repo (with leading slash) on Windows, which path.join() preserves, creating an invalid path. The codebase already uses vscode.Uri.parse(uri).fsPath in commands.ts for this exact purpose—it correctly handles Windows paths by removing the leading slash.

Suggested fix
-      if (!isAbsolute(path)) {
-        let wsRoot = state.data.workspaceRoot;
-        if (wsRoot.startsWith("file://")) {
-          wsRoot = new URL(wsRoot).pathname;
-        }
-        absPath = join(wsRoot, path);
-      }
+      if (!isAbsolute(path)) {
+        let wsRoot = state.data.workspaceRoot;
+        if (wsRoot.startsWith("file://")) {
+          wsRoot = vscode.Uri.parse(wsRoot).fsPath;
+        }
+        absPath = join(wsRoot, path);
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/webviewMessageHandler.ts` around lines 471 - 479, The code
uses new URL(wsRoot).pathname which yields a leading slash on Windows (e.g.,
/C:/repo) and breaks path.join; update the workspaceRoot conversion to use
vscode.Uri.parse(wsRoot).fsPath instead (replace new URL(wsRoot).pathname with
Uri.parse(wsRoot).fsPath) so Windows paths are normalized, and ensure Uri is
imported/available in webviewMessageHandler.ts (or use await import('vscode') to
grab Uri) where the variables join, isAbsolute and absPath are handled.
vscode/core/src/agentConfigReader.ts-35-44 (1)

35-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

OpenCode provider/model updates are currently dropped.

readOpencodeConfig() reads provider/model from provider-settings.yaml, but the OpenCode branch of writeAgentConfig() is a no-op. A provider/model-only update will therefore read back the old values on the next refresh unless some other path happens to rewrite the YAML too.

Also applies to: 90-96

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

In `@vscode/core/src/agentConfigReader.ts` around lines 35 - 44, writeAgentConfig
currently drops provider/model-only updates for the OpenCode backend because the
OpenCode branch doesn't persist provider-settings.yaml; update
writeOpencodeConfig (or add a helper like writeOpencodeProviderSettings) to
accept and persist provider and model fields from WriteAgentConfigChanges into
provider-settings.yaml (the same keys read by readOpencodeConfig), and ensure
writeAgentConfig forwards the full changes object to writeOpencodeConfig; also
mirror this fix for the other OpenCode handling at the other location referenced
(lines ~90-96) so provider/model-only updates are saved and later read back
correctly.
vscode/core/src/client/opencodeClient.ts-147-154 (1)

147-154: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

allow_always is currently mapped to one-time approval.

The generic includes("allow") branch also matches IDs like allow_always, so a persistent approval gets downgraded to "once". Check the "always" case first, or match the option kind exactly.

Suggested change
-      if (optionId.includes("allow_once") || optionId.includes("allow")) {
-        response = "once";
-      } else if (optionId.includes("always")) {
+      if (optionId.includes("always")) {
         response = "always";
+      } else if (optionId.includes("allow_once") || optionId.includes("allow")) {
+        response = "once";
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/client/opencodeClient.ts` around lines 147 - 154, The code in
opencodeClient.ts misclassifies "allow_always" because the
optionId.includes("allow") check runs before the "always" logic; update the
logic in the block handling outcome?.outcome === "selected" (where optionId is
derived and response is set) to check for the "always" case before the generic
"allow" check or use exact-kind matching (e.g., optionId === "allow_always" or
regex for whole-word kind) so that optionId values like "allow_always" map to
response = "always" and only true one-time kinds map to "once".
webview-ui/src/components/ChatPage/ChatPage.tsx-112-119 (1)

112-119: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear chat here doesn't clear the host-side conversation.

This only wipes the webview store. The extension store and agent session stay intact, so a later sync/reload can repopulate the old messages and the “new conversation” button does not actually start fresh. This path should round-trip to the extension host and reset the persisted chat/session there too.

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

In `@webview-ui/src/components/ChatPage/ChatPage.tsx` around lines 112 - 119,
handleClearChat currently only clears the webview store; after calling
useExtensionStore.getState().setAgentMessages([]) and .clearChatMessages() you
must also notify the extension host to reset the persisted conversation/session
so it won't be repopulated on sync/reload—add a call from handleClearChat to the
host RPC (e.g. use the existing postMessage/vscode API or the project's host API
such as resetHostConversation / clearHostConversation) with a
clearConversation/clearSession action, and ensure the extension host
implements/handles that action to clear the extension-side agent session and
persisted chat.
webview-ui/src/components/ChatPage/ChatPage.tsx-387-401 (1)

387-401: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resource-only assistant turns can disappear.

When msg.content is empty, ReceivedMessage short-circuits, so the resource blocks and cancelled label passed via extraContent never render. Add a fallback container for non-text content instead of routing everything through ReceivedMessage.
Based on learnings, the ReceivedMessage component doesn't render if it has no content.

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

In `@webview-ui/src/components/ChatPage/ChatPage.tsx` around lines 387 - 401, The
resource-only assistant turns disappear because ReceivedMessage short-circuits
when msg.content is empty; update the rendering in the ChatPage component (the
JSX around MessageWrapper / ReceivedMessage) to detect when msg.content is falsy
and render a fallback container that displays non-text children (extraContent,
resource blocks, and the cancelled label) instead of passing them into
ReceivedMessage; keep the existing ReceivedMessage usage for text messages (when
msg.content exists) and ensure the fallback uses the same styling/structure so
resource-only turns and msg.isCancelled still render.
vscode/core/src/features/agent/fileTracker.ts-307-313 (1)

307-313: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

clear() can be undone by late snapshot completions.

The outstanding fs.readFile() promises keep running after clear(). When one resolves, it can repopulate originalContentCache for the next iteration, which leaks stale originals across runs. This needs an epoch/token or an await-before-clear path, not just clearing the maps.

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

In `@vscode/core/src/features/agent/fileTracker.ts` around lines 307 - 313,
clear() currently wipes maps but does not prevent in-flight fs.readFile()
snapshot completions from re-populating originalContentCache; add a
generation/token mechanism (e.g., this.generation number) on the FileTracker and
increment it in clear(), capture the current generation when starting any async
snapshot/read operation, and on promise resolution only write into
originalContentCache/pendingToolFiles if the resolved generation matches
this.generation; update places that set scanPromise or start snapshots to
capture and check this token so late completions are ignored rather than
repopulating state.
vscode/core/src/client/directLLMClient.ts-52-77 (1)

52-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

getState() never reflects start()/stop().

This backend always reports "running", even before start() and after stop(). Callers use getState() for lifecycle decisions, so the UI can think the agent is active when it is not. Track the current AgentState on the instance, like the other backends do.

Suggested change
 export class DirectLLMClient extends EventEmitter implements AgentClient {
+  private agentState: AgentState = "stopped";
   private readonly workflow: KaiInteractiveWorkflow;
   private readonly workflowInput: KaiInteractiveWorkflowInput;
   private readonly logger: winston.Logger;
   private promptActive = false;
@@
   async start(): Promise<void> {
+    this.agentState = "running";
     this.emit("stateChange", "running");
   }
@@
   async stop(): Promise<void> {
     try {
       this.workflow.stop();
     } catch {
       // workflow may not be running
     }
+    this.agentState = "stopped";
     this.emit("stateChange", "stopped");
   }
@@
   getState(): AgentState {
-    return "running";
+    return this.agentState;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/client/directLLMClient.ts` around lines 52 - 77, getState()
currently always returns "running" and doesn't reflect lifecycle changes; add an
instance property (e.g., this.state: AgentState initialized to "stopped" or
"idle") and update it in start(), stop(), and dispose() to "running" and
"stopped" respectively (ensure stop/dispose set state to "stopped" even if
this.workflow.stop() throws), then have getState() return this.state; update any
disposal logic around this.workflow.removeAllListeners() /
this.removeAllListeners() to occur after setting the state so callers see the
correct lifecycle.
vscode/core/src/features/agent/handlers.ts-154-168 (1)

154-168: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the merged credential set when regenerating provider-settings.yaml.

This write path currently uses payload.credentials, so editing one secret can drop previously stored keys from the YAML fallback even though they still exist in secret storage. That makes the DirectLLM fallback drift from the actual saved config.

Suggested change
           const yamlContent = generateProviderSettingsYaml(
             payload.provider,
             payload.model,
-            payload.credentials,
+            cleaned,
           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/handlers.ts` around lines 154 - 168, The write
currently uses only payload.credentials which can drop keys already stored
elsewhere; when regenerating provider-settings.yaml (the call to
generateProviderSettingsYaml with payload.provider, payload.model,
payload.credentials) you must merge the incoming payload.credentials with the
existing/installed credentials before generating YAML. Fix by reading the
current credential set (e.g., load and parse the existing file at
paths().settingsYaml or fetch stored provider secrets for payload.provider),
combine/override with payload.credentials to produce a merged credentials
object, and pass that merged credentials object into
generateProviderSettingsYaml so the file written to paths().settingsYaml
reflects the complete credential set used by DirectLLMClient fallback.
webview-ui/src/store/store.ts-417-420 (1)

417-420: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cap agentMessages the same way regular chat is capped.

These paths all append or replace agent messages without trimming, so long-running agent sessions can grow the Zustand state indefinitely.

Suggested fix
       setAgentMessages: (messages) =>
         set((state) => {
-          state.agentMessages = messages;
+          state.agentMessages = messages.slice(-MAX_CHAT_MESSAGES);
         }),
@@
             state.agentMessages.push(msg);
+            if (state.agentMessages.length > MAX_CHAT_MESSAGES) {
+              state.agentMessages = state.agentMessages.slice(-MAX_CHAT_MESSAGES);
+            }
           }
@@
             state.agentMessages.push(msg);
+            if (state.agentMessages.length > MAX_CHAT_MESSAGES) {
+              state.agentMessages = state.agentMessages.slice(-MAX_CHAT_MESSAGES);
+            }
           }

Also applies to: 432-446, 519-531

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

In `@webview-ui/src/store/store.ts` around lines 417 - 420, The agentMessages
state setters (notably setAgentMessages and the other functions that
append/replace agentMessages in this file) do not trim the array and can grow
unbounded; update every mutator that writes to state.agentMessages to apply the
same capping logic used for regular chat (either call the existing trim/cap
utility or slice to keep only the last N items using the same MAX_CHAT_MESSAGES
constant) so that after any assignment or append you set state.agentMessages =
cappedArray (preserving newest messages).
vscode/core/src/client/gooseClient.ts-247-275 (1)

247-275: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject overlapping prompts before reusing currentResponseId.

A second sendMessage() while the first is still streaming overwrites the correlation id, so later chunks and tool events can land on the wrong chat message.

Suggested fix
   async sendMessage(
     content: string,
     responseMessageId: string,
   ): Promise<AcpPromptResponse["stopReason"]> {
     if (this.state !== "running" || !this.sessionId) {
       throw new Error("GooseClient: not running");
     }
+    if (this.currentResponseId) {
+      throw new Error("GooseClient: prompt already in progress");
+    }
 
     this.currentResponseId = responseMessageId;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/client/gooseClient.ts` around lines 247 - 275, sendMessage is
overwriting this.currentResponseId when called while a previous send is still in
progress, causing later chunks/events to attach to the wrong message; modify
sendMessage to first check this.currentResponseId (and/or this.state ===
"streaming" if applicable) and reject/throw immediately if a prompt is already
in flight, only set this.currentResponseId = responseMessageId after that guard,
and ensure the existing reset logic (currently in the try/catch) remains in a
finally-equivalent path so currentResponseId is reliably cleared when the
request completes or errors; reference the sendMessage function,
this.currentResponseId, and the emit("streamingComplete", ...) usage to locate
and update the logic.
vscode/core/src/features/agent/init.ts-363-365 (1)

363-365: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t treat every "not found" startup error as a missing CLI.

This will send users into the install flow for unrelated failures like missing models, files, or config. Restrict the fallback to known binary-discovery errors or ENOENT.

Suggested fix
-      if (errorMsg.includes("binary not found") || errorMsg.includes("not found")) {
+      const isMissingBinary =
+        /binary not found/i.test(errorMsg) || /\bENOENT\b/.test(errorMsg);
+
+      if (isMissingBinary) {
         await promptAgentInstall(agentClient, ctx);
       } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/init.ts` around lines 363 - 365, The startup
error handling currently treats any errorMsg containing "not found" as a missing
CLI and calls promptAgentInstall(agentClient, ctx); tighten this by only
triggering the install flow for true binary-discovery/ENOENT failures: check the
error object for error.code === 'ENOENT' and/or match errorMsg against a strict
regex like /binary.*not found/i or other known binary-discovery messages instead
of a generic "not found" substring; update the conditional that references
errorMsg (and the branches calling promptAgentInstall(agentClient, ctx)) so
unrelated "not found" cases (missing models/files/config) fall through to the
normal error handling.
vscode/core/src/features/agent/init.ts-83-96 (1)

83-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a timeout to the analyzer startup poll.

If analyzerClient.start() leaves serverState in a transitional value, this MCP run_analysis call never resolves and the agent can hang on the tool forever.

Suggested fix
-        await new Promise<void>((resolve) => {
+        await new Promise<void>((resolve) => {
+          const startedAt = Date.now();
           const checkInterval = setInterval(() => {
             if (analyzerClient.serverState === "running") {
               clearInterval(checkInterval);
               resolve();
             } else if (
               analyzerClient.serverState === "startFailed" ||
               analyzerClient.serverState === "stopped"
             ) {
               clearInterval(checkInterval);
               resolve();
+            } else if (Date.now() - startedAt > 30_000) {
+              clearInterval(checkInterval);
+              ctx.logger.warn("Timed out waiting for analyzer to start");
+              resolve();
             }
           }, 500);
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/init.ts` around lines 83 - 96, The poll
waiting for analyzerClient.serverState currently has no timeout so it can hang;
modify the Promise inside init to include a startup timeout (e.g., const
startupTimeoutMs = 30000) and set a setTimeout that clears the interval and
rejects the Promise with a descriptive Error like "Analyzer startup timed out"
if serverState doesn't reach "running" or a terminal state within that window;
ensure you clear both the interval and the timeout on success/failure to avoid
leaks and propagate the rejection so callers of the await (the run_analysis
flow) can handle the failure.
vscode/core/src/client/gooseClient.ts-11-18 (1)

11-18: ⚠️ Potential issue | 🟠 Major

Use fileURLToPath() for file:// workspace paths.

new URL(...).pathname breaks Windows drive letters and leaves percent-encoded characters in the path, causing Goose to start in the wrong working directory. Add the import and use fileURLToPath() instead.

Suggested fix
 import { ChildProcess, spawn, execFile } from "child_process";
 import { EventEmitter } from "events";
 import * as fs from "fs";
 import * as os from "os";
 import * as path from "path";
+import { fileURLToPath } from "url";
 import winston from "winston";
 import { AgentState } from "@editor-extensions/shared";
 import type { AgentClient, McpServerConfig, PermissionRequestData } from "./agentClient";
@@
     super();
     // Normalize workspaceDir — it may arrive as a file:// URI from vscode
     if (config.workspaceDir.startsWith("file://")) {
-      config = { ...config, workspaceDir: new URL(config.workspaceDir).pathname };
+      config = { ...config, workspaceDir: fileURLToPath(config.workspaceDir) };
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/client/gooseClient.ts` around lines 11 - 18, The code in
gooseClient.ts currently uses new URL(...).pathname which breaks Windows drive
letters and leaves percent-encoded characters; import fileURLToPath from "url"
and replace any occurrences of new URL(someWorkspaceUri).pathname with
fileURLToPath(new URL(someWorkspaceUri)) so the workspace path is correctly
decoded and platform-correct; update the import list at the top to include
fileURLToPath and adjust any variables or functions that construct the Goose
working directory (search for usages of new URL(...).pathname) to use
fileURLToPath instead.
🟡 Minor comments (3)
webview-ui/src/components/ChatPage/CompactMigrationScope.tsx-88-95 (1)

88-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

inc.uri + lineNumber is not unique enough for the list key.

Multiple incidents can share the same file and line, so this can emit duplicate keys and reconcile the wrong button. Include a stable discriminator such as violation_name or the local index.

Suggested fix
-                    {fileIncs.map((inc) => (
-                      <li key={`${inc.uri}-${inc.lineNumber}`}>
+                    {fileIncs.map((inc, index) => (
+                      <li key={`${inc.uri}-${inc.lineNumber}-${inc.violation_name ?? index}`}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webview-ui/src/components/ChatPage/CompactMigrationScope.tsx` around lines 88
- 95, The list item key using `${inc.uri}-${inc.lineNumber}` in
CompactMigrationScope (iterating fileIncs) can collide because multiple
incidents may share file and line; update the key to include a stable
discriminator such as inc.violation_name or the iteration index (e.g.,
`${inc.uri}-${inc.lineNumber}-${inc.violation_name}` or
`${inc.uri}-${inc.lineNumber}-${idx}`) when rendering the <li> in the map that
calls onIncidentSelect(inc) so each button has a truly unique key.
vscode/core/src/gooseConfig.ts-63-72 (1)

63-72: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider distinguishing error types in write path.

The catch block at lines 70-72 silently swallows all errors, but writeGooseConfig may fail for reasons other than missing file (e.g., permission denied). While starting from scratch is fine for missing/malformed files, permission errors should likely propagate.

🛡️ Proposed fix to handle permission errors
   let raw: Record<string, any> = {};
   try {
     const content = fs.readFileSync(configPath, "utf-8");
     raw = (parse(content) as Record<string, any>) ?? {};
-  } catch {
-    // Start from scratch if missing/malformed
+  } catch (err: any) {
+    // Start from scratch if missing/malformed, but rethrow permission errors
+    if (err?.code !== "ENOENT" && err?.code !== "ENOTFOUND") {
+      // Only swallow file-not-found errors; let permission errors propagate
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/gooseConfig.ts` around lines 63 - 72, The catch in
writeGooseConfig that surrounds fs.readFileSync(getGooseConfigPath()) swallows
all errors; change it to inspect the thrown error from fs.readFileSync/parse and
only treat missing or parse failures as recoverable (e.g., when error.code ===
'ENOENT' or a JSON/TOML parse error) while rethrowing permission-related errors
(e.g., error.code === 'EACCES' or 'EPERM') so callers are notified; update the
try/catch around fs.readFileSync and parse in writeGooseConfig to rethrow
non-recoverable errors and keep the current behavior of initializing raw = {}
for missing/malformed files.
webview-ui/src/components/ChatPage/AgentFileReview.tsx-12-22 (1)

12-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat line-ending-only diffs as unchanged everywhere.

InlineDiff filters line-ending-only hunks, but computeDiffStats, totalStats, and the noChanges/fileLabel checks still use the raw diff. That produces contradictory UI for CRLF-only changes: the header shows a modified file with +/- counts and actions, while the body says “No meaningful changes”.

Also applies to: 135-149, 176-179, 219-230

🧹 Nitpick comments (3)
vscode/core/src/features/agent/fileChangeRouter.ts (2)

91-93: 💤 Low value

Consider logging diff generation failures.

The catch block silently falls back to using raw content as the diff. Logging the error would aid debugging when diffs fail unexpectedly.

💡 Suggested improvement
-  } catch {
-    diff = content;
+  } catch (err) {
+    console.warn(`Failed to generate diff for ${relativePath}:`, err);
+    diff = content;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/fileChangeRouter.ts` around lines 91 - 93, The
catch block that falls back to diff = content in fileChangeRouter.ts should log
the exception to help debugging; update the catch to capture the error (e.g.,
catch (err)) and call the existing logger (or processLogger) to record a
warning/error with context (file path or identifier and that diff generation
failed) before assigning diff = content so failures are visible; reference the
diff variable and the content fallback in your change.

134-134: 💤 Low value

Fire-and-forget with swallowed errors may hide issues.

While the fire-and-forget pattern is intentional here, completely swallowing errors makes debugging difficult if changeApplied fails silently.

💡 Suggested improvement
-    Promise.resolve(executeExtensionCommand("changeApplied", filePath, content)).catch(() => {});
+    Promise.resolve(executeExtensionCommand("changeApplied", filePath, content)).catch((err) => {
+      console.warn(`changeApplied command failed for ${filePath}:`, err);
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vscode/core/src/features/agent/fileChangeRouter.ts` at line 134, The current
fire-and-forget call Promise.resolve(executeExtensionCommand("changeApplied",
filePath, content)).catch(() => {}) swallows all errors; update the catch to
surface/log failures instead of ignoring them by capturing the error and logging
contextual details (include the command name "changeApplied" and the filePath
and/or content identifier) so failures in executeExtensionCommand are visible;
locate this call in fileChangeRouter.ts and replace the empty catch with a call
to the existing logger or console.error (or rethrow to a monitoring service)
that records the error and context.
vscode/core/src/utilities/ModifiedFiles/handleModifiedFile.ts (1)

21-27: Remove unused _processedTokens parameter if not required for interface compatibility.

The _processedTokens parameter is never used in the function body. If maintaining the current function signature is not necessary for API stability, consider removing it to simplify the interface.

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

In `@vscode/core/src/utilities/ModifiedFiles/handleModifiedFile.ts` around lines
21 - 27, The function handleModifiedFileMessage currently declares an unused
parameter _processedTokens; remove this parameter from the function signature to
simplify the API (update the signature in ModifiedFiles/handleModifiedFile.ts to
drop _processedTokens) and then update all call sites and any related type
definitions or interfaces that reference handleModifiedFileMessage to match the
new signature (search for handleModifiedFileMessage usages and adjust arguments
accordingly), ensuring tests/consumers compile and no references to
_processedTokens remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2bf970d0-2326-4060-a014-0e53284c6b32

📥 Commits

Reviewing files that changed from the base of the PR and between d25ed92 and ffd34be.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (85)
  • agentic/src/types.ts
  • changes/unreleased/1351-pluggable-agent-backend.yaml
  • shared/src/types/actions.ts
  • shared/src/types/agent.ts
  • shared/src/types/agentActions.ts
  • shared/src/types/agentMessages.ts
  • shared/src/types/index.ts
  • shared/src/types/messages.ts
  • shared/src/types/types.ts
  • vscode/core/package.json
  • vscode/core/src/KonveyorGUIWebviewViewProvider.ts
  • vscode/core/src/__tests__/mocks.ts
  • vscode/core/src/agentConfigReader.ts
  • vscode/core/src/analysis/__tests__/batchedAnalysisTrigger.test.ts
  • vscode/core/src/analysis/batchedAnalysisTrigger.ts
  • vscode/core/src/analysis/runAnalysis.ts
  • vscode/core/src/client/agentClient.ts
  • vscode/core/src/client/analyzerClient.ts
  • vscode/core/src/client/directLLMClient.ts
  • vscode/core/src/client/gooseClient.ts
  • vscode/core/src/client/opencodeClient.ts
  • vscode/core/src/clients/ProfileSyncClient.ts
  • vscode/core/src/commands.ts
  • vscode/core/src/data/loadResults.ts
  • vscode/core/src/diff/vertical/manager.ts
  • vscode/core/src/extension.ts
  • vscode/core/src/extensionState.ts
  • vscode/core/src/features/agent/__tests__/toolPermissionHandler.test.ts
  • vscode/core/src/features/agent/agentOrchestrator.ts
  • vscode/core/src/features/agent/batchReviewHandlers.ts
  • vscode/core/src/features/agent/fileChangeRouter.ts
  • vscode/core/src/features/agent/fileTracker.ts
  • vscode/core/src/features/agent/handlers.ts
  • vscode/core/src/features/agent/index.ts
  • vscode/core/src/features/agent/init.ts
  • vscode/core/src/features/agent/promptBuilder.ts
  • vscode/core/src/features/agent/toolPermissionHandler.ts
  • vscode/core/src/features/agent/toolPermissionPolicy.ts
  • vscode/core/src/features/featureRegistry.ts
  • vscode/core/src/features/index.ts
  • vscode/core/src/gooseConfig.ts
  • vscode/core/src/modelProvider/config.ts
  • vscode/core/src/modelProvider/providerConfigGenerator.ts
  • vscode/core/src/paths.ts
  • vscode/core/src/solutionWorkflowOrchestrator.ts
  • vscode/core/src/store/extensionStore.ts
  • vscode/core/src/store/syncBridges.ts
  • vscode/core/src/utilities/ModifiedFiles/handleFileResponse.ts
  • vscode/core/src/utilities/ModifiedFiles/handleModifiedFile.ts
  • vscode/core/src/utilities/ModifiedFiles/handleQuickResponse.ts
  • vscode/core/src/utilities/ModifiedFiles/processMessage.ts
  • vscode/core/src/utilities/ModifiedFiles/processModifiedFile.ts
  • vscode/core/src/utilities/ModifiedFiles/queueManager.ts
  • vscode/core/src/utilities/agentCredentialStorage.ts
  • vscode/core/src/utilities/configuration.ts
  • vscode/core/src/utilities/gooseCredentialStorage.ts
  • vscode/core/src/utilities/profiles/profileActions.ts
  • vscode/core/src/utilities/profiles/profileService.ts
  • vscode/core/src/webviewMessageHandler.ts
  • webview-ui/package.json
  • webview-ui/src/App.tsx
  • webview-ui/src/components/AnalysisPage/AnalysisPage.tsx
  • webview-ui/src/components/ChatPage/AgentFileReview.css
  • webview-ui/src/components/ChatPage/AgentFileReview.tsx
  • webview-ui/src/components/ChatPage/AgentSettings.tsx
  • webview-ui/src/components/ChatPage/ChatPage.css
  • webview-ui/src/components/ChatPage/ChatPage.tsx
  • webview-ui/src/components/ChatPage/CompactBatchReview.css
  • webview-ui/src/components/ChatPage/CompactBatchReview.tsx
  • webview-ui/src/components/ChatPage/CompactMigrationScope.tsx
  • webview-ui/src/components/ChatPage/CompactModifiedFile.css
  • webview-ui/src/components/ChatPage/CompactModifiedFile.tsx
  • webview-ui/src/components/ChatPage/PermissionReviewMessage.css
  • webview-ui/src/components/ChatPage/PermissionReviewMessage.tsx
  • webview-ui/src/components/ChatPage/ResourceBlock.tsx
  • webview-ui/src/components/ChatPage/ResourceLink.tsx
  • webview-ui/src/components/ChatPage/ThinkingIndicator.tsx
  • webview-ui/src/components/ChatPage/providerOptions.ts
  • webview-ui/src/components/ResolutionsPage/MessageWrapper.css
  • webview-ui/src/components/ResolutionsPage/ModifiedFile/useModifiedFileData.ts
  • webview-ui/src/components/ResolutionsPage/ReceivedMessage.tsx
  • webview-ui/src/components/ResolutionsPage/ResolutionsPage.tsx
  • webview-ui/src/hooks/actions.ts
  • webview-ui/src/hooks/useVSCodeMessageHandler.ts
  • webview-ui/src/store/store.ts
💤 Files with no reviewable changes (5)
  • vscode/core/src/tests/mocks.ts
  • webview-ui/src/hooks/actions.ts
  • webview-ui/src/components/ResolutionsPage/ResolutionsPage.tsx
  • webview-ui/src/components/AnalysisPage/AnalysisPage.tsx
  • vscode/core/src/solutionWorkflowOrchestrator.ts
✅ Files skipped from review due to trivial changes (17)
  • changes/unreleased/1351-pluggable-agent-backend.yaml
  • shared/src/types/index.ts
  • agentic/src/types.ts
  • webview-ui/src/components/ResolutionsPage/MessageWrapper.css
  • webview-ui/src/components/ChatPage/CompactModifiedFile.css
  • shared/src/types/agentActions.ts
  • vscode/core/src/analysis/tests/batchedAnalysisTrigger.test.ts
  • webview-ui/src/components/ChatPage/AgentFileReview.css
  • webview-ui/src/components/ChatPage/providerOptions.ts
  • vscode/core/src/utilities/gooseCredentialStorage.ts
  • vscode/core/src/features/agent/toolPermissionPolicy.ts
  • shared/src/types/agentMessages.ts
  • vscode/core/src/utilities/agentCredentialStorage.ts
  • webview-ui/src/components/ChatPage/PermissionReviewMessage.css
  • shared/src/types/agent.ts
  • webview-ui/src/components/ChatPage/CompactBatchReview.css
  • vscode/core/src/utilities/profiles/profileService.ts
🚧 Files skipped from review as they are similar to previous changes (25)
  • vscode/core/src/features/index.ts
  • webview-ui/package.json
  • vscode/core/src/modelProvider/config.ts
  • vscode/core/src/utilities/ModifiedFiles/queueManager.ts
  • vscode/core/src/analysis/runAnalysis.ts
  • vscode/core/src/store/extensionStore.ts
  • webview-ui/src/components/ChatPage/ResourceBlock.tsx
  • webview-ui/src/components/ChatPage/ThinkingIndicator.tsx
  • vscode/core/src/utilities/ModifiedFiles/handleQuickResponse.ts
  • webview-ui/src/App.tsx
  • webview-ui/src/components/ResolutionsPage/ModifiedFile/useModifiedFileData.ts
  • webview-ui/src/components/ChatPage/CompactBatchReview.tsx
  • webview-ui/src/components/ChatPage/CompactModifiedFile.tsx
  • vscode/core/src/analysis/batchedAnalysisTrigger.ts
  • webview-ui/src/components/ResolutionsPage/ReceivedMessage.tsx
  • shared/src/types/types.ts
  • shared/src/types/actions.ts
  • vscode/core/src/extensionState.ts
  • vscode/core/package.json
  • vscode/core/src/features/agent/batchReviewHandlers.ts
  • vscode/core/src/utilities/ModifiedFiles/handleFileResponse.ts
  • vscode/core/src/features/agent/agentOrchestrator.ts
  • webview-ui/src/components/ChatPage/PermissionReviewMessage.tsx
  • vscode/core/src/KonveyorGUIWebviewViewProvider.ts
  • webview-ui/src/components/ChatPage/ResourceLink.tsx

- Add mcpBridgeServer.ts stub (full impl in PR konveyor#1396)
- Adapt checkBatchReviewComplete to use state.mutate() API (konveyor#1386 compat)
- Revert removeListener back to removeAllListeners (KaiWorkflowEventEmitter lacks it)
- Remove duplicate fileUriToPath import in handleFileResponse
- Include useContainerWidth hook (from PR konveyor#1393)
- Include updated useScrollManagement (from PR konveyor#1393)
- Include ToolMessage with CollapsibleToolGroup/AgentToolGroup exports (from PR konveyor#1394)

Signed-off-by: Ian Bolton <ibolton@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant