Update CLI chat session bridge (#697)
* feat: add CLI chat sessions with Python agent bridge Introduce a new CLI chat mode that connects Web UI directly to Hermes Agent's AIAgent via a Python bridge subprocess and Socket.IO, bypassing the API Server /v1/responses path. Supports streaming, slash commands (/new, /undo, /retry, /branch, /compress, /save, /title), interrupt, and steer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat: update CLI chat session bridge * fix: extend agent bridge startup timeouts * docs: update bridge chat session design * feat: align bridge compression and provider registry * chore: bump version to 0.5.20 --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ export interface StartRunRequest {
|
||||
session_id?: string
|
||||
model?: string
|
||||
queue_id?: string
|
||||
source?: 'api_server' | 'cli'
|
||||
}
|
||||
|
||||
export interface StartRunResponse {
|
||||
@@ -77,6 +78,8 @@ const sessionEventHandlers = new Map<string, {
|
||||
onAbortCompleted: (event: RunEvent) => void
|
||||
onUsageUpdated: (event: RunEvent) => void
|
||||
onRunQueued?: (event: RunEvent) => void
|
||||
onApprovalRequested?: (event: RunEvent) => void
|
||||
onApprovalResolved?: (event: RunEvent) => void
|
||||
}>()
|
||||
|
||||
/**
|
||||
@@ -288,6 +291,26 @@ function globalUsageUpdatedHandler(event: RunEvent): void {
|
||||
}
|
||||
}
|
||||
|
||||
function globalApprovalRequestedHandler(event: RunEvent): void {
|
||||
const sid = event.session_id
|
||||
if (!sid) return
|
||||
|
||||
const handlers = sessionEventHandlers.get(sid)
|
||||
if (handlers?.onApprovalRequested) {
|
||||
handlers.onApprovalRequested(event)
|
||||
}
|
||||
}
|
||||
|
||||
function globalApprovalResolvedHandler(event: RunEvent): void {
|
||||
const sid = event.session_id
|
||||
if (!sid) return
|
||||
|
||||
const handlers = sessionEventHandlers.get(sid)
|
||||
if (handlers?.onApprovalResolved) {
|
||||
handlers.onApprovalResolved(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event handlers for a session
|
||||
* @param sessionId - Session ID
|
||||
@@ -312,6 +335,8 @@ export function registerSessionHandlers(
|
||||
onAbortCompleted: (event: RunEvent) => void
|
||||
onUsageUpdated: (event: RunEvent) => void
|
||||
onRunQueued?: (event: RunEvent) => void
|
||||
onApprovalRequested?: (event: RunEvent) => void
|
||||
onApprovalResolved?: (event: RunEvent) => void
|
||||
}
|
||||
): () => void {
|
||||
sessionEventHandlers.set(sessionId, handlers)
|
||||
@@ -330,6 +355,19 @@ export function unregisterSessionHandlers(sessionId: string): void {
|
||||
sessionEventHandlers.delete(sessionId)
|
||||
}
|
||||
|
||||
export function respondToolApproval(
|
||||
sessionId: string,
|
||||
approvalId: string,
|
||||
choice: 'once' | 'session' | 'always' | 'deny',
|
||||
): void {
|
||||
const socket = connectChatRun()
|
||||
socket.emit('approval.respond', {
|
||||
session_id: sessionId,
|
||||
approval_id: approvalId,
|
||||
choice,
|
||||
})
|
||||
}
|
||||
|
||||
export function getChatRunSocket(): Socket | null {
|
||||
return chatRunSocket
|
||||
}
|
||||
@@ -365,7 +403,9 @@ export function connectChatRun(): Socket {
|
||||
reconnection: true,
|
||||
reconnectionAttempts: Infinity,
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionDelayMax: 10000,
|
||||
reconnectionDelayMax: 30000,
|
||||
randomizationFactor: 0.5,
|
||||
timeout: 30000,
|
||||
})
|
||||
|
||||
// Register global listeners only once per socket connection
|
||||
@@ -385,6 +425,8 @@ export function connectChatRun(): Socket {
|
||||
chatRunSocket.on('run.failed', globalRunFailedHandler)
|
||||
chatRunSocket.on('run.completed', globalRunCompletedHandler)
|
||||
chatRunSocket.on('run.queued', globalRunQueuedHandler)
|
||||
chatRunSocket.on('approval.requested', globalApprovalRequestedHandler)
|
||||
chatRunSocket.on('approval.resolved', globalApprovalResolvedHandler)
|
||||
|
||||
// Compression events
|
||||
chatRunSocket.on('compression.started', globalCompressionStartedHandler)
|
||||
@@ -527,6 +569,14 @@ export function startRunViaSocket(
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
},
|
||||
onApprovalRequested: (evt: RunEvent) => {
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
},
|
||||
onApprovalResolved: (evt: RunEvent) => {
|
||||
if (closed) return
|
||||
onEvent(evt)
|
||||
},
|
||||
}
|
||||
|
||||
// Register handlers in the global session map
|
||||
|
||||
@@ -66,11 +66,13 @@ export function connectGroupChat(opts?: { userId?: string; userName?: string; de
|
||||
name: opts?.userName || localStorage.getItem('gc_user_name') || undefined,
|
||||
description: opts?.description || localStorage.getItem('gc_user_description') || undefined,
|
||||
},
|
||||
transports: ['websocket'],
|
||||
transports: ['websocket', 'polling'],
|
||||
reconnection: true,
|
||||
reconnectionAttempts: Infinity,
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionDelayMax: 30000,
|
||||
randomizationFactor: 0.5,
|
||||
timeout: 30000,
|
||||
})
|
||||
|
||||
return socket
|
||||
@@ -185,4 +187,3 @@ export async function forceCompress(roomId: string): Promise<{ success: boolean;
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user