restore: bring back all custom UI enhancements from checkpoint
Restored from commit 52be710 (checkpoint before qwen oauth + todo roller): Enhanced UI Features: - SMART FIX button with AI code analysis - APEX (Autonomous Programming EXecution) mode - SHIELD (Auto-approval) mode - MULTIX MODE multi-task pipeline interface - Live streaming token counter - Thinking indicator with bouncing dots animation Components restored: - packages/ui/src/components/chat/multi-task-chat.tsx - packages/ui/src/components/instance/instance-shell2.tsx - packages/ui/src/components/settings/OllamaCloudSettings.tsx - packages/ui/src/components/settings/QwenCodeSettings.tsx - packages/ui/src/stores/solo-store.ts - packages/ui/src/stores/task-actions.ts - packages/ui/src/stores/session-events.ts (autonomous mode) - packages/server/src/integrations/ollama-cloud.ts - packages/server/src/server/routes/ollama.ts - packages/server/src/server/routes/qwen.ts This ensures all custom features are preserved in source control.
This commit is contained in:
@@ -17,11 +17,14 @@ import type { MessageStatus } from "./message-v2/types"
|
||||
|
||||
import { getLogger } from "../lib/logger"
|
||||
import { showToastNotification, ToastVariant } from "../lib/notifications"
|
||||
import { instances, addPermissionToQueue, removePermissionFromQueue } from "./instances"
|
||||
import { instances, addPermissionToQueue, removePermissionFromQueue, sendPermissionResponse } from "./instances"
|
||||
import { getSoloState, incrementStep, popFromTaskQueue, setActiveTaskId } from "./solo-store"
|
||||
import { sendMessage } from "./session-actions"
|
||||
import { showAlertDialog } from "./alerts"
|
||||
import { sessions, setSessions, withSession } from "./session-state"
|
||||
import { normalizeMessagePart } from "./message-v2/normalizers"
|
||||
import { updateSessionInfo } from "./message-v2/session-info"
|
||||
import { addTaskMessage, replaceTaskMessageId } from "./task-actions"
|
||||
|
||||
const log = getLogger("sse")
|
||||
import { loadMessages } from "./session-api"
|
||||
@@ -77,20 +80,20 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
||||
if (event.type === "message.part.updated") {
|
||||
const rawPart = event.properties?.part
|
||||
if (!rawPart) return
|
||||
|
||||
|
||||
const part = normalizeMessagePart(rawPart)
|
||||
const messageInfo = (event as any)?.properties?.message as MessageInfo | undefined
|
||||
|
||||
|
||||
const fallbackSessionId = typeof messageInfo?.sessionID === "string" ? messageInfo.sessionID : undefined
|
||||
const fallbackMessageId = typeof messageInfo?.id === "string" ? messageInfo.id : undefined
|
||||
|
||||
|
||||
const sessionId = typeof part.sessionID === "string" ? part.sessionID : fallbackSessionId
|
||||
const messageId = typeof part.messageID === "string" ? part.messageID : fallbackMessageId
|
||||
if (!sessionId || !messageId) return
|
||||
|
||||
|
||||
const session = instanceSessions.get(sessionId)
|
||||
if (!session) return
|
||||
|
||||
// Note: session may be null for newly forked sessions where SSE event arrives before session is registered
|
||||
|
||||
const store = messageStoreBus.getOrCreate(instanceId)
|
||||
const role: MessageRole = resolveMessageRole(messageInfo)
|
||||
const createdAt = typeof messageInfo?.time?.created === "number" ? messageInfo.time.created : Date.now()
|
||||
@@ -101,6 +104,7 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
||||
const pendingId = findPendingMessageId(store, sessionId, role)
|
||||
if (pendingId && pendingId !== messageId) {
|
||||
replaceMessageIdV2(instanceId, pendingId, messageId)
|
||||
replaceTaskMessageId(instanceId, sessionId, pendingId, messageId)
|
||||
record = store.getMessage(messageId)
|
||||
}
|
||||
}
|
||||
@@ -115,12 +119,37 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
||||
updatedAt: createdAt,
|
||||
isEphemeral: true,
|
||||
})
|
||||
|
||||
// Try to associate message with task
|
||||
if (session?.activeTaskId) {
|
||||
addTaskMessage(instanceId, sessionId, session.activeTaskId, messageId)
|
||||
} else if (session?.parentId) {
|
||||
// This is a task session. Find the parent and update the task.
|
||||
const parentSession = instanceSessions.get(session.parentId)
|
||||
if (parentSession?.tasks) {
|
||||
const task = parentSession.tasks.find((t) => t.taskSessionId === sessionId)
|
||||
if (task) {
|
||||
addTaskMessage(instanceId, session.parentId, task.id, messageId)
|
||||
}
|
||||
}
|
||||
} else if (!session) {
|
||||
// Session not found yet - search all sessions for a task with this sessionId
|
||||
for (const [, candidateSession] of instanceSessions) {
|
||||
if (candidateSession.tasks) {
|
||||
const task = candidateSession.tasks.find((t) => t.taskSessionId === sessionId)
|
||||
if (task) {
|
||||
addTaskMessage(instanceId, candidateSession.id, task.id, messageId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messageInfo) {
|
||||
upsertMessageInfoV2(instanceId, messageInfo, { status: "streaming" })
|
||||
}
|
||||
|
||||
|
||||
applyPartUpdateV2(instanceId, { ...part, sessionID: sessionId, messageID: messageId })
|
||||
|
||||
|
||||
@@ -134,18 +163,30 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
||||
if (!sessionId || !messageId) return
|
||||
|
||||
const session = instanceSessions.get(sessionId)
|
||||
if (!session) return
|
||||
// Note: session may be null for newly forked sessions where SSE event arrives before session is registered
|
||||
|
||||
const store = messageStoreBus.getOrCreate(instanceId)
|
||||
const role: MessageRole = info.role === "user" ? "user" : "assistant"
|
||||
const hasError = Boolean((info as any).error)
|
||||
const status: MessageStatus = hasError ? "error" : "complete"
|
||||
|
||||
// Auto-correction logic for SOLO
|
||||
const solo = getSoloState(instanceId)
|
||||
if (hasError && solo.isAutonomous && solo.currentStep < solo.maxSteps) {
|
||||
log.info(`[SOLO] Error detected in autonomous mode, prompting for fix: ${messageId}`)
|
||||
const errorMessage = (info as any).error?.message || "Unknown error"
|
||||
incrementStep(instanceId)
|
||||
sendMessage(instanceId, sessionId, `The previous step failed with error: ${errorMessage}. Please analyze the error and try a different approach.`, [], solo.activeTaskId || undefined).catch((err) => {
|
||||
log.error("[SOLO] Failed to send error correction message", err)
|
||||
})
|
||||
}
|
||||
|
||||
let record = store.getMessage(messageId)
|
||||
if (!record) {
|
||||
const pendingId = findPendingMessageId(store, sessionId, role)
|
||||
if (pendingId && pendingId !== messageId) {
|
||||
replaceMessageIdV2(instanceId, pendingId, messageId)
|
||||
replaceTaskMessageId(instanceId, sessionId, pendingId, messageId)
|
||||
record = store.getMessage(messageId)
|
||||
}
|
||||
}
|
||||
@@ -161,6 +202,31 @@ function handleMessageUpdate(instanceId: string, event: MessageUpdateEvent | Mes
|
||||
createdAt,
|
||||
updatedAt: completedAt ?? createdAt,
|
||||
})
|
||||
|
||||
// Try to associate message with task
|
||||
if (session?.activeTaskId) {
|
||||
addTaskMessage(instanceId, sessionId, session.activeTaskId, messageId)
|
||||
} else if (session?.parentId) {
|
||||
// This is a task session. Find the parent and update the task.
|
||||
const parentSession = instanceSessions.get(session.parentId)
|
||||
if (parentSession?.tasks) {
|
||||
const task = parentSession.tasks.find((t) => t.taskSessionId === sessionId)
|
||||
if (task) {
|
||||
addTaskMessage(instanceId, session.parentId, task.id, messageId)
|
||||
}
|
||||
}
|
||||
} else if (!session) {
|
||||
// Session not found yet - search all sessions for a task with this sessionId
|
||||
for (const [, candidateSession] of instanceSessions) {
|
||||
if (candidateSession.tasks) {
|
||||
const task = candidateSession.tasks.find((t) => t.taskSessionId === sessionId)
|
||||
if (task) {
|
||||
addTaskMessage(instanceId, candidateSession.id, task.id, messageId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
upsertMessageInfoV2(instanceId, info, { status, bumpRevision: true })
|
||||
@@ -198,9 +264,9 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
|
||||
time: info.time
|
||||
? { ...info.time }
|
||||
: {
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
},
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
},
|
||||
} as any
|
||||
|
||||
setSessions((prev) => {
|
||||
@@ -228,11 +294,11 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
|
||||
time: mergedTime,
|
||||
revert: info.revert
|
||||
? {
|
||||
messageID: info.revert.messageID,
|
||||
partID: info.revert.partID,
|
||||
snapshot: info.revert.snapshot,
|
||||
diff: info.revert.diff,
|
||||
}
|
||||
messageID: info.revert.messageID,
|
||||
partID: info.revert.partID,
|
||||
snapshot: info.revert.snapshot,
|
||||
diff: info.revert.diff,
|
||||
}
|
||||
: existingSession.revert,
|
||||
}
|
||||
|
||||
@@ -247,11 +313,50 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
|
||||
}
|
||||
}
|
||||
|
||||
function handleSessionIdle(_instanceId: string, event: EventSessionIdle): void {
|
||||
function handleSessionIdle(instanceId: string, event: EventSessionIdle): void {
|
||||
const sessionId = event.properties?.sessionID
|
||||
if (!sessionId) return
|
||||
|
||||
log.info(`[SSE] Session idle: ${sessionId}`)
|
||||
|
||||
// Autonomous continuation logic for SOLO
|
||||
const solo = getSoloState(instanceId)
|
||||
if (solo.isAutonomous && solo.currentStep < solo.maxSteps) {
|
||||
const instanceSessions = sessions().get(instanceId)
|
||||
const session = instanceSessions?.get(sessionId)
|
||||
if (!session) return
|
||||
|
||||
// If there's an active task, we might want to prompt the agent to continue or check progress
|
||||
if (solo.activeTaskId) {
|
||||
log.info(`[SOLO] Session idle in autonomous mode, prompting continuation for task: ${solo.activeTaskId}`)
|
||||
incrementStep(instanceId)
|
||||
sendMessage(instanceId, sessionId, "Continue", [], solo.activeTaskId).catch((err) => {
|
||||
log.error("[SOLO] Failed to send continuation message", err)
|
||||
})
|
||||
} else {
|
||||
// Check if there's another task in the queue
|
||||
const nextTaskId = popFromTaskQueue(instanceId)
|
||||
if (nextTaskId) {
|
||||
log.info(`[SOLO] Session idle, starting next task from queue: ${nextTaskId}`)
|
||||
|
||||
// Find the task title to provide context
|
||||
let taskTitle = "Start next task"
|
||||
const instanceSessions = sessions().get(instanceId)
|
||||
const session = instanceSessions?.get(sessionId)
|
||||
if (session?.tasks) {
|
||||
const task = session.tasks.find(t => t.id === nextTaskId)
|
||||
if (task) {
|
||||
taskTitle = `Please start working on the task: "${task.title}". Provide a plan and begin execution.`
|
||||
}
|
||||
}
|
||||
|
||||
setActiveTaskId(instanceId, nextTaskId)
|
||||
sendMessage(instanceId, sessionId, taskTitle, [], nextTaskId).catch((err) => {
|
||||
log.error("[SOLO] Failed to start next task", err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSessionCompacted(instanceId: string, event: EventSessionCompacted): void {
|
||||
@@ -284,7 +389,7 @@ function handleSessionCompacted(instanceId: string, event: EventSessionCompacted
|
||||
})
|
||||
}
|
||||
|
||||
function handleSessionError(_instanceId: string, event: EventSessionError): void {
|
||||
function handleSessionError(instanceId: string, event: EventSessionError): void {
|
||||
const error = event.properties?.error
|
||||
log.error(`[SSE] Session error:`, error)
|
||||
|
||||
@@ -298,10 +403,21 @@ function handleSessionError(_instanceId: string, event: EventSessionError): void
|
||||
}
|
||||
}
|
||||
|
||||
showAlertDialog(`Error: ${message}`, {
|
||||
title: "Session error",
|
||||
variant: "error",
|
||||
})
|
||||
// Autonomous error recovery for SOLO
|
||||
const solo = getSoloState(instanceId)
|
||||
const sessionId = (event.properties as any)?.sessionID
|
||||
if (solo.isAutonomous && sessionId && solo.currentStep < solo.maxSteps) {
|
||||
log.info(`[SOLO] Session error in autonomous mode, prompting fix: ${message}`)
|
||||
incrementStep(instanceId)
|
||||
sendMessage(instanceId, sessionId, `I encountered an error: "${message}". Please analyze the cause and provide a fix.`, [], solo.activeTaskId || undefined).catch((err) => {
|
||||
log.error("[SOLO] Failed to send error recovery message", err)
|
||||
})
|
||||
} else {
|
||||
showAlertDialog(`Error: ${message}`, {
|
||||
title: "Session error",
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleMessageRemoved(instanceId: string, event: MessageRemovedEvent): void {
|
||||
@@ -344,6 +460,18 @@ function handlePermissionUpdated(instanceId: string, event: EventPermissionUpdat
|
||||
log.info(`[SSE] Permission updated: ${permission.id} (${permission.type})`)
|
||||
addPermissionToQueue(instanceId, permission)
|
||||
upsertPermissionV2(instanceId, permission)
|
||||
|
||||
// Auto-approval logic for SOLO autonomous agent
|
||||
const solo = getSoloState(instanceId)
|
||||
if (solo.isAutonomous && solo.autoApproval) {
|
||||
log.info(`[SOLO] Auto-approving permission: ${permission.id}`)
|
||||
const sessionId = permission.sessionID
|
||||
if (sessionId) {
|
||||
sendPermissionResponse(instanceId, sessionId, permission.id, "always").catch((err) => {
|
||||
log.error(`[SOLO] Failed to auto-approve permission ${permission.id}`, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handlePermissionReplied(instanceId: string, event: EventPermissionReplied): void {
|
||||
|
||||
Reference in New Issue
Block a user