fix(agent-hooks): register forward-slash Windows hook paths for Grok, Devin, ZCode and Antigravity (#27713)

* fix(agent-hooks): register forward-slash Windows hook paths for Grok, Devin, ZCode and Antigravity

These four agents registered their native-Windows status hook as a bare
backslash path. A Git Bash hook host strips the backslashes, the command exits
127, and no status event reaches Orca. Use the same direct forward-slash form
Claude, Codex and Muse already register, keeping the cmd-safe gate and the
encoded PowerShell fallback. Existing backslash entries still match the
managed-command matcher, so the next install replaces them and keeps user hooks.

* 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

* test(agent-hooks): expect forward-slash Antigravity hooks after the Unicode upgrade

ASCII profiles now register the direct forward-slash wrapper path, so the
upgrade-delivery test expects that form and also runs it through Git Bash.
Unicode profiles keep the encoded launcher.

* test(agent-hooks): assert the registered hook lives under the mocked home

Pins that the os mock reaches the agents' node:os imports, so the host test
never installs into the real profile.

* 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

* refactor(agent-hooks): register Grok, Devin, ZCode and Antigravity through wrapWindowsBatchHookCommand

Use the shared batch-hook helper from #27717 instead of an inline direct-or-cmd
fallback. A UNC home is cmd-safe but has no drive letter; the old fallback
registered it as a bare backslash path that Git Bash cannot start, while the
helper sends it to the encoded launcher.

wrapWindowsCmdHookCommand has no production callers left, so it is removed and
its tests point at the helper. Grok, Devin and ZCode join the Windows payload
delivery table with a legacy backslash entry to sweep, replacing the separate
host test, and the Antigravity migration test now runs on every platform.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
This commit is contained in:
OrcaWin
2026-10-12 14:31:04 -07:00
committed by GitHub
co-authored by Orca Worker
parent c0e2b73395
commit 889ddcc6bd
18 changed files with 227 additions and 100 deletions
+33 -1
View File
@@ -12,7 +12,12 @@ vi.mock('node:os', async (importOriginal) => {
vi.mock('electron', () => ({ app: { getPath: () => hoisted.home } }))
import { zcodeHookService } from './hook-service'
import { getZCodeConfigPath, ZCODE_HOOK_EVENTS } from './hook-settings'
import {
applyZCodeManagedHooks,
getZCodeConfigPath,
getZCodeManagedCommand,
ZCODE_HOOK_EVENTS
} from './hook-settings'
type ManagedHookEntry = { type: string; command: string; timeout?: number }
type ZCodeConfigFile = {
@@ -159,6 +164,33 @@ describe('ZCodeHookService', () => {
})
})
it('registers a forward-slash cmd path on Windows and replaces the legacy backslash entry', () => {
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
const scriptPath = 'C:\\Users\\ada\\.orca\\agent-hooks\\zcode-hook.cmd'
const command = getZCodeManagedCommand(scriptPath)
// Why: Git Bash strips backslashes from a bare path; forward slashes survive every host.
expect(command).toBe('C:/Users/ada/.orca/agent-hooks/zcode-hook.cmd')
const next = applyZCodeManagedHooks(
{
hooks: {
events: {
Stop: [
{ hooks: [{ type: 'command', command: scriptPath }] },
{ hooks: [{ type: 'command', command: 'C:\\tools\\user-hook.cmd' }] }
]
}
}
},
command,
'zcode-hook.cmd'
)
expect(next.hooks?.events?.Stop).toEqual([
{ hooks: [{ type: 'command', command: 'C:\\tools\\user-hook.cmd' }] },
{ hooks: [expect.objectContaining({ command })] }
])
})
it('writes an executable managed hook script that posts to the ZCode endpoint', () => {
zcodeHookService.install()
const scriptPath = join(
+2 -4
View File
@@ -8,9 +8,9 @@ import {
isPlainObject,
removeManagedCommands,
wrapPosixHookCommand,
wrapWindowsCmdHookCommand,
type HookDefinition
} from '../agent-hooks/installer-utils'
import { wrapWindowsBatchHookCommand } from '../agent-hooks/windows-direct-cmd-hook-command'
const ZCODE_SCRIPT_BASE = 'zcode-hook'
@@ -72,9 +72,7 @@ export function getZCodeManagedScriptPath(): string {
export function getZCodeManagedCommand(scriptPath: string): string {
if (process.platform === 'win32') {
// Why: ZCode spawns a `type: "command"` hook through its own shell resolver, so keep the
// bare directly-spawnable .cmd on the safe path and fall back to the encoded form otherwise.
return wrapWindowsCmdHookCommand(scriptPath)
return wrapWindowsBatchHookCommand(scriptPath)
}
return wrapPosixHookCommand(scriptPath)
}