Skip to content

feat(agent): support configurable Windows command shells - #2109

Merged
zerob13 merged 25 commits into
devfrom
feat/windows-command-shell
Aug 10, 2026
Merged

feat(agent): support configurable Windows command shells#2109
zerob13 merged 25 commits into
devfrom
feat/windows-command-shell

Conversation

@yyhhyyyyyy

@yyhhyyyyyy yyhhyyyyyy commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add an explicit Windows command-shell contract for Agent execution, with support for:

  • Auto, preserving the existing Windows PowerShell/CMD selection behavior
  • Explicit Windows PowerShell
  • Validated Git Bash installations
  • Existing POSIX behavior on macOS and Linux

A single immutable ResolvedCommandShell is now resolved for each turn and propagated through prompt generation, permission analysis, filesystem path handling, skills, deferred execution, RPC, and process spawning.

What Changed

Command shell resolution

  • Add closed, typed shell profiles, dialects, and path styles.
  • Preserve the existing Auto behavior without implicitly selecting PowerShell 7.
  • Discover Git Bash from an optional override, known Git for Windows locations, or where git.
  • Validate Git Bash using Bash and MSYS runtime semantics.
  • Cache successful validation and briefly cache failures to avoid repeated bounded discovery delays.
  • Never fall back to another shell when explicitly selected Git Bash is unavailable.

Settings UI

Add Windows-only command-shell settings with:

  • Auto, Windows PowerShell, and Git Bash selection
  • Optional Git Bash executable override
  • File picker and availability check
  • Actionable validation and error states
  • Synchronized updates across multiple settings windows
  • Localized copy for all supported locales

UI layout

Before

Agent command shell: implicit

After

Agent command shell  [Auto                 ▾]

Git Bash selected:
Agent command shell  [Git Bash             ▾]
Executable           [C:\Program Files\Git\bin\bash.exe] [Browse]
Status               Available

Prompt and execution consistency

  • Resolve one command-shell specification before prompt assembly.
  • Generate profile-specific command syntax guidance.
  • Require the resolved specification in background execution RPC.
  • Keep utility processes free of shell-selection decisions.
  • Preserve executable-plus-argv direct invocation for scripts and skills.
  • Restrict RTK shell rewriting to POSIX-compatible profiles.
  • Apply UTF-8 output setup according to the resolved shell dialect.
  • Hide Windows console windows for command and helper processes.

Permission safety

  • Make command parsing and risk analysis dialect-aware.
  • Namespace permission signatures by shell profile.
  • Persist the reviewed shell profile in pending permission requests.
  • Rehydrate deferred approvals using the originally reviewed profile instead of the current setting.
  • Fail closed when legacy or malformed approvals lack shell identity.
  • Bind one-shot command grants to ephemeral lease IDs so concurrent identical approvals cannot consume or revoke each other.
  • Validate the signature, profile, server identity, and approved filesystem paths before granting deferred execution.

Git Bash path handling

  • Accept supported MSYS drive paths such as /c/workspace/file.txt.
  • Convert MSYS paths to canonical Windows paths before permission and containment checks.
  • Resolve traversal before checking allowed-directory boundaries.
  • Use case-insensitive containment for Windows and MSYS paths.
  • Reject malformed, unsupported, or out-of-scope paths conservatively.

Persistence and compatibility

  • Keep agentCommandShell device-local.
  • Exclude the setting from backup exports.
  • Preserve the receiving device’s setting during backup import.
  • Keep macOS and Linux shell resolution unchanged.
  • Keep the default Windows behavior compatible through Auto.

Summary by CodeRabbit

  • New Features

    • Added Windows command-shell support with automatic selection and PowerShell, Command Prompt, and Git Bash profiles.
    • Added settings to configure, browse for, refresh, and validate Git Bash.
    • Added shell-aware prompts, commands, permissions, paths, skills, and deferred actions.
    • Added one-time command approvals with secure grant and revocation handling.
    • Added localized settings text across supported languages.
  • Bug Fixes

    • Hid spawned Windows console windows.
    • Preserved machine-local shell settings during synchronization.
    • Improved Windows and MSYS path validation and conversion.
  • Documentation

    • Added command-shell specifications, implementation plans, and validation checklists.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 93079019-1d0c-4b8f-9121-032ffc2a3cdd

📥 Commits

Reviewing files that changed from the base of the PR and between 36921f2 and 91b8ae2.

