Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Works with **any CLI agent** — if it runs in a terminal, it runs in Orca.
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="docs/assets/droid-logo.svg" alt="Droid logo" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Kilocode logo" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Kimi logo" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Kiro logo" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Mistral Vibe logo" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Qwen Code logo" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
1 change: 1 addition & 0 deletions docs/readme/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Funciona con **cualquier agente CLI** — si corre en una terminal, corre en Orc
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="../assets/droid-logo.svg" alt="Droid logo" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Kilocode logo" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Kimi logo" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Kiro logo" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Mistral Vibe logo" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Qwen Code logo" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
1 change: 1 addition & 0 deletions docs/readme/README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ Fonctionne avec **n'importe quel agent CLI** — s'il tourne dans un terminal, i
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="../assets/droid-logo.svg" alt="Logo Droid" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Logo Kilocode" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Logo Kimi" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Logo Kiro" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Logo Mistral Vibe" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Logo Qwen Code" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
1 change: 1 addition & 0 deletions docs/readme/README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ PR、Issue、プロジェクトボードをアプリ内で閲覧 — 任意の
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="../assets/droid-logo.svg" alt="Droid logo" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Kilocode logo" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Kimi logo" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Kiro logo" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Mistral Vibe logo" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Qwen Code logo" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
1 change: 1 addition & 0 deletions docs/readme/README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ diff의 어느 줄에든 코멘트를 남기고 에이전트에게 바로 보내
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="../assets/droid-logo.svg" alt="Droid logo" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Kilocode logo" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Kimi logo" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Kiro logo" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Mistral Vibe logo" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Qwen Code logo" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
1 change: 1 addition & 0 deletions docs/readme/README.pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Funciona com **qualquer agente CLI** — se roda em um terminal, roda no Orca.
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="../assets/droid-logo.svg" alt="Logotipo do Droid" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Logotipo do Kilocode" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Logotipo do Kimi" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Logotipo do Kiro" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Logotipo do Mistral Vibe" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Logotipo do Qwen Code" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
1 change: 1 addition & 0 deletions docs/readme/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ VS Code 的编辑器,处处自动保存 — 把文件或图片直接拖入智
<a href="https://docs.factory.ai/cli/getting-started/quickstart"><kbd><img src="../assets/droid-logo.svg" alt="Droid logo" width="16" valign="middle" /> Droid</kbd></a> &nbsp;
<a href="https://kilo.ai/docs/cli"><kbd><img src="https://raw.githubusercontent.com/Kilo-Org/kilocode/main/packages/kilo-vscode/assets/icons/kilo-light.svg" alt="Kilocode logo" width="16" valign="middle" /> Kilocode</kbd></a> &nbsp;
<a href="https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html"><kbd><img src="https://www.google.com/s2/favicons?domain=moonshot.cn&sz=64" alt="Kimi logo" width="16" valign="middle" /> Kimi</kbd></a> &nbsp;
<a href="https://zcode.z.ai/en/docs/install"><kbd><img src="https://www.google.com/s2/favicons?domain=z.ai&sz=64" alt="ZCode logo" width="16" valign="middle" /> ZCode</kbd></a> &nbsp;
<a href="https://kiro.dev/docs/cli/"><kbd><img src="https://www.google.com/s2/favicons?domain=kiro.dev&sz=64" alt="Kiro logo" width="16" valign="middle" /> Kiro</kbd></a> &nbsp;
<a href="https://github.com/mistralai/mistral-vibe"><kbd><img src="https://www.google.com/s2/favicons?domain=mistral.ai&sz=64" alt="Mistral Vibe logo" width="16" valign="middle" /> Mistral Vibe</kbd></a> &nbsp;
<a href="https://github.com/QwenLM/qwen-code"><kbd><img src="https://www.google.com/s2/favicons?domain=qwenlm.github.io&sz=64" alt="Qwen Code logo" width="16" valign="middle" /> Qwen Code</kbd></a> &nbsp;
Expand Down
9 changes: 6 additions & 3 deletions mobile/src/tasks/mobile-tui-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export const MOBILE_TUI_AGENT_AUTO_PICK_ORDER = [
'rovo',
'hermes',
'devin',
'openclaw'
'openclaw',
'zcode'
] as const satisfies readonly TuiAgent[]

