mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
@@ -44,10 +44,11 @@ export type CliHandlersDeps = {
|
||||
onBackgroundTaskDelta?: (sessionId: string, delta: { started: number; completed: number }) => void
|
||||
onSessionActivity?: (sessionId: string, updatedAt: number) => void
|
||||
onSweepImmediateQueued?: (sessionId: string, now: number) => void
|
||||
onMessagesConsumed?: (sessionId: string) => void
|
||||
}
|
||||
|
||||
export function registerCliHandlers(socket: CliSocketWithData, deps: CliHandlersDeps): void {
|
||||
const { io, store, rpcRegistry, terminalRegistry, onSessionAlive, onSessionEnd, onMachineAlive, onWebappEvent, onBackgroundTaskDelta, onSessionActivity, onSweepImmediateQueued } = deps
|
||||
const { io, store, rpcRegistry, terminalRegistry, onSessionAlive, onSessionEnd, onMachineAlive, onWebappEvent, onBackgroundTaskDelta, onSessionActivity, onSweepImmediateQueued, onMessagesConsumed } = deps
|
||||
const terminalNamespace = io.of('/terminal')
|
||||
const namespace = typeof socket.data.namespace === 'string' ? socket.data.namespace : null
|
||||
|
||||
@@ -109,7 +110,8 @@ export function registerCliHandlers(socket: CliSocketWithData, deps: CliHandlers
|
||||
onWebappEvent,
|
||||
onBackgroundTaskDelta,
|
||||
onSessionActivity,
|
||||
onSweepImmediateQueued
|
||||
onSweepImmediateQueued,
|
||||
onMessagesConsumed
|
||||
})
|
||||
registerMachineHandlers(socket, {
|
||||
store,
|
||||
|
||||
@@ -67,10 +67,13 @@ export type SessionHandlersDeps = {
|
||||
onSessionActivity?: (sessionId: string, updatedAt: number) => void
|
||||
/** Delegates session-end immediate-queue sweep to the MessageService layer. */
|
||||
onSweepImmediateQueued?: (sessionId: string, now: number) => void
|
||||
/** Drops the queued-thinking grace so synchronous CLI handlers (e.g. slash
|
||||
* commands) don't leave the spinner stuck for the full grace window. */
|
||||
onMessagesConsumed?: (sessionId: string) => void
|
||||
}
|
||||
|
||||
export function registerSessionHandlers(socket: CliSocketWithData, deps: SessionHandlersDeps): void {
|
||||
const { store, resolveSessionAccess, emitAccessError, onSessionAlive, onSessionEnd, onWebappEvent, onBackgroundTaskDelta, onSessionActivity, onSweepImmediateQueued } = deps
|
||||
const { store, resolveSessionAccess, emitAccessError, onSessionAlive, onSessionEnd, onWebappEvent, onBackgroundTaskDelta, onSessionActivity, onSweepImmediateQueued, onMessagesConsumed } = deps
|
||||
|
||||
socket.on('message', (data: unknown) => {
|
||||
const parsed = messageSchema.safeParse(data)
|
||||
@@ -269,7 +272,7 @@ export function registerSessionHandlers(socket: CliSocketWithData, deps: Session
|
||||
onSessionAlive?.(data)
|
||||
})
|
||||
|
||||
socket.on('messages-consumed', (data: { sid: string; localIds: string[] }) => {
|
||||
socket.on('messages-consumed', (data: { sid: string; localIds: string[]; clearQueuedThinkingGrace?: boolean }) => {
|
||||
if (!data || typeof data.sid !== 'string' || !Array.isArray(data.localIds)) {
|
||||
return
|
||||
}
|
||||
@@ -286,6 +289,14 @@ export function registerSessionHandlers(socket: CliSocketWithData, deps: Session
|
||||
try {
|
||||
store.messages.markMessagesInvoked(data.sid, localIds, invokedAt)
|
||||
onSessionActivity?.(data.sid, invokedAt)
|
||||
// Only drop the queued-thinking grace when the CLI explicitly opts in
|
||||
// (synchronous handlers like slash commands that will never send
|
||||
// their own `thinking=true` keepalive). Normal queue drains still
|
||||
// need the grace so the spinner doesn't flicker between the queue
|
||||
// shift and `backend.prompt` start.
|
||||
if (data.clearQueuedThinkingGrace === true) {
|
||||
onMessagesConsumed?.(data.sid)
|
||||
}
|
||||
// Emit only after the DB write succeeds. Otherwise a transient SQLite
|
||||
// failure would broadcast an `invokedAt` that was never persisted —
|
||||
// live clients would hide the queued rows while a refresh / secondary
|
||||
|
||||
@@ -42,6 +42,7 @@ export type SocketServerDeps = {
|
||||
onBackgroundTaskDelta?: (sessionId: string, delta: { started: number; completed: number }) => void
|
||||
onSessionActivity?: (sessionId: string, updatedAt: number) => void
|
||||
onSweepImmediateQueued?: (sessionId: string, now: number) => void
|
||||
onMessagesConsumed?: (sessionId: string) => void
|
||||
}
|
||||
|
||||
export function createSocketServer(deps: SocketServerDeps): {
|
||||
@@ -120,7 +121,8 @@ export function createSocketServer(deps: SocketServerDeps): {
|
||||
onWebappEvent: deps.onWebappEvent,
|
||||
onBackgroundTaskDelta: deps.onBackgroundTaskDelta,
|
||||
onSessionActivity: deps.onSessionActivity,
|
||||
onSweepImmediateQueued: deps.onSweepImmediateQueued
|
||||
onSweepImmediateQueued: deps.onSweepImmediateQueued,
|
||||
onMessagesConsumed: deps.onMessagesConsumed
|
||||
}))
|
||||
|
||||
terminalNs.use(async (socket, next) => {
|
||||
|
||||
+2
-1
@@ -189,7 +189,8 @@ export async function startHub(options: StartHubOptions = {}): Promise<HubInstan
|
||||
onMachineAlive: (payload) => syncEngine?.handleMachineAlive(payload),
|
||||
onBackgroundTaskDelta: (sessionId, delta) => syncEngine?.handleBackgroundTaskDelta(sessionId, delta),
|
||||
onSessionActivity: (sessionId, updatedAt) => syncEngine?.recordSessionActivity(sessionId, updatedAt),
|
||||
onSweepImmediateQueued: (sessionId, now) => syncEngine?.sweepImmediateQueuedOnSessionEnd(sessionId, now)
|
||||
onSweepImmediateQueued: (sessionId, now) => syncEngine?.sweepImmediateQueuedOnSessionEnd(sessionId, now),
|
||||
onMessagesConsumed: (sessionId) => syncEngine?.clearQueuedThinkingGrace(sessionId)
|
||||
})
|
||||
|
||||
syncEngine = new SyncEngine(store, socketServer.io, socketServer.rpcRegistry, sseManager)
|
||||
|
||||
@@ -260,6 +260,22 @@ export class SessionCache {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the queued-message thinking grace timer for a session.
|
||||
*
|
||||
* `markMessageQueued` sets a 15s grace during which we keep `thinking=true`
|
||||
* even if the CLI sends `keepAlive(thinking=false)` — that grace exists to
|
||||
* cover the gap between the user POSTing a prompt and the CLI starting to
|
||||
* stream. Sessions that handle the message synchronously (e.g. slash
|
||||
* commands intercepted in `onUserMessage`) never call onThinkingChange and
|
||||
* would otherwise leave the spinner stuck for the full grace window. The
|
||||
* messages-consumed socket event signals the CLI has finished its
|
||||
* synchronous handling, so it's safe to drop the grace.
|
||||
*/
|
||||
clearQueuedThinkingGrace(sessionId: string): void {
|
||||
this.pendingThinkingUntilBySessionId.delete(sessionId)
|
||||
}
|
||||
|
||||
markMessageQueued(sessionId: string, time: number = Date.now()): void {
|
||||
const session = this.sessions.get(sessionId) ?? this.refreshSession(sessionId)
|
||||
if (!session) return
|
||||
|
||||
@@ -291,6 +291,10 @@ export class SyncEngine {
|
||||
this.triggerDedupIfNeeded(payload.sid)
|
||||
}
|
||||
|
||||
clearQueuedThinkingGrace(sessionId: string): void {
|
||||
this.sessionCache.clearQueuedThinkingGrace(sessionId)
|
||||
}
|
||||
|
||||
handleSessionEnd(payload: { sid: string; time: number; reason?: 'completed' | 'terminated' | 'error' }): void {
|
||||
this.sessionCache.handleSessionEnd(payload)
|
||||
this.eventPublisher.emit({
|
||||
|
||||
Reference in New Issue
Block a user