📒 Files selected for processing (2)
  • test/main/agent/shared/process/backgroundExecSessionManager.test.ts
  • test/main/tool/agentTools/agentFileSystemHandler.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/main/tool/agentTools/agentFileSystemHandler.test.ts
  • test/main/agent/shared/process/backgroundExecSessionManager.test.ts

📝 Walkthrough

Walkthrough

Adds configurable Windows command-shell support across shell resolution, prompts, permissions, deferred execution, paths, skills, process spawning, settings, synchronization, localization, and tests. Shell profiles are validated and propagated per turn.

Changes

Windows command-shell support

Layer / File(s) Summary
Shell contracts and resolution
src/shared/commandShell.ts, src/main/agent/shared/process/commandShellService.ts, src/main/agent/shared/process/commandShellPath.ts, docs/features/windows-command-shell/*
Defines shell profiles, configuration, resolution, Git Bash discovery, caching, validation, path conversion, compatibility rules, and rollback behavior.
Turn and prompt propagation
src/main/agent/deepchat/**
Resolves a shell per turn and passes validated shell data through loop resources, prompts, compaction, deferred execution, and tool options.
Shell-aware permissions
src/main/tool/permission/*, src/main/agent/deepchat/runtime/{dispatch,interactionCoordinator,deferredToolExecutor}.ts, src/main/app/sessionPermissionAdapter.ts
Namespaces command signatures by shell profile, validates permission metadata, issues one-shot grants, and revokes grants after execution or failure.
Execution, skills, and paths
src/main/tool/agentTools/*, src/main/skill/skillExecutionService.ts, src/main/agent/shared/process/*
Uses resolved shells for commands, skills, background sessions, UTF-8 preparation, RTK rewriting, Windows hidden spawning, and Windows/MSYS path authorization.
Settings and integration
src/main/app/*, src/renderer/settings/*, src/shared/contracts/*, src/main/sync/index.ts
Adds command-shell settings routes, events, renderer controls, localized strings, application wiring, and machine-local persistence.
Validation coverage
test/main/**, test/renderer/**
Adds coverage for resolver behavior, shell propagation, permissions, leases, paths, process spawning, settings routes, synchronization, and renderer race handling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: configurable Windows command-shell support for agent execution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/windows-command-shell

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
src/main/agent/deepchat/runtime/deferredToolExecutor.ts (1)

326-363: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require the shell profile when a one-shot command grant is supplied.

oneShotCommandGrantId is forwarded at line 432 even when commandShellProfile is undefined. In that case line 360 resolves the current turn shell, which can differ from the shell used to build the grant signature. The grant then does not describe the shell that executes the command.

The caller in interactionCoordinator.ts validates the profile before it mints the grant, so this pairing holds today. Enforce it here so the contract does not depend on caller discipline.

🛡️ Proposed guard
       if (
         !parsedCommandShellProfile &&
-        targetServerName === 'agent-filesystem'
+        (targetServerName === 'agent-filesystem' || oneShotCommandGrantId)
       ) {
         return {
-          responseText: 'Deferred file execution is missing its shell profile.',
+          responseText: 'Deferred execution is missing its shell profile.',
           isError: true,
           invoked
         }
       }
🤖 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/main/agent/deepchat/runtime/deferredToolExecutor.ts` around lines 326 -
363, Require a non-empty parsedCommandShellProfile whenever a
oneShotCommandGrantId is supplied, before resolving the command shell in the
deferred execution flow. Reject the request with the existing error response
pattern if the grant lacks its matching shell profile, while preserving the
current resolveForTurn behavior for executions without a one-shot grant.
🧹 Nitpick comments (12)
test/main/cli/agentCommandAccess.test.ts (1)

103-141: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add CMD token-expansion coverage.

Add a test with %deepchat_cli_agent_token% and CMD_COMMAND_SHELL. Assert that createEnvironment returns the unprivileged environment and that the authority issues no token. The current CMD test only verifies caret control syntax. It does not verify the case-insensitive token branch for CMD.

As per coding guidelines, “Add the smallest regression test for user-visible behavior or a documented contract.”

🤖 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 `@test/main/cli/agentCommandAccess.test.ts` around lines 103 - 141, Add a
focused CMD test alongside the existing caret-syntax case using
`%deepchat_cli_agent_token%` in the command. Assert that
`AgentCliCommandAccess.createEnvironment` returns the unprivileged environment
with an empty local token and that `AgentCliTokenAuthority.snapshot()` reports
no issued tokens or conversations, covering CMD’s case-insensitive
token-expansion path.

Source: Coding guidelines

src/shared/commandShell.ts (1)

107-110: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider preserving preference when only the override is invalid.

normalizeAgentCommandShellConfig discards the complete config on any parse failure. A corrupted gitBashExecutableOverride therefore resets an explicit git-bash preference to auto. That silently downgrades the selected profile, while docs/features/windows-command-shell/plan.md Line 105 states that explicit profile resolution must not silently downgrade. The route boundary rejects invalid writes, so this only affects corrupted stored settings.

♻️ Optional refinement
 export function normalizeAgentCommandShellConfig(value: unknown): AgentCommandShellConfig {
   const parsed = AgentCommandShellConfigSchema.safeParse(value)
-  return parsed.success ? parsed.data : DEFAULT_AGENT_COMMAND_SHELL_CONFIG
+  if (parsed.success) return parsed.data
+  const preference = AgentCommandShellPreferenceSchema.safeParse(
+    (value as { preference?: unknown } | null | undefined)?.preference
+  )
+  return preference.success
+    ? { preference: preference.data }
+    : DEFAULT_AGENT_COMMAND_SHELL_CONFIG
 }
🤖 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/shared/commandShell.ts` around lines 107 - 110, Update
normalizeAgentCommandShellConfig so an invalid gitBashExecutableOverride falls
back only to the default override while preserving a valid explicit preference.
Keep valid configurations unchanged, retain auto as the preference only when it
is absent or invalid, and ensure corrupted stored settings do not silently
downgrade a valid git-bash profile.
src/shared/types/core/agent-events.ts (1)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inline import(...) type references for CommandShellProfile in both shared event type files. Both files reference the new CommandShellProfile type through an inline import('../../commandShell') expression instead of a top-level import type. Both files already use top-level imports for their other types.

  • src/shared/types/core/agent-events.ts#L42-L42: add import type { CommandShellProfile } from '../../commandShell' at the top of the file and change the field to shellProfile?: CommandShellProfile.
  • src/shared/types/core/llm-events.ts#L332-L332: add the same top-level import type and change the field to shellProfile?: CommandShellProfile.
🤖 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/shared/types/core/agent-events.ts` at line 42, Replace the inline
CommandShellProfile type references with a top-level type import in both
affected files: add import type { CommandShellProfile } from
'../../commandShell' in src/shared/types/core/agent-events.ts (line 42) and
src/shared/types/core/llm-events.ts (line 332), then use CommandShellProfile for
each shellProfile field.
src/main/agent/deepchat/runtime/dispatch.ts (1)

1186-1188: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the parsed value instead of casting the raw input.

safeParse already produces a typed value. Reading request?.shellProfile again and casting it removes the guarantee the parse provides.

♻️ Proposed refactor
+  const parsedShellProfile = CommandShellProfileSchema.safeParse(request?.shellProfile)
   ...
-    shellProfile: CommandShellProfileSchema.safeParse(request?.shellProfile).success
-      ? (request?.shellProfile as CommandShellProfile)
-      : undefined,
+    shellProfile: parsedShellProfile.success ? parsedShellProfile.data : 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/main/agent/deepchat/runtime/dispatch.ts` around lines 1186 - 1188, Update
the shellProfile assignment to retain and use the typed value returned by
CommandShellProfileSchema.safeParse instead of casting request?.shellProfile.
Preserve the undefined result when parsing fails, while ensuring successful
parsing passes the parsed data onward.
test/main/tool/agentTools/agentToolManagerRead.test.ts (1)

161-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the validation error message for the malformed shell spec.

rejects.toThrow() accepts any error. A future unrelated failure, for example a missing mock, would keep this test green. The sibling test at Lines 142-147 asserts the message text. Use the same approach here so the test pins the shell-validation contract.

♻️ Proposed change to assert the validation message
     await expect(
       callToolWithoutCommandShell('read', { path: 'note.txt' }, 'conv1', {
         commandShell: malformedCommandShell as never
       })
-    ).rejects.toThrow()
+    ).rejects.toThrow(/command shell/i)
     await expect(
       preCheckWithoutCommandShell('read', { path: 'note.txt' }, 'conv1', {
         commandShell: malformedCommandShell as never
       })
-    ).rejects.toThrow()
+    ).rejects.toThrow(/command shell/i)
🤖 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 `@test/main/tool/agentTools/agentToolManagerRead.test.ts` around lines 161 -
170, Update the rejection assertions for callToolWithoutCommandShell and
preCheckWithoutCommandShell to verify the specific malformed shell validation
error message, matching the sibling test’s assertion near lines 142-147. Keep
both cases covered while replacing broad rejects.toThrow() checks with
message-specific expectations.
test/main/tool/agentTools/agentBashHandler.test.ts (1)

242-246: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a typed cast instead of as never to reach the private method.

(handler as never).prepareCommand(...) relies on never accepting any property access. A cast that names the method keeps the test readable and fails to compile if the signature changes.

♻️ Proposed change
-    const prepared = await (handler as never).prepareCommand(
-      'Get-ChildItem',
-      {},
-      WINDOWS_POWERSHELL_COMMAND_SHELL
-    )
+    const prepared = await (
+      handler as unknown as {
+        prepareCommand: (
+          command: string,
+          env: Record<string, string>,
+          commandShell: typeof WINDOWS_POWERSHELL_COMMAND_SHELL
+        ) => Promise<{ command: string; rtkFallbackReason?: string }>
+      }
+    ).prepareCommand('Get-ChildItem', {}, WINDOWS_POWERSHELL_COMMAND_SHELL)
🤖 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 `@test/main/tool/agentTools/agentBashHandler.test.ts` around lines 242 - 246,
Replace the `(handler as never)` cast in the test call to `prepareCommand` with
a typed cast that explicitly exposes the private `prepareCommand` method and its
signature, preserving the existing invocation and ensuring signature changes are
caught at compile time.
test/main/app/sessionPermissionAdapter.test.ts (2)

108-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the file and settings approval branches.

The current non-command test only covers the broker path at Line 73 of src/main/app/sessionPermissionAdapter.ts. The agent-filesystem branch and the deepchat-settings branch are untested. A test for a write approval with paths would assert that filePermissionService.approve receives the session ID, the paths, and false. That case is the main non-command path used by deferred file approvals.

🤖 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 `@test/main/app/sessionPermissionAdapter.test.ts` around lines 108 - 118,
Extend the non-command approval tests around port.approvePermission to cover the
agent-filesystem and deepchat-settings branches. Add a write approval with paths
and assert filePermissionService.approve receives the session ID, paths, and
false, while also adding coverage for the settings approval branch.

40-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer vi.resetAllMocks() for shared module-level mocks.

vi.clearAllMocks() clears call history but keeps implementations, including any queued mockReturnValueOnce values that a test did not consume. The mocks at Lines 6-29 are shared by every test in this file. An unconsumed queued value would leak into the next test. Use vi.resetAllMocks() to remove queued implementations as well.

♻️ Proposed change
   beforeEach(() => {
-    vi.clearAllMocks()
+    vi.resetAllMocks()
   })
🤖 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 `@test/main/app/sessionPermissionAdapter.test.ts` around lines 40 - 42, Replace
vi.clearAllMocks() in the shared beforeEach setup with vi.resetAllMocks() so
module-level mocks and any queued mockReturnValueOnce implementations are reset
between tests.
src/main/tool/permission/commandPermissionService.ts (1)

191-221: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

hasCmdControlSyntax never resets the expansion flags.

pendingPercentExpansion and pendingDelayedExpansion are set on the first occurrence and are never cleared. A command such as echo 50% off 20% reports control syntax. The result is fail-closed, so it only forces an extra approval prompt for CMD commands that contain two literal % or ! characters. Consider pairing the markers per token if the extra prompts prove noisy.

🤖 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/main/tool/permission/commandPermissionService.ts` around lines 191 - 221,
Update hasCmdControlSyntax so pendingPercentExpansion and
pendingDelayedExpansion are cleared when their corresponding expansion marker is
not immediately paired, preventing separate literal % or ! characters in the
same command from being treated as expansions. Preserve detection of genuinely
paired markers and existing control-character handling.
test/main/agent/shared/process/rtkRuntimeService.test.ts (1)

1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reset the spawn mock between tests.

The last test asserts expect(spawn).toHaveBeenCalledTimes(2). That assertion depends on no earlier test calling spawn. Today the other tests inject runCommand, so the count stays clean. A future test that reaches the real spawn path would make this assertion fail for an unrelated reason.

Add a beforeEach that calls vi.mocked(spawn).mockReset().

🤖 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 `@test/main/agent/shared/process/rtkRuntimeService.test.ts` around lines 1 -
11, Reset the child-process mock before each test by importing beforeEach and
adding a beforeEach hook that calls vi.mocked(spawn).mockReset(). Keep the
existing spawn mock and test behavior unchanged.
src/main/tool/agentTools/agentToolManager.ts (1)

2386-2396: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant second requireCommandShell call.

Line 2353 already validates options.commandShell and assigns the result to commandShell. Line 2395 validates that same validated value again. Use commandShell directly.

♻️ Proposed refactor
-        const requiredCommandShell = this.requireCommandShell(commandShell)

Then replace the later requiredCommandShell references with commandShell.

🤖 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/main/tool/agentTools/agentToolManager.ts` around lines 2386 - 2396,
Remove the redundant requireCommandShell call in the exec branch of the agent
tool manager. Reuse the already validated commandShell value from the earlier
options.commandShell validation, replacing all subsequent requiredCommandShell
references while preserving the existing execution behavior.
src/main/skill/skillExecutionService.ts (1)

702-731: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the duplicate cmd and powershell branches.

Both branches perform the same work. Only the error message differs. A single branch with a dialect-to-label lookup removes the duplication.

♻️ Proposed refactor
-    if (commandShell.dialect === 'cmd') {
-      if (plan.spawnMode === 'shell') {
-        throw new Error('Skill shell execution is unavailable under Command Prompt')
-      }
-      return {
-        ...plan,
-        shellCommand: undefined,
-        env: await rtkRuntimeService.prepareExecutionEnv(plan.env)
-      }
-    }
-
-    if (commandShell.dialect === 'powershell') {
-      if (plan.spawnMode === 'shell') {
-        throw new Error('Skill shell execution is unavailable under Windows PowerShell')
-      }
-      return {
-        ...plan,
-        shellCommand: undefined,
-        env: await rtkRuntimeService.prepareExecutionEnv(plan.env)
-      }
-    }
+    const directOnlyShellLabels: Partial<Record<CommandShellDialect, string>> = {
+      cmd: 'Command Prompt',
+      powershell: 'Windows PowerShell'
+    }
+    const directOnlyLabel = directOnlyShellLabels[commandShell.dialect]
+    if (directOnlyLabel) {
+      if (plan.spawnMode === 'shell') {
+        throw new Error(`Skill shell execution is unavailable under ${directOnlyLabel}`)
+      }
+      return {
+        ...plan,
+        shellCommand: undefined,
+        env: await rtkRuntimeService.prepareExecutionEnv(plan.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 `@src/main/skill/skillExecutionService.ts` around lines 702 - 731, Merge the
duplicate dialect handling in preparePlanForExecution into one branch for cmd
and powershell, using a dialect-to-label lookup to produce the dialect-specific
error message. Preserve the existing shell-mode rejection and returned plan
behavior, including shellCommand removal and environment preparation.
🤖 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/main/agent/deepchat/harness/createDeepChatAgentHarness.ts`:
- Around line 485-492: Update the ACP prompt assembly flow around
buildSystemPrompt to resolve the turn-scoped command shell once and reuse that
same resolved shell for every prompt build, rather than calling
commandShell.resolveForTurn() on each invocation. Preserve the shell instance
through refreshed prompts and subsequent tool execution so prompt guidance
remains consistent with the executing shell.

In `@src/main/agent/deepchat/runtime/dispatch.ts`:
- Around line 1242-1246: Protect the revokeOneShotCommandPermission call in the
finally block so failures cannot replace the value or error produced by run.
Catch revocation errors and log them through the existing logging mechanism,
while preserving the current grant?.kind === 'command' guard and one-shot
permission arguments.

In `@src/main/tool/index.ts`:
- Around line 398-399: Resolve a concrete default command shell before dispatch
in callFileSystemTool and preCheckToolPermission, then pass that resolved value
to AgentBashHandler.executeCommand and AgentFileSystemHandler instead of the
optional commandShell. Preserve explicitly supplied shells while ensuring
omitted values always provide the required commandShellPathStyle.

In `@src/renderer/settings/components/common/CommandShellSettingsSection.vue`:
- Around line 261-282: Update the interaction between saveOverride and
updatePreference so moving focus by keyboard from a dirty executable input to
the shell selector does not leave the selector unusable; either suppress the
blur save during selector focus transitions or serialize the override and
preference into one update. Add the smallest focused keyboard regression test
covering this user-visible behavior.

In `@test/main/agent/deepchat/loop/loopRun.test.ts`:
- Around line 30-48: Add a separate assertion in the createLoopRun test that
uses an invalid resources fixture with commandShell omitted, and verify it
throws. Keep the existing contradictory-shell assertion unchanged so both
missing and inconsistent command shell contracts are covered.

In `@test/main/agent/deepchat/resources/systemPromptBuilder.test.ts`:
- Around line 12-18: Update the test around buildSystemPromptWithSkills to
provide an observable optional prompt contributor, such as a mocked contributor
that records invocation, while retaining the invalid commandShell input. Assert
both that validation rejects and that the contributor was not called, proving
validation occurs before optional contributors execute.

In `@test/main/agent/shared/process/backgroundExecSessionManager.test.ts`:
- Around line 548-562: Update the parameterized test around manager.start to pin
process.platform to a non-Windows value before asserting missing or
contradictory command shells are rejected, ensuring the
WINDOWS_POWERSHELL_COMMAND_SHELL case remains invalid on every runner.

---

Outside diff comments:
In `@src/main/agent/deepchat/runtime/deferredToolExecutor.ts`:
- Around line 326-363: Require a non-empty parsedCommandShellProfile whenever a
oneShotCommandGrantId is supplied, before resolving the command shell in the
deferred execution flow. Reject the request with the existing error response
pattern if the grant lacks its matching shell profile, while preserving the
current resolveForTurn behavior for executions without a one-shot grant.

---

Nitpick comments:
In `@src/main/agent/deepchat/runtime/dispatch.ts`:
- Around line 1186-1188: Update the shellProfile assignment to retain and use
the typed value returned by CommandShellProfileSchema.safeParse instead of
casting request?.shellProfile. Preserve the undefined result when parsing fails,
while ensuring successful parsing passes the parsed data onward.

In `@src/main/skill/skillExecutionService.ts`:
- Around line 702-731: Merge the duplicate dialect handling in
preparePlanForExecution into one branch for cmd and powershell, using a
dialect-to-label lookup to produce the dialect-specific error message. Preserve
the existing shell-mode rejection and returned plan behavior, including
shellCommand removal and environment preparation.

In `@src/main/tool/agentTools/agentToolManager.ts`:
- Around line 2386-2396: Remove the redundant requireCommandShell call in the
exec branch of the agent tool manager. Reuse the already validated commandShell
value from the earlier options.commandShell validation, replacing all subsequent
requiredCommandShell references while preserving the existing execution
behavior.

In `@src/main/tool/permission/commandPermissionService.ts`:
- Around line 191-221: Update hasCmdControlSyntax so pendingPercentExpansion and
pendingDelayedExpansion are cleared when their corresponding expansion marker is
not immediately paired, preventing separate literal % or ! characters in the
same command from being treated as expansions. Preserve detection of genuinely
paired markers and existing control-character handling.

In `@src/shared/commandShell.ts`:
- Around line 107-110: Update normalizeAgentCommandShellConfig so an invalid
gitBashExecutableOverride falls back only to the default override while
preserving a valid explicit preference. Keep valid configurations unchanged,
retain auto as the preference only when it is absent or invalid, and ensure
corrupted stored settings do not silently downgrade a valid git-bash profile.

In `@src/shared/types/core/agent-events.ts`:
- Line 42: Replace the inline CommandShellProfile type references with a
top-level type import in both affected files: add import type {
CommandShellProfile } from '../../commandShell' in
src/shared/types/core/agent-events.ts (line 42) and
src/shared/types/core/llm-events.ts (line 332), then use CommandShellProfile for
each shellProfile field.

In `@test/main/agent/shared/process/rtkRuntimeService.test.ts`:
- Around line 1-11: Reset the child-process mock before each test by importing
beforeEach and adding a beforeEach hook that calls vi.mocked(spawn).mockReset().
Keep the existing spawn mock and test behavior unchanged.

In `@test/main/app/sessionPermissionAdapter.test.ts`:
- Around line 108-118: Extend the non-command approval tests around
port.approvePermission to cover the agent-filesystem and deepchat-settings
branches. Add a write approval with paths and assert
filePermissionService.approve receives the session ID, paths, and false, while
also adding coverage for the settings approval branch.
- Around line 40-42: Replace vi.clearAllMocks() in the shared beforeEach setup
with vi.resetAllMocks() so module-level mocks and any queued mockReturnValueOnce
implementations are reset between tests.

In `@test/main/cli/agentCommandAccess.test.ts`:
- Around line 103-141: Add a focused CMD test alongside the existing
caret-syntax case using `%deepchat_cli_agent_token%` in the command. Assert that
`AgentCliCommandAccess.createEnvironment` returns the unprivileged environment
with an empty local token and that `AgentCliTokenAuthority.snapshot()` reports
no issued tokens or conversations, covering CMD’s case-insensitive
token-expansion path.

In `@test/main/tool/agentTools/agentBashHandler.test.ts`:
- Around line 242-246: Replace the `(handler as never)` cast in the test call to
`prepareCommand` with a typed cast that explicitly exposes the private
`prepareCommand` method and its signature, preserving the existing invocation
and ensuring signature changes are caught at compile time.

In `@test/main/tool/agentTools/agentToolManagerRead.test.ts`:
- Around line 161-170: Update the rejection assertions for
callToolWithoutCommandShell and preCheckWithoutCommandShell to verify the
specific malformed shell validation error message, matching the sibling test’s
assertion near lines 142-147. Keep both cases covered while replacing broad
rejects.toThrow() checks with message-specific expectations.
🪄 Autofix

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: 0ee60492-cd5c-4346-994a-c5011e9ba533

📥 Commits

Reviewing files that changed from the base of the PR and between da5fc89 and 2918f8f.

📒 Files selected for processing (117)
  • docs/features/windows-command-shell/plan.md
  • docs/features/windows-command-shell/spec.md
  • docs/features/windows-command-shell/tasks.md
  • scripts/build-cli.mjs
  • src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts
  • src/main/agent/deepchat/harness/runtimeServices.ts
  • src/main/agent/deepchat/loop/loopRun.ts
  • src/main/agent/deepchat/loop/ports.ts
  • src/main/agent/deepchat/resources/systemEnvPromptBuilder.ts
  • src/main/agent/deepchat/resources/systemPromptBuilder.ts
  • src/main/agent/deepchat/runtime/compactionRuntimeCoordinator.ts
  • src/main/agent/deepchat/runtime/deepChatLoopRunner.ts
  • src/main/agent/deepchat/runtime/deferredToolExecutor.ts
  • src/main/agent/deepchat/runtime/dispatch.ts
  • src/main/agent/deepchat/runtime/interactionCoordinator.ts
  • src/main/agent/deepchat/runtime/process.ts
  • src/main/agent/deepchat/runtime/promptAssemblyService.ts
  • src/main/agent/deepchat/runtime/turnCoordinator.ts
  • src/main/agent/deepchat/runtime/types.ts
  • src/main/agent/shared/process/backgroundExecSessionManager.ts
  • src/main/agent/shared/process/commandShellPath.ts
  • src/main/agent/shared/process/commandShellService.ts
  • src/main/agent/shared/process/processTree.ts
  • src/main/agent/shared/process/rtkRuntimeService.ts
  • src/main/agent/shared/process/shellOutputEncoding.ts
  • src/main/app/composition.ts
  • src/main/app/sessionPermissionAdapter.ts
  • src/main/app/settingsRoutes.ts
  • src/main/cli/agentCommandAccess.ts
  • src/main/config/settingsStore.ts
  • src/main/remote/conversation/interaction.ts
  • src/main/remote/types.ts
  • src/main/session/contracts.ts
  • src/main/skill/skillExecutionService.ts
  • src/main/sync/index.ts
  • src/main/tool/agentTools/agentBashHandler.ts
  • src/main/tool/agentTools/agentFffSearchHandler.ts
  • src/main/tool/agentTools/agentFileSystemHandler.ts
  • src/main/tool/agentTools/agentToolManager.ts
  • src/main/tool/index.ts
  • src/main/tool/permission/commandPermissionCache.ts
  • src/main/tool/permission/commandPermissionService.ts
  • src/main/tool/permission/index.ts
  • src/renderer/api/SettingsClient.ts
  • src/renderer/settings/components/CommonSettings.vue
  • src/renderer/settings/components/common/CommandShellSettingsSection.vue
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/de-DE/settings.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/es-ES/settings.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/id-ID/settings.json
  • src/renderer/src/i18n/it-IT/settings.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/ms-MY/settings.json
  • src/renderer/src/i18n/pl-PL/settings.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/tr-TR/settings.json
  • src/renderer/src/i18n/vi-VN/settings.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/shared/commandShell.ts
  • src/shared/contracts/events.ts
  • src/shared/contracts/events/settings.events.ts
  • src/shared/contracts/routes.ts
  • src/shared/contracts/routes/settings.routes.ts
  • src/shared/types/core/agent-events.ts
  • src/shared/types/core/llm-events.ts
  • src/shared/types/core/mcp.ts
  • src/shared/types/mcp.ts
  • src/shared/types/tool.d.ts
  • test/helpers/commandShell.ts
  • test/main/agent/deepchat/harness/deepChatAgentHarness.test.ts
  • test/main/agent/deepchat/instance/deepChatAgentRuntime.test.ts
  • test/main/agent/deepchat/loop/contextCoordinator.test.ts
  • test/main/agent/deepchat/loop/deepChatLoopEngine.test.ts
  • test/main/agent/deepchat/loop/loopRun.test.ts
  • test/main/agent/deepchat/resources/systemEnvPromptBuilder.test.ts
  • test/main/agent/deepchat/resources/systemPromptBuilder.test.ts
  • test/main/agent/deepchat/runtime/compactionRuntimeCoordinator.test.ts
  • test/main/agent/deepchat/runtime/deferredToolExecutor.test.ts
  • test/main/agent/deepchat/runtime/dispatch.test.ts
  • test/main/agent/deepchat/runtime/messageProjectionService.test.ts
  • test/main/agent/deepchat/runtime/process.test.ts
  • test/main/agent/deepchat/runtime/promptAssemblyService.test.ts
  • test/main/agent/deepchat/runtime/runLifecycleCoordinator.test.ts
  • test/main/agent/shared/process/backgroundExecSessionManager.test.ts
  • test/main/agent/shared/process/commandShellPath.test.ts
  • test/main/agent/shared/process/commandShellService.test.ts
  • test/main/agent/shared/process/processTree.test.ts
  • test/main/agent/shared/process/rtkRuntimeService.test.ts
  • test/main/agent/shared/process/shellOutputEncoding.test.ts
  • test/main/app/sessionPermissionAdapter.test.ts
  • test/main/cli/agentCommandAccess.test.ts
  • test/main/evals/nativeAgent/harness.ts
  • test/main/routes/contracts.test.ts
  • test/main/routes/dispatcher.test.ts
  • test/main/scripts/buildCli.test.ts
  • test/main/session/runtimeIntegration.test.ts
  • test/main/session/session.integration.test.ts
  • test/main/skill/skillExecutionService.test.ts
  • test/main/sync/syncService.test.ts
  • test/main/tool/agentTools/agentBashHandler.test.ts
  • test/main/tool/agentTools/agentBashHandlerEncoding.test.ts
  • test/main/tool/agentTools/agentFffSearchHandler.test.ts
  • test/main/tool/agentTools/agentFileSystemHandler.test.ts
  • test/main/tool/agentTools/agentToolManagerFffSearch.test.ts
  • test/main/tool/agentTools/agentToolManagerRead.test.ts
  • test/main/tool/agentTools/agentToolManagerSkillAccess.test.ts
  • test/main/tool/permission/commandPermissionService.test.ts
  • test/renderer/api/clients.test.ts
  • test/renderer/components/CommandShellSettingsSection.test.ts

Comment thread src/main/agent/deepchat/harness/createDeepChatAgentHarness.ts
Comment thread src/main/agent/deepchat/runtime/dispatch.ts
Comment thread src/main/tool/index.ts
Comment thread src/renderer/settings/components/common/CommandShellSettingsSection.vue Outdated
Comment thread test/main/agent/deepchat/loop/loopRun.test.ts Outdated
Comment thread test/main/agent/deepchat/resources/systemPromptBuilder.test.ts
Comment thread test/main/agent/shared/process/backgroundExecSessionManager.test.ts
@yyhhyyyyyy
yyhhyyyyyy requested a review from zerob13 August 9, 2026 07:21

@zerob13 zerob13 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Update: I additionally validated the feature locally on Windows. Switching between Git Bash and Windows PowerShell works correctly, so the functional path is good. I am withdrawing the packaged-Windows/manual-validation concern from this review. The design direction and user-facing behavior are sound.

The remaining change request is limited to test portability.

On Windows at 36921f239ede33c70b43fccfd52e455819454862, I ran all 37 main-process test files changed by this PR. The result was 1131 passed, 10 failed, and 1 skipped. Six failures reproduce unchanged on dev, so I am not attributing those to this PR. Four failures are introduced here:

  • backgroundExecSessionManager.test.ts: the env merge, output spooling, and split UTF-8 tests now pass POSIX_COMMAND_SHELL, which the manager correctly rejects on win32.
  • agentFileSystemHandler.test.ts: the new POSIX case-sensitivity test runs against Windows containment semantics and expects the wrong result.

Please make those fixtures platform-valid: pin a non-Windows platform for tests that specifically assert POSIX semantics, or use the Windows shell/path fixture where the behavior is platform-independent. No additional test cases are needed. Once these four fixtures pass on Windows, I have no remaining blocker from this review.

@zerob13
zerob13 self-requested a review August 10, 2026 01:23
@zerob13
zerob13 merged commit dff1fe1 into dev Aug 10, 2026
12 checks passed
@zhangmo8
zhangmo8 deleted the feat/windows-command-shell branch August 10, 2026 02:37
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.

2 participants