Skip to content

feat(terminal): add VS Code integrated-terminal shell override#277

Open
proyectoauraorg wants to merge 20 commits into
Zoo-Code-Org:mainfrom
proyectoauraorg:feat/119-inline-terminal-profile
Open

feat(terminal): add VS Code integrated-terminal shell override#277
proyectoauraorg wants to merge 20 commits into
Zoo-Code-Org:mainfrom
proyectoauraorg:feat/119-inline-terminal-profile

Conversation

@proyectoauraorg
Copy link
Copy Markdown
Contributor

@proyectoauraorg proyectoauraorg commented May 24, 2026

Summary

Adds an optional Zoo Code shell override for commands executed through VS Code's integrated terminal.

By default, Zoo Code continues to use VS Code's configured terminal profile. Users can opt into a Zoo-specific shell from resolvable, path-based VS Code profiles. This setting does not apply to inline execution, which uses the Execa provider.

Key behavior

  • Adds an optional terminalProfile setting. Leaving it unset preserves VS Code's default behavior.
  • Resolves selected profiles to shellPath, shellArgs, and sanitized env values passed to vscode.window.createTerminal.
  • Lists only resolvable profiles from VS Code defaults and user settings. Workspace-local profiles are ignored to prevent repositories from selecting an executable.
  • Excludes source-only profiles and cmd.exe, which cannot provide the shell integration required by this execution path.
  • Opens VS Code's native profile picker when users want to change their default profile, and clears the Zoo-specific override when they do.
  • Starts a fresh idle terminal after an override changes so commands use the newly selected shell.
  • Falls back to Execa only when shell integration fails before command submission. If submission status is unknown, Zoo Code reports the failure without rerunning the command.
  • Avoids Zoo-specific ZDOTDIR injection while an override is active so VS Code can manage shell integration for the selected profile.

Tests

Adds coverage for profile resolution and filtering, settings persistence and UI states, terminal recreation after profile changes, cmd.exe fallback, shell-integration startup handling, safe retry boundaries, and platform test-helper exit codes.

Scope

This PR does not close #119. That issue concerns inline execution through ExecaTerminalProcess and requires a separate Execa-specific shell selection design.

Documentation follow-up: Zoo-Code-Org/Zoo-Code-Docs#4

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a selectable VS Code terminal profile for the inline terminal: schema and message types, profile storage and resolution, extension↔webview plumbing for discovery and persistence, settings UI with profile dropdown and native picker, tests for backend/frontend behavior, and translations across locales.

Changes

Terminal profile selection feature

Layer / File(s) Summary
Type definitions and schema
packages/types/src/global-settings.ts, packages/types/src/vscode-extension-host.ts
globalSettingsSchema and extension/webview message/state types now include terminalProfile and terminal-profiles messages.
Terminal storage and resolution
src/integrations/terminal/BaseTerminal.ts, src/integrations/terminal/Terminal.ts
BaseTerminal stores/normalizes the configured profile name; Terminal resolves it from terminal.integrated.profiles.<platform> and applies shellPath/shellArgs/env when creating inline terminals.
Terminal backend tests
src/integrations/terminal/__tests__/TerminalProfile.spec.ts
Tests for getter/setter behavior, resolution across Windows/macOS/Linux, path/args normalization, env sanitization, and terminal creation integration.
Backend state & messaging plumbing
src/core/webview/ClineProvider.ts, src/core/webview/webviewMessageHandler.ts
ClineProvider hydrates/applies terminalProfile; webviewMessageHandler forwards updateSettings.terminalProfile, handles requestTerminalProfiles to enumerate VS Code profiles and respond with terminalProfiles, and supports opening the native profile picker.
Backend message-handler tests
src/core/webview/__tests__/webviewMessageHandler.spec.ts, src/core/webview/__tests__/ClineProvider.spec.ts
Tests verify updateSettings → Terminal.setTerminalProfile, requestTerminalProfiles filtering/sorting/error handling, openTerminalProfilePicker command, and persisted-state hydration into Terminal.
Frontend extension state context
webview-ui/src/context/ExtensionStateContext.tsx
Adds terminalProfile and setTerminalProfile to the extension state context and initializes them.
Settings view integration
webview-ui/src/components/settings/SettingsView.tsx
Wires terminalProfile through cached state, includes it in save/update payloads, and passes it into TerminalSettings props.
Terminal settings dropdown UI
webview-ui/src/components/settings/TerminalSettings.tsx
Requests profile lists on mount, renders a searchable Select with a Default sentinel, supports Default/Override radios, a configure button that opens the native picker, and persists chosen profile (or undefined for Default).
Frontend settings tests
webview-ui/src/components/settings/__tests__/*
Tests validate SettingsView mock state shape, TerminalSettings posts requestTerminalProfiles on mount, processes profile lists, renders UI states, and persists selections including Default mapping.
Internationalization
webview-ui/src/i18n/locales/*/settings.json
Adds translations for the terminal profile setting (label, default, description, override/configure UI text, and empty-state) across supported locales.

