perf(agent-hooks): drop the extra PowerShell per hook event for Gemini, dsh, Kiro, Droid and Command Code (#27717)

* perf(hooks): register direct .cmd hooks for Gemini, DSH, Kiro, Droid and Command Code on Windows

On a cmd-safe profile path these agents now register the managed .cmd path
itself instead of a `powershell.exe -EncodedCommand` launcher, so each hook
event no longer pays a second PowerShell start-up on top of the runner's own
shell. Profiles the bare path cannot carry (spaces, non-ASCII, UNC) keep the
encoded launcher. Same pattern as Claude (#18875) and Muse (#26755).

Refs #20913

* refactor(hooks): one Windows batch-hook helper and one payload-delivery table

- Add wrapWindowsBatchHookCommand (bare .cmd path, encoded launcher
  otherwise) and use it for Muse, Gemini, DSH, Kiro, Droid and Command Code.
- Fold the five agents into windows-hook-payload-delivery.test.ts as rows
  run under each vendor's shell, from a plain and a spaced profile, with a
  stale encoded registration seeded per row; drop the separate runner suite
  and its CI list entries.
- Replace the Windows-only per-agent registration tests with ones that stub
  process.platform, so they run on every CI OS.
- Fix stale launcher, helper and DSH comments.

Refs #20913

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
This commit is contained in:
OrcaWin
2026-10-12 11:37:25 -07:00
committed by GitHub
co-authored by Orca Worker
parent 982b3d4234
commit c62c5a4cfc
16 changed files with 394 additions and 179 deletions
+22 -25
View File
@@ -20,8 +20,7 @@ vi.mock('os', async () => {
import { CommandCodeHookService } from './hook-service'
const WINDOWS_POWERSHELL_LAUNCHER =
/^[A-Za-z]:\/[^"]*\/System32\/WindowsPowerShell\/v1\.0\/powershell\.exe -NoProfile -EncodedCommand \S+$/
import { wrapWindowsBatchHookCommand } from '../agent-hooks/windows-direct-cmd-hook-command'
describe('CommandCodeHookService', () => {
let homeDir: string
@@ -52,7 +51,11 @@ describe('CommandCodeHookService', () => {
expect(config.hooks.PostToolUse[0].matcher).toBe('.*')
expect(config.hooks.Stop[0].matcher).toBeUndefined()
expect(config.hooks.PreToolUse[0].hooks[0].command).toMatch(
process.platform === 'win32' ? WINDOWS_POWERSHELL_LAUNCHER : /command-code-hook/
process.platform === 'win32'
? wrapWindowsBatchHookCommand(
join(homeDir, '.orca', 'agent-hooks', 'command-code-hook.cmd')
)
: /command-code-hook/
)
if (process.platform !== 'win32') {
expect(config.hooks.PreToolUse[0].hooks[0].command).toContain(join(homeDir, '.orca'))
@@ -62,30 +65,24 @@ describe('CommandCodeHookService', () => {
}
})
// Why: #6078 — a Windows user profile path with a space used to be written
// verbatim as the hook command, so the agent split it at the space. The
// managed command must use an encoded launcher so the path never appears raw
// on the cmd.exe command line.
it.skipIf(process.platform !== 'win32')(
'wraps the managed hook command to survive spaces in the profile path (#6078)',
() => {
const spaceHome = join(tmpdir(), 'orca command-code home with spaces')
mkdirSync(spaceHome, { recursive: true })
homedirMock.mockReturnValue(spaceHome)
try {
expect(new CommandCodeHookService().install().state).toBe('installed')
const config = JSON.parse(
readFileSync(join(spaceHome, '.commandcode', 'settings.json'), 'utf8')
) as { hooks: Record<string, { hooks: { command: string }[] }[]> }
const command = config.hooks.PreToolUse[0].hooks[0].command
expect(command).toMatch(WINDOWS_POWERSHELL_LAUNCHER)
} finally {
rmSync(spaceHome, { recursive: true, force: true })
// Why: runs on every OS; windows-hook-payload-delivery.test.ts runs each form under Command Code's shell.
it('registers the Windows batch hook command for every event', () => {
const platform = vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
try {
expect(new CommandCodeHookService().install().state).toBe('installed')
const command = wrapWindowsBatchHookCommand(
join(homeDir, '.orca', 'agent-hooks', 'command-code-hook.cmd')
)
const config = JSON.parse(
readFileSync(join(homeDir, '.commandcode', 'settings.json'), 'utf8')
)
for (const eventName of ['PreToolUse', 'PostToolUse', 'Stop']) {
expect(config.hooks[eventName][0].hooks[0].command).toBe(command)
}
} finally {
platform.mockRestore()
}
)
})
it('installs a hook script that can recover the endpoint when Command Code strips token env', () => {
new CommandCodeHookService().install()
+2 -2
View File
@@ -9,11 +9,11 @@ import {
readHooksJson,
removeManagedCommands,
wrapPosixHookCommand,
wrapWindowsHookCommand,
writeHooksJson,
writeManagedScript,
type HookDefinition
} from '../agent-hooks/installer-utils'
import { wrapWindowsBatchHookCommand } from '../agent-hooks/windows-direct-cmd-hook-command'
import { refreshManagedScriptIfPresent } from '../agent-hooks/managed-hook-script-refresh'
import {
readHooksJsonRemote,
@@ -48,7 +48,7 @@ function getManagedScriptPath(): string {
function getManagedCommand(scriptPath: string): string {
return process.platform === 'win32'
? wrapWindowsHookCommand(scriptPath)
? wrapWindowsBatchHookCommand(scriptPath)
: wrapPosixHookCommand(scriptPath)
}