From b9eafb160b03cb461ba245f8038f93fa71ad511b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 06:11:43 +0000 Subject: [PATCH 1/4] feat: add Grok as first-class harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires Grok (xAI) across all harness enumeration points so it has the same unified UI experience as Claude and Codex: type definitions, burn stamping, proactive agent types and editor, cloud agent picker, spawn dialogs, toolbar, icons, and empty-state quick-spawn buttons. Note: Grok is PTY-based — no structured output mode is available yet. Structured rendering (equivalent to Claude stream-json / Codex app-server) is deferred pending xAI shipping a JSON output interface. https://claude.ai/code/session_01KXU1uAUwx3L82TMLnAmU4z --- src/main/broker.ts | 2 +- src/main/burn-spawn-hook.ts | 3 ++- src/main/proactive-agent.bundle.ts | 2 +- src/main/proactive-agent.ts | 4 ++-- src/main/proactive-agent.types.ts | 2 +- .../src/components/agents/CloudAgentPicker.tsx | 6 ++++-- .../src/components/common/AgentIcons.tsx | 14 +++++++++++++- .../src/components/common/AgentToolbar.tsx | 5 +++-- .../components/proactive/ProactiveAgentCard.tsx | 2 +- .../proactive/ProactiveAgentEditor.tsx | 4 +++- .../src/components/sidebar/SpawnAgentDialog.tsx | 7 ++++--- .../src/components/terminal/TerminalPane.tsx | 17 ++++++++++++++--- src/renderer/src/lib/spawn-agent.ts | 2 +- src/shared/types/ipc.ts | 2 +- 14 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/main/broker.ts b/src/main/broker.ts index 1a0ad219..452a8eff 100644 --- a/src/main/broker.ts +++ b/src/main/broker.ts @@ -1280,7 +1280,7 @@ function preflightSpawnCli(input: SpawnPtyInput): SpawnPtyInput { } const label = spawnCliLabel(input.cli) - if (!['claude', 'codex', 'opencode'].includes(label)) return input + if (!['claude', 'codex', 'opencode', 'grok'].includes(label)) return input const resolved = resolveCommandWithAugmentedPath(input.cli) if (!resolved) { throw new Error( diff --git a/src/main/burn-spawn-hook.ts b/src/main/burn-spawn-hook.ts index f638557f..a77858fe 100644 --- a/src/main/burn-spawn-hook.ts +++ b/src/main/burn-spawn-hook.ts @@ -59,7 +59,7 @@ export type BeforeAgentSpawnHandler = ( ctx: BeforeAgentSpawnContext ) => void | SpawnPatch | Promise -type Harness = 'claude' | 'codex' | 'opencode' +type Harness = 'claude' | 'codex' | 'opencode' | 'grok' const CODEX_SESSION_LOOKBACK_MS = 10_000 const CODEX_SESSION_LOOKAHEAD_MS = 10 * 60_000 @@ -88,6 +88,7 @@ function inferHarness(input: SpawnPtyInput | SpawnProviderInput): Harness | 'unk if (launcher === 'claude') return 'claude' if (launcher === 'codex') return 'codex' if (launcher === 'opencode') return 'opencode' + if (launcher === 'grok') return 'grok' return 'unknown' } diff --git a/src/main/proactive-agent.bundle.ts b/src/main/proactive-agent.bundle.ts index 50da3692..3e7923ea 100644 --- a/src/main/proactive-agent.bundle.ts +++ b/src/main/proactive-agent.bundle.ts @@ -12,7 +12,7 @@ export type ProactiveAgentDraft = { name: string description?: string cloudAgentId: string - harness: 'claude' | 'codex' | 'opencode' + harness: 'claude' | 'codex' | 'opencode' | 'grok' model: string systemPrompt: string integrations: Record> diff --git a/src/main/proactive-agent.ts b/src/main/proactive-agent.ts index a3877d04..a133ac0f 100644 --- a/src/main/proactive-agent.ts +++ b/src/main/proactive-agent.ts @@ -43,7 +43,7 @@ type PersonaKitModule = { const PERSONA_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/ const WATCH_EVENTS = new Set(['created', 'updated', 'deleted']) -const HARNESSES = new Set(['claude', 'codex', 'opencode']) +const HARNESSES = new Set(['claude', 'codex', 'opencode', 'grok']) const RUN_MODES = new Set(['cloud', 'local']) const MEMORY_SCOPES = new Set(['workspace', 'project', 'persona']) const REASONING_LEVELS = new Set(['low', 'medium', 'high']) @@ -390,7 +390,7 @@ export class ProactiveAgentManager { throw new Error('Agent id must be kebab-case') } if (!HARNESSES.has(draft.harness)) { - throw new Error('Harness must be one of claude, codex, or opencode') + throw new Error('Harness must be one of claude, codex, opencode, or grok') } if (!RUN_MODES.has(draft.runMode || 'cloud')) { throw new Error('Run mode must be cloud or local') diff --git a/src/main/proactive-agent.types.ts b/src/main/proactive-agent.types.ts index 13b683b5..09570217 100644 --- a/src/main/proactive-agent.types.ts +++ b/src/main/proactive-agent.types.ts @@ -1,4 +1,4 @@ -export type ProactiveAgentHarness = 'claude' | 'codex' | 'opencode' +export type ProactiveAgentHarness = 'claude' | 'codex' | 'opencode' | 'grok' export type ProactiveAgentStatus = 'draft' | 'warming' | 'active' | 'paused' | 'error' export type ProactiveAgentRunStatus = 'running' | 'succeeded' | 'failed' export type ProactiveAgentRunMode = 'cloud' | 'local' diff --git a/src/renderer/src/components/agents/CloudAgentPicker.tsx b/src/renderer/src/components/agents/CloudAgentPicker.tsx index 95233e96..d4d50974 100644 --- a/src/renderer/src/components/agents/CloudAgentPicker.tsx +++ b/src/renderer/src/components/agents/CloudAgentPicker.tsx @@ -57,7 +57,8 @@ export type CloudAgentPickerProps = { const HARNESS_OPTIONS = [ { value: 'claude', label: 'Claude', defaultModel: 'claude-opus-4-7' }, { value: 'codex', label: 'Codex', defaultModel: 'gpt-5.2' }, - { value: 'opencode', label: 'OpenCode', defaultModel: 'claude-sonnet-4-6' } + { value: 'opencode', label: 'OpenCode', defaultModel: 'claude-sonnet-4-6' }, + { value: 'grok', label: 'Grok', defaultModel: 'grok-build' } ] function getErrorMessage(error: unknown): string { @@ -140,9 +141,10 @@ function cloudWorkerName(agent: CloudAgentRecord): string { function cloudWorkerCli(agent: CloudAgentRecord): string { const harness = agent.harness.trim().toLowerCase() - if (harness === 'claude' || harness === 'codex' || harness === 'opencode') return harness + if (harness === 'claude' || harness === 'codex' || harness === 'opencode' || harness === 'grok') return harness if (harness === 'anthropic') return 'claude' if (harness === 'openai' || harness === 'gpt' || harness === 'chatgpt') return 'codex' + if (harness === 'xai' || harness === 'x.ai') return 'grok' return 'claude' } diff --git a/src/renderer/src/components/common/AgentIcons.tsx b/src/renderer/src/components/common/AgentIcons.tsx index 8ab3bdd4..9abc1ed7 100644 --- a/src/renderer/src/components/common/AgentIcons.tsx +++ b/src/renderer/src/components/common/AgentIcons.tsx @@ -70,12 +70,24 @@ export function GeminiIcon({ className }: AgentIconProps): React.ReactNode { ) } +export function GrokIcon({ className }: AgentIconProps): React.ReactNode { + return ( + + ) +} + const AGENT_ICON_COMPONENTS = { claude: ClaudeIcon, codex: CodexIcon, copilot: CopilotIcon, opencode: OpenCodeIcon, - gemini: GeminiIcon + gemini: GeminiIcon, + grok: GrokIcon } satisfies Record> export function getAgentIcon(cli?: string): React.ComponentType | null { diff --git a/src/renderer/src/components/common/AgentToolbar.tsx b/src/renderer/src/components/common/AgentToolbar.tsx index 500702f9..2906ecf2 100644 --- a/src/renderer/src/components/common/AgentToolbar.tsx +++ b/src/renderer/src/components/common/AgentToolbar.tsx @@ -1,6 +1,6 @@ import type React from 'react' import { Settings } from 'lucide-react' -import { ClaudeIcon, CodexIcon, CopilotIcon, GeminiIcon, OpenCodeIcon } from '@/components/common/AgentIcons' +import { ClaudeIcon, CodexIcon, CopilotIcon, GeminiIcon, GrokIcon, OpenCodeIcon } from '@/components/common/AgentIcons' import { useUIStore } from '@/stores/ui-store' const AGENTS = [ @@ -8,7 +8,8 @@ const AGENTS = [ { cli: 'codex', label: 'codex', Icon: CodexIcon }, { cli: 'copilot', label: 'copilot', Icon: CopilotIcon }, { cli: 'opencode', label: 'opencode', Icon: OpenCodeIcon }, - { cli: 'gemini', label: 'gemini', Icon: GeminiIcon } + { cli: 'gemini', label: 'gemini', Icon: GeminiIcon }, + { cli: 'grok', label: 'grok', Icon: GrokIcon } ] export function AgentToolbar(): React.ReactNode { diff --git a/src/renderer/src/components/proactive/ProactiveAgentCard.tsx b/src/renderer/src/components/proactive/ProactiveAgentCard.tsx index fa6ab6e4..9475c3b8 100644 --- a/src/renderer/src/components/proactive/ProactiveAgentCard.tsx +++ b/src/renderer/src/components/proactive/ProactiveAgentCard.tsx @@ -17,7 +17,7 @@ export type ProactiveAgentDraft = { name: string description?: string cloudAgentId: string - harness: 'claude' | 'codex' | 'opencode' + harness: 'claude' | 'codex' | 'opencode' | 'grok' model: string systemPrompt: string integrations: Record> diff --git a/src/renderer/src/components/proactive/ProactiveAgentEditor.tsx b/src/renderer/src/components/proactive/ProactiveAgentEditor.tsx index 9508707c..e025cdf8 100644 --- a/src/renderer/src/components/proactive/ProactiveAgentEditor.tsx +++ b/src/renderer/src/components/proactive/ProactiveAgentEditor.tsx @@ -90,7 +90,8 @@ type KeyValueRow = { const MODEL_OPTIONS: Record = { claude: ['claude-opus-4-7', 'claude-sonnet-4-6', 'claude-haiku-4-5'], codex: ['gpt-5.2', 'gpt-5.1-codex', 'gpt-5.1-codex-mini'], - opencode: ['claude-sonnet-4-6', 'gpt-5.2', 'qwen3-coder'] + opencode: ['claude-sonnet-4-6', 'gpt-5.2', 'qwen3-coder'], + grok: ['grok-build', 'grok-composer-2.5-fast'] } const DEPLOY_PHASES: DeployPhase[] = [ @@ -926,6 +927,7 @@ export function ProactiveAgentEditor({ + diff --git a/src/renderer/src/components/sidebar/SpawnAgentDialog.tsx b/src/renderer/src/components/sidebar/SpawnAgentDialog.tsx index 0e990495..afc5b5c4 100644 --- a/src/renderer/src/components/sidebar/SpawnAgentDialog.tsx +++ b/src/renderer/src/components/sidebar/SpawnAgentDialog.tsx @@ -1,7 +1,7 @@ import type React from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { Loader2, X } from 'lucide-react' -import { ClaudeIcon, CodexIcon } from '@/components/common/AgentIcons' +import { ClaudeIcon, CodexIcon, GrokIcon } from '@/components/common/AgentIcons' import { listProjectPersonas, spawnProjectAgent, spawnProjectPersona, type SpawnAgentCli } from '@/lib/spawn-agent' import type { WorkforcePersona } from '@/lib/ipc' import { useProjectStore, type ProjectRoot } from '@/stores/project-store' @@ -9,7 +9,8 @@ import { useUIStore } from '@/stores/ui-store' const AGENT_OPTIONS: Array<{ cli: SpawnAgentCli; label: string; Icon: typeof ClaudeIcon }> = [ { cli: 'claude', label: 'Claude', Icon: ClaudeIcon }, - { cli: 'codex', label: 'Codex', Icon: CodexIcon } + { cli: 'codex', label: 'Codex', Icon: CodexIcon }, + { cli: 'grok', label: 'Grok', Icon: GrokIcon } ] export function SpawnAgentDialog(): React.ReactNode { @@ -207,7 +208,7 @@ export function SpawnAgentDialog(): React.ReactNode { className="h-9 w-full rounded-md border border-[var(--pear-border-subtle)] bg-[var(--pear-bg)] px-3 text-sm text-[var(--pear-text)] outline-none placeholder:text-[var(--pear-text-faint)] focus:border-[var(--pear-accent-dim)] disabled:opacity-50" /> -
+
{AGENT_OPTIONS.map(({ cli, label, Icon }) => ( +
) : (