mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: migrate socket types and payloads to shared protocol package
Move socket-related type definitions and schemas from cli and server packages into a shared @hapi/protocol package for centralized type management. This includes migrating Update types, socket event interfaces (ClientToServerEvents, ServerToClientEvents), and terminal payload schemas. Add strongly-typed socket handlers that reference the protocol package instead of duplicating definitions.
This commit is contained in:
@@ -6,7 +6,8 @@ import { io, type Socket } from 'socket.io-client'
|
||||
import { stat } from 'node:fs/promises'
|
||||
import { logger } from '@/ui/logger'
|
||||
import { configuration } from '@/configuration'
|
||||
import type { RunnerState, Machine, MachineMetadata, Update, UpdateMachineBody } from './types'
|
||||
import type { Update, UpdateMachineBody } from '@hapi/protocol'
|
||||
import type { RunnerState, Machine, MachineMetadata } from './types'
|
||||
import { RunnerStateSchema, MachineMetadataSchema } from './types'
|
||||
import { backoff } from '@/utils/time'
|
||||
import { RpcHandlerManager } from './rpc/RpcHandlerManager'
|
||||
|
||||
@@ -8,17 +8,21 @@ import { backoff } from '@/utils/time'
|
||||
import { AsyncLock } from '@/utils/lock'
|
||||
import type { RawJSONLines } from '@/claude/types'
|
||||
import { configuration } from '@/configuration'
|
||||
import type { ClientToServerEvents, ServerToClientEvents, Update } from '@hapi/protocol'
|
||||
import {
|
||||
TerminalClosePayloadSchema,
|
||||
TerminalOpenPayloadSchema,
|
||||
TerminalResizePayloadSchema,
|
||||
TerminalWritePayloadSchema
|
||||
} from '@hapi/protocol'
|
||||
import type {
|
||||
AgentState,
|
||||
ClientToServerEvents,
|
||||
MessageContent,
|
||||
MessageMeta,
|
||||
Metadata,
|
||||
ServerToClientEvents,
|
||||
Session,
|
||||
SessionModelMode,
|
||||
SessionPermissionMode,
|
||||
Update,
|
||||
UserMessage
|
||||
} from './types'
|
||||
import { AgentStateSchema, CliMessagesResponseSchema, MetadataSchema, UserMessageSchema } from './types'
|
||||
@@ -26,12 +30,6 @@ import { RpcHandlerManager } from './rpc/RpcHandlerManager'
|
||||
import { registerCommonHandlers } from '../modules/common/registerCommonHandlers'
|
||||
import { cleanupUploadDir } from '../modules/common/handlers/uploads'
|
||||
import { TerminalManager } from '@/terminal/TerminalManager'
|
||||
import {
|
||||
TerminalClosePayloadSchema,
|
||||
TerminalOpenPayloadSchema,
|
||||
TerminalResizePayloadSchema,
|
||||
TerminalWritePayloadSchema
|
||||
} from '@/terminal/types'
|
||||
import { applyVersionedAck } from './versionedUpdate'
|
||||
|
||||
export class ApiSessionClient extends EventEmitter {
|
||||
|
||||
@@ -2,16 +2,6 @@ import { AgentStateSchema, MetadataSchema, ModelModeSchema, PermissionModeSchema
|
||||
import type { ModelMode, PermissionMode } from '@hapi/protocol/types'
|
||||
import { z } from 'zod'
|
||||
import { UsageSchema } from '@/claude/types'
|
||||
import type {
|
||||
TerminalClosePayload,
|
||||
TerminalExitPayload,
|
||||
TerminalOpenPayload,
|
||||
TerminalOutputPayload,
|
||||
TerminalReadyPayload,
|
||||
TerminalResizePayload,
|
||||
TerminalWritePayload,
|
||||
TerminalErrorPayload
|
||||
} from '@/terminal/types'
|
||||
|
||||
export type Usage = z.infer<typeof UsageSchema>
|
||||
|
||||
@@ -56,59 +46,6 @@ export type Machine = {
|
||||
runnerStateVersion: number
|
||||
}
|
||||
|
||||
export const UpdateNewMessageBodySchema = z.object({
|
||||
t: z.literal('new-message'),
|
||||
sid: z.string(),
|
||||
message: z.object({
|
||||
id: z.string(),
|
||||
seq: z.number(),
|
||||
createdAt: z.number(),
|
||||
localId: z.string().nullable().optional(),
|
||||
content: z.unknown()
|
||||
})
|
||||
})
|
||||
|
||||
export type UpdateNewMessageBody = z.infer<typeof UpdateNewMessageBodySchema>
|
||||
|
||||
export const UpdateSessionBodySchema = z.object({
|
||||
t: z.literal('update-session'),
|
||||
sid: z.string(),
|
||||
metadata: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown()
|
||||
}).nullable(),
|
||||
agentState: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown().nullable()
|
||||
}).nullable()
|
||||
})
|
||||
|
||||
export type UpdateSessionBody = z.infer<typeof UpdateSessionBodySchema>
|
||||
|
||||
export const UpdateMachineBodySchema = z.object({
|
||||
t: z.literal('update-machine'),
|
||||
machineId: z.string(),
|
||||
metadata: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown()
|
||||
}).nullable(),
|
||||
runnerState: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown().nullable()
|
||||
}).nullable()
|
||||
})
|
||||
|
||||
export type UpdateMachineBody = z.infer<typeof UpdateMachineBodySchema>
|
||||
|
||||
export const UpdateSchema = z.object({
|
||||
id: z.string(),
|
||||
seq: z.number(),
|
||||
body: z.union([UpdateNewMessageBodySchema, UpdateSessionBodySchema, UpdateMachineBodySchema]),
|
||||
createdAt: z.number()
|
||||
})
|
||||
|
||||
export type Update = z.infer<typeof UpdateSchema>
|
||||
|
||||
export const CliMessagesResponseSchema = z.object({
|
||||
messages: z.array(z.object({
|
||||
id: z.string(),
|
||||
@@ -210,85 +147,3 @@ export type AgentMessage = z.infer<typeof AgentMessageSchema>
|
||||
export const MessageContentSchema = z.union([UserMessageSchema, AgentMessageSchema])
|
||||
|
||||
export type MessageContent = z.infer<typeof MessageContentSchema>
|
||||
|
||||
export type SocketErrorReason = 'namespace-missing' | 'access-denied' | 'not-found'
|
||||
|
||||
export interface ServerToClientEvents {
|
||||
update: (data: Update) => void
|
||||
'rpc-request': (data: { method: string; params: string }, callback: (response: string) => void) => void
|
||||
'terminal:open': (data: TerminalOpenPayload) => void
|
||||
'terminal:write': (data: TerminalWritePayload) => void
|
||||
'terminal:resize': (data: TerminalResizePayload) => void
|
||||
'terminal:close': (data: TerminalClosePayload) => void
|
||||
error: (data: { message: string; code?: SocketErrorReason; scope?: 'session' | 'machine'; id?: string }) => void
|
||||
}
|
||||
|
||||
export interface ClientToServerEvents {
|
||||
message: (data: { sid: string; message: unknown; localId?: string }) => void
|
||||
'session-alive': (data: {
|
||||
sid: string
|
||||
time: number
|
||||
thinking: boolean
|
||||
mode?: 'local' | 'remote'
|
||||
permissionMode?: SessionPermissionMode
|
||||
modelMode?: SessionModelMode
|
||||
}) => void
|
||||
'session-end': (data: { sid: string; time: number }) => void
|
||||
'update-metadata': (data: { sid: string; expectedVersion: number; metadata: unknown }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
}) => void) => void
|
||||
'update-state': (data: { sid: string; expectedVersion: number; agentState: unknown | null }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
agentState: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
agentState: unknown | null
|
||||
}) => void) => void
|
||||
'machine-alive': (data: { machineId: string; time: number }) => void
|
||||
'machine-update-metadata': (data: { machineId: string; expectedVersion: number; metadata: unknown }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
}) => void) => void
|
||||
'machine-update-state': (data: { machineId: string; expectedVersion: number; runnerState: unknown | null }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
runnerState: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
runnerState: unknown | null
|
||||
}) => void) => void
|
||||
'rpc-register': (data: { method: string }) => void
|
||||
'rpc-unregister': (data: { method: string }) => void
|
||||
'terminal:ready': (data: TerminalReadyPayload) => void
|
||||
'terminal:output': (data: TerminalOutputPayload) => void
|
||||
'terminal:exit': (data: TerminalExitPayload) => void
|
||||
'terminal:error': (data: TerminalErrorPayload) => void
|
||||
ping: (callback: () => void) => void
|
||||
'usage-report': (data: unknown) => void
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import type {
|
||||
TerminalErrorPayload,
|
||||
TerminalExitPayload,
|
||||
TerminalOutputPayload,
|
||||
TerminalReadyPayload,
|
||||
TerminalSession
|
||||
} from './types'
|
||||
TerminalReadyPayload
|
||||
} from '@hapi/protocol'
|
||||
import type { TerminalSession } from './types'
|
||||
|
||||
type TerminalRuntime = TerminalSession & {
|
||||
proc: Bun.Subprocess
|
||||
|
||||
@@ -1,72 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export type TerminalSession = {
|
||||
terminalId: string
|
||||
cols: number
|
||||
rows: number
|
||||
}
|
||||
|
||||
export const TerminalOpenPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive()
|
||||
})
|
||||
|
||||
export type TerminalOpenPayload = z.infer<typeof TerminalOpenPayloadSchema>
|
||||
|
||||
export const TerminalWritePayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
data: z.string()
|
||||
})
|
||||
|
||||
export type TerminalWritePayload = z.infer<typeof TerminalWritePayloadSchema>
|
||||
|
||||
export const TerminalResizePayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive()
|
||||
})
|
||||
|
||||
export type TerminalResizePayload = z.infer<typeof TerminalResizePayloadSchema>
|
||||
|
||||
export const TerminalClosePayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1)
|
||||
})
|
||||
|
||||
export type TerminalClosePayload = z.infer<typeof TerminalClosePayloadSchema>
|
||||
|
||||
export const TerminalReadyPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1)
|
||||
})
|
||||
|
||||
export type TerminalReadyPayload = z.infer<typeof TerminalReadyPayloadSchema>
|
||||
|
||||
export const TerminalOutputPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
data: z.string()
|
||||
})
|
||||
|
||||
export type TerminalOutputPayload = z.infer<typeof TerminalOutputPayloadSchema>
|
||||
|
||||
export const TerminalExitPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
code: z.number().int().nullable(),
|
||||
signal: z.string().nullable()
|
||||
})
|
||||
|
||||
export type TerminalExitPayload = z.infer<typeof TerminalExitPayloadSchema>
|
||||
|
||||
export const TerminalErrorPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
message: z.string()
|
||||
})
|
||||
|
||||
export type TerminalErrorPayload = z.infer<typeof TerminalErrorPayloadSchema>
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Store, StoredMachine, StoredSession } from '../../../store'
|
||||
import type { RpcRegistry } from '../../rpcRegistry'
|
||||
import type { SyncEvent } from '../../../sync/syncEngine'
|
||||
import type { TerminalRegistry } from '../../terminalRegistry'
|
||||
import type { SocketServer, SocketWithData } from '../../socketTypes'
|
||||
import type { CliSocketWithData, SocketServer } from '../../socketTypes'
|
||||
import { registerMachineHandlers } from './machineHandlers'
|
||||
import { registerRpcHandlers } from './rpcHandlers'
|
||||
import { registerSessionHandlers } from './sessionHandlers'
|
||||
@@ -45,7 +45,7 @@ type AccessResult<T> =
|
||||
| { ok: true; value: T }
|
||||
| { ok: false; reason: AccessErrorReason }
|
||||
|
||||
export function registerCliHandlers(socket: SocketWithData, deps: CliHandlersDeps): void {
|
||||
export function registerCliHandlers(socket: CliSocketWithData, deps: CliHandlersDeps): void {
|
||||
const { io, store, rpcRegistry, terminalRegistry, onSessionAlive, onSessionEnd, onMachineAlive, onWebappEvent } = deps
|
||||
const terminalNamespace = io.of('/terminal')
|
||||
const namespace = typeof socket.data.namespace === 'string' ? socket.data.namespace : null
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { ClientToServerEvents } from '@hapi/protocol'
|
||||
import { z } from 'zod'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import type { Store, StoredMachine } from '../../../store'
|
||||
import type { SyncEvent } from '../../../sync/syncEngine'
|
||||
import type { SocketWithData } from '../../socketTypes'
|
||||
import type { CliSocketWithData } from '../../socketTypes'
|
||||
|
||||
type MachineAlivePayload = {
|
||||
machineId: string
|
||||
@@ -19,6 +20,9 @@ type ResolveMachineAccess = (machineId: string) => AccessResult<StoredMachine>
|
||||
|
||||
type EmitAccessError = (scope: 'session' | 'machine', id: string, reason: AccessErrorReason) => void
|
||||
|
||||
type MachineUpdateMetadataHandler = ClientToServerEvents['machine-update-metadata']
|
||||
type MachineUpdateStateHandler = ClientToServerEvents['machine-update-state']
|
||||
|
||||
const machineUpdateMetadataSchema = z.object({
|
||||
machineId: z.string(),
|
||||
expectedVersion: z.number().int(),
|
||||
@@ -39,7 +43,7 @@ export type MachineHandlersDeps = {
|
||||
onWebappEvent?: (event: SyncEvent) => void
|
||||
}
|
||||
|
||||
export function registerMachineHandlers(socket: SocketWithData, deps: MachineHandlersDeps): void {
|
||||
export function registerMachineHandlers(socket: CliSocketWithData, deps: MachineHandlersDeps): void {
|
||||
const { store, resolveMachineAccess, emitAccessError, onMachineAlive, onWebappEvent } = deps
|
||||
|
||||
socket.on('machine-alive', (data: MachineAlivePayload) => {
|
||||
@@ -54,7 +58,7 @@ export function registerMachineHandlers(socket: SocketWithData, deps: MachineHan
|
||||
onMachineAlive?.(data)
|
||||
})
|
||||
|
||||
const handleMachineMetadataUpdate = (data: unknown, cb: (answer: unknown) => void) => {
|
||||
const handleMachineMetadataUpdate: MachineUpdateMetadataHandler = (data, cb) => {
|
||||
const parsed = machineUpdateMetadataSchema.safeParse(data)
|
||||
if (!parsed.success) {
|
||||
cb({ result: 'error' })
|
||||
@@ -94,7 +98,7 @@ export function registerMachineHandlers(socket: SocketWithData, deps: MachineHan
|
||||
}
|
||||
}
|
||||
|
||||
const handleMachineStateUpdate = (data: unknown, cb: (answer: unknown) => void) => {
|
||||
const handleMachineStateUpdate: MachineUpdateStateHandler = (data, cb) => {
|
||||
const parsed = machineUpdateStateSchema.safeParse(data)
|
||||
if (!parsed.success) {
|
||||
cb({ result: 'error' })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import type { RpcRegistry } from '../../rpcRegistry'
|
||||
import type { SocketWithData } from '../../socketTypes'
|
||||
import type { CliSocketWithData } from '../../socketTypes'
|
||||
|
||||
const rpcRegisterSchema = z.object({
|
||||
method: z.string().min(1)
|
||||
@@ -10,7 +10,7 @@ const rpcUnregisterSchema = z.object({
|
||||
method: z.string().min(1)
|
||||
})
|
||||
|
||||
export function registerRpcHandlers(socket: SocketWithData, rpcRegistry: RpcRegistry): void {
|
||||
export function registerRpcHandlers(socket: CliSocketWithData, rpcRegistry: RpcRegistry): void {
|
||||
socket.on('rpc-register', (data: unknown) => {
|
||||
const parsed = rpcRegisterSchema.safeParse(data)
|
||||
if (!parsed.success) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { ClientToServerEvents } from '@hapi/protocol'
|
||||
import { z } from 'zod'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import type { ModelMode, PermissionMode } from '@hapi/protocol/types'
|
||||
import type { Store, StoredSession } from '../../../store'
|
||||
import type { SyncEvent } from '../../../sync/syncEngine'
|
||||
import { extractTodoWriteTodosFromMessageContent } from '../../../sync/todos'
|
||||
import type { SocketWithData } from '../../socketTypes'
|
||||
import type { CliSocketWithData } from '../../socketTypes'
|
||||
|
||||
type SessionAlivePayload = {
|
||||
sid: string
|
||||
@@ -30,6 +31,9 @@ type ResolveSessionAccess = (sessionId: string) => AccessResult<StoredSession>
|
||||
|
||||
type EmitAccessError = (scope: 'session' | 'machine', id: string, reason: AccessErrorReason) => void
|
||||
|
||||
type UpdateMetadataHandler = ClientToServerEvents['update-metadata']
|
||||
type UpdateStateHandler = ClientToServerEvents['update-state']
|
||||
|
||||
const messageSchema = z.object({
|
||||
sid: z.string(),
|
||||
message: z.union([z.string(), z.unknown()]),
|
||||
@@ -57,7 +61,7 @@ export type SessionHandlersDeps = {
|
||||
onWebappEvent?: (event: SyncEvent) => void
|
||||
}
|
||||
|
||||
export function registerSessionHandlers(socket: SocketWithData, deps: SessionHandlersDeps): void {
|
||||
export function registerSessionHandlers(socket: CliSocketWithData, deps: SessionHandlersDeps): void {
|
||||
const { store, resolveSessionAccess, emitAccessError, onSessionAlive, onSessionEnd, onWebappEvent } = deps
|
||||
|
||||
socket.on('message', (data: unknown) => {
|
||||
@@ -127,7 +131,7 @@ export function registerSessionHandlers(socket: SocketWithData, deps: SessionHan
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('update-metadata', (data: unknown, cb: (answer: unknown) => void) => {
|
||||
const handleUpdateMetadata: UpdateMetadataHandler = (data, cb) => {
|
||||
const parsed = updateMetadataSchema.safeParse(data)
|
||||
if (!parsed.success) {
|
||||
cb({ result: 'error' })
|
||||
@@ -170,9 +174,11 @@ export function registerSessionHandlers(socket: SocketWithData, deps: SessionHan
|
||||
socket.to(`session:${sid}`).emit('update', update)
|
||||
onWebappEvent?.({ type: 'session-updated', sessionId: sid, data: { sid } })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
socket.on('update-state', (data: unknown, cb: (answer: unknown) => void) => {
|
||||
socket.on('update-metadata', handleUpdateMetadata)
|
||||
|
||||
const handleUpdateState: UpdateStateHandler = (data, cb) => {
|
||||
const parsed = updateStateSchema.safeParse(data)
|
||||
if (!parsed.success) {
|
||||
cb({ result: 'error' })
|
||||
@@ -215,7 +221,9 @@ export function registerSessionHandlers(socket: SocketWithData, deps: SessionHan
|
||||
socket.to(`session:${sid}`).emit('update', update)
|
||||
onWebappEvent?.({ type: 'session-updated', sessionId: sid, data: { sid } })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
socket.on('update-state', handleUpdateState)
|
||||
|
||||
socket.on('session-alive', (data: SessionAlivePayload) => {
|
||||
if (!data || typeof data.sid !== 'string' || typeof data.time !== 'number') {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
TerminalErrorPayloadSchema,
|
||||
TerminalExitPayloadSchema,
|
||||
TerminalOutputPayloadSchema,
|
||||
TerminalReadyPayloadSchema
|
||||
} from '@hapi/protocol'
|
||||
import type { StoredSession } from '../../../store'
|
||||
import type { TerminalRegistry } from '../../terminalRegistry'
|
||||
import type { SocketServer, SocketWithData } from '../../socketTypes'
|
||||
import type { CliSocketWithData, SocketServer } from '../../socketTypes'
|
||||
|
||||
type AccessErrorReason = 'namespace-missing' | 'access-denied' | 'not-found'
|
||||
|
||||
@@ -15,29 +20,10 @@ type EmitAccessError = (scope: 'session' | 'machine', id: string, reason: Access
|
||||
|
||||
type SocketNamespace = ReturnType<SocketServer['of']>
|
||||
|
||||
const terminalReadySchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1)
|
||||
})
|
||||
|
||||
const terminalOutputSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
data: z.string()
|
||||
})
|
||||
|
||||
const terminalExitSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
code: z.number().int().nullable(),
|
||||
signal: z.string().nullable()
|
||||
})
|
||||
|
||||
const terminalErrorSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
message: z.string()
|
||||
})
|
||||
const terminalReadySchema = TerminalReadyPayloadSchema
|
||||
const terminalOutputSchema = TerminalOutputPayloadSchema
|
||||
const terminalExitSchema = TerminalExitPayloadSchema
|
||||
const terminalErrorSchema = TerminalErrorPayloadSchema
|
||||
|
||||
export type TerminalHandlersDeps = {
|
||||
terminalRegistry: TerminalRegistry
|
||||
@@ -46,7 +32,7 @@ export type TerminalHandlersDeps = {
|
||||
emitAccessError: EmitAccessError
|
||||
}
|
||||
|
||||
export function registerTerminalHandlers(socket: SocketWithData, deps: TerminalHandlersDeps): void {
|
||||
export function registerTerminalHandlers(socket: CliSocketWithData, deps: TerminalHandlersDeps): void {
|
||||
const { terminalRegistry, terminalNamespace, resolveSessionAccess, emitAccessError } = deps
|
||||
|
||||
const forwardTerminalEvent = (event: string, payload: { sessionId: string; terminalId: string } & Record<string, unknown>) => {
|
||||
@@ -116,7 +102,7 @@ export function registerTerminalHandlers(socket: SocketWithData, deps: TerminalH
|
||||
})
|
||||
}
|
||||
|
||||
export function cleanupTerminalHandlers(socket: SocketWithData, deps: { terminalRegistry: TerminalRegistry; terminalNamespace: SocketNamespace }): void {
|
||||
export function cleanupTerminalHandlers(socket: CliSocketWithData, deps: { terminalRegistry: TerminalRegistry; terminalNamespace: SocketNamespace }): void {
|
||||
const removed = deps.terminalRegistry.removeByCliSocket(socket.id)
|
||||
for (const entry of removed) {
|
||||
const terminalSocket = deps.terminalNamespace.sockets.get(entry.socketId)
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { TerminalOpenPayloadSchema } from '@hapi/protocol'
|
||||
import { z } from 'zod'
|
||||
import type { TerminalRegistry, TerminalRegistryEntry } from '../terminalRegistry'
|
||||
import type { SocketServer, SocketWithData } from '../socketTypes'
|
||||
|
||||
const terminalCreateSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive()
|
||||
})
|
||||
const terminalCreateSchema = TerminalOpenPayloadSchema
|
||||
|
||||
const terminalWriteSchema = z.object({
|
||||
terminalId: z.string().min(1),
|
||||
|
||||
@@ -11,7 +11,7 @@ import { registerTerminalHandlers } from './handlers/terminal'
|
||||
import { RpcRegistry } from './rpcRegistry'
|
||||
import type { SyncEvent } from '../sync/syncEngine'
|
||||
import { TerminalRegistry } from './terminalRegistry'
|
||||
import type { SocketData, SocketServer } from './socketTypes'
|
||||
import type { CliSocketWithData, SocketData, SocketServer } from './socketTypes'
|
||||
|
||||
const jwtPayloadSchema = z.object({
|
||||
uid: z.number(),
|
||||
@@ -105,7 +105,7 @@ export function createSocketServer(deps: SocketServerDeps): {
|
||||
socket.data.namespace = parsedToken.namespace
|
||||
next()
|
||||
})
|
||||
cliNs.on('connection', (socket) => registerCliHandlers(socket, {
|
||||
cliNs.on('connection', (socket) => registerCliHandlers(socket as CliSocketWithData, {
|
||||
io,
|
||||
store: deps.store,
|
||||
rpcRegistry,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ClientToServerEvents, ServerToClientEvents } from '@hapi/protocol'
|
||||
import type { DefaultEventsMap, Server, Socket } from 'socket.io'
|
||||
|
||||
export type SocketData = {
|
||||
@@ -7,3 +8,5 @@ export type SocketData = {
|
||||
|
||||
export type SocketServer = Server<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, SocketData>
|
||||
export type SocketWithData = Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, SocketData>
|
||||
export type CliSocketServer = Server<ServerToClientEvents, ClientToServerEvents, DefaultEventsMap, SocketData>
|
||||
export type CliSocketWithData = Socket<ClientToServerEvents, ServerToClientEvents, DefaultEventsMap, SocketData>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './messages'
|
||||
export * from './modes'
|
||||
export * from './socket'
|
||||
export * from './sessionSummary'
|
||||
export * from './utils'
|
||||
export type * from './types'
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
import { z } from 'zod'
|
||||
import type { ModelMode, PermissionMode } from './modes'
|
||||
|
||||
export type SocketErrorReason = 'namespace-missing' | 'access-denied' | 'not-found'
|
||||
|
||||
export const TerminalOpenPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive()
|
||||
})
|
||||
|
||||
export type TerminalOpenPayload = z.infer<typeof TerminalOpenPayloadSchema>
|
||||
|
||||
export const TerminalWritePayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
data: z.string()
|
||||
})
|
||||
|
||||
export type TerminalWritePayload = z.infer<typeof TerminalWritePayloadSchema>
|
||||
|
||||
export const TerminalResizePayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
cols: z.number().int().positive(),
|
||||
rows: z.number().int().positive()
|
||||
})
|
||||
|
||||
export type TerminalResizePayload = z.infer<typeof TerminalResizePayloadSchema>
|
||||
|
||||
export const TerminalClosePayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1)
|
||||
})
|
||||
|
||||
export type TerminalClosePayload = z.infer<typeof TerminalClosePayloadSchema>
|
||||
|
||||
export const TerminalReadyPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1)
|
||||
})
|
||||
|
||||
export type TerminalReadyPayload = z.infer<typeof TerminalReadyPayloadSchema>
|
||||
|
||||
export const TerminalOutputPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
data: z.string()
|
||||
})
|
||||
|
||||
export type TerminalOutputPayload = z.infer<typeof TerminalOutputPayloadSchema>
|
||||
|
||||
export const TerminalExitPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
code: z.number().int().nullable(),
|
||||
signal: z.string().nullable()
|
||||
})
|
||||
|
||||
export type TerminalExitPayload = z.infer<typeof TerminalExitPayloadSchema>
|
||||
|
||||
export const TerminalErrorPayloadSchema = z.object({
|
||||
sessionId: z.string().min(1),
|
||||
terminalId: z.string().min(1),
|
||||
message: z.string()
|
||||
})
|
||||
|
||||
export type TerminalErrorPayload = z.infer<typeof TerminalErrorPayloadSchema>
|
||||
|
||||
export const UpdateNewMessageBodySchema = z.object({
|
||||
t: z.literal('new-message'),
|
||||
sid: z.string(),
|
||||
message: z.object({
|
||||
id: z.string(),
|
||||
seq: z.number(),
|
||||
createdAt: z.number(),
|
||||
localId: z.string().nullable().optional(),
|
||||
content: z.unknown()
|
||||
})
|
||||
})
|
||||
|
||||
export type UpdateNewMessageBody = z.infer<typeof UpdateNewMessageBodySchema>
|
||||
|
||||
export const UpdateSessionBodySchema = z.object({
|
||||
t: z.literal('update-session'),
|
||||
sid: z.string(),
|
||||
metadata: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown()
|
||||
}).nullable(),
|
||||
agentState: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown().nullable()
|
||||
}).nullable()
|
||||
})
|
||||
|
||||
export type UpdateSessionBody = z.infer<typeof UpdateSessionBodySchema>
|
||||
|
||||
export const UpdateMachineBodySchema = z.object({
|
||||
t: z.literal('update-machine'),
|
||||
machineId: z.string(),
|
||||
metadata: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown()
|
||||
}).nullable(),
|
||||
runnerState: z.object({
|
||||
version: z.number(),
|
||||
value: z.unknown().nullable()
|
||||
}).nullable()
|
||||
})
|
||||
|
||||
export type UpdateMachineBody = z.infer<typeof UpdateMachineBodySchema>
|
||||
|
||||
export const UpdateSchema = z.object({
|
||||
id: z.string(),
|
||||
seq: z.number(),
|
||||
body: z.union([UpdateNewMessageBodySchema, UpdateSessionBodySchema, UpdateMachineBodySchema]),
|
||||
createdAt: z.number()
|
||||
})
|
||||
|
||||
export type Update = z.infer<typeof UpdateSchema>
|
||||
|
||||
export interface ServerToClientEvents {
|
||||
update: (data: Update) => void
|
||||
'rpc-request': (data: { method: string; params: string }, callback: (response: string) => void) => void
|
||||
'terminal:open': (data: TerminalOpenPayload) => void
|
||||
'terminal:write': (data: TerminalWritePayload) => void
|
||||
'terminal:resize': (data: TerminalResizePayload) => void
|
||||
'terminal:close': (data: TerminalClosePayload) => void
|
||||
error: (data: { message: string; code?: SocketErrorReason; scope?: 'session' | 'machine'; id?: string }) => void
|
||||
}
|
||||
|
||||
export interface ClientToServerEvents {
|
||||
message: (data: { sid: string; message: unknown; localId?: string }) => void
|
||||
'session-alive': (data: {
|
||||
sid: string
|
||||
time: number
|
||||
thinking: boolean
|
||||
mode?: 'local' | 'remote'
|
||||
permissionMode?: PermissionMode
|
||||
modelMode?: ModelMode
|
||||
}) => void
|
||||
'session-end': (data: { sid: string; time: number }) => void
|
||||
'update-metadata': (data: { sid: string; expectedVersion: number; metadata: unknown }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
}) => void) => void
|
||||
'update-state': (data: { sid: string; expectedVersion: number; agentState: unknown | null }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
agentState: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
agentState: unknown | null
|
||||
}) => void) => void
|
||||
'machine-alive': (data: { machineId: string; time: number }) => void
|
||||
'machine-update-metadata': (data: { machineId: string; expectedVersion: number; metadata: unknown }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
metadata: unknown | null
|
||||
}) => void) => void
|
||||
'machine-update-state': (data: { machineId: string; expectedVersion: number; runnerState: unknown | null }, cb: (answer: {
|
||||
result: 'error'
|
||||
reason?: SocketErrorReason
|
||||
} | {
|
||||
result: 'version-mismatch'
|
||||
version: number
|
||||
runnerState: unknown | null
|
||||
} | {
|
||||
result: 'success'
|
||||
version: number
|
||||
runnerState: unknown | null
|
||||
}) => void) => void
|
||||
'rpc-register': (data: { method: string }) => void
|
||||
'rpc-unregister': (data: { method: string }) => void
|
||||
'terminal:ready': (data: TerminalReadyPayload) => void
|
||||
'terminal:output': (data: TerminalOutputPayload) => void
|
||||
'terminal:exit': (data: TerminalExitPayload) => void
|
||||
'terminal:error': (data: TerminalErrorPayload) => void
|
||||
ping: (callback: () => void) => void
|
||||
'usage-report': (data: unknown) => void
|
||||
}
|
||||
Reference in New Issue
Block a user