diff --git a/cli/src/modules/common/rpcTypes.ts b/cli/src/modules/common/rpcTypes.ts index 6336f57d..5e243eb8 100644 --- a/cli/src/modules/common/rpcTypes.ts +++ b/cli/src/modules/common/rpcTypes.ts @@ -1,10 +1,12 @@ +import type { AgentFlavor } from '@hapi/protocol' + export interface SpawnSessionOptions { machineId?: string directory: string sessionId?: string resumeSessionId?: string approvedNewDirectoryCreation?: boolean - agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' + agent?: AgentFlavor model?: string effort?: string modelReasoningEffort?: string diff --git a/hub/src/sync/rpcGateway.ts b/hub/src/sync/rpcGateway.ts index 882d87c5..a13fb2e5 100644 --- a/hub/src/sync/rpcGateway.ts +++ b/hub/src/sync/rpcGateway.ts @@ -1,4 +1,4 @@ -import type { CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types' +import type { AgentFlavor, CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types' import type { Server } from 'socket.io' import type { RpcRegistry } from '../socket/rpcRegistry' @@ -150,7 +150,7 @@ export class RpcGateway { async spawnSession( machineId: string, directory: string, - agent: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' = 'claude', + agent: AgentFlavor = 'claude', model?: string, modelReasoningEffort?: string, yolo?: boolean, diff --git a/hub/src/sync/syncEngine.ts b/hub/src/sync/syncEngine.ts index 76d56e73..166691fc 100644 --- a/hub/src/sync/syncEngine.ts +++ b/hub/src/sync/syncEngine.ts @@ -7,7 +7,7 @@ * - No E2E encryption; data is stored as JSON in SQLite */ -import type { LocalResumeTarget, ResumableSession } from '@hapi/protocol' +import { isKnownFlavor, type LocalResumeTarget, type ResumableSession } from '@hapi/protocol' import type { AgentFlavor, CodexCollaborationMode, DecryptedMessage, PermissionMode, Session, SyncEvent } from '@hapi/protocol/types' import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages' import type { Server } from 'socket.io' @@ -459,7 +459,7 @@ export class SyncEngine { async spawnSession( machineId: string, directory: string, - agent: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode' = 'claude', + agent: AgentFlavor = 'claude', model?: string, modelReasoningEffort?: string, yolo?: boolean, @@ -486,9 +486,7 @@ export class SyncEngine { private resolveFlavor(session: Session): AgentFlavor { const flavor = session.metadata?.flavor - return flavor === 'codex' || flavor === 'gemini' || flavor === 'opencode' || flavor === 'cursor' - ? flavor - : 'claude' + return isKnownFlavor(flavor) ? flavor : 'claude' } private resolveAgentResumeId(session: Session, namespace: string): string | null { diff --git a/hub/src/web/routes/machines.ts b/hub/src/web/routes/machines.ts index 9f201fbe..7bd65eea 100644 --- a/hub/src/web/routes/machines.ts +++ b/hub/src/web/routes/machines.ts @@ -1,3 +1,4 @@ +import { AgentFlavorSchema } from '@hapi/protocol' import { Hono } from 'hono' import { z } from 'zod' import type { SyncEngine } from '../../sync/syncEngine' @@ -6,7 +7,7 @@ import { requireMachine } from './guards' const spawnBodySchema = z.object({ directory: z.string().min(1), - agent: z.enum(['claude', 'codex', 'cursor', 'gemini', 'opencode']).optional(), + agent: AgentFlavorSchema.optional(), model: z.string().optional(), effort: z.string().optional(), modelReasoningEffort: z.string().optional(), diff --git a/shared/src/modes.ts b/shared/src/modes.ts index d59320a9..ac6a04df 100644 --- a/shared/src/modes.ts +++ b/shared/src/modes.ts @@ -1,3 +1,5 @@ +import { z } from 'zod' + /** * @description The legacy payload type identifier used for all generic agent messages. * Changing this value will affect the communication schema between CLI, Hub, and Web. @@ -5,6 +7,10 @@ */ export const AGENT_MESSAGE_PAYLOAD_TYPE = 'codex' as const +export const AGENT_FLAVORS = ['claude', 'codex', 'cursor', 'gemini', 'opencode'] as const +export type AgentFlavor = typeof AGENT_FLAVORS[number] +export const AgentFlavorSchema = z.enum(AGENT_FLAVORS) + export const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan'] as const export type ClaudePermissionMode = typeof CLAUDE_PERMISSION_MODES[number] @@ -35,8 +41,6 @@ export const PERMISSION_MODES = [ ] as const export type PermissionMode = typeof PERMISSION_MODES[number] -export type AgentFlavor = 'claude' | 'codex' | 'gemini' | 'opencode' | 'cursor' - export const PERMISSION_MODE_LABELS: Record = { default: 'Default', acceptEdits: 'Accept Edits', diff --git a/shared/src/resume.ts b/shared/src/resume.ts index d5cbe8eb..4c75c2cd 100644 --- a/shared/src/resume.ts +++ b/shared/src/resume.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { CodexCollaborationModeSchema, PermissionModeSchema } from './schemas' - -export const AgentFlavorSchema = z.enum(['claude', 'codex', 'gemini', 'opencode', 'cursor']) +import { AgentFlavorSchema } from './modes' export const LocalResumeTargetSchema = z.object({ sessionId: z.string().min(1), diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 4b15aab7..883e555a 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -25,6 +25,7 @@ import type { SessionResponse, SessionsResponse } from '@/types/api' +import type { AgentFlavor } from '@hapi/protocol' import type { CancelMessageResponse } from '@hapi/protocol/schemas' type ApiClientOptions = { @@ -459,7 +460,7 @@ export class ApiClient { async spawnSession( machineId: string, directory: string, - agent?: 'claude' | 'codex' | 'cursor' | 'gemini' | 'opencode', + agent?: AgentFlavor, model?: string, modelReasoningEffort?: string, yolo?: boolean, diff --git a/web/src/components/NewSession/AgentSelector.tsx b/web/src/components/NewSession/AgentSelector.tsx index 73e87d46..4146f308 100644 --- a/web/src/components/NewSession/AgentSelector.tsx +++ b/web/src/components/NewSession/AgentSelector.tsx @@ -1,3 +1,4 @@ +import { AGENT_FLAVORS } from '@hapi/protocol' import type { AgentType } from './types' import { useTranslation } from '@/lib/use-translation' @@ -14,7 +15,7 @@ export function AgentSelector(props: { {t('newSession.agent')}
- {(['claude', 'codex', 'cursor', 'gemini', 'opencode'] as const).map((agentType) => ( + {AGENT_FLAVORS.map((agentType) => (