Share RPC method constants

This commit is contained in:
weishu
2026-05-21 10:53:31 +08:00
parent 41f6b37d69
commit b79f50f815
29 changed files with 129 additions and 68 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
import type { SessionEndReason } from '@hapi/protocol'
import { RPC_METHODS } from '@hapi/protocol/rpcMethods'
type RpcHandlerManagerLike = {
registerHandler<TRequest = unknown, TResponse = unknown>(
@@ -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(() => {
+2 -1
View File
@@ -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<PermissionResponseMessage, void>(
'permission',
RPC_METHODS.Permission,
async (response) => {
await this.handlePermissionResponse(response);
}
+3 -2
View File
@@ -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();
});
+7 -6
View File
@@ -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<PathExistsRequest, PathExistsResponse>('path-exists', async (params) => {
this.rpcHandlerManager.registerHandler<PathExistsRequest, PathExistsResponse>(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<string, boolean> = {}
@@ -146,7 +147,7 @@ export class ApiMachineClient {
return { exists }
})
this.rpcHandlerManager.registerHandler<ListMachineDirectoryRequest, MachineListDirectoryResponse>('list-directory', async (params) => {
this.rpcHandlerManager.registerHandler<ListMachineDirectoryRequest, MachineListDirectoryResponse>(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<ListOpencodeModelsForCwdRequest, ListOpencodeModelsForCwdResponse>(
'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' }
})
+2 -1
View File
@@ -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<void>
) {
rpcHandlerManager.registerHandler<KillSessionRequest, KillSessionResponse>('killSession', async () => {
rpcHandlerManager.registerHandler<KillSessionRequest, KillSessionResponse>(RPC_METHODS.KillSession, async () => {
logger.debug('Kill session request received');
// This will start the cleanup process
+2 -1
View File
@@ -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<void> {
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');
}
+2 -1
View File
@@ -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');
}
+2 -1
View File
@@ -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');
}
+2 -1
View File
@@ -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');
}
+2 -1
View File
@@ -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<BashRequest, BashResponse>('bash', async (data) => {
rpcHandlerManager.registerHandler<BashRequest, BashResponse>(RPC_METHODS.Bash, async (data) => {
logger.debug('Shell command request:', data.command)
if (data.cwd) {
@@ -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<ListCodexModelsRequest, ListCodexModelsResponse>('listCodexModels', async (data) => {
rpcHandlerManager.registerHandler<ListCodexModelsRequest, ListCodexModelsResponse>(RPC_METHODS.ListCodexModels, async (data) => {
logger.debug('List Codex models request');
try {
@@ -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<DifftasticRequest, DifftasticResponse>('difftastic', async (data) => {
rpcHandlerManager.registerHandler<DifftasticRequest, DifftasticResponse>(RPC_METHODS.Difftastic, async (data) => {
logger.debug('Difftastic request with args:', data.args, 'cwd:', data.cwd)
if (data.cwd) {
@@ -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<ListDirectoryRequest, ListDirectoryResponse>('listDirectory', async (data) => {
rpcHandlerManager.registerHandler<ListDirectoryRequest, ListDirectoryResponse>(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<GetDirectoryTreeRequest, GetDirectoryTreeResponse>('getDirectoryTree', async (data) => {
rpcHandlerManager.registerHandler<GetDirectoryTreeRequest, GetDirectoryTreeResponse>(RPC_METHODS.GetDirectoryTree, async (data) => {
logger.debug('Get directory tree request:', data.path, 'maxDepth:', data.maxDepth)
const targetPath = data.path || '.'
+4 -3
View File
@@ -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<ReadFileRequest, ReadFileResponse>('readFile', async (data) => {
rpcHandlerManager.registerHandler<ReadFileRequest, ReadFileResponse>(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<ReadGeneratedImageRequest, ReadGeneratedImageResponse>('readGeneratedImage', async (data) => {
rpcHandlerManager.registerHandler<ReadGeneratedImageRequest, ReadGeneratedImageResponse>(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<WriteFileRequest, WriteFileResponse>('writeFile', async (data) => {
rpcHandlerManager.registerHandler<WriteFileRequest, WriteFileResponse>(RPC_METHODS.WriteFile, async (data) => {
logger.debug('Write file request:', data.path)
const validation = validatePath(data.path, workingDirectory)
+4 -3
View File
@@ -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<GitStatusRequest, GitCommandResponse>('git-status', async (data) => {
rpcHandlerManager.registerHandler<GitStatusRequest, GitCommandResponse>(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<GitDiffNumstatRequest, GitCommandResponse>('git-diff-numstat', async (data) => {
rpcHandlerManager.registerHandler<GitDiffNumstatRequest, GitCommandResponse>(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<GitDiffFileRequest, GitCommandResponse>('git-diff-file', async (data) => {
rpcHandlerManager.registerHandler<GitDiffFileRequest, GitCommandResponse>(RPC_METHODS.GitDiffFile, async (data) => {
const resolved = resolveCwd(data.cwd, workingDirectory)
if (resolved.error) {
return rpcError(resolved.error)
@@ -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<ListOpencodeModelsForCwdRequest, ListOpencodeModelsForCwdResponse>(
'listOpencodeModelsForCwd',
RPC_METHODS.ListOpencodeModelsForCwd,
async (data) => {
logger.debug('List OpenCode models for cwd request', { cwd: data?.cwd });
+2 -1
View File
@@ -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<RipgrepRequest, RipgrepResponse>('ripgrep', async (data) => {
rpcHandlerManager.registerHandler<RipgrepRequest, RipgrepResponse>(RPC_METHODS.Ripgrep, async (data) => {
logger.debug('Ripgrep request with args:', data.args, 'cwd:', data.cwd)
if (data.cwd) {
+2 -1
View File
@@ -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<ListSkillsRequest, ListSkillsResponse>('listSkills', async () => {
rpcHandlerManager.registerHandler<ListSkillsRequest, ListSkillsResponse>(RPC_METHODS.ListSkills, async () => {
logger.debug('List skills request')
try {
@@ -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<ListSlashCommandsRequest, ListSlashCommandsResponse>('listSlashCommands', async (data) => {
rpcHandlerManager.registerHandler<ListSlashCommandsRequest, ListSlashCommandsResponse>(RPC_METHODS.ListSlashCommands, async (data) => {
logger.debug('List slash commands request for agent:', data.agent)
try {
+3 -2
View File
@@ -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<UploadFileRequest, UploadFileResponse>('uploadFile', async (data) => {
rpcHandlerManager.registerHandler<UploadFileRequest, UploadFileResponse>(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<DeleteUploadRequest, DeleteUploadResponse>('deleteUpload', async (data) => {
rpcHandlerManager.registerHandler<DeleteUploadRequest, DeleteUploadResponse>(RPC_METHODS.DeleteUpload, async (data) => {
const path = data?.path?.trim()
if (!path) {
return rpcError('Path is required')
@@ -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)
}
@@ -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<TRequest = unknown, TResponse = unknown>(
@@ -204,7 +205,7 @@ export abstract class BasePermissionHandler<TResponse extends { id: string }, TR
}
private setupRpcHandler(): void {
this.client.rpcHandlerManager.registerHandler<TResponse, void>('permission', async (response) => {
this.client.rpcHandlerManager.registerHandler<TResponse, void>(RPC_METHODS.Permission, async (response) => {
const pending = this.pendingRequests.get(response.id);
if (!pending) {
@@ -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(
+2 -1
View File
@@ -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 };
+2 -1
View File
@@ -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');
}
+26 -25
View File
@@ -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<string, string[]> | Record<string, { answers: string[] }>
): Promise<void> {
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<void> {
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<void> {
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<void> {
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<unknown> {
return await this.sessionRpc(sessionId, 'set-session-config', config)
return await this.sessionRpc(sessionId, RPC_METHODS.SetSessionConfig, config)
}
async killSession(sessionId: string): Promise<void> {
await this.sessionRpc(sessionId, 'killSession', {})
await this.sessionRpc(sessionId, RPC_METHODS.KillSession, {})
}
async handoffSessionToLocal(sessionId: string): Promise<void> {
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<RpcListDirectoryResponse> {
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<Record<string, boolean>> {
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<RpcCommandResponse> {
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<RpcCommandResponse> {
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<RpcCommandResponse> {
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<RpcReadFileResponse> {
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<RpcGeneratedImageResponse> {
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<RpcListDirectoryResponse> {
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<RpcUploadFileResponse> {
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<RpcDeleteUploadResponse> {
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<RpcCommandResponse> {
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<SlashCommandsResponse> {
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<RpcListCodexModelsResponse> {
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<RpcListCodexModelsResponse> {
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<RpcListOpencodeModelsResponse> {
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<RpcListOpencodeModelsResponse> {
return await this.machineRpc(machineId, 'listOpencodeModelsForCwd', { cwd }) as RpcListOpencodeModelsResponse
return await this.machineRpc(machineId, RPC_METHODS.ListOpencodeModelsForCwd, { cwd }) as RpcListOpencodeModelsResponse
}
private async sessionRpc(
+1
View File
@@ -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"
+1
View File
@@ -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'
+33
View File
@@ -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]