diff --git a/packages/client/src/api/hermes/chat.ts b/packages/client/src/api/hermes/chat.ts index dc7c9f7..b4f08b9 100644 --- a/packages/client/src/api/hermes/chat.ts +++ b/packages/client/src/api/hermes/chat.ts @@ -119,6 +119,7 @@ const sessionEventHandlers = new Map void onAbortCompleted: (event: RunEvent) => void onUsageUpdated: (event: RunEvent) => void + onAgentEvent?: (event: RunEvent) => void onSessionCommand?: (event: RunEvent) => void onRunQueued?: (event: RunEvent) => void onApprovalRequested?: (event: RunEvent) => void @@ -129,6 +130,7 @@ const sessionEventHandlers = new Map() const peerUserMessageHandlers = new Set<(event: RunEvent) => void>() +const sessionCommandHandlers = new Set<(event: RunEvent) => void>() /** * Global message.delta event handler @@ -357,6 +359,20 @@ function globalSessionCommandHandler(event: RunEvent): void { if (handlers?.onSessionCommand) { handlers.onSessionCommand(event) } + + for (const handler of sessionCommandHandlers) { + handler(event) + } +} + +function globalAgentEventHandler(event: RunEvent): void { + const sid = event.session_id + if (!sid) return + + const handlers = sessionEventHandlers.get(sid) + if (handlers?.onAgentEvent) { + handlers.onAgentEvent(event) + } } function globalApprovalRequestedHandler(event: RunEvent): void { @@ -437,6 +453,7 @@ export function registerSessionHandlers( onAbortStarted: (event: RunEvent) => void onAbortCompleted: (event: RunEvent) => void onUsageUpdated: (event: RunEvent) => void + onAgentEvent?: (event: RunEvent) => void onSessionCommand?: (event: RunEvent) => void onRunQueued?: (event: RunEvent) => void onApprovalRequested?: (event: RunEvent) => void @@ -469,6 +486,13 @@ export function onPeerUserMessage(handler: (event: RunEvent) => void): () => voi } } +export function onSessionCommand(handler: (event: RunEvent) => void): () => void { + sessionCommandHandlers.add(handler) + return () => { + sessionCommandHandlers.delete(handler) + } +} + export function respondClarify( sessionId: string, clarifyId: string, @@ -577,6 +601,7 @@ export function connectChatRun(requestedProfile?: string | null): Socket { // Usage events chatRunSocket.on('usage.updated', globalUsageUpdatedHandler) + chatRunSocket.on('agent.event', globalAgentEventHandler) chatRunSocket.on('session.command', globalSessionCommandHandler) globalListenersRegistered = true @@ -790,6 +815,10 @@ export function startRunViaSocket( if (closed) return onEvent(evt) }, + onAgentEvent: (evt: RunEvent) => { + if (closed) return + onEvent(evt) + }, onSessionCommand: (evt: RunEvent) => { if (closed) return onEvent(evt) diff --git a/packages/client/src/assets/thinking-dark.gif b/packages/client/src/assets/thinking-dark.gif new file mode 100644 index 0000000..9da2a35 Binary files /dev/null and b/packages/client/src/assets/thinking-dark.gif differ diff --git a/packages/client/src/assets/thinking-light.gif b/packages/client/src/assets/thinking-light.gif new file mode 100644 index 0000000..72d4ddc Binary files /dev/null and b/packages/client/src/assets/thinking-light.gif differ diff --git a/packages/client/src/components/hermes/chat/ChatInput.vue b/packages/client/src/components/hermes/chat/ChatInput.vue index 44d96d3..2f103b9 100644 --- a/packages/client/src/components/hermes/chat/ChatInput.vue +++ b/packages/client/src/components/hermes/chat/ChatInput.vue @@ -29,6 +29,13 @@ const bridgeCommands = computed(() => [ { name: 'abort', args: '', description: t('chat.slashCommands.abort') }, { name: 'queue', args: t('chat.slashCommandArgs.message'), description: t('chat.slashCommands.queue') }, { name: 'plan', args: t('chat.slashCommandArgs.text'), description: t('chat.slashCommands.plan') }, + { name: 'goal', args: t('chat.slashCommandArgs.text'), description: t('chat.slashCommands.goal') }, + { name: 'goal', args: 'status', insertText: 'goal status', description: t('chat.slashCommands.goalStatus') }, + { name: 'goal', args: 'pause', insertText: 'goal pause', description: t('chat.slashCommands.goalPause') }, + { name: 'goal', args: 'resume', insertText: 'goal resume', description: t('chat.slashCommands.goalResume') }, + { name: 'goal', args: 'done', insertText: 'goal done', description: t('chat.slashCommands.goalDone') }, + { name: 'goal', args: 'clear', insertText: 'goal clear', description: t('chat.slashCommands.goalClear') }, + { name: 'subgoal', args: t('chat.slashCommandArgs.text'), description: t('chat.slashCommands.subgoal') }, { name: 'clear', args: '', description: t('chat.slashCommands.clear') }, { name: 'clear', args: '--history', insertText: 'clear --history', description: t('chat.slashCommands.clearHistory') }, { name: 'title', args: t('chat.slashCommandArgs.title'), description: t('chat.slashCommands.title') }, diff --git a/packages/client/src/components/hermes/chat/MessageItem.vue b/packages/client/src/components/hermes/chat/MessageItem.vue index c6509bf..c411098 100644 --- a/packages/client/src/components/hermes/chat/MessageItem.vue +++ b/packages/client/src/components/hermes/chat/MessageItem.vue @@ -38,7 +38,11 @@ const isAgentError = computed(() => props.message.role === "assistant" && props. const effectiveHeadingIdPrefix = computed(() => props.headingIdPrefix || `msg-${props.message.id}`); const isCommandMessage = computed(() => props.message.role === "command" || props.message.systemType === "command"); const isCommandError = computed(() => props.message.role === "command" && props.message.systemType === "error"); -const isStatusCommand = computed(() => isCommandMessage.value && props.message.commandAction === "status"); +const isStatusCommand = computed(() => + isCommandMessage.value + && props.message.commandAction === "status" + && props.message.commandData?.type !== "goal" +); const statusItems = computed(() => { const data = props.message.commandData || {}; return [ diff --git a/packages/client/src/components/hermes/chat/MessageList.vue b/packages/client/src/components/hermes/chat/MessageList.vue index 670a4e4..30b3ae0 100644 --- a/packages/client/src/components/hermes/chat/MessageList.vue +++ b/packages/client/src/components/hermes/chat/MessageList.vue @@ -3,8 +3,8 @@ import { ref, computed, watch, nextTick } from "vue"; import { useI18n } from "vue-i18n"; import MessageItem from "./MessageItem.vue"; import { useChatStore } from "@/stores/hermes/chat"; -import thinkingVideoLight from "@/assets/thinking-light.mp4"; -import thinkingVideoDark from "@/assets/thinking-dark.mp4"; +import thinkingImageLight from "@/assets/thinking-light.gif"; +import thinkingImageDark from "@/assets/thinking-dark.gif"; import { useTheme } from "@/composables/useTheme"; import { useToolTraceVisibility } from "@/composables/useToolTraceVisibility"; @@ -172,14 +172,12 @@ watch(currentToolCalls, () => { />
-