[codex] integrate goal command workflow (#1025)
* feat: integrate goal command workflow * fix: keep goal done visible * fix: add goal done slash command * fix: promote queued message on run start
This commit is contained in:
@@ -119,6 +119,7 @@ const sessionEventHandlers = new Map<string, {
|
||||
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
|
||||
@@ -129,6 +130,7 @@ const sessionEventHandlers = new Map<string, {
|
||||
}>()
|
||||
|
||||
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)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 MiB |
@@ -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') },
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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, () => {
|
||||
/>
|
||||
<Transition name="fade">
|
||||
<div v-if="chatStore.isRunActive || chatStore.abortState" class="streaming-indicator">
|
||||
<video
|
||||
:src="isDark ? thinkingVideoDark : thinkingVideoLight"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
<img
|
||||
:src="isDark ? thinkingImageDark : thinkingImageLight"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
class="thinking-video"
|
||||
/>
|
||||
>
|
||||
<div v-if="visibleToolCalls.length > 0 || chatStore.compressionState || chatStore.abortState" class="tool-calls-panel">
|
||||
<!-- Abort indicator -->
|
||||
<div v-if="chatStore.abortState" class="tool-call-item compression-item">
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: 'Aktiven Bridge-Lauf stoppen',
|
||||
queue: 'Nachricht hinter dem aktiven Lauf einreihen',
|
||||
plan: 'Markdown-Implementierungsplan schreiben',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: 'Aktuelle Anzeige leeren',
|
||||
clearHistory: 'Gespeicherten Nachrichtenverlauf dieser Sitzung löschen',
|
||||
title: 'Diese Sitzung umbenennen',
|
||||
|
||||
@@ -220,6 +220,13 @@ export default {
|
||||
abort: 'Stop the active bridge run',
|
||||
queue: 'Queue a message behind the active run',
|
||||
plan: 'Write a markdown implementation plan',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: 'Clear the current display',
|
||||
clearHistory: 'Delete this session’s stored message history',
|
||||
title: 'Rename this session',
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: 'Detener la ejecución activa de Bridge',
|
||||
queue: 'Poner un mensaje en cola tras la ejecución activa',
|
||||
plan: 'Escribir un plan de implementación en Markdown',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: 'Limpiar la vista actual',
|
||||
clearHistory: 'Eliminar el historial de mensajes guardado de esta sesión',
|
||||
title: 'Renombrar esta sesión',
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: 'Arrêter l’exécution Bridge active',
|
||||
queue: 'Mettre un message en file après l’exécution active',
|
||||
plan: 'Rédiger un plan d’implémentation Markdown',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: 'Effacer l’affichage actuel',
|
||||
clearHistory: 'Supprimer l’historique des messages enregistrés de cette session',
|
||||
title: 'Renommer cette session',
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: '実行中の Bridge を停止',
|
||||
queue: '実行中の処理の後ろにメッセージをキュー追加',
|
||||
plan: 'Markdown の実装計画を作成',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: '現在の表示をクリア',
|
||||
clearHistory: 'このセッションの保存済みメッセージ履歴を削除',
|
||||
title: 'このセッション名を変更',
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: '활성 Bridge 실행 중지',
|
||||
queue: '활성 실행 뒤에 메시지 대기열 추가',
|
||||
plan: 'Markdown 구현 계획 작성',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: '현재 표시 내용 지우기',
|
||||
clearHistory: '이 세션의 저장된 메시지 기록 삭제',
|
||||
title: '이 세션 이름 변경',
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: 'Parar a execução ativa do Bridge',
|
||||
queue: 'Enfileirar uma mensagem após a execução ativa',
|
||||
plan: 'Escrever um plano de implementação em Markdown',
|
||||
goal: 'Set a standing goal that continues across turns',
|
||||
goalStatus: 'Show the active goal status',
|
||||
goalPause: 'Pause the active goal loop',
|
||||
goalResume: 'Resume the paused goal loop',
|
||||
goalDone: 'Complete and clear the active goal',
|
||||
goalClear: 'Clear the active goal',
|
||||
subgoal: 'Add a criterion to the active goal',
|
||||
clear: 'Limpar a visualização atual',
|
||||
clearHistory: 'Excluir o histórico de mensagens salvo desta sessão',
|
||||
title: 'Renomear esta sessão',
|
||||
|
||||
@@ -219,6 +219,13 @@ export default {
|
||||
abort: '停止目前 Bridge 執行',
|
||||
queue: '將訊息加入目前執行後的佇列',
|
||||
plan: '產生一份 Markdown 實作計畫',
|
||||
goal: '設定一個跨輪次持續推進的目標',
|
||||
goalStatus: '查看目前目標狀態',
|
||||
goalPause: '暫停目前目標循環',
|
||||
goalResume: '繼續已暫停的目標循環',
|
||||
goalDone: '完成並清除目前目標',
|
||||
goalClear: '清除目前目標',
|
||||
subgoal: '為目前目標追加驗收條件',
|
||||
clear: '清空目前顯示內容',
|
||||
clearHistory: '刪除目前會話已儲存的訊息歷史',
|
||||
title: '重新命名目前會話',
|
||||
|
||||
@@ -220,6 +220,13 @@ export default {
|
||||
abort: '停止当前 Bridge 运行',
|
||||
queue: '把消息加入当前运行后的队列',
|
||||
plan: '生成一份 Markdown 实施计划',
|
||||
goal: '设置一个跨轮次持续推进的目标',
|
||||
goalStatus: '查看当前目标状态',
|
||||
goalPause: '暂停当前目标循环',
|
||||
goalResume: '继续已暂停的目标循环',
|
||||
goalDone: '完成并清除当前目标',
|
||||
goalClear: '清除当前目标',
|
||||
subgoal: '为当前目标追加验收条件',
|
||||
clear: '清空当前显示内容',
|
||||
clearHistory: '删除当前会话已入库的消息历史',
|
||||
title: '重命名当前会话',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { startRunViaSocket, resumeSession, registerSessionHandlers, unregisterSessionHandlers, getChatRunSocket, respondToolApproval, onPeerUserMessage, respondClarify, type RunEvent, type ResumeSessionPayload, type ContentBlock as ContentBlockImport } from '@/api/hermes/chat'
|
||||
import { startRunViaSocket, resumeSession, registerSessionHandlers, unregisterSessionHandlers, getChatRunSocket, respondToolApproval, onPeerUserMessage, onSessionCommand, respondClarify, type RunEvent, type ResumeSessionPayload, type ContentBlock as ContentBlockImport } from '@/api/hermes/chat'
|
||||
import { deleteSession as deleteSessionApi, fetchSession, fetchSessions, setSessionModel, type HermesMessage, type SessionSummary } from '@/api/hermes/sessions'
|
||||
import { getActiveProfileName } from '@/api/client'
|
||||
import { getDownloadUrl } from '@/api/hermes/download'
|
||||
@@ -365,6 +365,7 @@ function removeItem(key: string) {
|
||||
// File objects don't serialize and we only need name/type/size/url for display.
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const seenSessionCommandEvents = new WeakSet<RunEvent>()
|
||||
const sessions = ref<Session[]>([])
|
||||
const activeSessionId = ref<string | null>(null)
|
||||
const focusMessageId = ref<string | null>(null)
|
||||
@@ -778,6 +779,12 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function clearAgentEventMessages(sessionId: string) {
|
||||
const s = sessions.value.find(s => s.id === sessionId)
|
||||
if (!s) return
|
||||
s.messages = s.messages.filter(m => m.commandAction !== 'agent.event')
|
||||
}
|
||||
|
||||
function handleSubagentEvent(sessionId: string, evt: RunEvent) {
|
||||
const eventName = String(evt.event || '')
|
||||
if (!eventName.startsWith('subagent.')) return
|
||||
@@ -867,12 +874,19 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
function handleSessionCommandEvent(evt: RunEvent) {
|
||||
if (seenSessionCommandEvents.has(evt)) return
|
||||
seenSessionCommandEvents.add(evt)
|
||||
|
||||
const sid = evt.session_id
|
||||
if (!sid) return
|
||||
const target = sessions.value.find(s => s.id === sid)
|
||||
const action = (evt as any).action as string | undefined
|
||||
const command = String((evt as any).command || '').toLowerCase()
|
||||
if ((evt as any).started === true && (evt as any).terminal === false) {
|
||||
serverWorking.value.add(sid)
|
||||
}
|
||||
|
||||
if (action === 'clear') {
|
||||
if (action === 'clear' && command === 'clear') {
|
||||
if (target) target.messages = []
|
||||
queuedUserMessages.value.delete(sid)
|
||||
queueLengths.value.delete(sid)
|
||||
@@ -931,6 +945,36 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function handleAgentEvent(evt: RunEvent) {
|
||||
const sid = evt.session_id
|
||||
if (!sid) return
|
||||
const text = String((evt as any).text || (evt as any).message || '').trim()
|
||||
if (!text) return
|
||||
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const last = msgs[msgs.length - 1]
|
||||
const commandData = { ...(evt as any) }
|
||||
if (last?.role === 'system' && last.commandAction === 'agent.event') {
|
||||
if (last.content === text) return
|
||||
updateMessage(sid, last.id, {
|
||||
content: text,
|
||||
timestamp: Date.now(),
|
||||
commandData,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
addMessage(sid, {
|
||||
id: uid(),
|
||||
role: 'system',
|
||||
content: text,
|
||||
timestamp: Date.now(),
|
||||
systemType: 'command',
|
||||
commandAction: 'agent.event',
|
||||
commandData,
|
||||
})
|
||||
}
|
||||
|
||||
function enqueueUserMessage(sessionId: string, message: Message) {
|
||||
const queue = queuedUserMessages.value.get(sessionId) || []
|
||||
if (queue.some(item => item.id === message.id)) return
|
||||
@@ -957,6 +1001,23 @@ export const useChatStore = defineStore('chat', () => {
|
||||
})
|
||||
}
|
||||
|
||||
function promoteNextQueuedUserMessage(sessionId: string) {
|
||||
const queue = queuedUserMessages.value.get(sessionId)
|
||||
if (!queue?.length) return
|
||||
const [next, ...rest] = queue
|
||||
const nextMap = new Map(queuedUserMessages.value)
|
||||
if (rest.length > 0) {
|
||||
nextMap.set(sessionId, rest)
|
||||
} else {
|
||||
nextMap.delete(sessionId)
|
||||
}
|
||||
queuedUserMessages.value = nextMap
|
||||
if (!getSessionMsgs(sessionId).some(message => message.id === next.id)) {
|
||||
addMessage(sessionId, { ...next, queued: false })
|
||||
updateSessionTitle(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeQueuedUserMessages(rawMessages: unknown): Message[] {
|
||||
if (!Array.isArray(rawMessages)) return []
|
||||
return rawMessages.flatMap((raw) => {
|
||||
@@ -1004,7 +1065,27 @@ export const useChatStore = defineStore('chat', () => {
|
||||
queueLengths.value.delete(sessionId)
|
||||
}
|
||||
|
||||
if (Array.isArray((evt as any).queued_messages) && !(evt as any).dequeued_queue_id) {
|
||||
const dequeuedId = (evt as any).dequeued_queue_id != null
|
||||
? String((evt as any).dequeued_queue_id)
|
||||
: ''
|
||||
if (dequeuedId) {
|
||||
const existingQueue = queuedUserMessages.value.get(sessionId) || []
|
||||
const dequeued = existingQueue.find(message => message.id === dequeuedId)
|
||||
if (Array.isArray((evt as any).queued_messages)) {
|
||||
const queued = normalizeQueuedUserMessages((evt as any).queued_messages)
|
||||
replaceQueuedUserMessages(sessionId, queued)
|
||||
} else {
|
||||
const nextQueue = existingQueue.filter(message => message.id !== dequeuedId)
|
||||
replaceQueuedUserMessages(sessionId, nextQueue)
|
||||
}
|
||||
if (dequeued && !getSessionMsgs(sessionId).some(message => message.id === dequeued.id)) {
|
||||
addMessage(sessionId, { ...dequeued, queued: false })
|
||||
updateSessionTitle(sessionId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (Array.isArray((evt as any).queued_messages)) {
|
||||
const queued = normalizeQueuedUserMessages((evt as any).queued_messages)
|
||||
replaceQueuedUserMessages(sessionId, queued)
|
||||
return
|
||||
@@ -1129,21 +1210,6 @@ export const useChatStore = defineStore('chat', () => {
|
||||
pendingApprovals.value = new Map(pendingApprovals.value)
|
||||
}
|
||||
|
||||
function showNextQueuedUserMessage(sessionId: string) {
|
||||
const queue = queuedUserMessages.value.get(sessionId)
|
||||
if (!queue?.length) return
|
||||
const [next, ...rest] = queue
|
||||
const nextMap = new Map(queuedUserMessages.value)
|
||||
if (rest.length > 0) {
|
||||
nextMap.set(sessionId, rest)
|
||||
} else {
|
||||
nextMap.delete(sessionId)
|
||||
}
|
||||
queuedUserMessages.value = nextMap
|
||||
addMessage(sessionId, { ...next, queued: false })
|
||||
updateSessionTitle(sessionId)
|
||||
}
|
||||
|
||||
function updateSessionTitle(sessionId: string) {
|
||||
const target = sessions.value.find(s => s.id === sessionId)
|
||||
if (!target) return
|
||||
@@ -1189,6 +1255,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const isBridgeSlashCommand = content.trim().startsWith('/')
|
||||
const isBridgeCompressCommand = isBridgeSlashCommand && /^\/compress(?:\s|$)/i.test(content.trim())
|
||||
const isBridgePlanCommand = isBridgeSlashCommand && /^\/plan(?:\s|$)/i.test(content.trim())
|
||||
const isBridgeGoalCommand = isBridgeSlashCommand && /^\/goal(?:\s|$)/i.test(content.trim())
|
||||
const wasLiveBeforeSend = isSessionLive(sid)
|
||||
const shouldQueue = wasLiveBeforeSend && (!isBridgeSlashCommand || isBridgePlanCommand)
|
||||
|
||||
@@ -1283,10 +1350,6 @@ export const useChatStore = defineStore('chat', () => {
|
||||
let runHadToolActivity = false
|
||||
let activeAssistantMessageId: string | null = null
|
||||
|
||||
const startNextQueuedUser = () => {
|
||||
showNextQueuedUserMessage(sid)
|
||||
}
|
||||
|
||||
const closeStreamingAssistant = () => {
|
||||
const msgs = getSessionMsgs(sid)
|
||||
msgs.forEach(m => {
|
||||
@@ -1386,12 +1449,16 @@ export const useChatStore = defineStore('chat', () => {
|
||||
case 'run.failed':
|
||||
addAgentErrorMessage(sid, e.error)
|
||||
break
|
||||
case 'agent.event':
|
||||
handleAgentEvent(e)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSessionId.value === sid) activeSession.value = target
|
||||
if (!data.isWorking && !(data.queueLength && data.queueLength > 0)) {
|
||||
clearAgentEventMessages(sid)
|
||||
cleanup()
|
||||
activeAssistantMessageId = null
|
||||
updateSessionTitle(sid)
|
||||
@@ -1405,12 +1472,13 @@ export const useChatStore = defineStore('chat', () => {
|
||||
(evt: RunEvent) => {
|
||||
switch (evt.event) {
|
||||
case 'run.started':
|
||||
clearAgentEventMessages(sid)
|
||||
setAbortState(null)
|
||||
setCompressionState(null)
|
||||
runProducedAssistantText = false
|
||||
runHadToolActivity = false
|
||||
closeStreamingAssistant()
|
||||
startNextQueuedUser()
|
||||
promoteNextQueuedUserMessage(sid)
|
||||
if ((evt as any).queue_length > 0) {
|
||||
queueLengths.value.set(sid, (evt as any).queue_length)
|
||||
} else {
|
||||
@@ -1428,6 +1496,11 @@ export const useChatStore = defineStore('chat', () => {
|
||||
break
|
||||
}
|
||||
|
||||
case 'agent.event': {
|
||||
handleAgentEvent(evt)
|
||||
break
|
||||
}
|
||||
|
||||
case 'compression.started': {
|
||||
setCompressionState({
|
||||
compressing: true,
|
||||
@@ -1656,6 +1729,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
case 'run.completed': {
|
||||
clearAgentEventMessages(sid)
|
||||
const msgs = getSessionMsgs(sid)
|
||||
const lastMsg = activeAssistantMessageId
|
||||
? msgs.find(m => m.id === activeAssistantMessageId)
|
||||
@@ -1759,6 +1833,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
case 'run.failed': {
|
||||
clearAgentEventMessages(sid)
|
||||
if ((evt as any).inputTokens != null) {
|
||||
const target = sessions.value.find(s => s.id === sid)
|
||||
if (target) {
|
||||
@@ -1819,7 +1894,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
{ onReconnectResume: applyReconnectResume },
|
||||
)
|
||||
|
||||
if (!isBridgeSlashCommand || isBridgeCompressCommand || isBridgePlanCommand) {
|
||||
if (!isBridgeSlashCommand || isBridgeCompressCommand || isBridgePlanCommand || isBridgeGoalCommand) {
|
||||
streamStates.value.set(sid, ctrl)
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -1857,10 +1932,6 @@ export const useChatStore = defineStore('chat', () => {
|
||||
unregisterSessionHandlers(sid)
|
||||
}
|
||||
|
||||
const startNextQueuedUser = () => {
|
||||
showNextQueuedUserMessage(sid)
|
||||
}
|
||||
|
||||
const closeStreamingAssistant = () => {
|
||||
const msgs = getSessionMsgs(sid)
|
||||
msgs.forEach(m => {
|
||||
@@ -1887,13 +1958,19 @@ export const useChatStore = defineStore('chat', () => {
|
||||
break
|
||||
}
|
||||
|
||||
case 'agent.event': {
|
||||
handleAgentEvent(evt)
|
||||
break
|
||||
}
|
||||
|
||||
case 'run.started':
|
||||
clearAgentEventMessages(sid)
|
||||
setAbortState(null)
|
||||
setCompressionState(null)
|
||||
runProducedAssistantText = false
|
||||
runHadToolActivity = false
|
||||
closeStreamingAssistant()
|
||||
startNextQueuedUser()
|
||||
promoteNextQueuedUserMessage(sid)
|
||||
if ((evt as any).queue_length > 0) {
|
||||
queueLengths.value.set(sid, (evt as any).queue_length)
|
||||
} else {
|
||||
@@ -2118,6 +2195,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
case 'run.completed': {
|
||||
clearAgentEventMessages(sid)
|
||||
const hasQueue = (evt as any).queue_remaining > 0
|
||||
if (hasQueue) {
|
||||
queueLengths.value.set(sid, (evt as any).queue_remaining)
|
||||
@@ -2207,6 +2285,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
}
|
||||
|
||||
case 'run.failed': {
|
||||
clearAgentEventMessages(sid)
|
||||
if ((evt as any).inputTokens != null) {
|
||||
const target = sessions.value.find(s => s.id === sid)
|
||||
if (target) {
|
||||
@@ -2263,6 +2342,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
onAbortStarted: (evt) => handleEvent(evt),
|
||||
onAbortCompleted: (evt) => handleEvent(evt),
|
||||
onUsageUpdated: (evt) => handleEvent(evt),
|
||||
onAgentEvent: (evt) => handleEvent(evt),
|
||||
onSessionCommand: (evt) => handleEvent(evt),
|
||||
onRunQueued: (evt) => handleEvent(evt),
|
||||
onClarifyRequested: (evt) => handleEvent(evt),
|
||||
@@ -2326,6 +2406,19 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
onPeerUserMessage(handlePeerUserMessage)
|
||||
|
||||
function handleGlobalSessionCommand(evt: RunEvent) {
|
||||
const sid = evt.session_id
|
||||
if (!sid || activeSessionId.value !== sid || !activeSession.value) return
|
||||
const shouldAttachToStartedRun = (evt as any).started === true && (evt as any).terminal === false
|
||||
handleSessionCommandEvent(evt)
|
||||
if (shouldAttachToStartedRun) {
|
||||
serverWorking.value.add(sid)
|
||||
resumeServerWorkingRun(sid, true)
|
||||
}
|
||||
}
|
||||
|
||||
onSessionCommand(handleGlobalSessionCommand)
|
||||
|
||||
function stopStreaming() {
|
||||
const sid = activeSessionId.value
|
||||
if (!sid) return
|
||||
|
||||
Reference in New Issue
Block a user