diff --git a/cli/src/agent/localHandoff.ts b/cli/src/agent/localHandoff.ts index d9c54b4f..d50b783e 100644 --- a/cli/src/agent/localHandoff.ts +++ b/cli/src/agent/localHandoff.ts @@ -1,4 +1,5 @@ import type { SessionEndReason } from '@hapi/protocol' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' type RpcHandlerManagerLike = { registerHandler( @@ -17,7 +18,7 @@ export function registerLocalHandoffHandler( rpcHandlerManager: RpcHandlerManagerLike, lifecycle: LocalHandoffLifecycle ): void { - rpcHandlerManager.registerHandler('handoff-local', () => { + rpcHandlerManager.registerHandler(RPC_METHODS.HandoffLocal, () => { lifecycle.setArchiveReason('Handed off to local terminal') lifecycle.setSessionEndReason('handoff') setImmediate(() => { diff --git a/cli/src/agent/permissionAdapter.ts b/cli/src/agent/permissionAdapter.ts index d08ff6e9..37990cec 100644 --- a/cli/src/agent/permissionAdapter.ts +++ b/cli/src/agent/permissionAdapter.ts @@ -3,6 +3,7 @@ import type { AgentState, SessionPermissionMode } from '@/api/types'; import type { ApiSessionClient } from '@/api/apiSession'; import { logger } from '@/ui/logger'; import { deriveToolName } from '@/agent/utils'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { resolveToolAutoApprovalDecision, type AutoApprovalDecision @@ -46,7 +47,7 @@ export class PermissionAdapter { ) { this.backend.onPermissionRequest((request) => this.handlePermissionRequest(request)); this.session.rpcHandlerManager.registerHandler( - 'permission', + RPC_METHODS.Permission, async (response) => { await this.handlePermissionResponse(response); } diff --git a/cli/src/agent/runners/runAgentSession.ts b/cli/src/agent/runners/runAgentSession.ts index 4a0b0e9f..bd1ccb43 100644 --- a/cli/src/agent/runners/runAgentSession.ts +++ b/cli/src/agent/runners/runAgentSession.ts @@ -14,6 +14,7 @@ import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; import { getInvokedCwd } from '@/utils/invokedCwd'; import { PermissionModeSchema } from '@hapi/protocol/schemas'; import { isPermissionModeAllowedForFlavor } from '@hapi/protocol'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import type { SessionEndReason } from '@hapi/protocol'; function emitReadyIfIdle(props: { @@ -103,7 +104,7 @@ export async function runAgentSession(opts: { return parsed.data as SessionPermissionMode; }; - session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.SetSessionConfig, async (payload: unknown) => { if (!payload || typeof payload !== 'object') { throw new Error('Invalid session config payload'); } @@ -138,7 +139,7 @@ export async function runAgentSession(opts: { } }; - session.rpcHandlerManager.registerHandler('abort', async () => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.Abort, async () => { await handleAbort(); }); diff --git a/cli/src/api/apiMachine.ts b/cli/src/api/apiMachine.ts index 340b14fa..4cb3fe13 100644 --- a/cli/src/api/apiMachine.ts +++ b/cli/src/api/apiMachine.ts @@ -10,6 +10,7 @@ import { logger } from '@/ui/logger' import { configuration } from '@/configuration' import type { Update, UpdateMachineBody } from '@hapi/protocol' import type { MachineDirectoryEntry, MachineListDirectoryResponse, PathExistsResponse } from '@hapi/protocol/apiTypes' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RunnerState, Machine, MachineMetadata } from './types' import { RunnerStateSchema, MachineMetadataSchema } from './types' import { backoff } from '@/utils/time' @@ -127,7 +128,7 @@ export class ApiMachineClient { registerCommonHandlers(this.rpcHandlerManager, getInvokedCwd()) - this.rpcHandlerManager.registerHandler('path-exists', async (params) => { + this.rpcHandlerManager.registerHandler(RPC_METHODS.PathExists, async (params) => { const rawPaths = Array.isArray(params?.paths) ? params.paths : [] const uniquePaths = Array.from(new Set(rawPaths.filter((path): path is string => typeof path === 'string'))) const exists: Record = {} @@ -146,7 +147,7 @@ export class ApiMachineClient { return { exists } }) - this.rpcHandlerManager.registerHandler('list-directory', async (params) => { + this.rpcHandlerManager.registerHandler(RPC_METHODS.ListMachineDirectory, async (params) => { if (!this.normalizedWorkspaceRoots?.length) { return { success: false, error: 'Workspace browsing is not enabled for this machine' } } @@ -224,7 +225,7 @@ export class ApiMachineClient { // delegating to the lower-level probe. This intentionally overwrites the // earlier registration on the same scoped method name. this.rpcHandlerManager.registerHandler( - 'listOpencodeModelsForCwd', + RPC_METHODS.ListOpencodeModelsForCwd, async (params) => { const rawCwd = typeof params?.cwd === 'string' ? params.cwd.trim() : '' if (!rawCwd) { @@ -281,7 +282,7 @@ export class ApiMachineClient { } setRPCHandlers({ spawnSession, stopSession, requestShutdown }: MachineRpcHandlers): void { - this.rpcHandlerManager.registerHandler('spawn-happy-session', async (params: any) => { + this.rpcHandlerManager.registerHandler(RPC_METHODS.SpawnHappySession, async (params: any) => { const { directory, sessionId, resumeSessionId, machineId, approvedNewDirectoryCreation, agent, model, effort, modelReasoningEffort, yolo, permissionMode, token, sessionType, worktreeName } = params || {} if (!directory) { @@ -320,7 +321,7 @@ export class ApiMachineClient { } }) - this.rpcHandlerManager.registerHandler('stop-session', (params: any) => { + this.rpcHandlerManager.registerHandler(RPC_METHODS.StopSession, (params: any) => { const { sessionId } = params || {} if (!sessionId) { throw new Error('Session ID is required') @@ -334,7 +335,7 @@ export class ApiMachineClient { return { message: 'Session stopped' } }) - this.rpcHandlerManager.registerHandler('stop-runner', () => { + this.rpcHandlerManager.registerHandler(RPC_METHODS.StopRunner, () => { setTimeout(() => requestShutdown(), 100) return { message: 'Runner stop request acknowledged' } }) diff --git a/cli/src/claude/registerKillSessionHandler.ts b/cli/src/claude/registerKillSessionHandler.ts index 02788d46..37936b79 100644 --- a/cli/src/claude/registerKillSessionHandler.ts +++ b/cli/src/claude/registerKillSessionHandler.ts @@ -1,5 +1,6 @@ import { RpcHandlerManager } from "@/api/rpc/RpcHandlerManager"; import { logger } from "@/lib"; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; interface KillSessionRequest { // No parameters needed @@ -15,7 +16,7 @@ export function registerKillSessionHandler( rpcHandlerManager: RpcHandlerManager, killThisHappy: () => Promise ) { - rpcHandlerManager.registerHandler('killSession', async () => { + rpcHandlerManager.registerHandler(RPC_METHODS.KillSession, async () => { logger.debug('Kill session request received'); // This will start the cleanup process diff --git a/cli/src/claude/runClaude.ts b/cli/src/claude/runClaude.ts index 7eb78cc4..44fd4baa 100644 --- a/cli/src/claude/runClaude.ts +++ b/cli/src/claude/runClaude.ts @@ -16,6 +16,7 @@ import { bootstrapExistingSession, bootstrapSession } from '@/agent/sessionFacto import { registerLocalHandoffHandler } from '@/agent/localHandoff'; import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } from '@/agent/runnerLifecycle'; import { isPermissionModeAllowedForFlavor } from '@hapi/protocol'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { PermissionModeSchema } from '@hapi/protocol/schemas'; import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; import { normalizeClaudeSessionModel } from './model'; @@ -383,7 +384,7 @@ export async function runClaude(options: StartOptions = {}): Promise { return normalizeClaudeSessionEffort(value); }; - session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.SetSessionConfig, async (payload: unknown) => { if (!payload || typeof payload !== 'object') { throw new Error('Invalid session config payload'); } diff --git a/cli/src/codex/runCodex.ts b/cli/src/codex/runCodex.ts index d53a7194..2f676250 100644 --- a/cli/src/codex/runCodex.ts +++ b/cli/src/codex/runCodex.ts @@ -11,6 +11,7 @@ import { bootstrapExistingSession, bootstrapSession } from '@/agent/sessionFacto import { registerLocalHandoffHandler } from '@/agent/localHandoff'; import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } from '@/agent/runnerLifecycle'; import { isPermissionModeAllowedForFlavor } from '@hapi/protocol'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { CodexCollaborationModeSchema, PermissionModeSchema } from '@hapi/protocol/schemas'; import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; import { getInvokedCwd } from '@/utils/invokedCwd'; @@ -293,7 +294,7 @@ export async function runCodex(opts: { return trimmedValue; }; - session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.SetSessionConfig, async (payload: unknown) => { if (!payload || typeof payload !== 'object') { throw new Error('Invalid session config payload'); } diff --git a/cli/src/cursor/runCursor.ts b/cli/src/cursor/runCursor.ts index ccf52729..988c1ff7 100644 --- a/cli/src/cursor/runCursor.ts +++ b/cli/src/cursor/runCursor.ts @@ -9,6 +9,7 @@ import { bootstrapExistingSession, bootstrapSession } from '@/agent/sessionFacto import { registerLocalHandoffHandler } from '@/agent/localHandoff'; import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } from '@/agent/runnerLifecycle'; import { isPermissionModeAllowedForFlavor } from '@hapi/protocol'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { PermissionModeSchema } from '@hapi/protocol/schemas'; import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; import { getInvokedCwd } from '@/utils/invokedCwd'; @@ -112,7 +113,7 @@ export async function runCursor(opts: { return parsed.data as PermissionMode; }; - session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.SetSessionConfig, async (payload: unknown) => { if (!payload || typeof payload !== 'object') { throw new Error('Invalid session config payload'); } diff --git a/cli/src/gemini/runGemini.ts b/cli/src/gemini/runGemini.ts index 17517e02..888fab93 100644 --- a/cli/src/gemini/runGemini.ts +++ b/cli/src/gemini/runGemini.ts @@ -13,6 +13,7 @@ import { startHookServer } from '@/claude/utils/startHookServer'; import { cleanupHookSettingsFile, generateHookSettingsFile } from '@/modules/common/hooks/generateHookSettings'; import { resolveGeminiRuntimeConfig } from './utils/config'; import { isPermissionModeAllowedForFlavor } from '@hapi/protocol'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { PermissionModeSchema } from '@hapi/protocol/schemas'; import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; import { getInvokedCwd } from '@/utils/invokedCwd'; @@ -164,7 +165,7 @@ export async function runGemini(opts: { return value.trim(); }; - session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.SetSessionConfig, async (payload: unknown) => { if (!payload || typeof payload !== 'object') { throw new Error('Invalid session config payload'); } diff --git a/cli/src/modules/common/handlers/bash.ts b/cli/src/modules/common/handlers/bash.ts index e5cffdd2..47b0ed9e 100644 --- a/cli/src/modules/common/handlers/bash.ts +++ b/cli/src/modules/common/handlers/bash.ts @@ -1,6 +1,7 @@ import { logger } from '@/ui/logger' import { exec, type ExecOptions } from 'child_process' import { promisify } from 'util' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { getErrorMessage, rpcError } from '../rpcResponses' @@ -22,7 +23,7 @@ interface BashResponse { } export function registerBashHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('bash', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.Bash, async (data) => { logger.debug('Shell command request:', data.command) if (data.cwd) { diff --git a/cli/src/modules/common/handlers/codexModels.ts b/cli/src/modules/common/handlers/codexModels.ts index c6cef6fa..2cdcb0e8 100644 --- a/cli/src/modules/common/handlers/codexModels.ts +++ b/cli/src/modules/common/handlers/codexModels.ts @@ -1,4 +1,5 @@ import { logger } from '@/ui/logger'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager'; import { listCodexModels, @@ -8,7 +9,7 @@ import { import { getErrorMessage, rpcError } from '../rpcResponses'; export function registerCodexModelHandlers(rpcHandlerManager: RpcHandlerManager): void { - rpcHandlerManager.registerHandler('listCodexModels', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.ListCodexModels, async (data) => { logger.debug('List Codex models request'); try { diff --git a/cli/src/modules/common/handlers/difftastic.ts b/cli/src/modules/common/handlers/difftastic.ts index 15de02f7..0b5886f9 100644 --- a/cli/src/modules/common/handlers/difftastic.ts +++ b/cli/src/modules/common/handlers/difftastic.ts @@ -1,4 +1,5 @@ import { logger } from '@/ui/logger' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { run as runDifftastic } from '@/modules/difftastic/index' import { validatePath } from '../pathSecurity' @@ -18,7 +19,7 @@ interface DifftasticResponse { } export function registerDifftasticHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('difftastic', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.Difftastic, async (data) => { logger.debug('Difftastic request with args:', data.args, 'cwd:', data.cwd) if (data.cwd) { diff --git a/cli/src/modules/common/handlers/directories.ts b/cli/src/modules/common/handlers/directories.ts index fb21d8f2..bc142d73 100644 --- a/cli/src/modules/common/handlers/directories.ts +++ b/cli/src/modules/common/handlers/directories.ts @@ -2,6 +2,7 @@ import { logger } from '@/ui/logger' import { readdir, stat } from 'fs/promises' import { basename, join, resolve } from 'path' import type { DirectoryEntry, ListDirectoryResponse } from '@hapi/protocol/apiTypes' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { getErrorMessage, rpcError } from '../rpcResponses' @@ -31,7 +32,7 @@ interface GetDirectoryTreeResponse { } export function registerDirectoryHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('listDirectory', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.ListDirectory, async (data) => { logger.debug('List directory request:', data.path) const targetPath = data.path || '.' @@ -92,7 +93,7 @@ export function registerDirectoryHandlers(rpcHandlerManager: RpcHandlerManager, } }) - rpcHandlerManager.registerHandler('getDirectoryTree', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.GetDirectoryTree, async (data) => { logger.debug('Get directory tree request:', data.path, 'maxDepth:', data.maxDepth) const targetPath = data.path || '.' diff --git a/cli/src/modules/common/handlers/files.ts b/cli/src/modules/common/handlers/files.ts index d8f34a4c..460d2b7d 100644 --- a/cli/src/modules/common/handlers/files.ts +++ b/cli/src/modules/common/handlers/files.ts @@ -3,6 +3,7 @@ import { readFile, stat, writeFile } from 'fs/promises' import { createHash } from 'crypto' import { resolve } from 'path' import type { FileReadResponse, GeneratedImageResponse } from '@hapi/protocol/apiTypes' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { getGeneratedImage } from '../generatedImages' @@ -33,7 +34,7 @@ interface WriteFileResponse { } export function registerFileHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('readFile', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.ReadFile, async (data) => { logger.debug('Read file request:', data.path) const validation = validatePath(data.path, workingDirectory) @@ -52,7 +53,7 @@ export function registerFileHandlers(rpcHandlerManager: RpcHandlerManager, worki } }) - rpcHandlerManager.registerHandler('readGeneratedImage', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.ReadGeneratedImage, async (data) => { logger.debug('Read generated image request:', data.id) const image = getGeneratedImage(data.id) @@ -74,7 +75,7 @@ export function registerFileHandlers(rpcHandlerManager: RpcHandlerManager, worki } }) - rpcHandlerManager.registerHandler('writeFile', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.WriteFile, async (data) => { logger.debug('Write file request:', data.path) const validation = validatePath(data.path, workingDirectory) diff --git a/cli/src/modules/common/handlers/git.ts b/cli/src/modules/common/handlers/git.ts index ba280edd..cc19c8cb 100644 --- a/cli/src/modules/common/handlers/git.ts +++ b/cli/src/modules/common/handlers/git.ts @@ -1,6 +1,7 @@ import { execFile, type ExecFileOptions } from 'child_process' import { promisify } from 'util' import type { CommandResponse } from '@hapi/protocol/apiTypes' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { rpcError } from '../rpcResponses' @@ -86,7 +87,7 @@ async function runGitCommand( } export function registerGitHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('git-status', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.GitStatus, async (data) => { const resolved = resolveCwd(data.cwd, workingDirectory) if (resolved.error) { return rpcError(resolved.error) @@ -98,7 +99,7 @@ export function registerGitHandlers(rpcHandlerManager: RpcHandlerManager, workin ) }) - rpcHandlerManager.registerHandler('git-diff-numstat', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.GitDiffNumstat, async (data) => { const resolved = resolveCwd(data.cwd, workingDirectory) if (resolved.error) { return rpcError(resolved.error) @@ -109,7 +110,7 @@ export function registerGitHandlers(rpcHandlerManager: RpcHandlerManager, workin return await runGitCommand(args, resolved.cwd, data.timeout) }) - rpcHandlerManager.registerHandler('git-diff-file', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.GitDiffFile, async (data) => { const resolved = resolveCwd(data.cwd, workingDirectory) if (resolved.error) { return rpcError(resolved.error) diff --git a/cli/src/modules/common/handlers/opencodeModels.ts b/cli/src/modules/common/handlers/opencodeModels.ts index 73657a8e..12ddd2d5 100644 --- a/cli/src/modules/common/handlers/opencodeModels.ts +++ b/cli/src/modules/common/handlers/opencodeModels.ts @@ -1,4 +1,5 @@ import { logger } from '@/ui/logger'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager'; import { listOpencodeModelsForCwd, @@ -9,7 +10,7 @@ import { getErrorMessage, rpcError } from '../rpcResponses'; export function registerOpencodeModelHandlers(rpcHandlerManager: RpcHandlerManager): void { rpcHandlerManager.registerHandler( - 'listOpencodeModelsForCwd', + RPC_METHODS.ListOpencodeModelsForCwd, async (data) => { logger.debug('List OpenCode models for cwd request', { cwd: data?.cwd }); diff --git a/cli/src/modules/common/handlers/ripgrep.ts b/cli/src/modules/common/handlers/ripgrep.ts index 54ec38b3..5b14a5b5 100644 --- a/cli/src/modules/common/handlers/ripgrep.ts +++ b/cli/src/modules/common/handlers/ripgrep.ts @@ -1,4 +1,5 @@ import { logger } from '@/ui/logger' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { run as runRipgrep } from '@/modules/ripgrep/index' import { validatePath } from '../pathSecurity' @@ -18,7 +19,7 @@ interface RipgrepResponse { } export function registerRipgrepHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('ripgrep', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.Ripgrep, async (data) => { logger.debug('Ripgrep request with args:', data.args, 'cwd:', data.cwd) if (data.cwd) { diff --git a/cli/src/modules/common/handlers/skills.ts b/cli/src/modules/common/handlers/skills.ts index 87a45bc5..36fa96fb 100644 --- a/cli/src/modules/common/handlers/skills.ts +++ b/cli/src/modules/common/handlers/skills.ts @@ -1,10 +1,11 @@ import { logger } from '@/ui/logger' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { listSkills, type ListSkillsRequest, type ListSkillsResponse } from '../skills' import { getErrorMessage, rpcError } from '../rpcResponses' export function registerSkillsHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('listSkills', async () => { + rpcHandlerManager.registerHandler(RPC_METHODS.ListSkills, async () => { logger.debug('List skills request') try { diff --git a/cli/src/modules/common/handlers/slashCommands.ts b/cli/src/modules/common/handlers/slashCommands.ts index 1bad6823..bceacd5e 100644 --- a/cli/src/modules/common/handlers/slashCommands.ts +++ b/cli/src/modules/common/handlers/slashCommands.ts @@ -1,10 +1,11 @@ import { logger } from '@/ui/logger' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { listSlashCommands, type ListSlashCommandsRequest, type ListSlashCommandsResponse } from '../slashCommands' import { getErrorMessage, rpcError } from '../rpcResponses' export function registerSlashCommandHandlers(rpcHandlerManager: RpcHandlerManager, workingDirectory: string): void { - rpcHandlerManager.registerHandler('listSlashCommands', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.ListSlashCommands, async (data) => { logger.debug('List slash commands request for agent:', data.agent) try { diff --git a/cli/src/modules/common/handlers/uploads.ts b/cli/src/modules/common/handlers/uploads.ts index c5c089fc..d74fdc3c 100644 --- a/cli/src/modules/common/handlers/uploads.ts +++ b/cli/src/modules/common/handlers/uploads.ts @@ -3,6 +3,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises' import { join, resolve, sep } from 'path' import { rmSync } from 'node:fs' import type { DeleteUploadResponse, UploadFileResponse } from '@hapi/protocol/apiTypes' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { getErrorMessage, rpcError } from '../rpcResponses' import { getHapiBlobsDir } from '@/constants/uploadPaths' @@ -153,7 +154,7 @@ export function registerUploadHandlers(rpcHandlerManager: RpcHandlerManager): vo process.once('exit', cleanupUploadDirsSync) } - rpcHandlerManager.registerHandler('uploadFile', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.UploadFile, async (data) => { logger.debug('Upload file request:', data.filename, 'mimeType:', data.mimeType) if (!data.filename) { @@ -193,7 +194,7 @@ export function registerUploadHandlers(rpcHandlerManager: RpcHandlerManager): vo } }) - rpcHandlerManager.registerHandler('deleteUpload', async (data) => { + rpcHandlerManager.registerHandler(RPC_METHODS.DeleteUpload, async (data) => { const path = data?.path?.trim() if (!path) { return rpcError('Path is required') diff --git a/cli/src/modules/common/launcher/BaseLocalLauncher.ts b/cli/src/modules/common/launcher/BaseLocalLauncher.ts index c208fab2..b4c6bb1d 100644 --- a/cli/src/modules/common/launcher/BaseLocalLauncher.ts +++ b/cli/src/modules/common/launcher/BaseLocalLauncher.ts @@ -2,6 +2,7 @@ import { logger } from '@/ui/logger' import { Future } from '@/utils/future' import { getLocalLaunchExitReason } from '@/agent/localLaunchPolicy' import type { LocalLaunchExitReason, StartedBy } from '@/agent/localLaunchPolicy' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' type QueueLike = { size(): number @@ -88,8 +89,8 @@ export class BaseLocalLauncher { await abortProcess() } - rpcHandlerManager.registerHandler('abort', doAbort) - rpcHandlerManager.registerHandler('switch', doSwitch) + rpcHandlerManager.registerHandler(RPC_METHODS.Abort, doAbort) + rpcHandlerManager.registerHandler(RPC_METHODS.Switch, doSwitch) queue.setOnMessage(() => { void doSwitch() }) @@ -139,8 +140,8 @@ export class BaseLocalLauncher { } } finally { this.exitFuture.resolve(undefined) - rpcHandlerManager.registerHandler('abort', async () => {}) - rpcHandlerManager.registerHandler('switch', async () => {}) + rpcHandlerManager.registerHandler(RPC_METHODS.Abort, async () => {}) + rpcHandlerManager.registerHandler(RPC_METHODS.Switch, async () => {}) queue.setOnMessage(null) } diff --git a/cli/src/modules/common/permission/BasePermissionHandler.ts b/cli/src/modules/common/permission/BasePermissionHandler.ts index d4c9df58..f70908f5 100644 --- a/cli/src/modules/common/permission/BasePermissionHandler.ts +++ b/cli/src/modules/common/permission/BasePermissionHandler.ts @@ -1,5 +1,6 @@ import type { AgentState } from "@/api/types"; import type { PermissionMode } from "@hapi/protocol/types"; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; type RpcHandlerManagerLike = { registerHandler( @@ -204,7 +205,7 @@ export abstract class BasePermissionHandler('permission', async (response) => { + this.client.rpcHandlerManager.registerHandler(RPC_METHODS.Permission, async (response) => { const pending = this.pendingRequests.get(response.id); if (!pending) { diff --git a/cli/src/modules/common/remote/RemoteLauncherBase.ts b/cli/src/modules/common/remote/RemoteLauncherBase.ts index bb2a3224..bce64e0d 100644 --- a/cli/src/modules/common/remote/RemoteLauncherBase.ts +++ b/cli/src/modules/common/remote/RemoteLauncherBase.ts @@ -2,6 +2,7 @@ import { render } from 'ink'; import type { ReactElement } from 'react'; import { MessageBuffer } from '@/ui/ink/messageBuffer'; import { restoreTerminalState } from '@/ui/terminalState'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; export type RemoteLauncherExitReason = 'switch' | 'exit'; @@ -76,18 +77,18 @@ export abstract class RemoteLauncherBase { rpcHandlerManager: RpcHandlerManagerLike, handlers: RemoteLauncherAbortHandlers ): void { - rpcHandlerManager.registerHandler('abort', async () => { + rpcHandlerManager.registerHandler(RPC_METHODS.Abort, async () => { await handlers.onAbort(); }); - rpcHandlerManager.registerHandler('switch', async () => { + rpcHandlerManager.registerHandler(RPC_METHODS.Switch, async () => { await handlers.onSwitch(); }); } protected clearAbortHandlers(rpcHandlerManager: RpcHandlerManagerLike): void { - rpcHandlerManager.registerHandler('abort', async () => {}); - rpcHandlerManager.registerHandler('switch', async () => {}); + rpcHandlerManager.registerHandler(RPC_METHODS.Abort, async () => {}); + rpcHandlerManager.registerHandler(RPC_METHODS.Switch, async () => {}); } protected async requestExit( diff --git a/cli/src/opencode/opencodeRemoteLauncher.ts b/cli/src/opencode/opencodeRemoteLauncher.ts index f28f6af0..ab1d22ee 100644 --- a/cli/src/opencode/opencodeRemoteLauncher.ts +++ b/cli/src/opencode/opencodeRemoteLauncher.ts @@ -7,6 +7,7 @@ import { RemoteLauncherBase, type RemoteLauncherDisplayContext, type RemoteLaunc import { OpencodeDisplay } from '@/ui/ink/OpencodeDisplay'; import type { OpencodeSession } from './session'; import type { PermissionMode } from './types'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { createOpencodeBackend } from './utils/opencodeBackend'; import { OpencodePermissionHandler } from './utils/permissionHandler'; import { TITLE_INSTRUCTION } from './utils/systemPrompt'; @@ -95,7 +96,7 @@ class OpencodeRemoteLauncher extends RemoteLauncherBase { // Expose the cached models metadata via per-session RPC so the hub can // forward it to the web UI's model selector without round-tripping ACP. - session.client.rpcHandlerManager.registerHandler('listOpencodeModels', async () => { + session.client.rpcHandlerManager.registerHandler(RPC_METHODS.ListOpencodeModels, async () => { const metadata = backend.getSessionModelsMetadata?.(acpSessionId); if (!metadata) { return { success: true, availableModels: [], currentModelId: null }; diff --git a/cli/src/opencode/runOpencode.ts b/cli/src/opencode/runOpencode.ts index c5deea44..74c63681 100644 --- a/cli/src/opencode/runOpencode.ts +++ b/cli/src/opencode/runOpencode.ts @@ -10,6 +10,7 @@ import { bootstrapExistingSession, bootstrapSession } from '@/agent/sessionFacto import { registerLocalHandoffHandler } from '@/agent/localHandoff'; import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } from '@/agent/runnerLifecycle'; import { isPermissionModeAllowedForFlavor } from '@hapi/protocol'; +import { RPC_METHODS } from '@hapi/protocol/rpcMethods'; import { PermissionModeSchema } from '@hapi/protocol/schemas'; import { startOpencodeHookServer } from './utils/startOpencodeHookServer'; import { formatMessageWithAttachments } from '@/utils/attachmentFormatter'; @@ -144,7 +145,7 @@ export async function runOpencode(opts: { return value.trim(); }; - session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => { + session.rpcHandlerManager.registerHandler(RPC_METHODS.SetSessionConfig, async (payload: unknown) => { if (!payload || typeof payload !== 'object') { throw new Error('Invalid session config payload'); } diff --git a/hub/src/sync/rpcGateway.ts b/hub/src/sync/rpcGateway.ts index 67a463d8..419f5560 100644 --- a/hub/src/sync/rpcGateway.ts +++ b/hub/src/sync/rpcGateway.ts @@ -1,4 +1,5 @@ import type { AgentFlavor, CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types' +import { RPC_METHODS } from '@hapi/protocol/rpcMethods' import type { CodexModelSummary, CodexModelsResponse, @@ -48,7 +49,7 @@ export class RpcGateway { decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort', answers?: Record | Record ): Promise { - await this.sessionRpc(sessionId, 'permission', { + await this.sessionRpc(sessionId, RPC_METHODS.Permission, { id: requestId, approved: true, mode, @@ -63,7 +64,7 @@ export class RpcGateway { requestId: string, decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort' ): Promise { - await this.sessionRpc(sessionId, 'permission', { + await this.sessionRpc(sessionId, RPC_METHODS.Permission, { id: requestId, approved: false, decision @@ -71,11 +72,11 @@ export class RpcGateway { } async abortSession(sessionId: string): Promise { - await this.sessionRpc(sessionId, 'abort', { reason: 'User aborted via Telegram Bot' }) + await this.sessionRpc(sessionId, RPC_METHODS.Abort, { reason: 'User aborted via Telegram Bot' }) } async switchSession(sessionId: string, to: 'remote' | 'local'): Promise { - await this.sessionRpc(sessionId, 'switch', { to }) + await this.sessionRpc(sessionId, RPC_METHODS.Switch, { to }) } async requestSessionConfig( @@ -88,15 +89,15 @@ export class RpcGateway { collaborationMode?: CodexCollaborationMode } ): Promise { - return await this.sessionRpc(sessionId, 'set-session-config', config) + return await this.sessionRpc(sessionId, RPC_METHODS.SetSessionConfig, config) } async killSession(sessionId: string): Promise { - await this.sessionRpc(sessionId, 'killSession', {}) + await this.sessionRpc(sessionId, RPC_METHODS.KillSession, {}) } async handoffSessionToLocal(sessionId: string): Promise { - await this.sessionRpc(sessionId, 'handoff-local', {}) + await this.sessionRpc(sessionId, RPC_METHODS.HandoffLocal, {}) } async spawnSession( @@ -115,7 +116,7 @@ export class RpcGateway { try { const result = await this.machineRpc( machineId, - 'spawn-happy-session', + RPC_METHODS.SpawnHappySession, { type: 'spawn-in-directory', directory, agent, model, modelReasoningEffort, yolo, sessionType, worktreeName, resumeSessionId, effort, permissionMode } ) if (result && typeof result === 'object') { @@ -152,7 +153,7 @@ export class RpcGateway { } async listMachineDirectory(machineId: string, path: string): Promise { - const result = await this.machineRpc(machineId, 'list-directory', { path }) as RpcListDirectoryResponse | unknown + const result = await this.machineRpc(machineId, RPC_METHODS.ListMachineDirectory, { path }) as RpcListDirectoryResponse | unknown if (!result || typeof result !== 'object') { return { success: false, error: 'Unexpected list-directory result' } } @@ -160,7 +161,7 @@ export class RpcGateway { } async checkPathsExist(machineId: string, paths: string[]): Promise> { - const result = await this.machineRpc(machineId, 'path-exists', { paths }) as RpcPathExistsResponse | unknown + const result = await this.machineRpc(machineId, RPC_METHODS.PathExists, { paths }) as RpcPathExistsResponse | unknown if (!result || typeof result !== 'object') { throw new Error('Unexpected path-exists result') } @@ -178,43 +179,43 @@ export class RpcGateway { } async getGitStatus(sessionId: string, cwd?: string): Promise { - return await this.sessionRpc(sessionId, 'git-status', { cwd }) as RpcCommandResponse + return await this.sessionRpc(sessionId, RPC_METHODS.GitStatus, { cwd }) as RpcCommandResponse } async getGitDiffNumstat(sessionId: string, options: { cwd?: string; staged?: boolean }): Promise { - return await this.sessionRpc(sessionId, 'git-diff-numstat', options) as RpcCommandResponse + return await this.sessionRpc(sessionId, RPC_METHODS.GitDiffNumstat, options) as RpcCommandResponse } async getGitDiffFile(sessionId: string, options: { cwd?: string; filePath: string; staged?: boolean }): Promise { - return await this.sessionRpc(sessionId, 'git-diff-file', options) as RpcCommandResponse + return await this.sessionRpc(sessionId, RPC_METHODS.GitDiffFile, options) as RpcCommandResponse } async readSessionFile(sessionId: string, path: string): Promise { - return await this.sessionRpc(sessionId, 'readFile', { path }) as RpcReadFileResponse + return await this.sessionRpc(sessionId, RPC_METHODS.ReadFile, { path }) as RpcReadFileResponse } async readGeneratedImage(sessionId: string, imageId: string): Promise { - return await this.sessionRpc(sessionId, 'readGeneratedImage', { id: imageId }) as RpcGeneratedImageResponse + return await this.sessionRpc(sessionId, RPC_METHODS.ReadGeneratedImage, { id: imageId }) as RpcGeneratedImageResponse } async listDirectory(sessionId: string, path: string): Promise { - return await this.sessionRpc(sessionId, 'listDirectory', { path }) as RpcListDirectoryResponse + return await this.sessionRpc(sessionId, RPC_METHODS.ListDirectory, { path }) as RpcListDirectoryResponse } async uploadFile(sessionId: string, filename: string, content: string, mimeType: string): Promise { - return await this.sessionRpc(sessionId, 'uploadFile', { sessionId, filename, content, mimeType }) as RpcUploadFileResponse + return await this.sessionRpc(sessionId, RPC_METHODS.UploadFile, { sessionId, filename, content, mimeType }) as RpcUploadFileResponse } async deleteUploadFile(sessionId: string, path: string): Promise { - return await this.sessionRpc(sessionId, 'deleteUpload', { sessionId, path }) as RpcDeleteUploadResponse + return await this.sessionRpc(sessionId, RPC_METHODS.DeleteUpload, { sessionId, path }) as RpcDeleteUploadResponse } async runRipgrep(sessionId: string, args: string[], cwd?: string): Promise { - return await this.sessionRpc(sessionId, 'ripgrep', { args, cwd }) as RpcCommandResponse + return await this.sessionRpc(sessionId, RPC_METHODS.Ripgrep, { args, cwd }) as RpcCommandResponse } async listSlashCommands(sessionId: string, agent: string): Promise { - return await this.sessionRpc(sessionId, 'listSlashCommands', { agent }) as SlashCommandsResponse + return await this.sessionRpc(sessionId, RPC_METHODS.ListSlashCommands, { agent }) as SlashCommandsResponse } async listSkills(sessionId: string): Promise<{ @@ -222,7 +223,7 @@ export class RpcGateway { skills?: Array<{ name: string; description?: string }> error?: string }> { - return await this.sessionRpc(sessionId, 'listSkills', {}) as { + return await this.sessionRpc(sessionId, RPC_METHODS.ListSkills, {}) as { success: boolean skills?: Array<{ name: string; description?: string }> error?: string @@ -230,19 +231,19 @@ export class RpcGateway { } async listCodexModelsForSession(sessionId: string): Promise { - return await this.sessionRpc(sessionId, 'listCodexModels', {}, MODEL_LIST_RPC_TIMEOUT_MS) as RpcListCodexModelsResponse + return await this.sessionRpc(sessionId, RPC_METHODS.ListCodexModels, {}, MODEL_LIST_RPC_TIMEOUT_MS) as RpcListCodexModelsResponse } async listCodexModelsForMachine(machineId: string): Promise { - return await this.machineRpc(machineId, 'listCodexModels', {}, MODEL_LIST_RPC_TIMEOUT_MS) as RpcListCodexModelsResponse + return await this.machineRpc(machineId, RPC_METHODS.ListCodexModels, {}, MODEL_LIST_RPC_TIMEOUT_MS) as RpcListCodexModelsResponse } async listOpencodeModelsForSession(sessionId: string): Promise { - return await this.sessionRpc(sessionId, 'listOpencodeModels', {}) as RpcListOpencodeModelsResponse + return await this.sessionRpc(sessionId, RPC_METHODS.ListOpencodeModels, {}) as RpcListOpencodeModelsResponse } async listOpencodeModelsForCwd(machineId: string, cwd: string): Promise { - return await this.machineRpc(machineId, 'listOpencodeModelsForCwd', { cwd }) as RpcListOpencodeModelsResponse + return await this.machineRpc(machineId, RPC_METHODS.ListOpencodeModelsForCwd, { cwd }) as RpcListOpencodeModelsResponse } private async sessionRpc( diff --git a/shared/package.json b/shared/package.json index 54070ab7..d81bf2cb 100644 --- a/shared/package.json +++ b/shared/package.json @@ -12,6 +12,7 @@ "./slashCommands": "./src/slashCommands.ts", "./buildInfo": "./src/buildInfo.ts", "./modes": "./src/modes.ts", + "./rpcMethods": "./src/rpcMethods.ts", "./schemas": "./src/schemas.ts", "./types": "./src/types.ts", "./voice": "./src/voice.ts" diff --git a/shared/src/index.ts b/shared/src/index.ts index 09b7f18d..879ad35d 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -5,6 +5,7 @@ export * from './flavors' export * from './models' export * from './modes' export * from './resume' +export * from './rpcMethods' export * from './socket' export * from './sessionSummary' export * from './slashCommands' diff --git a/shared/src/rpcMethods.ts b/shared/src/rpcMethods.ts new file mode 100644 index 00000000..dd41a4e5 --- /dev/null +++ b/shared/src/rpcMethods.ts @@ -0,0 +1,33 @@ +export const RPC_METHODS = { + Permission: 'permission', + Abort: 'abort', + Switch: 'switch', + SetSessionConfig: 'set-session-config', + KillSession: 'killSession', + HandoffLocal: 'handoff-local', + SpawnHappySession: 'spawn-happy-session', + StopSession: 'stop-session', + StopRunner: 'stop-runner', + ListMachineDirectory: 'list-directory', + PathExists: 'path-exists', + GitStatus: 'git-status', + GitDiffNumstat: 'git-diff-numstat', + GitDiffFile: 'git-diff-file', + ReadFile: 'readFile', + ReadGeneratedImage: 'readGeneratedImage', + WriteFile: 'writeFile', + ListDirectory: 'listDirectory', + GetDirectoryTree: 'getDirectoryTree', + UploadFile: 'uploadFile', + DeleteUpload: 'deleteUpload', + Ripgrep: 'ripgrep', + Difftastic: 'difftastic', + Bash: 'bash', + ListSlashCommands: 'listSlashCommands', + ListSkills: 'listSkills', + ListCodexModels: 'listCodexModels', + ListOpencodeModels: 'listOpencodeModels', + ListOpencodeModelsForCwd: 'listOpencodeModelsForCwd' +} as const + +export type RpcMethod = typeof RPC_METHODS[keyof typeof RPC_METHODS]