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:
SSU-WEI HUANG
2026-07-13 08:41:30 +08:00
committed by GitHub
parent de07643828
commit b9eed7c071
92 changed files with 3796 additions and 63 deletions
+4 -2
View File
@@ -1,5 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import type { AgentFlavor } from '@hapi/protocol'
import type { AgentFlavor, PermissionMode } from '@hapi/protocol'
import type { ApiClient } from '@/api/client'
import type { SpawnResponse } from '@/types/api'
import { queryKeys } from '@/lib/query-keys'
@@ -12,6 +12,7 @@ type SpawnInput = {
effort?: string
modelReasoningEffort?: string
yolo?: boolean
permissionMode?: PermissionMode
sessionType?: 'simple' | 'worktree'
worktreeName?: string
}
@@ -37,7 +38,8 @@ export function useSpawnSession(api: ApiClient | null): {
input.yolo,
input.sessionType,
input.worktreeName,
input.effort
input.effort,
input.permissionMode
)
},
onSuccess: () => {
+38
View File
@@ -0,0 +1,38 @@
import { useQuery } from '@tanstack/react-query'
import type { ApiClient } from '@/api/client'
import type { GrokModelSummary } from '@/types/api'
import { queryKeys } from '@/lib/query-keys'
export function useGrokModels(args: {
api: ApiClient | null
sessionId?: string | null
enabled?: boolean
}): {
availableModels: GrokModelSummary[]
currentModelId: string | null
isLoading: boolean
error: string | null
} {
const enabled = Boolean(args.enabled && args.api && args.sessionId)
const query = useQuery({
queryKey: args.sessionId
? queryKeys.sessionGrokModels(args.sessionId)
: ['session-grok-models', 'unknown'] as const,
queryFn: async () => {
if (!args.api || !args.sessionId) throw new Error('Grok session unavailable')
return await args.api.getSessionGrokModels(args.sessionId)
},
enabled,
staleTime: 30_000,
retry: false,
})
return {
availableModels: query.data?.availableModels ?? [],
currentModelId: query.data?.currentModelId ?? null,
isLoading: query.isLoading,
error: query.data?.success === false
? (query.data.error ?? 'Failed to load Grok models')
: query.error instanceof Error ? query.error.message : null,
}
}
@@ -0,0 +1,50 @@
import { useQuery } from '@tanstack/react-query'
import type { ApiClient } from '@/api/client'
import type { GrokModelSummary } from '@/types/api'
import { queryKeys } from '@/lib/query-keys'
export function useGrokModelsForCwd(args: {
api: ApiClient | null
machineId?: string | null
cwd?: string | null
enabled?: boolean
}): {
availableModels: GrokModelSummary[]
currentModelId: string | null
autoPermissionModeSupported: boolean | null
isLoading: boolean
error: string | null
} {
const { api, machineId, cwd } = args
const trimmedCwd = typeof cwd === 'string' ? cwd.trim() : ''
const enabled = Boolean(args.enabled && api && machineId && trimmedCwd)
const query = useQuery({
queryKey: machineId && trimmedCwd
? queryKeys.machineGrokModelsForCwd(machineId, trimmedCwd)
: ['machine-grok-models', 'unknown', 'unknown'] as const,
queryFn: async () => {
if (!api || !machineId || !trimmedCwd) {
throw new Error('Grok models target unavailable')
}
return await api.getMachineGrokModelsForCwd(machineId, trimmedCwd)
},
enabled,
staleTime: 60_000,
retry: false,
})
return {
availableModels: query.data?.availableModels ?? [],
currentModelId: query.data?.currentModelId ?? null,
autoPermissionModeSupported: query.data?.autoPermissionModeSupported ?? null,
isLoading: query.isLoading,
error: query.data?.success === false
? (query.data.error ?? 'Failed to load Grok models')
: query.error instanceof Error
? query.error.message
: query.error
? 'Failed to load Grok models'
: null,
}
}
@@ -0,0 +1,38 @@
import { useQuery } from '@tanstack/react-query'
import type { ApiClient } from '@/api/client'
import type { GrokReasoningEffortOption } from '@/types/api'
import { queryKeys } from '@/lib/query-keys'
export function useGrokReasoningEffortOptions(args: {
api: ApiClient | null
sessionId?: string | null
enabled?: boolean
}): {
options: GrokReasoningEffortOption[]
currentValue: string | null
isLoading: boolean
error: string | null
} {
const enabled = Boolean(args.enabled && args.api && args.sessionId)
const query = useQuery({
queryKey: args.sessionId
? queryKeys.sessionGrokReasoningEffortOptions(args.sessionId)
: ['session-grok-reasoning-effort-options', 'unknown'] as const,
queryFn: async () => {
if (!args.api || !args.sessionId) throw new Error('Grok session unavailable')
return await args.api.getSessionGrokReasoningEffortOptions(args.sessionId)
},
enabled,
staleTime: 30_000,
retry: false,
})
return {
options: query.data?.options ?? [],
currentValue: query.data?.currentValue ?? null,
isLoading: query.isLoading,
error: query.data?.success === false
? (query.data.error ?? 'Failed to load Grok effort options')
: query.error instanceof Error ? query.error.message : null,
}
}