feat(ui): add runtime logger and replace console usage

This commit is contained in:
Shantur Rathore
2025-12-05 15:07:49 +00:00
parent 49143bd049
commit 971abe24d7
44 changed files with 406 additions and 138 deletions

View File

@@ -1,6 +1,9 @@
import { createContext, createMemo, createSignal, onCleanup, type Accessor, type ParentComponent, useContext } from "solid-js"
import type { InstanceData } from "../../../server/src/api-types"
import { storage } from "../lib/storage"
import { getLogger } from "../lib/logger"
const log = getLogger("api")
const DEFAULT_INSTANCE_DATA: InstanceData = { messageHistory: [], agentModelSelections: {} }
@@ -54,7 +57,7 @@ async function ensureInstanceConfig(instanceId: string): Promise<void> {
attachSubscription(instanceId)
})
.catch((error) => {
console.warn("Failed to load instance data:", error)
log.warn("Failed to load instance data", error)
setInstanceData(instanceId, DEFAULT_INSTANCE_DATA)
attachSubscription(instanceId)
})
@@ -74,7 +77,7 @@ async function updateInstanceConfig(instanceId: string, mutator: (draft: Instanc
try {
await storage.saveInstanceData(instanceId, draft)
} catch (error) {
console.warn("Failed to persist instance data:", error)
log.warn("Failed to persist instance data", error)
}
setInstanceData(instanceId, draft)
}

View File

@@ -19,6 +19,9 @@ import { setSessionPendingPermission } from "./session-state"
import { setHasInstances } from "./ui"
import { messageStoreBus } from "./message-v2/bus"
import { clearCacheForInstance } from "../lib/global-cache"
import { getLogger } from "../lib/logger"
const log = getLogger("api")
const [instances, setInstances] = createSignal<Map<string, Instance>>(new Map())
@@ -99,7 +102,7 @@ function attachClient(descriptor: WorkspaceDescriptor) {
})
sseManager.seedStatus(descriptor.id, "connecting")
void hydrateInstanceData(descriptor.id).catch((error) => {
console.error("Failed to hydrate instance data", error)
log.error("Failed to hydrate instance data", error)
})
}
@@ -123,7 +126,7 @@ async function hydrateInstanceData(instanceId: string) {
if (!instance?.client) return
await fetchCommands(instanceId, instance.client)
} catch (error) {
console.error("Failed to fetch initial data:", error)
log.error("Failed to fetch initial data", error)
}
}
@@ -135,7 +138,7 @@ void (async function initializeWorkspaces() {
setHasInstances(false)
}
} catch (error) {
console.error("Failed to load workspaces", error)
log.error("Failed to load workspaces", error)
}
})()
@@ -305,7 +308,7 @@ async function createInstance(folder: string, _binaryPath?: string): Promise<str
setActiveInstanceId(workspace.id)
return workspace.id
} catch (error) {
console.error("Failed to create workspace", error)
log.error("Failed to create workspace", error)
throw error
}
}
@@ -319,7 +322,7 @@ async function stopInstance(id: string) {
try {
await serverApi.deleteWorkspace(id)
} catch (error) {
console.error("Failed to stop workspace", error)
log.error("Failed to stop workspace", error)
}
removeInstance(id)
@@ -331,19 +334,19 @@ async function stopInstance(id: string) {
async function fetchLspStatus(instanceId: string): Promise<LspStatus[] | undefined> {
const instance = instances().get(instanceId)
if (!instance) {
console.warn(`[LSP] Skipping fetch; instance ${instanceId} not found`)
log.warn("[LSP] Skipping status fetch; instance not found", { instanceId })
return undefined
}
if (!instance.client) {
console.warn(`[LSP] Skipping fetch; instance ${instanceId} client not ready`)
log.warn("[LSP] Skipping status fetch; client not ready", { instanceId })
return undefined
}
const lsp = instance.client.lsp
if (!lsp?.status) {
console.warn(`[LSP] Skipping fetch; lsp.status API unavailable for instance ${instanceId}`)
log.warn("[LSP] Skipping status fetch; API unavailable", { instanceId })
return undefined
}
console.log(`[HTTP] GET /lsp.status for instance ${instanceId}`)
log.info("lsp.status", { instanceId })
const response = await lsp.status()
return response.data ?? []
}
@@ -536,13 +539,13 @@ async function sendPermissionResponse(
try {
await instance.client.postSessionIdPermissionsPermissionId({
path: { id: sessionId, permissionID: permissionId },
body: { response }
body: { response },
})
// Remove from queue after successful response
removePermissionFromQueue(instanceId, permissionId)
} catch (error) {
console.error("Failed to send permission response:", error)
log.error("Failed to send permission response", error)
throw error
}
}
@@ -561,7 +564,7 @@ sseManager.onConnectionLost = (instanceId, reason) => {
}
sseManager.onLspUpdated = async (instanceId) => {
console.log(`[LSP] Received lsp.updated event for instance ${instanceId}`)
log.info("lsp.updated", { instanceId })
try {
const lspStatus = await fetchLspStatus(instanceId)
if (!lspStatus) {
@@ -569,7 +572,7 @@ sseManager.onLspUpdated = async (instanceId) => {
}
const instance = instances().get(instanceId)
if (!instance) {
console.warn(`[LSP] Instance ${instanceId} disappeared before metadata update`)
log.warn("[LSP] Instance disappeared before metadata update", { instanceId })
return
}
updateInstance(instanceId, {
@@ -579,7 +582,7 @@ sseManager.onLspUpdated = async (instanceId) => {
},
})
} catch (error) {
console.error("Failed to refresh LSP status:", error)
log.error("Failed to refresh LSP status", error)
}
}
@@ -592,7 +595,7 @@ async function acknowledgeDisconnectedInstance(): Promise<void> {
try {
await stopInstance(pending.id)
} catch (error) {
console.error("Failed to stop disconnected instance:", error)
log.error("Failed to stop disconnected instance", error)
} finally {
setDisconnectedInstance(null)
if (instances().size === 0) {

View File

@@ -1,6 +1,9 @@
import { createInstanceMessageStore } from "./instance-store"
import type { InstanceMessageStore } from "./instance-store"
import { clearCacheForInstance } from "../../lib/global-cache"
import { getLogger } from "../../lib/logger"
const log = getLogger("session")
class MessageStoreBus {
private stores = new Map<string, InstanceMessageStore>()
@@ -55,7 +58,7 @@ class MessageStoreBus {
try {
handler(instanceId)
} catch (error) {
console.error("Failed to run message store teardown handler", error)
log.error("Failed to run message store teardown handler", error)
}
}
}

View File

@@ -6,6 +6,9 @@ import {
getInstanceConfig,
updateInstanceConfig as updateInstanceData,
} from "./instance-config"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
type DeepReadonly<T> = T extends (...args: any[]) => unknown
? T
@@ -81,7 +84,7 @@ function deepEqual(a: unknown, b: unknown): boolean {
try {
return JSON.stringify(a) === JSON.stringify(b)
} catch (error) {
console.warn("Failed to compare preference values", error)
log.warn("Failed to compare preference values", error)
}
}
return false
@@ -148,11 +151,11 @@ async function syncConfig(source?: ConfigData): Promise<void> {
applyConfig(cleaned)
if (migrated) {
void storage.updateConfig(cleaned).catch((error: unknown) => {
console.error("Failed to persist legacy config cleanup:", error)
log.error("Failed to persist legacy config cleanup", error)
})
}
} catch (error) {
console.error("Failed to load config:", error)
log.error("Failed to load config", error)
applyConfig(buildFallbackConfig())
}
}
@@ -172,7 +175,7 @@ function logConfigDiff(previous: ConfigData, next: ConfigData) {
}
const changes = diffObjects(previous, next)
if (changes.length > 0) {
console.debug("[Config] Changes", changes)
log.info("[Config] Changes", changes)
}
}
@@ -214,9 +217,9 @@ async function persistFullConfig(next: ConfigData): Promise<void> {
await ensureConfigLoaded()
await storage.updateConfig(next)
} catch (error) {
console.error("Failed to save config:", error)
log.error("Failed to save config", error)
void syncConfig().catch((syncError: unknown) => {
console.error("Failed to refresh config:", syncError)
log.error("Failed to refresh config", syncError)
})
}
}
@@ -303,8 +306,9 @@ function toggleUsageMetrics(): void {
}
function toggleAutoCleanupBlankSessions(): void {
console.log("toggle auto cleanup")
updatePreferences({ autoCleanupBlankSessions: !preferences().autoCleanupBlankSessions })
const nextValue = !preferences().autoCleanupBlankSessions
log.info("toggle auto cleanup", { value: nextValue })
updatePreferences({ autoCleanupBlankSessions: nextValue })
}
function addRecentFolder(path: string): void {
@@ -394,7 +398,7 @@ async function getAgentModelPreference(instanceId: string, agent: string): Promi
}
void ensureConfigLoaded().catch((error: unknown) => {
console.error("Failed to initialize config:", error)
log.error("Failed to initialize config", error)
})
interface ConfigContextValue {
@@ -466,12 +470,12 @@ const configContextValue: ConfigContextValue = {
const ConfigProvider: ParentComponent = (props) => {
onMount(() => {
ensureConfigLoaded().catch((error: unknown) => {
console.error("Failed to initialize config:", error)
log.error("Failed to initialize config", error)
})
const unsubscribe = storage.onConfigChanged((config) => {
syncConfig(config).catch((error: unknown) => {
console.error("Failed to refresh config:", error)
log.error("Failed to refresh config", error)
})
})

View File

@@ -6,6 +6,9 @@ import { sessions, withSession } from "./session-state"
import { getDefaultModel, isModelValid } from "./session-models"
import { updateSessionInfo } from "./message-v2/session-info"
import { messageStoreBus } from "./message-v2/bus"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
const ID_LENGTH = 26
const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
@@ -168,26 +171,27 @@ async function sendMessage(
}),
}
console.log("[sendMessage] Sending prompt:", {
log.info("sendMessage", {
instanceId,
sessionId,
requestBody,
})
try {
console.log(`[HTTP] POST /session.prompt_async for instance ${instanceId}`, { sessionId, requestBody })
const response = await instance.client.session.promptAsync({
log.info("session.prompt", { instanceId, sessionId, requestBody })
const response = await instance.client.session.prompt({
path: { id: sessionId },
body: requestBody,
})
console.log("[sendMessage] Response:", response)
log.info("sendMessage response", response)
if (response.error) {
console.error("[sendMessage] Server returned error:", response.error)
log.error("sendMessage server error", response.error)
throw new Error(JSON.stringify(response.error) || "Failed to send message")
}
} catch (error) {
console.error("[sendMessage] Failed to send prompt:", error)
log.error("Failed to send prompt", error)
throw error
}
}
@@ -262,16 +266,16 @@ async function abortSession(instanceId: string, sessionId: string): Promise<void
throw new Error("Instance not ready")
}
console.log("[abortSession] Aborting session:", { instanceId, sessionId })
log.info("abortSession", { instanceId, sessionId })
try {
console.log(`[HTTP] POST /session.abort for instance ${instanceId}`, { sessionId })
log.info("session.abort", { instanceId, sessionId })
await instance.client.session.abort({
path: { id: sessionId },
})
console.log("[abortSession] Session aborted successfully")
log.info("abortSession complete", { instanceId, sessionId })
} catch (error) {
console.error("[abortSession] Failed to abort session:", error)
log.error("Failed to abort session", error)
throw error
}
}
@@ -314,7 +318,7 @@ async function updateSessionModel(
}
if (!isModelValid(instanceId, model)) {
console.warn("Invalid model selection", model)
log.warn("Invalid model selection", model)
return
}

View File

@@ -30,6 +30,9 @@ import { updateSessionInfo } from "./message-v2/session-info"
import { seedSessionMessagesV2 } from "./message-v2/bridge"
import { messageStoreBus } from "./message-v2/bus"
import { clearCacheForSession } from "../lib/global-cache"
import { getLogger } from "../lib/logger"
const log = getLogger("api")
interface SessionForkResponse {
id: string
@@ -65,7 +68,7 @@ async function fetchSessions(instanceId: string): Promise<void> {
})
try {
console.log(`[HTTP] GET /session.list for instance ${instanceId}`)
log.info("session.list", { instanceId })
const response = await instance.client.session.list()
const sessionMap = new Map<string, Session>()
@@ -132,7 +135,7 @@ async function fetchSessions(instanceId: string): Promise<void> {
pruneDraftPrompts(instanceId, new Set(sessionMap.keys()))
} catch (error) {
console.error("Failed to fetch sessions:", error)
log.error("Failed to fetch sessions:", error)
throw error
} finally {
setLoading((prev) => {
@@ -166,7 +169,7 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
})
try {
console.log(`[HTTP] POST /session.create for instance ${instanceId}`)
log.info(`[HTTP] POST /session.create for instance ${instanceId}`)
const response = await instance.client.session.create()
if (!response.data) {
@@ -237,7 +240,7 @@ async function createSession(instanceId: string, agent?: string): Promise<Sessio
return session
} catch (error) {
console.error("Failed to create session:", error)
log.error("Failed to create session:", error)
throw error
} finally {
setLoading((prev) => {
@@ -269,7 +272,7 @@ async function forkSession(
request.body = { messageID: options.messageId }
}
console.log(`[HTTP] POST /session.fork for instance ${instanceId}`, request)
log.info(`[HTTP] POST /session.fork for instance ${instanceId}`, request)
const response = await instance.client.session.fork(request)
if (!response.data) {
@@ -352,7 +355,7 @@ async function deleteSession(instanceId: string, sessionId: string): Promise<voi
})
try {
console.log(`[HTTP] DELETE /session.delete for instance ${instanceId}`, { sessionId })
log.info(`[HTTP] DELETE /session.delete for instance ${instanceId}`, { sessionId })
await instance.client.session.delete({ path: { id: sessionId } })
setSessions((prev) => {
@@ -394,7 +397,7 @@ async function deleteSession(instanceId: string, sessionId: string): Promise<voi
})
}
} catch (error) {
console.error("Failed to delete session:", error)
log.error("Failed to delete session:", error)
throw error
} finally {
setLoading((prev) => {
@@ -415,7 +418,7 @@ async function fetchAgents(instanceId: string): Promise<void> {
}
try {
console.log(`[HTTP] GET /app.agents for instance ${instanceId}`)
log.info(`[HTTP] GET /app.agents for instance ${instanceId}`)
const response = await instance.client.app.agents()
const agentList = (response.data ?? []).map((agent) => ({
name: agent.name,
@@ -435,7 +438,7 @@ async function fetchAgents(instanceId: string): Promise<void> {
return next
})
} catch (error) {
console.error("Failed to fetch agents:", error)
log.error("Failed to fetch agents:", error)
}
}
@@ -446,7 +449,7 @@ async function fetchProviders(instanceId: string): Promise<void> {
}
try {
console.log(`[HTTP] GET /config.providers for instance ${instanceId}`)
log.info(`[HTTP] GET /config.providers for instance ${instanceId}`)
const response = await instance.client.config.providers()
if (!response.data) return
@@ -469,7 +472,7 @@ async function fetchProviders(instanceId: string): Promise<void> {
return next
})
} catch (error) {
console.error("Failed to fetch providers:", error)
log.error("Failed to fetch providers:", error)
}
}
@@ -515,7 +518,7 @@ async function loadMessages(instanceId: string, sessionId: string, force = false
})
try {
console.log(`[HTTP] GET /session.${"messages"} for instance ${instanceId}`, { sessionId })
log.info(`[HTTP] GET /session.${"messages"} for instance ${instanceId}`, { sessionId })
const response = await instance.client.session["messages"]({ path: { id: sessionId } })
if (!response.data || !Array.isArray(response.data)) {
@@ -604,7 +607,7 @@ async function loadMessages(instanceId: string, sessionId: string, force = false
seedSessionMessagesV2(instanceId, sessionForV2, messages, messagesInfo)
} catch (error) {
console.error("Failed to load messages:", error)
log.error("Failed to load messages:", error)
throw error
} finally {
setLoading((prev) => {

View File

@@ -15,16 +15,15 @@ import type {
} from "@opencode-ai/sdk"
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 { showAlertDialog } from "./alerts"
import {
sessions,
setSessions,
withSession,
} from "./session-state"
import { sessions, setSessions, withSession } from "./session-state"
import { normalizeMessagePart } from "./message-v2/normalizers"
import { updateSessionInfo } from "./message-v2/session-info"
const log = getLogger("sse")
import { loadMessages } from "./session-api"
import { setSessionCompactionState } from "./session-compaction"
import {
@@ -213,7 +212,7 @@ function handleSessionUpdate(instanceId: string, event: EventSessionUpdated): vo
})
setSessionRevertV2(instanceId, info.id, info.revert ?? null)
console.log(`[SSE] New session created: ${info.id}`, newSession)
log.info(`[SSE] New session created: ${info.id}`, newSession)
} else {
const mergedTime = {
...existingSession.time,
@@ -252,14 +251,14 @@ function handleSessionIdle(_instanceId: string, event: EventSessionIdle): void {
const sessionId = event.properties?.sessionID
if (!sessionId) return
console.log(`[SSE] Session idle: ${sessionId}`)
log.info(`[SSE] Session idle: ${sessionId}`)
}
function handleSessionCompacted(instanceId: string, event: EventSessionCompacted): void {
const sessionID = event.properties?.sessionID
if (!sessionID) return
console.log(`[SSE] Session compacted: ${sessionID}`)
log.info(`[SSE] Session compacted: ${sessionID}`)
setSessionCompactionState(instanceId, sessionID, false)
@@ -269,7 +268,7 @@ function handleSessionCompacted(instanceId: string, event: EventSessionCompacted
session.time = time
})
loadMessages(instanceId, sessionID, true).catch(console.error)
loadMessages(instanceId, sessionID, true).catch((error) => log.error("Failed to reload session after compaction", error))
const instanceSessions = sessions().get(instanceId)
const session = instanceSessions?.get(sessionID)
@@ -287,7 +286,7 @@ function handleSessionCompacted(instanceId: string, event: EventSessionCompacted
function handleSessionError(_instanceId: string, event: EventSessionError): void {
const error = event.properties?.error
console.error(`[SSE] Session error:`, error)
log.error(`[SSE] Session error:`, error)
let message = "Unknown error"
@@ -309,16 +308,16 @@ function handleMessageRemoved(instanceId: string, event: MessageRemovedEvent): v
const sessionID = event.properties?.sessionID
if (!sessionID) return
console.log(`[SSE] Message removed from session ${sessionID}, reloading messages`)
loadMessages(instanceId, sessionID, true).catch(console.error)
log.info(`[SSE] Message removed from session ${sessionID}, reloading messages`)
loadMessages(instanceId, sessionID, true).catch((error) => log.error("Failed to reload messages after removal", error))
}
function handleMessagePartRemoved(instanceId: string, event: MessagePartRemovedEvent): void {
const sessionID = event.properties?.sessionID
if (!sessionID) return
console.log(`[SSE] Message part removed from session ${sessionID}, reloading messages`)
loadMessages(instanceId, sessionID, true).catch(console.error)
log.info(`[SSE] Message part removed from session ${sessionID}, reloading messages`)
loadMessages(instanceId, sessionID, true).catch((error) => log.error("Failed to reload messages after part removal", error))
}
function handleTuiToast(_instanceId: string, event: TuiToastEvent): void {
@@ -342,7 +341,7 @@ function handlePermissionUpdated(instanceId: string, event: EventPermissionUpdat
const permission = event.properties
if (!permission) return
console.log(`[SSE] Permission updated: ${permission.id} (${permission.type})`)
log.info(`[SSE] Permission updated: ${permission.id} (${permission.type})`)
addPermissionToQueue(instanceId, permission)
upsertPermissionV2(instanceId, permission)
}
@@ -351,7 +350,7 @@ function handlePermissionReplied(instanceId: string, event: EventPermissionRepli
const { permissionID } = event.properties
if (!permissionID) return
console.log(`[SSE] Permission replied: ${permissionID}`)
log.info(`[SSE] Permission replied: ${permissionID}`)
removePermissionFromQueue(instanceId, permissionID)
removePermissionV2(instanceId, permissionID)
}

View File

@@ -6,6 +6,9 @@ import { showToastNotification } from "../lib/notifications"
import { messageStoreBus } from "./message-v2/bus"
import { instances } from "./instances"
import { showConfirmDialog } from "./alerts"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
export interface SessionInfo {
cost: number
@@ -248,7 +251,7 @@ async function isBlankSession(session: Session, instanceId: string, fetchIfNeede
const response = await instance.client.session.messages({ path: { id: session.id } })
messages = response.data || []
} catch (error) {
console.error(`Failed to fetch messages for session ${session.id}:`, error)
log.error(`Failed to fetch messages for session ${session.id}`, error)
return isFreshSession
}
@@ -309,13 +312,13 @@ async function cleanupBlankSessions(instanceId: string, excludeSessionId?: strin
if (!isBlank) return false
await deleteSession(instanceId, sessionId).catch((error: Error) => {
console.error(`Failed to delete blank session ${sessionId}:`, error)
log.error(`Failed to delete blank session ${sessionId}`, error)
})
return true
})
if (cleanupPromises.length > 0) {
console.log(`Cleaning up ${cleanupPromises.length} blank sessions`)
log.info(`Cleaning up ${cleanupPromises.length} blank sessions`)
const deletionResults = await Promise.all(cleanupPromises)
const deletedCount = deletionResults.filter(Boolean).length