Sequence Diagram(s)

sequenceDiagram
  participant SettingsView
  participant TerminalSettings
  participant Extension
  participant Terminal
  participant VSCodeConfig

  SettingsView->>TerminalSettings: pass terminalProfile prop
  TerminalSettings->>Extension: postMessage(type: "requestTerminalProfiles")
  Extension->>VSCodeConfig: read terminal.integrated.profiles.{windows,osx,linux}
  VSCodeConfig-->>Extension: profile lists
  Extension-->>TerminalSettings: postMessage(type: "terminalProfiles", profiles)
  User->>TerminalSettings: select profile
  TerminalSettings->>Extension: setCachedStateField("terminalProfile", name|undefined)
  Extension->>Terminal: Terminal.setTerminalProfile(name|undefined)
  Terminal->>VSCodeConfig: resolve configured profile -> shellPath/shellArgs/env
  Terminal->>Terminal: create inline terminal with resolved options
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • taltas
  • navedmerchant
  • hannesrudolph
  • JamesRobert20
  • edelauna

🐰 I found a list of shells tonight,
I nudged the default to set things right,
From Git Bash to PowerShell's cheer,
UTF‑8 whispers, no more garbled fear,
Hop, select, and run — the output’s bright!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive PR description covers the feature purpose and implementation approach; however, some required template sections are missing. Complete missing template sections: clearly link to the related issue number (Closes: #), detail test procedures including manual reproduction steps, and verify the pre-submission checklist is explicitly confirmed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: adding VS Code integrated-terminal shell override support for the inline terminal.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #119: allows selecting a VS Code terminal profile via a terminalProfile setting to solve UTF-8 encoding issues on Windows, includes comprehensive tests, i18n support, and integration with the webview settings UI.
Out of Scope Changes check ✅ Passed All changes are directly in scope: they implement the terminal profile override feature, its UI, tests, and localizations. No unrelated modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

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

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

❤️ Share

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 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: 3

🧹 Nitpick comments (1)
webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx (1)

129-135: ⚡ Quick win

Add a save-path regression for “Default” profile clearing

This test stops at setCachedStateField. It won’t catch serialization drops when SettingsView posts updateSettings. Add one integration assertion that clicking Save emits a clearable value for terminalProfile (not an omitted field).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx`
around lines 129 - 135, The test currently only asserts setCachedStateField when
selecting the "__default__" option but doesn’t verify what SettingsView posts on
Save, allowing serialization regressions to omit the field; after firing the
existing click on screen.getByTestId("option-__default__"), simulate clicking
the Save control (e.g. the Save button used by SettingsView) and add an
assertion that the mocked updateSettings/postMessage handler was called with a
payload that explicitly includes terminalProfile: null (i.e. a clearable value)
rather than omitting terminalProfile, referencing the existing setup and
setCachedStateField helpers and the SettingsView updateSettings/postMessage
entry point.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/integrations/terminal/BaseTerminal.ts`:
- Around line 306-308: The setter currently validates using profile.trim() but
stores the original untrimmed string; update BaseTerminal.setTerminalProfile to
persist a normalized value (use const normalized = profile?.trim();) and set
BaseTerminal.terminalProfile to normalized if normalized.length > 0, otherwise
undefined, so stored profile keys match lookups (reference:
BaseTerminal.setTerminalProfile and BaseTerminal.terminalProfile).

In `@src/integrations/terminal/Terminal.ts`:
- Around line 269-272: Terminal.getProfileShell() currently picks
profile.path[0] without checking filesystem existence; change it to iterate the
array when profile.path is an array and choose the first path that exists (use
fs.existsSync or the project's async existence helper) before passing to
createTerminal, while preserving fallback when no candidate exists; update unit
tests that assume index-0 selection for terminal.integrated.profiles.*.path[] to
assert selection of the first existing candidate.

In `@webview-ui/src/components/settings/SettingsView.tsx`:
- Line 400: When resetting settings to Default in SettingsView, don't leave
terminalProfile as undefined (which gets omitted during serialization);
explicitly assign a clear value such as an empty string (e.g. terminalProfile:
'') or a "Default" sentinel when building the settings object (the code paths
around terminalProfile in SettingsView / the reset handler), so the saved JSON
contains a persistent, clear value instead of omitting the key.

---

Nitpick comments:
In
`@webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx`:
- Around line 129-135: The test currently only asserts setCachedStateField when
selecting the "__default__" option but doesn’t verify what SettingsView posts on
Save, allowing serialization regressions to omit the field; after firing the
existing click on screen.getByTestId("option-__default__"), simulate clicking
the Save control (e.g. the Save button used by SettingsView) and add an
assertion that the mocked updateSettings/postMessage handler was called with a
payload that explicitly includes terminalProfile: null (i.e. a clearable value)
rather than omitting terminalProfile, referencing the existing setup and
setCachedStateField helpers and the SettingsView updateSettings/postMessage
entry point.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: db59c226-79a8-4714-b752-3545454c95be

📥 Commits

Reviewing files that changed from the base of the PR and between b5c5e21 and 8b12ca0.

📒 Files selected for processing (30)
  • packages/types/src/global-settings.ts
  • packages/types/src/vscode-extension-host.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/integrations/terminal/BaseTerminal.ts
  • src/integrations/terminal/Terminal.ts
  • src/integrations/terminal/__tests__/TerminalProfile.spec.ts
  • webview-ui/src/components/settings/SettingsView.tsx
  • webview-ui/src/components/settings/TerminalSettings.tsx
  • webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx
  • webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx
  • webview-ui/src/context/ExtensionStateContext.tsx
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json

Comment thread src/integrations/terminal/BaseTerminal.ts
Comment thread src/integrations/terminal/Terminal.ts Outdated
Comment thread webview-ui/src/components/settings/SettingsView.tsx Outdated
Copy link
Copy Markdown
Contributor

@edelauna edelauna left a comment

Choose a reason for hiding this comment

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

Nice this a good first pass - could you add a screenshot / screen recording of the terminal in action?

Comment thread webview-ui/src/components/settings/TerminalSettings.tsx Outdated
Comment thread src/integrations/terminal/Terminal.ts Outdated
Comment thread webview-ui/src/i18n/locales/en/settings.json Outdated
Comment thread src/core/webview/webviewMessageHandler.ts
Comment thread src/core/webview/ClineProvider.ts
@proyectoauraorg
Copy link
Copy Markdown
Contributor Author

Nice this a good first pass - could you add a screenshot / screen recording of the terminal in action?

Thanks for the review! Here's the terminal profile selector in action:

The new Inline terminal profile dropdown (Settings → Terminal → Advanced) set to zsh.
The inline terminal running echo "perfil activo: $0" && pwd, confirming the selected profile resolves to /bin/zsh.

zoo-277-terminal-profile image image

I'm also working through your inline comments — the allowlisted profile-name request, fuller VS Code profile preservation, the copy clarification, and the updateSettings / startup-hydration tests. Will push those in a follow-up. 🦘

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: 1

♻️ Duplicate comments (1)
src/integrations/terminal/Terminal.ts (1)

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

Resolve path[] using the first existing candidate, not index 0.

Current logic always picks profile.path[0]. If that path is missing but a later candidate exists, profile resolution fails unnecessarily.

In VS Code terminal profile settings (`terminal.integrated.profiles.windows|osx|linux`), when `path` is an array, does VS Code select the first existing executable path candidate?
Proposed fix
+import { existsSync } from "fs"
 import * as vscode from "vscode"
 import pWaitFor from "p-wait-for"
@@
-		const pathValue = Array.isArray(profile.path) ? profile.path[0] : profile.path
+		const candidates = Array.isArray(profile.path) ? profile.path : [profile.path]
+		const firstNonEmpty = candidates.find((p): p is string => typeof p === "string" && p.length > 0)
+		const pathValue =
+			candidates.find((p): p is string => typeof p === "string" && p.length > 0 && existsSync(p)) ?? firstNonEmpty
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/integrations/terminal/Terminal.ts` around lines 280 - 283, The code in
Terminal.ts currently picks profile.path[0] unconditionally which fails if that
candidate doesn't exist; update the resolution for profile.path (used to compute
pathValue passed to createTerminal) to iterate candidates and pick the first
existing executable path (e.g., check each string in profile.path with
fs.existsSync or fs.promises.access) and fall back to the original string when
profile.path is not an array; ensure you update the variable referenced as
pathValue and preserve existing behavior when no candidate exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/webview/webviewMessageHandler.ts`:
- Around line 1523-1538: The code currently iterates ["windows","osx","linux"]
and aggregates terminal profile names across all platforms; change it to only
read profiles for the active runtime platform so we don't surface non-resolvable
names. Detect the active platform (e.g. using process.platform and map
"win32"→"windows","darwin"→"osx","linux"→"linux"), then call
vscode.workspace.getConfiguration("terminal.integrated.profiles").get<Record<string,
unknown>>(mappedPlatform) and add keys from that single object into the existing
names Set before calling provider.postMessageToWebview({ type:
"terminalProfiles", profiles: Array.from(names).sort() }).

---

Duplicate comments:
In `@src/integrations/terminal/Terminal.ts`:
- Around line 280-283: The code in Terminal.ts currently picks profile.path[0]
unconditionally which fails if that candidate doesn't exist; update the
resolution for profile.path (used to compute pathValue passed to createTerminal)
to iterate candidates and pick the first existing executable path (e.g., check
each string in profile.path with fs.existsSync or fs.promises.access) and fall
back to the original string when profile.path is not an array; ensure you update
the variable referenced as pathValue and preserve existing behavior when no
candidate exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f6372979-a8cc-46a4-bc65-be490c0584dc

📥 Commits

Reviewing files that changed from the base of the PR and between 8b12ca0 and 4fcbb4b.

📒 Files selected for processing (10)
  • packages/types/src/vscode-extension-host.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/integrations/terminal/Terminal.ts
  • src/integrations/terminal/__tests__/TerminalProfile.spec.ts
  • webview-ui/src/components/settings/TerminalSettings.tsx
  • webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
✅ Files skipped from review due to trivial changes (1)
  • webview-ui/src/i18n/locales/es/settings.json

Comment thread src/core/webview/webviewMessageHandler.ts Outdated
proyectoauraorg added a commit to proyectoauraorg/Zoo-Code that referenced this pull request May 25, 2026
…o-Code-Org#119)

VS Code selects the first terminal-profile path candidate that exists on
disk; mirror that instead of always taking index 0, falling back to the
first non-empty candidate when none exist. Addresses CodeRabbit review on Zoo-Code-Org#277.
proyectoauraorg added a commit to proyectoauraorg/Zoo-Code that referenced this pull request May 26, 2026
…o-Code-Org#119)

VS Code selects the first terminal-profile path candidate that exists on
disk; mirror that instead of always taking index 0, falling back to the
first non-empty candidate when none exist. Addresses CodeRabbit review on Zoo-Code-Org#277.
@proyectoauraorg proyectoauraorg force-pushed the feat/119-inline-terminal-profile branch from 92f45be to cb3734f Compare May 26, 2026 05:59
@proyectoauraorg
Copy link
Copy Markdown
Contributor Author

Thanks for the review @edelauna! Responses:

Screenshot/Recording: Will add a screen recording showing the terminal profile switching in action — targeting EOD.

Code comments:

  1. Allowlisted request for profile names — Good call. Will route profile name lookups through a scoped handler that returns only sanitized names, rather than exposing the full getVSCodeSetting path to the webview.

  2. Preserve source and env from profiles — Agreed. The current shellPath/shellArgs extraction is incomplete. Will extend to capture source and env so profiles that depend on those fields don't silently fall back.

  3. Copy clarity vs inline terminal description — Will update the setting description to explicitly note the difference: inline terminal uses VS Code terminal profiles, while inline mode bypasses shell profiles. The wording should make it clear they're distinct behaviors.

  4. Test via updateSettings — Makes sense. Will add an integration test that exercises the full save → bridge → Terminal.setTerminalProfile path instead of calling the method directly.

  5. Test resolveWebviewView hydration — Will add a test covering the startup path where terminalProfile is hydrated from saved settings, ensuring it survives a reload.

</div>
</div>
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
<SearchableSetting
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.

Should this block be wrapped in {terminalShellIntegrationDisabled && (...)} so the dropdown is hidden when the user has turned off the inline terminal? The other inline-terminal-only settings (inheritEnv, shellIntegrationTimeout) disappear under the !terminalShellIntegrationDisabled guard at line 213, but the profile picker stays visible and editable even when it has no effect.

proyectoauraorg added a commit to proyectoauraorg/Zoo-Code that referenced this pull request May 27, 2026
The terminal-profile dropdown only affects inline execution, which is active
when shell integration is disabled. It previously stayed visible and editable
even when inline mode was off, where it has no effect. Guard it with
terminalShellIntegrationDisabled (defaulting to shown, matching the checkbox),
mirroring the inline-only settings below. Addresses PR Zoo-Code-Org#277 review (edelauna).
@proyectoauraorg
Copy link
Copy Markdown
Contributor Author

Done — the profile picker is now guarded by terminalShellIntegrationDisabled (defaulting to shown, matching the checkbox), so it only appears in inline mode and disappears when inline execution is off, mirroring the other inline-only settings below. Added a test for the hidden state. Screen recording coming up. Thanks @edelauna! 🦓

Copy link
Copy Markdown
Contributor

@edelauna edelauna left a comment

Choose a reason for hiding this comment

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

could more questions, also lets close out the coderabbit feedback please

Comment on lines +1515 to +1545
case "requestTerminalProfiles": {
// Allowlisted request: read VS Code's terminal profiles server-side and
// return only the sanitized profile names. The terminal profile dropdown
// only needs names, so this avoids routing it through the generic
// `getVSCodeSetting` handler (which reads any key the webview supplies).
try {
const names = new Set<string>()

for (const platform of ["windows", "osx", "linux"] as const) {
const profiles = vscode.workspace
.getConfiguration("terminal.integrated.profiles")
.get<Record<string, unknown>>(platform)

if (profiles && typeof profiles === "object") {
for (const name of Object.keys(profiles)) {
names.add(name)
}
}
}

await provider.postMessageToWebview({
type: "terminalProfiles",
profiles: Array.from(names).sort(),
})
} catch (error) {
console.error("Failed to get terminal profiles:", error)
await provider.postMessageToWebview({ type: "terminalProfiles", profiles: [] })
}

break
}
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.

The updateSettings -> Terminal.setTerminalProfile path has tests, but does this handler itself have any? The dedup+sort logic, the catch fallback (empty-array response), and edge cases like an all-null profiles object all seem untested -- worth adding a case to the webviewMessageHandler - terminalProfile suite that sends requestTerminalProfiles and asserts on what gets posted back.

integration is disabled. Hide it otherwise so it isn't shown but ineffective
(mirrors the inline-only settings guarded below). Defaults to shown, matching
the checkbox's `?? true`. See PR #277 review. */}
{(terminalShellIntegrationDisabled ?? true) && (
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.

why ?? true ?

@proyectoauraorg
Copy link
Copy Markdown
Contributor Author

Conflicts resolved and branch rebased on latest main. CI is passing. Ready for re-review. 🙏

@chenyuanrun
Copy link
Copy Markdown

Nice jobs!! ZooCode is purely unusable for my enviroment until this PR is merged, I am looking forward to this feature so I can switch from the buggy vscode termianl, Thanks for all of you!!

image

@edelauna edelauna changed the title feat(terminal): allow choosing the terminal profile for inline execution (#119) feat(terminal): add VS Code integrated-terminal shell override May 31, 2026
@edelauna
Copy link
Copy Markdown
Contributor

edelauna commented May 31, 2026

Ok I pushed an refactor update to get this PR on track since it was mixing 2 concerns. I think for this PR it would be great to specify the profile to use when integrated terminal is off. I updated the title and description, as well as a follow up for how to more specifically close issue #119

On windows this PR allows specifying the default terminal to use, created an issue in our docs repository to show how to overwrite the default profile and select a terminal by setting powershell or gir in settings.json

image image image

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: 4

🧹 Nitpick comments (3)
src/core/webview/__tests__/webviewMessageHandler.spec.ts (2)

868-898: 💤 Low value

Consider adding afterEach for consistency.

While the manual mockRestore() calls work correctly, adding an afterEach(() => { vi.restoreAllMocks() }) block would align with the cleanup pattern used in other test suites (e.g., lines 913-915) and provide extra protection against test pollution.

♻️ Proposed consistency improvement
 describe("webviewMessageHandler - terminalProfile", () => {
 	beforeEach(() => {
 		vi.clearAllMocks()
 	})
+
+	afterEach(() => {
+		vi.restoreAllMocks()
+	})
 
 	it("bridges a saved terminalProfile from updateSettings into the process-wide terminal state", async () => {
-		const setTerminalProfileSpy = vi.spyOn(Terminal, "setTerminalProfile").mockImplementation(() => {})
+		vi.spyOn(Terminal, "setTerminalProfile").mockImplementation(() => {})
 
 		await webviewMessageHandler(mockClineProvider, {
 			type: "updateSettings",
 			updatedSettings: { terminalProfile: "Git Bash" },
 		})
 
-		expect(setTerminalProfileSpy).toHaveBeenCalledWith("Git Bash")
-
-		setTerminalProfileSpy.mockRestore()
+		expect(Terminal.setTerminalProfile).toHaveBeenCalledWith("Git Bash")
 	})
 
 	it("clears the terminal profile when updateSettings sends undefined", async () => {
-		const setTerminalProfileSpy = vi.spyOn(Terminal, "setTerminalProfile").mockImplementation(() => {})
+		vi.spyOn(Terminal, "setTerminalProfile").mockImplementation(() => {})
 
 		await webviewMessageHandler(mockClineProvider, {
 			type: "updateSettings",
 			updatedSettings: { terminalProfile: undefined },
 		})
 
-		expect(setTerminalProfileSpy).toHaveBeenCalledWith(undefined)
-
-		setTerminalProfileSpy.mockRestore()
+		expect(Terminal.setTerminalProfile).toHaveBeenCalledWith(undefined)
 	})
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts` around lines 868 -
898, The tests for webviewMessageHandler that spy on Terminal.setTerminalProfile
lack a global teardown; add an afterEach that calls vi.restoreAllMocks() so
spies are reliably cleaned between tests (in the describe block containing the
tests that call webviewMessageHandler and spy on Terminal.setTerminalProfile) to
match other suites and prevent test pollution.

900-1019: ⚡ Quick win

Consider mocking Terminal.getPlatformProfileKey() for test stability.

The handler calls Terminal.getPlatformProfileKey() to determine which platform-specific configuration to read (line 1527 in the handler), but this method is not mocked in the tests. While the tests currently work because getConfiguration is mocked, explicitly mocking getPlatformProfileKey() would:

  • Make tests platform-independent
  • Make the test intent clearer
  • Prevent unexpected failures if the method's behavior changes
♻️ Proposed improvement
 describe("webviewMessageHandler - requestTerminalProfiles", () => {
 	beforeEach(() => {
 		vi.clearAllMocks()
+		vi.spyOn(Terminal, "getPlatformProfileKey").mockReturnValue("windows")
 		vi.spyOn(Terminal, "resolveProfilePath").mockImplementation((profilePath) => {
 			const candidates = Array.isArray(profilePath) ? profilePath : [profilePath]
 			const value = candidates.find(
 				(candidate) =>
 					typeof candidate === "string" && candidate.trim().length > 0 && !candidate.includes("missing"),
 			)
 			return typeof value === "string" ? value.trim() : undefined
 		})
 	})

Then update the test expectations to use the mocked platform key:

 	it("posts sorted path-resolvable profile names for the active platform", async () => {
 		const mockGet = vi.fn().mockReturnValue({
 			"Git Bash": { path: "C:\\Git\\bin\\bash.exe" },
 			bash: { path: "/bin/bash" },
 			PowerShell: { source: "PowerShell" }, // source-only — must be excluded
 		})
 		vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({ get: mockGet } as any)
 
 		await webviewMessageHandler(mockClineProvider, { type: "requestTerminalProfiles" })
 
+		expect(vscode.workspace.getConfiguration).toHaveBeenCalledWith("terminal.integrated.profiles")
+		expect(mockGet).toHaveBeenCalledWith("windows")
 		expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({
 			type: "terminalProfiles",
 			profiles: ["Git Bash", "bash"],
 		})
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts` around lines 900 -
1019, Tests call Terminal.getPlatformProfileKey() indirectly via
webviewMessageHandler but never mock it, making behavior platform-dependent;
mock Terminal.getPlatformProfileKey() in the test setup (e.g., in beforeEach) to
return a stable platform key string that matches the keys you use in your mocked
vscode.workspace.getConfiguration().get(...) and update any expectations if they
rely on a platform-specific key; specifically reference mocking
Terminal.getPlatformProfileKey and ensure the mocked value aligns with the
profile object names used in the mockGet return values and the
webviewMessageHandler test assertions.
webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx (1)

120-133: 💤 Low value

Consider testing profile switching for stronger validation.

The test clicks the already-selected profile ("Git Bash"). To verify that switching between profiles works correctly, consider clicking a different option like "zsh" instead.

💡 Optional enhancement to test profile switching
 	it("populates the dropdown from received profile names and selecting one sets the profile", () => {
 		const { setCachedStateField } = setup("Git Bash")

 		act(() => {
 			window.dispatchEvent(
 				new MessageEvent("message", {
 					data: { type: "terminalProfiles", profiles: ["Git Bash", "zsh"] },
 				}),
 			)
 		})

-		fireEvent.click(screen.getByTestId("option-Git Bash"))
-		expect(setCachedStateField).toHaveBeenCalledWith("terminalProfile", "Git Bash")
+		fireEvent.click(screen.getByTestId("option-zsh"))
+		expect(setCachedStateField).toHaveBeenCalledWith("terminalProfile", "zsh")
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx`
around lines 120 - 133, The test in TerminalSettings.profile.spec.tsx currently
clicks the already-selected "Git Bash"; update the test (inside the same it
block using setup and setCachedStateField) to simulate selecting a different
profile by dispatching the same MessageEvent with profiles ["Git Bash", "zsh"],
then fireEvent.click on screen.getByTestId("option-zsh") instead of "option-Git
Bash" and assert that setCachedStateField was called with ("terminalProfile",
"zsh") to verify profile switching; keep the existing act wrapper around the
window.dispatchEvent and reuse the setup helper and setCachedStateField mock.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webview-ui/src/i18n/locales/ca/settings.json`:
- Around line 743-748: Update the "description" value for the terminal override
keys (the JSON entry with "label", "description", "overrideLabel",
"configureButton", "noProfiles") to mirror the English clarification: state
explicitly that this override only selects the shell binary/path/args/env
exposed by VS Code and does not enable full shell integration (so inline
execution still bypasses shell integration, prompts, and plugins); modify the
existing Catalan "description" string to include a short clause to that effect
and keep the existing <0>Més informació</0> link marker.

In `@webview-ui/src/i18n/locales/de/settings.json`:
- Around line 743-748: Update the German translation for the shell override so
it clarifies that this setting only selects the shell binary/path/arguments and
environment, not the full VS Code shell integration (no
prompts/plugins/interactive integration); modify the "description" value (and if
helpful, the "overrideLabel" or nearby text keys such as "label" /
"configureButton") to mirror the English clarification about inline mode
bypassing shell integration and explicitly state that only the executable/path
and args/env are used.

In `@webview-ui/src/i18n/locales/hi/settings.json`:
- Around line 743-748: Update the Hindi translation to clarify that selecting a
VS Code profile only chooses the shell binary/path/args/env and does not apply
full shell integration (prompts, plugins, or other in-shell behaviors); edit the
"description" string (and if needed "overrideLabel") in this locale entry (keys:
"description", "overrideLabel") to mirror the English clarification used for the
inline-terminal copy by appending a short sentence like "यह केवल शेल बाइनरी
(पथ/आर्ग/पर्यावरण) का चयन करता है — शेल इंटीग्रेशन/प्रॉम्प्ट/प्लगइन्स लागू नहीं
होते।" so users understand it does not enable full shell integration.

In `@webview-ui/src/i18n/locales/ja/settings.json`:
- Around line 743-748: Update the Japanese "description" (and optionally add a
short clarification near "overrideLabel" or "configureButton") so it mirrors the
English note: explicitly state that selecting a VS Code profile here only
chooses the shell binary/arguments/environment (path-based profile) and does not
enable full shell integration, prompts, or plugins for inline mode; edit the
"description" string used in settings.json to append a concise clarifying
sentence that the override only picks the shell executable and args, not full
shell integration.

---

Nitpick comments:
In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts`:
- Around line 868-898: The tests for webviewMessageHandler that spy on
Terminal.setTerminalProfile lack a global teardown; add an afterEach that calls
vi.restoreAllMocks() so spies are reliably cleaned between tests (in the
describe block containing the tests that call webviewMessageHandler and spy on
Terminal.setTerminalProfile) to match other suites and prevent test pollution.
- Around line 900-1019: Tests call Terminal.getPlatformProfileKey() indirectly
via webviewMessageHandler but never mock it, making behavior platform-dependent;
mock Terminal.getPlatformProfileKey() in the test setup (e.g., in beforeEach) to
return a stable platform key string that matches the keys you use in your mocked
vscode.workspace.getConfiguration().get(...) and update any expectations if they
rely on a platform-specific key; specifically reference mocking
Terminal.getPlatformProfileKey and ensure the mocked value aligns with the
profile object names used in the mockGet return values and the
webviewMessageHandler test assertions.

In
`@webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx`:
- Around line 120-133: The test in TerminalSettings.profile.spec.tsx currently
clicks the already-selected "Git Bash"; update the test (inside the same it
block using setup and setCachedStateField) to simulate selecting a different
profile by dispatching the same MessageEvent with profiles ["Git Bash", "zsh"],
then fireEvent.click on screen.getByTestId("option-zsh") instead of "option-Git
Bash" and assert that setCachedStateField was called with ("terminalProfile",
"zsh") to verify profile switching; keep the existing act wrapper around the
window.dispatchEvent and reuse the setup helper and setCachedStateField mock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 549b96ae-c96e-493e-beab-b28dcfa67273

📥 Commits

Reviewing files that changed from the base of the PR and between bee49f4 and 2286933.

📒 Files selected for processing (28)
  • packages/types/src/vscode-extension-host.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/integrations/terminal/BaseTerminal.ts
  • src/integrations/terminal/Terminal.ts
  • src/integrations/terminal/__tests__/TerminalProfile.spec.ts
  • webview-ui/src/components/settings/SettingsView.tsx
  • webview-ui/src/components/settings/TerminalSettings.tsx
  • webview-ui/src/components/settings/__tests__/TerminalSettings.profile.spec.tsx
  • webview-ui/src/context/ExtensionStateContext.tsx
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json
✅ Files skipped from review due to trivial changes (4)
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
🚧 Files skipped from review as they are similar to previous changes (8)
  • webview-ui/src/i18n/locales/vi/settings.json
  • src/core/webview/webviewMessageHandler.ts
  • src/integrations/terminal/BaseTerminal.ts
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/components/settings/SettingsView.tsx
  • src/integrations/terminal/tests/TerminalProfile.spec.ts
  • webview-ui/src/components/settings/TerminalSettings.tsx
  • src/integrations/terminal/Terminal.ts

Comment thread webview-ui/src/i18n/locales/ca/settings.json
Comment thread webview-ui/src/i18n/locales/de/settings.json
Comment on lines +743 to +748
"label": "Zoo Code टर्मिनल ओवरराइड",
"default": "VS Code डिफ़ॉल्ट प्रोफ़ाइल उपयोग करें (अनुशंसित)",
"description": "डिफ़ॉल्ट रूप से Zoo Code VS Code में कॉन्फ़िगर की गई शेल का उपयोग करता है। VS Code द्वारा प्रदर्शित पथ-आधारित शेल प्रोफ़ाइल चुनने के लिए ओवरराइड चुनें। केवल-स्रोत प्रोफ़ाइल (जैसे, अंतर्निर्मित PowerShell प्रविष्टि) यहाँ सूचीबद्ध नहीं किए जा सकते। <0>अधिक जानें</0>",
"overrideLabel": "Zoo Code के लिए शेल ओवरराइड करें",
"configureButton": "VS Code में डिफ़ॉल्ट प्रोफ़ाइल चुनें",
"noProfiles": "(terminal.integrated.profiles में कोई पथ-आधारित प्रोफ़ाइल नहीं मिली)"
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 | 🟡 Minor | ⚡ Quick win

Clarify that this only selects the shell binary, not full shell integration.

This translation still reads as if inline execution uses the selected VS Code profile wholesale, but the nearby inline-terminal copy says inline mode bypasses shell integration/prompts/plugins. Please mirror the English clarification here so users understand this override only picks the shell path/args/env.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webview-ui/src/i18n/locales/hi/settings.json` around lines 743 - 748, Update
the Hindi translation to clarify that selecting a VS Code profile only chooses
the shell binary/path/args/env and does not apply full shell integration
(prompts, plugins, or other in-shell behaviors); edit the "description" string
(and if needed "overrideLabel") in this locale entry (keys: "description",
"overrideLabel") to mirror the English clarification used for the
inline-terminal copy by appending a short sentence like "यह केवल शेल बाइनरी
(पथ/आर्ग/पर्यावरण) का चयन करता है — शेल इंटीग्रेशन/प्रॉम्प्ट/प्लगइन्स लागू नहीं
होते।" so users understand it does not enable full shell integration.

Comment thread webview-ui/src/i18n/locales/ja/settings.json
@edelauna edelauna force-pushed the feat/119-inline-terminal-profile branch from 49283d9 to a19a6b5 Compare May 31, 2026 13:05
Add a 'terminalProfile' setting that lets users choose which VS Code
terminal profile the inline terminal uses. On Windows the default
cmd/PowerShell shell may use a non-UTF-8 code page (e.g. GBK) and garble
output; selecting a UTF-8 profile such as Git Bash resolves this.

The setting reuses VS Code's terminal profile concept: when set, the
profile name is resolved against terminal.integrated.profiles.<platform>
to derive shellPath/shellArgs for createTerminal. When empty/unset the
default terminal behavior is preserved unchanged.

Adds backend unit tests for profile resolution and a webview test for
the settings dropdown wiring.
Zoo-Code-Org#119)

- Route profile names through a dedicated allowlisted `requestTerminalProfiles`
  message instead of the generic `getVSCodeSetting` (which reads any key the
  webview supplies); the extension reads the profiles and returns only names.
- Preserve the profile's `env` (sanitized to string/null; null unsets a var),
  merged onto the base env in createTerminal.
- Clarify the setting copy (en + es) vs the 'Use Inline Terminal' description.
- Add tests: updateSettings->setTerminalProfile bridge, resolveWebviewView
  startup hydration, and profile env preservation/sanitization.
proyectoauraorg and others added 13 commits May 31, 2026 20:07
…o-Code-Org#119)

VS Code selects the first terminal-profile path candidate that exists on
disk; mirror that instead of always taking index 0, falling back to the
first non-empty candidate when none exist. Addresses CodeRabbit review on Zoo-Code-Org#277.
The terminal-profile dropdown only affects inline execution, which is active
when shell integration is disabled. It previously stayed visible and editable
even when inline mode was off, where it has no effect. Guard it with
terminalShellIntegrationDisabled (defaulting to shown, matching the checkbox),
mirroring the inline-only settings below. Addresses PR Zoo-Code-Org#277 review (edelauna).
@edelauna edelauna force-pushed the feat/119-inline-terminal-profile branch from 4a3107f to 7be20a2 Compare May 31, 2026 20:10
@proyectoauraorg
Copy link
Copy Markdown
Contributor Author

Thanks for taking the wheel on this @edelauna — splitting the two concerns and narrowing to "specify the profile to use when the integrated terminal is off" is the right call, and the refactor reads cleanly. Pulled your 17 commits and went through them locally:

  • The typed ShellIntegrationErrorDetails payload (with commandSubmitted) and the event-based shell-integration wait are solid improvements over the old string / pWaitFor path.
  • The resolveWebviewView hydration + updateSettingsTerminal.setTerminalProfile bridge line up with the two test paths you'd flagged earlier — 👍

Verified on the refactored branch locally:

  • TerminalProfile.spec.ts + TerminalRegistry.spec.ts → 47 passing
  • TerminalSettings.profile.spec.tsx + SettingsView.change-detection.spec.tsx → 15 passing
  • tsc --noEmit (extension) clean

Fully aligned with the direction. Glad to help with the docs follow-up (Zoo-Code-Docs#4) or the more specific #119 close-out — just say the word. 🦓

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.

Support choosing terminal profile for inline terminal executaion

3 participants