export const MOBILE_TUI_AGENT_LABELS: Record<TuiAgent, string> = {
Expand Down Expand Up @@ -76,7 +77,8 @@ export const MOBILE_TUI_AGENT_LABELS: Record<TuiAgent, string> = {
rovo: 'Rovo Dev',
hermes: 'Hermes',
devin: 'Devin',
openclaw: 'OpenClaw'
openclaw: 'OpenClaw',
zcode: 'ZCode'
}

export const MOBILE_TUI_AGENT_FAVICON_DOMAINS: Partial<Record<TuiAgent, string>> = {
Expand Down Expand Up @@ -109,7 +111,8 @@ export const MOBILE_TUI_AGENT_FAVICON_DOMAINS: Partial<Record<TuiAgent, string>>
rovo: 'atlassian.com',
hermes: 'nousresearch.com',
devin: 'devin.ai',
openclaw: 'openclaw.ai'
openclaw: 'openclaw.ai',
zcode: 'z.ai'
}

export function isMobileTuiAgent(value: unknown): value is TuiAgent {
Expand Down
9 changes: 8 additions & 1 deletion src/main/agent-hooks/agent-hook-memory-sftp.test-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export type AgentHookMemoryFileSystem = {
files: Map<string, string>
dirs: Set<string>
modes: Map<string, number>
/** Why: tests inject rename failures (e.g. Codex trust write) without a real SFTP. */
failRenameTo: Set<string>
}

export function createAgentHookMemorySftp(initialFiles: Record<string, string> = {}): {
Expand All @@ -13,7 +15,8 @@ export function createAgentHookMemorySftp(initialFiles: Record<string, string> =
const fs: AgentHookMemoryFileSystem = {
files: new Map(Object.entries(initialFiles)),
dirs: new Set(['/']),
modes: new Map()
modes: new Map(),
failRenameTo: new Set()
}
const missing = (path: string): { code: number; message: string } => ({
code: 2,
Expand Down Expand Up @@ -41,6 +44,10 @@ export function createAgentHookMemorySftp(initialFiles: Record<string, string> =
done(null)
},
rename: (source: string, target: string, done: (error: unknown) => void) => {
if (fs.failRenameTo.has(target)) {
done({ code: 4, message: `rename failed ${target}` })
return
}
const content = fs.files.get(source)
if (content === undefined) {
done(missing(source))
Expand Down
10 changes: 7 additions & 3 deletions src/main/agent-hooks/managed-agent-hook-controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { grokHookService } from '../grok/hook-service'
import { hermesHookService } from '../hermes/hook-service'
import { kimiHookService } from '../kimi/hook-service'
import { openClaudeHookService } from '../openclaude/hook-service'
import { zcodeHookService } from '../zcode/hook-service'

export type ManagedAgentHookInstaller = readonly [HookInstallAgent, () => void]
type ManagedHookRemover = readonly [HookInstallAgent, () => AgentHookInstallStatus]
Expand All @@ -34,7 +35,8 @@ export const MANAGED_AGENT_HOOK_INSTALLERS: readonly ManagedAgentHookInstaller[]
['copilot', () => copilotHookService.install()],
['hermes', () => hermesHookService.install()],
['devin', () => devinHookService.install()],
['kimi', () => kimiHookService.install()]
['kimi', () => kimiHookService.install()],
['zcode', () => zcodeHookService.install()]
]

const LOCAL_MANAGED_HOOK_REMOVERS: readonly ManagedHookRemover[] = [
Expand All @@ -51,7 +53,8 @@ const LOCAL_MANAGED_HOOK_REMOVERS: readonly ManagedHookRemover[] = [
['copilot', () => copilotHookService.remove()],
['hermes', () => hermesHookService.remove()],
['devin', () => devinHookService.remove()],
['kimi', () => kimiHookService.remove()]
['kimi', () => kimiHookService.remove()],
['zcode', () => zcodeHookService.remove()]
]

const LOCAL_MANAGED_HOOK_STATUS_READERS: readonly ManagedHookStatusReader[] = [
Expand All @@ -68,7 +71,8 @@ const LOCAL_MANAGED_HOOK_STATUS_READERS: readonly ManagedHookStatusReader[] = [
['copilot', () => copilotHookService.getStatus()],
['hermes', () => hermesHookService.getStatus()],
['devin', () => devinHookService.getStatus()],
['kimi', () => kimiHookService.getStatus()]
['kimi', () => kimiHookService.getStatus()],
['zcode', () => zcodeHookService.getStatus()]
]

export function isAgentStatusHooksEnabled(
Expand Down
120 changes: 26 additions & 94 deletions src/main/agent-hooks/remote-hook-service-installers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,106 +20,17 @@ import { CopilotHookService, copilotHookService } from '../copilot/hook-service'
import { HermesHookService, hermesHookService } from '../hermes/hook-service'
import { DevinHookService, devinHookService } from '../devin/hook-service'
import { KimiHookService, kimiHookService } from '../kimi/hook-service'
import { ZcodeHookService, zcodeHookService } from '../zcode/hook-service'
import { openClaudeHookService } from '../openclaude/hook-service'
import { MANAGED_AGENT_HOOK_INSTALLERS } from './managed-agent-hook-controls'
import {
installRemoteManagedAgentHooks,
REMOTE_MANAGED_HOOK_INSTALLER_AGENTS
} from './remote-managed-hook-installers'
import { createAgentHookMemorySftp } from './agent-hook-memory-sftp.test-fixture'

type FakeFs = {
files: Map<string, string>
dirs: Set<string>
modes: Map<string, number>
failRenameTo: Set<string>
}

function createFakeSftp(initialFiles: Record<string, string> = {}): {
sftp: SFTPWrapper
fs: FakeFs
} {
const fs: FakeFs = {
files: new Map(Object.entries(initialFiles)),
dirs: new Set(['/']),
modes: new Map(),
failRenameTo: new Set()
}
const noEntryError = (path: string): { code: number; message: string } => ({
code: 2,
message: `ENOENT ${path}`
})
const fakeStats = (mode: number): { mode: number } => ({ mode })

const sftp = {
readFile: (path: string, _enc: string, cb: (err: unknown, data?: string) => void): void => {
const v = fs.files.get(path)
if (v === undefined) {
cb(noEntryError(path))
return
}
cb(null, v)
},
writeFile: (
path: string,
content: string,
options: string | { mode?: number },
cb: (err: unknown) => void
): void => {
fs.files.set(path, content)
if (typeof options !== 'string' && options.mode !== undefined) {
fs.modes.set(path, options.mode)
}
cb(null)
},
rename: (src: string, dst: string, cb: (err: unknown) => void): void => {
if (fs.failRenameTo.has(dst)) {
cb({ code: 4, message: `rename failed ${dst}` })
return
}
const v = fs.files.get(src)
if (v === undefined) {
cb(noEntryError(src))
return
}
fs.files.set(dst, v)
fs.files.delete(src)
const mode = fs.modes.get(src)
if (mode !== undefined) {
fs.modes.set(dst, mode)
fs.modes.delete(src)
}
cb(null)
},
unlink: (path: string, cb: (err: unknown) => void): void => {
fs.files.delete(path)
fs.modes.delete(path)
cb(null)
},
chmod: (path: string, mode: number, cb: (err: unknown) => void): void => {
fs.modes.set(path, mode)
cb(null)
},
stat: (path: string, cb: (err: unknown, stats?: { mode: number }) => void): void => {
if (!fs.files.has(path)) {
cb(noEntryError(path))
return
}
cb(null, fakeStats(fs.modes.get(path) ?? 0o100644))
},
readdir: (path: string, cb: (err: unknown, list?: { filename: string }[]) => void): void => {
if (fs.dirs.has(path)) {
cb(null, [])
return
}
cb(noEntryError(path))
},
mkdir: (path: string, cb: (err: unknown) => void): void => {
fs.dirs.add(path)
cb(null)
}
} as unknown as SFTPWrapper
return { sftp, fs }
}
// Why: local alias keeps existing call sites short; fixture is shared with other SFTP hook tests.
const createFakeSftp = createAgentHookMemorySftp

describe('remote hook service installers', () => {
it('always writes POSIX scripts for SSH remotes even from a Windows host', async () => {
Expand Down Expand Up @@ -499,6 +410,26 @@ describe('remote hook service installers', () => {
expect(fs.files.get('/home/dev/.orca/agent-hooks/kimi-hook.sh')).toContain('/hook/kimi')
})

it('installs remote ZCode hooks into ~/.zcode/cli/config.json with hooks.enabled', async () => {
const { sftp, fs } = createFakeSftp({
'/home/dev/.zcode/cli/config.json': '{"theme":"dark","hooks":{"enabled":false}}'
})
const status = await new ZcodeHookService().installRemote(sftp, '/home/dev')
expect(status.state).toBe('installed')
const config = JSON.parse(fs.files.get('/home/dev/.zcode/cli/config.json')!) as {
theme?: string
hooks?: { enabled?: boolean; events?: Record<string, unknown[]> }
}
expect(config.theme).toBe('dark')
expect(config.hooks?.enabled).toBe(true)
expect(config.hooks?.events?.UserPromptSubmit).toBeDefined()
expect(config.hooks?.events?.Stop).toBeDefined()
expect(fs.files.get('/home/dev/.zcode/cli/config.json')).toContain(
'/home/dev/.orca/agent-hooks/zcode-hook.sh'
)
expect(fs.files.get('/home/dev/.orca/agent-hooks/zcode-hook.sh')).toContain('/hook/zcode')
})

it('does not overwrite malformed remote Devin JSONC', async () => {
const original = '{"hooks": }'
const { sftp, fs } = createFakeSftp({
Expand Down Expand Up @@ -687,7 +618,8 @@ describe('remote hook service installers', () => {
['copilot', copilotHookService],
['hermes', hermesHookService],
['devin', devinHookService],
['kimi', kimiHookService]
['kimi', kimiHookService],
['zcode', zcodeHookService]
])

// Guard against a service silently missing from the map above as new agents land.
Expand Down
Loading