From e732fdf550bf38575679c147b60af76c08f3006e Mon Sep 17 00:00:00 2001 From: sunganhao8-lgtm Date: Sun, 20 Sep 2026 14:26:51 +0800 Subject: [PATCH] feat(agents): add ZCode CLI as a supported TUI agent Add Z.ai's ZCode CLI to the built-in agent roster, following the same shape as the Trae addition: launch config, telemetry kind, skills CLI mapping, display names, bundled icon, mobile assets, i18n labels, docs. Detection runs on the standard `zcode` command. ZCode has no argv or prefill prompt form (unknown positionals exit with `Unknown command`), so prompts inject via the stdin-after-start path like Kimi/Aider. Usage/rate-limit tracking is intentionally out of scope: Z.ai does not publish a usage API for the coding plan yet, unlike Kimi's documented managed-usage endpoint. --- docs/site/content/docs/agents/supported.mdx | 1 + .../src/components/mobile-agent-icon-assets.ts | 1 + mobile/src/tasks/mobile-tui-agents.ts | 1 + src/renderer/src/i18n/locales/en.json | 1 + src/renderer/src/i18n/locales/es.json | 1 + src/renderer/src/i18n/locales/ja.json | 1 + src/renderer/src/i18n/locales/ko.json | 1 + src/renderer/src/i18n/locales/zh.json | 1 + src/renderer/src/lib/agent-catalog.tsx | 7 +++++++ src/renderer/src/lib/agent-favicon-assets.ts | 2 ++ src/renderer/src/lib/agent-status.test.ts | 5 +++++ src/renderer/src/lib/agent-status.ts | 3 ++- src/renderer/src/lib/tui-agent-startup.test.ts | 17 +++++++++++++++++ src/shared/agent-headless-command.ts | 7 ++++--- src/shared/agent-icons/zcode.png | Bin 0 -> 2221 bytes src/shared/agent-kind.ts | 3 ++- src/shared/agent-type-label.ts | 1 + src/shared/skills-cli-agent-keys.ts | 3 ++- src/shared/telemetry-property-schemas.ts | 1 + src/shared/tui-agent-config.ts | 6 ++++++ src/shared/tui-agent-display-names.ts | 1 + src/shared/tui-agent-permissions.ts | 2 ++ src/shared/tui-agent-selection.ts | 1 + src/shared/tui-agent.ts | 1 + 24 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 src/shared/agent-icons/zcode.png diff --git a/docs/site/content/docs/agents/supported.mdx b/docs/site/content/docs/agents/supported.mdx index 1d2f4964577b..cf174afebf8a 100644 --- a/docs/site/content/docs/agents/supported.mdx +++ b/docs/site/content/docs/agents/supported.mdx @@ -59,3 +59,4 @@ To restore prompts for one agent only, edit that agent's default arguments or en | Hermes | Auto-setup | [Nous](https://hermes-agent.nousresearch.com/docs/) | | OpenClaw | Auto-setup | [OpenClaw](https://github.com/openclaw/openclaw) | | Trae | Auto-setup via `traecli` (TRAE CN CLI) | [Trae](https://www.trae.ai/) | +| ZCode | Auto-setup | [Z.ai](https://zcode.z.ai) | diff --git a/mobile/src/components/mobile-agent-icon-assets.ts b/mobile/src/components/mobile-agent-icon-assets.ts index 38a48373734b..003dcc61401e 100644 --- a/mobile/src/components/mobile-agent-icon-assets.ts +++ b/mobile/src/components/mobile-agent-icon-assets.ts @@ -17,6 +17,7 @@ export const MOBILE_AGENT_ICON_ASSETS: Partial> 'mimo-code': 'mimo.xiaomi.com', ante: 'antigma.ai', trae: 'www.trae.cn', + zcode: 'z.ai', omp: 'omp.sh', 'prime-agent': 'primeintellect.ai', gemini: 'gemini.google.com', diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 487c8f9bdbe2..49cff6f1815a 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -606,6 +606,7 @@ "fc80296033": "Devin", "da41abbdd4": "Ante", "060d152fb5": "Trae", + "e6fdf4b9e0": "ZCode", "d443a47995": "Prime Agent", "mimo_code_label": "MiMo Code", "opencode2_label": "OpenCode 2" diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index cfccde752809..d304002501c1 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -354,6 +354,7 @@ "fc80296033": "Devin", "da41abbdd4": "Ante", "060d152fb5": "Trae", + "e6fdf4b9e0": "ZCode", "d443a47995": "Prime Agent", "mimo_code_label": "MiMo Code" }, diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 0a3b2ee6c867..5f61e56bc51e 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -354,6 +354,7 @@ "fc80296033": "Devin", "da41abbdd4": "Ante", "060d152fb5": "Trae", + "e6fdf4b9e0": "ZCode", "d443a47995": "Prime Agent", "mimo_code_label": "MiMo Code" }, diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 91aa9d8d74bb..b59b408cf594 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -357,6 +357,7 @@ "fc80296033": "Devin", "da41abbdd4": "Ante", "060d152fb5": "Trae", + "e6fdf4b9e0": "ZCode", "d443a47995": "Prime Agent", "mimo_code_label": "MiMo Code" }, diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index f460f007b2cd..0ff49f17951a 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -357,6 +357,7 @@ "fc80296033": "Devin", "da41abbdd4": "Ante", "060d152fb5": "Trae", + "e6fdf4b9e0": "ZCode", "d443a47995": "Prime Agent", "mimo_code_label": "MiMo Code" }, diff --git a/src/renderer/src/lib/agent-catalog.tsx b/src/renderer/src/lib/agent-catalog.tsx index 8217517ad8be..a175746e9055 100644 --- a/src/renderer/src/lib/agent-catalog.tsx +++ b/src/renderer/src/lib/agent-catalog.tsx @@ -120,6 +120,13 @@ export const getAgentCatalog = createLocalizedCatalog((): AgentCatalogEntry[] => faviconDomain: 'www.trae.cn', homepageUrl: 'https://docs.trae.cn/cli_get-started-with-trae-cli' }, + { + id: 'zcode', + label: translate('auto.lib.agent.catalog.e6fdf4b9e0', 'ZCode'), + cmd: 'zcode', + faviconDomain: 'z.ai', + homepageUrl: 'https://zcode.z.ai' + }, { id: 'pi', label: translate('auto.lib.agent.catalog.302934c5d9', 'Pi'), diff --git a/src/renderer/src/lib/agent-favicon-assets.ts b/src/renderer/src/lib/agent-favicon-assets.ts index 565437f75bef..13a8bbca8622 100644 --- a/src/renderer/src/lib/agent-favicon-assets.ts +++ b/src/renderer/src/lib/agent-favicon-assets.ts @@ -3,6 +3,7 @@ import grokUrl from '../../../shared/agent-icons/grok.png?url' import mimoCodeUrl from '../../../shared/agent-icons/mimo-code.png?url' import anteUrl from '../../../shared/agent-icons/ante.png?url' import traeUrl from '../../../shared/agent-icons/trae.png?url' +import zcodeUrl from '../../../shared/agent-icons/zcode.png?url' import primeAgentUrl from '../../../shared/agent-icons/prime-agent.png?url' import geminiUrl from '../../../shared/agent-icons/gemini.png?url' import antigravityUrl from '../../../shared/agent-icons/antigravity.png?url' @@ -37,6 +38,7 @@ export const AGENT_FAVICON_ASSETS: Partial> = { 'mimo-code': mimoCodeUrl, ante: anteUrl, trae: traeUrl, + zcode: zcodeUrl, 'prime-agent': primeAgentUrl, gemini: geminiUrl, antigravity: antigravityUrl, diff --git a/src/renderer/src/lib/agent-status.test.ts b/src/renderer/src/lib/agent-status.test.ts index 8bd1314d5774..333f9e1a2c84 100644 --- a/src/renderer/src/lib/agent-status.test.ts +++ b/src/renderer/src/lib/agent-status.test.ts @@ -866,6 +866,10 @@ describe('formatAgentTypeLabel', () => { expect(formatAgentTypeLabel('trae')).toBe('Trae') }) + it("maps 'zcode' to 'ZCode'", () => { + expect(formatAgentTypeLabel('zcode')).toBe('ZCode') + }) + it("maps 'prime-agent' to 'Prime Agent'", () => { expect(formatAgentTypeLabel('prime-agent')).toBe('Prime Agent') }) @@ -895,6 +899,7 @@ describe('agentTypeToIconAgent', () => { expect(agentTypeToIconAgent('command-code')).toBe('command-code') expect(agentTypeToIconAgent('ante')).toBe('ante') expect(agentTypeToIconAgent('trae')).toBe('trae') + expect(agentTypeToIconAgent('zcode')).toBe('zcode') expect(agentTypeToIconAgent('prime-agent')).toBe('prime-agent') }) diff --git a/src/renderer/src/lib/agent-status.ts b/src/renderer/src/lib/agent-status.ts index 3af7b38def92..d6b00492f234 100644 --- a/src/renderer/src/lib/agent-status.ts +++ b/src/renderer/src/lib/agent-status.ts @@ -134,7 +134,8 @@ const ICONABLE_AGENT_TYPES: Record = { grok: true, devin: true, ante: true, - trae: true + trae: true, + zcode: true } // Why: return null (not a 'claude' fallback) for unknown so Codex panes don't flash the Claude icon before the hook fires. diff --git a/src/renderer/src/lib/tui-agent-startup.test.ts b/src/renderer/src/lib/tui-agent-startup.test.ts index 422d7a8f6b44..89a9d4ca454d 100644 --- a/src/renderer/src/lib/tui-agent-startup.test.ts +++ b/src/renderer/src/lib/tui-agent-startup.test.ts @@ -144,6 +144,23 @@ describe('buildAgentStartupPlan', () => { ).toBe("traecli -- 'help me name this config'") }) + it('launches ZCode bare and injects the draft prompt after startup', () => { + expect( + buildAgentStartupPlan({ + agent: 'zcode', + prompt: 'Summarize the failing tests', + cmdOverrides: {}, + platform: 'linux' + }) + ).toEqual({ + agent: 'zcode', + launchCommand: 'zcode', + expectedProcess: 'zcode', + followupPrompt: 'Summarize the failing tests', + launchConfig: emptyLaunchConfig('zcode') + }) + }) + it('passes the prompt to Prime Agent as a positional argv behind a `--` separator', () => { expect( buildAgentStartupPlan({ diff --git a/src/shared/agent-headless-command.ts b/src/shared/agent-headless-command.ts index 9ff40e6af1d5..8b629e411262 100644 --- a/src/shared/agent-headless-command.ts +++ b/src/shared/agent-headless-command.ts @@ -4,15 +4,16 @@ import { isPrintModeHeadlessOneShotCommand } from './print-mode-headless-command import type { TuiAgent } from './tui-agent' // Why: a table (not an if-chain) so adding an agent is one entry; Claude and Trae share -// the same `--print` one-shot contract, Ante's `--prompt` form and Prime Agent's -// `--mode` forms need their own matchers. +// the same `--print` one-shot contract, Ante's and ZCode's `--prompt` form and Prime +// Agent's `--mode` forms need their own matchers. const HEADLESS_ONE_SHOT_MATCHERS: Partial< Record boolean> > = { claude: isPrintModeHeadlessOneShotCommand, trae: isPrintModeHeadlessOneShotCommand, 'prime-agent': isPrimeAgentHeadlessOneShotCommand, - ante: isAnteHeadlessOneShotCommand + ante: isAnteHeadlessOneShotCommand, + zcode: isAnteHeadlessOneShotCommand } export function isHeadlessOneShotAgentCommand(agent: TuiAgent, tokens: readonly string[]): boolean { diff --git a/src/shared/agent-icons/zcode.png b/src/shared/agent-icons/zcode.png new file mode 100644 index 0000000000000000000000000000000000000000..100269a57d26613f7de1a7c647328f8028f423c2 GIT binary patch literal 2221 zcmV;e2vYZnP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2tr9jK~#8N?O9uF z6xSKvvvb?o+1;7hoij5wf+Pw=p@M`)DhAS|RB7u*%0uC$k&>6vMnO?f6{k|6ZCr4` zA_!LMOIo0bNKqn@r-sG~F9E4(9uN;iNGO=LT+$a5TDipyK^Xh5{xdsf{m<@P)<$Hj z{iIJbK4<>R_s#Ll@i>?3p@$xN=%I&>!1?0)d0*ck_q-puu0Q0seZO#Af0%pTcJ6sQ z8pn|aU*(>+lY9Oy$Mr|s5$Cz*A@TY!b~Yd8$2wRK>x%lE-%oqHW2n^@_q)D7?0Wtu z-1DbgzYn~={<}u3FZ%HO)2{Eo;y6wp?RmlAxb9E6=l{#a!k&+XfqNcs*9*?^7&07> zp)=?D4&(7U&lg6xem{=?lY8EawDBgH&TIY`eBO zFrWr@wH6%1^N_Wga9KA>pcZt{;;6S6YtXc9k*8FtMCOv>%cdn_n|q4%=x{Kr)lF1m zENr{BwK=iXlr%iYqiNZ&V&y|Hux2f+Ub6;P53GT2tR6_s#~%L{ELyw-ibe_i{zZ}b zI6nH#$6)EQ2cTRrMK2*~pQyvM?IEf$7N%`)$K_*Ht8Ho8Rge@FKK$@FG-C!iGXpa- zcx+F^nKNg=aX2Wd1}9FO2;y5@ONp46__r{>pcf;(HB;!Ys@dKIrfu&OF51<$#Jexa z%539VF+DvEYu7yrxm*r*jJ!&x6Y=ApJSSq4`V{k4_19EmEKJMVEta@#w`Ik$Auq}B zmybWDy`{p>w{8^z9$&v+n2`lkb9{@adWjto>(^oBLk|PX=Y=SmH?F?WQ7(62Kv-x2iOY9r=w+%e5if4p(pdV@oJm9-MkfY{ z2UexBAQa*mgRejG2;8`FgX)XL(W6I&_+qIP$(x2^4b5O6>slG(~V8aFxJD*-K)-W;} zP%M?|=w9SimXVkP1q^=wNvbUoufP7=VCfsBNZu?IIt-%|126#=@04Yb6-E59^Vw(r zp!#BgyG6q=K-IJsd9zXI$ZSBdR7U~mey7xnMR5m@963Vu#bRn|3RVvch}ik|fW#bn zb_0rabgyU_gssL}u_Od++O&!4OT^Y~+Zs!s=FC$6Hc>}r0}91reNM4rgY>-BJc?%w zmM&Wc*REZo`eJeX_;C@7JHEF06YI~VfvZv1vl*Zli+eE`m$6=?$MC$Fx5x4KHlNSK zpFjGD>Wjt1#01=X-+jV>pf;_Wu0A~%xiTA|7x$v0x09tOF5g#%hpE0qJoD_cB6hBb z`BUh~Zh*cAgYk1yTH^I6VC}kfnLlv+;qYM*s}%~7yz?dYrDZih)AiQ^1N79CWLZ3L z&W(>#eX+Q3@gi831$jw|we@w)x{_XBIZ zGGi%pWHdl&7=W*|IO4L!yEisAM)k!4KX5#_e7T6-wQH14r^27!e_zDYo+wd=lGy-7 z+es5esA{5tmtGp8y(QxL=QoSkC!Y8Yol1>AzV}{WfU3^+6X|ib*Fv`c3n+@RBM4O6 z5|`hqRjXifauROdyqTIu-hEdz&N+wi@o~6y>sI8NGz{}7Eqa?vq2pInV=Pov-G+(d zh|3>;_m?mKn%MTHB|e!YNdnuh!ip6uBlBq~K9nd*q_0+#RrP07V=QD@egR)I3KnR~ zU=@T<@w8+tFJjRII+vBnJqCR$it-%Q7zY-OqP5{+6n$4^oXu&?HIvYqVhZQIe!j@t#tFiI5~|A8u46sf!sHu$yX2 z1yxlakR<6#d{}icztA*oDbSPL v`LPbx8^QY6N1;$yOndC1haP(9Ap!pb(q5@LA#moJ00000NkvXXu0mjf0t`~O literal 0 HcmV?d00001 diff --git a/src/shared/agent-kind.ts b/src/shared/agent-kind.ts index 8ca50cbcea64..f28845a0573d 100644 --- a/src/shared/agent-kind.ts +++ b/src/shared/agent-kind.ts @@ -50,7 +50,8 @@ const TUI_AGENT_KIND_BY_AGENT = { grok: 'grok', devin: 'devin', ante: 'ante', - trae: 'trae' + trae: 'trae', + zcode: 'zcode' } satisfies Record // Why: `satisfies Record` makes the lookup exhaustive at compile diff --git a/src/shared/agent-type-label.ts b/src/shared/agent-type-label.ts index 51f1695f5080..3d6d4a59c3c4 100644 --- a/src/shared/agent-type-label.ts +++ b/src/shared/agent-type-label.ts @@ -25,6 +25,7 @@ const WELL_KNOWN_LABELS: Record = { devin: 'Devin', ante: 'Ante', trae: 'Trae', + zcode: 'ZCode', kimi: 'Kimi' } diff --git a/src/shared/skills-cli-agent-keys.ts b/src/shared/skills-cli-agent-keys.ts index 7c49e0346cd0..7d0a9a5d679e 100644 --- a/src/shared/skills-cli-agent-keys.ts +++ b/src/shared/skills-cli-agent-keys.ts @@ -50,7 +50,8 @@ export const SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT = { devin: 'devin', ante: null, // Why: Orca detects trae by `traecli`, an alias only TRAE CN ships. - trae: 'trae-cn' + trae: 'trae-cn', + zcode: 'zcode' } satisfies Record /** diff --git a/src/shared/telemetry-property-schemas.ts b/src/shared/telemetry-property-schemas.ts index 9d373903fdb6..2fd9e4d1767e 100644 --- a/src/shared/telemetry-property-schemas.ts +++ b/src/shared/telemetry-property-schemas.ts @@ -45,6 +45,7 @@ export const AGENT_KIND_VALUES = [ 'devin', 'ante', 'trae', + 'zcode', 'other' ] as const export const agentKindSchema = z.enum(AGENT_KIND_VALUES) diff --git a/src/shared/tui-agent-config.ts b/src/shared/tui-agent-config.ts index 46d382e7075f..64372230fd13 100644 --- a/src/shared/tui-agent-config.ts +++ b/src/shared/tui-agent-config.ts @@ -122,6 +122,12 @@ const TUI_AGENT_CONFIG_SOURCE: Record = { // Trae subcommand or flag — `--` stops both in its Cobra parser. argvPromptSeparator: '--' }, + zcode: { + detectCmd: 'zcode', + // Why: unknown positionals exit with `Unknown command` (no argv prompt form), so + // launch the bare TUI and paste the prompt once the composer is up. + promptInjectionMode: 'stdin-after-start' + }, opencode: { detectCmd: 'opencode', promptInjectionMode: 'flag-prompt', diff --git a/src/shared/tui-agent-display-names.ts b/src/shared/tui-agent-display-names.ts index 4c2576bddc89..ba1ee6c2c160 100644 --- a/src/shared/tui-agent-display-names.ts +++ b/src/shared/tui-agent-display-names.ts @@ -13,6 +13,7 @@ export const TUI_AGENT_DISPLAY_NAMES: Record = { devin: 'Devin', ante: 'Ante', trae: 'Trae', + zcode: 'ZCode', autohand: 'Autohand Code', opencode: 'OpenCode', opencode2: 'OpenCode 2', diff --git a/src/shared/tui-agent-permissions.ts b/src/shared/tui-agent-permissions.ts index c0808b41a3d6..7aa2c6b334a8 100644 --- a/src/shared/tui-agent-permissions.ts +++ b/src/shared/tui-agent-permissions.ts @@ -29,6 +29,8 @@ export const YOLO_TUI_AGENT_ARGS: Partial> = { devin: '--permission-mode bypass', ante: '--yolo', trae: '--yolo', + // Why: `--mode yolo` is ZCode's permission-bypass flag (`zcode --help`). + zcode: '--mode yolo', droid: '--auto high' } diff --git a/src/shared/tui-agent-selection.ts b/src/shared/tui-agent-selection.ts index 1de6301bd2fd..2bcb8dfafbd6 100644 --- a/src/shared/tui-agent-selection.ts +++ b/src/shared/tui-agent-selection.ts @@ -15,6 +15,7 @@ export const TUI_AGENT_AUTO_PICK_ORDER = [ 'mimo-code', 'ante', 'trae', + 'zcode', 'pi', 'omp', 'prime-agent', diff --git a/src/shared/tui-agent.ts b/src/shared/tui-agent.ts index b6901aa8ef15..34c2c2a822e8 100644 --- a/src/shared/tui-agent.ts +++ b/src/shared/tui-agent.ts @@ -37,4 +37,5 @@ export type TuiAgent = | 'devin' // Devin CLI | 'ante' // Ante (Antigma Labs) | 'trae' // Trae CLI + | 'zcode' // ZCode CLI | 'prime-agent' // Prime Agent (Prime Intellect)