mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-06 06:41:56 +00:00
feat: add Grok Build support (#1030)
* test: define Grok Build integration behavior * feat: add Grok Build agent integration * test: cover Grok permissions and resume paths * docs: add Grok Build setup guide * fix: scope Grok ACP discovery to session cwd * fix: align Grok permission UI semantics * docs: clarify Grok runner setup * test: require Grok create model and effort options * feat: add Grok create model and effort pickers * test: define Grok runtime parity behavior * feat: add Grok runtime ACP controls and discovery * fix: tighten Grok runtime controls * fix: suppress nonfatal Grok title quota errors * feat: support Grok Auto permission mode * feat: forward ACP native session titles for Grok * fix: guard Grok Windows shell arguments
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import chalk from 'chalk'
|
||||
import { authAndSetupMachineIfNeeded } from '@/ui/auth'
|
||||
import { initializeToken } from '@/ui/tokenInit'
|
||||
import { maybeAutoStartServer } from '@/utils/autoStartServer'
|
||||
import type { CommandDefinition } from './types'
|
||||
import { GROK_PERMISSION_MODES } from '@hapi/protocol/modes'
|
||||
import { parseRemoteAgentCommandOptions } from './agentCommandOptions'
|
||||
|
||||
export const grokCommand: CommandDefinition = {
|
||||
name: 'grok',
|
||||
requiresRuntimeAssets: true,
|
||||
run: async ({ commandArgs }) => {
|
||||
try {
|
||||
const hasExplicitPermissionMode = commandArgs.includes('--permission-mode')
|
||||
const normalizedArgs = hasExplicitPermissionMode
|
||||
? commandArgs
|
||||
: commandArgs.flatMap((arg) => arg === '--yolo'
|
||||
? ['--permission-mode', 'bypassPermissions']
|
||||
: [arg])
|
||||
const options = parseRemoteAgentCommandOptions(normalizedArgs, GROK_PERMISSION_MODES)
|
||||
|
||||
await initializeToken()
|
||||
await maybeAutoStartServer()
|
||||
await authAndSetupMachineIfNeeded()
|
||||
|
||||
const { runGrok } = await import('@/grok/runGrok')
|
||||
await runGrok(options)
|
||||
} catch (error) {
|
||||
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error')
|
||||
if (process.env.DEBUG) console.error(error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { runnerCommand } from './runner'
|
||||
import { resumeCommand } from './resume'
|
||||
import { doctorCommand } from './doctor'
|
||||
import { kimiCommand } from './kimi'
|
||||
import { grokCommand } from './grok'
|
||||
import { opencodeCommand } from './opencode'
|
||||
import { piCommand } from './pi'
|
||||
import { hookForwarderCommand } from './hookForwarder'
|
||||
@@ -38,6 +39,7 @@ const COMMANDS: CommandDefinition[] = [
|
||||
codexCommand,
|
||||
cursorCommand,
|
||||
removedGeminiCommand,
|
||||
grokCommand,
|
||||
kimiCommand,
|
||||
opencodeCommand,
|
||||
piCommand,
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
renderMock,
|
||||
runCodexMock,
|
||||
runClaudeMock,
|
||||
runGrokMock,
|
||||
runPiMock,
|
||||
assertCodexLocalSupportedMock,
|
||||
existsSyncMock
|
||||
@@ -23,6 +24,7 @@ const {
|
||||
renderMock: vi.fn(),
|
||||
runCodexMock: vi.fn(async () => {}),
|
||||
runClaudeMock: vi.fn(async () => {}),
|
||||
runGrokMock: vi.fn(async () => {}),
|
||||
runPiMock: vi.fn(async () => {}),
|
||||
assertCodexLocalSupportedMock: vi.fn(),
|
||||
existsSyncMock: vi.fn(() => true)
|
||||
@@ -46,6 +48,7 @@ vi.mock('@/ui/ink/ResumeSessionPicker', () => ({
|
||||
}))
|
||||
vi.mock('@/codex/runCodex', () => ({ runCodex: runCodexMock }))
|
||||
vi.mock('@/claude/runClaude', () => ({ runClaude: runClaudeMock }))
|
||||
vi.mock('@/grok/runGrok', () => ({ runGrok: runGrokMock }))
|
||||
vi.mock('@/pi/runPi', () => ({ runPi: runPiMock }))
|
||||
vi.mock('@/codex/utils/codexVersion', () => ({ assertCodexLocalSupported: assertCodexLocalSupportedMock }))
|
||||
vi.mock('node:fs', () => ({ existsSync: existsSyncMock }))
|
||||
@@ -75,6 +78,7 @@ describe('resumeCommand', () => {
|
||||
})
|
||||
runCodexMock.mockClear()
|
||||
runClaudeMock.mockClear()
|
||||
runGrokMock.mockClear()
|
||||
runPiMock.mockClear()
|
||||
assertCodexLocalSupportedMock.mockClear()
|
||||
existsSyncMock.mockReturnValue(true)
|
||||
@@ -142,6 +146,35 @@ describe('resumeCommand', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('resumes a Grok target in the native local TUI', async () => {
|
||||
getLocalResumeTargetMock.mockResolvedValue({
|
||||
sessionId: 'hapi-session-grok',
|
||||
flavor: 'grok',
|
||||
directory: '/tmp/project',
|
||||
machineId: 'machine-1',
|
||||
active: false,
|
||||
thinking: false,
|
||||
controlledByUser: false,
|
||||
agentSessionId: 'grok-session-1',
|
||||
model: 'grok-4.5',
|
||||
effort: 'low',
|
||||
permissionMode: 'plan'
|
||||
})
|
||||
|
||||
await resumeCommand.run(createContext(['hapi-session-grok']))
|
||||
|
||||
expect(runGrokMock).toHaveBeenCalledWith({
|
||||
existingSessionId: 'hapi-session-grok',
|
||||
workingDirectory: '/tmp/project',
|
||||
resumeSessionId: 'grok-session-1',
|
||||
startedBy: 'terminal',
|
||||
permissionMode: 'plan',
|
||||
startingMode: 'local',
|
||||
model: 'grok-4.5',
|
||||
effort: 'low'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects an active Gemini target before handoff (no longer supported, leaves running session alone)', async () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code?: number) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
ClaudePermissionMode,
|
||||
CodexPermissionMode,
|
||||
CursorPermissionMode,
|
||||
GrokPermissionMode,
|
||||
KimiPermissionMode,
|
||||
OpencodePermissionMode
|
||||
} from '@hapi/protocol/types'
|
||||
@@ -120,6 +121,21 @@ async function dispatchLocalResume(target: LocalResumeTarget): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
if (target.flavor === 'grok') {
|
||||
const { runGrok } = await import('@/grok/runGrok')
|
||||
await runGrok({
|
||||
existingSessionId: base.existingSessionId,
|
||||
workingDirectory: base.workingDirectory,
|
||||
resumeSessionId: base.resumeSessionId,
|
||||
startedBy: base.startedBy,
|
||||
permissionMode: base.permissionMode as GrokPermissionMode | undefined,
|
||||
startingMode: 'local',
|
||||
model: target.model ?? undefined,
|
||||
effort: target.effort ?? undefined
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (target.flavor === 'kimi') {
|
||||
const { runKimi } = await import('@/kimi/runKimi')
|
||||
await runKimi({
|
||||
|
||||
Reference in New Issue
Block a user