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

1
package-lock.json generated
View File

@@ -8895,6 +8895,7 @@
"@kobalte/core": "0.13.11", "@kobalte/core": "0.13.11",
"@opencode-ai/sdk": "^1.0.133", "@opencode-ai/sdk": "^1.0.133",
"@solidjs/router": "^0.13.0", "@solidjs/router": "^0.13.0",
"debug": "^4.4.3",
"github-markdown-css": "^5.8.1", "github-markdown-css": "^5.8.1",
"lucide-solid": "^0.300.0", "lucide-solid": "^0.300.0",
"marked": "^12.0.0", "marked": "^12.0.0",

View File

@@ -26,8 +26,30 @@ This starts the Vite dev server at `http://localhost:3000`.
To build the production assets: To build the production assets:
```bash ```
npm run build npm run build
``` ```
The output will be generated in the `dist` directory, which is then consumed by the Server or Electron app. The output will be generated in the `dist` directory, which is then consumed by the Server or Electron app.
## Debug Logging
The UI now routes all logging through a lightweight wrapper around [`debug`](https://github.com/debug-js/debug). The logger exposes four namespaces that can be toggled at runtime:
- `sse` Server-sent event transport and handlers
- `api` HTTP/API calls and workspace lifecycle
- `session` Session/model state, prompt handling, tool calls
- `actions` User-driven interactions in UI components
You can enable or disable namespaces inside DevTools by importing the helpers:
```ts
import { listLoggerNamespaces, enableLogger, disableLogger } from "./src/lib/logger"
listLoggerNamespaces() // => [{ name: "sse", enabled: false }, ...]
enableLogger("sse") // turn on SSE logs
disableLogger("sse") // turn them off again
```
Enabled namespaces are persisted in `localStorage` under `opencode:logger:namespaces`, so your preference survives reloads.

View File

@@ -14,6 +14,7 @@
"@kobalte/core": "0.13.11", "@kobalte/core": "0.13.11",
"@opencode-ai/sdk": "^1.0.133", "@opencode-ai/sdk": "^1.0.133",
"@solidjs/router": "^0.13.0", "@solidjs/router": "^0.13.0",
"debug": "^4.4.3",
"github-markdown-css": "^5.8.1", "github-markdown-css": "^5.8.1",
"lucide-solid": "^0.300.0", "lucide-solid": "^0.300.0",
"marked": "^12.0.0", "marked": "^12.0.0",

View File

@@ -12,6 +12,7 @@ import { initMarkdown } from "./lib/markdown"
import { useTheme } from "./lib/theme" import { useTheme } from "./lib/theme"
import { useCommands } from "./lib/hooks/use-commands" import { useCommands } from "./lib/hooks/use-commands"
import { useAppLifecycle } from "./lib/hooks/use-app-lifecycle" import { useAppLifecycle } from "./lib/hooks/use-app-lifecycle"
import { getLogger } from "./lib/logger"
import { import {
hasInstances, hasInstances,
isSelectingFolder, isSelectingFolder,
@@ -42,6 +43,8 @@ import {
updateSessionModel, updateSessionModel,
} from "./stores/sessions" } from "./stores/sessions"
const log = getLogger("actions")
const App: Component = () => { const App: Component = () => {
const { isDark } = useTheme() const { isDark } = useTheme()
const { const {
@@ -61,7 +64,7 @@ const App: Component = () => {
const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false) const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false)
createEffect(() => { createEffect(() => {
void initMarkdown(isDark()).catch(console.error) void initMarkdown(isDark()).catch((error) => log.error("Failed to initialize markdown", error))
}) })
const activeInstance = createMemo(() => getActiveInstance()) const activeInstance = createMemo(() => getActiveInstance())
@@ -106,13 +109,16 @@ const App: Component = () => {
setShowFolderSelection(false) setShowFolderSelection(false)
setIsAdvancedSettingsOpen(false) setIsAdvancedSettingsOpen(false)
console.log("Created instance:", instanceId, "Port:", instances().get(instanceId)?.port) log.info("Created instance", {
instanceId,
port: instances().get(instanceId)?.port,
})
} catch (error) { } catch (error) {
clearLaunchError() clearLaunchError()
if (isMissingBinaryError(error)) { if (isMissingBinaryError(error)) {
setLaunchErrorBinary(selectedBinary) setLaunchErrorBinary(selectedBinary)
} }
console.error("Failed to create instance:", error) log.error("Failed to create instance", error)
} finally { } finally {
setIsSelectingFolder(false) setIsSelectingFolder(false)
} }
@@ -137,7 +143,7 @@ const App: Component = () => {
try { try {
await acknowledgeDisconnectedInstance() await acknowledgeDisconnectedInstance()
} catch (error) { } catch (error) {
console.error("Failed to finalize disconnected instance:", error) log.error("Failed to finalize disconnected instance", error)
} }
} }
@@ -165,7 +171,7 @@ const App: Component = () => {
const session = await createSession(instanceId) const session = await createSession(instanceId)
setActiveParentSession(instanceId, session.id) setActiveParentSession(instanceId, session.id)
} catch (error) { } catch (error) {
console.error("Failed to create session:", error) log.error("Failed to create session", error)
} }
} }
@@ -189,7 +195,7 @@ const App: Component = () => {
try { try {
await fetchSessions(instanceId) await fetchSessions(instanceId)
} catch (error) { } catch (error) {
console.error("Failed to refresh sessions after closing:", error) log.error("Failed to refresh sessions after closing", error)
} }
} }

View File

@@ -3,6 +3,9 @@ import { For, Show, createEffect, createMemo } from "solid-js"
import { agents, fetchAgents, sessions } from "../stores/sessions" import { agents, fetchAgents, sessions } from "../stores/sessions"
import { ChevronDown } from "lucide-solid" import { ChevronDown } from "lucide-solid"
import type { Agent } from "../types/session" import type { Agent } from "../types/session"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
interface AgentSelectorProps { interface AgentSelectorProps {
instanceId: string instanceId: string
@@ -49,10 +52,11 @@ export default function AgentSelector(props: AgentSelectorProps) {
createEffect(() => { createEffect(() => {
if (instanceAgents().length === 0) { if (instanceAgents().length === 0) {
fetchAgents(props.instanceId).catch(console.error) fetchAgents(props.instanceId).catch((error) => log.error("Failed to fetch agents", error))
} }
}) })
const handleChange = async (value: Agent | null) => { const handleChange = async (value: Agent | null) => {
if (value && value.name !== props.currentAgent) { if (value && value.name !== props.currentAgent) {
await props.onAgentChange(value.name) await props.onAgentChange(value.name)

View File

@@ -8,6 +8,9 @@ import { normalizeDiffText } from "../lib/diff-utils"
import { setCacheEntry } from "../lib/global-cache" import { setCacheEntry } from "../lib/global-cache"
import type { CacheEntryParams } from "../lib/global-cache" import type { CacheEntryParams } from "../lib/global-cache"
import type { DiffViewMode } from "../stores/preferences" import type { DiffViewMode } from "../stores/preferences"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
disableCache() disableCache()
@@ -110,7 +113,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
> >
{(data) => ( {(data) => (
<ErrorBoundary fallback={(error) => { <ErrorBoundary fallback={(error) => {
console.warn("Failed to render diff view", error) log.warn("Failed to render diff view", error)
return <pre class="tool-call-diff-fallback">{props.diffText}</pre> return <pre class="tool-call-diff-fallback">{props.diffText}</pre>
}}> }}>
<DiffView <DiffView

View File

@@ -2,6 +2,9 @@ import { Component, Show, For, createSignal, createMemo, createEffect, onCleanup
import { Folder as FolderIcon, File as FileIcon, Loader2, Search, X, ArrowUpLeft } from "lucide-solid" import { Folder as FolderIcon, File as FileIcon, Loader2, Search, X, ArrowUpLeft } from "lucide-solid"
import type { FileSystemEntry, FileSystemListingMetadata } from "../../../server/src/api-types" import type { FileSystemEntry, FileSystemListingMetadata } from "../../../server/src/api-types"
import { serverApi } from "../lib/api-client" import { serverApi } from "../lib/api-client"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
const MAX_RESULTS = 200 const MAX_RESULTS = 200
@@ -172,7 +175,7 @@ const FileSystemBrowserDialog: Component<FileSystemBrowserDialogProps> = (props)
function handleNavigateTo(path: string) { function handleNavigateTo(path: string) {
void fetchDirectory(path, true).catch((err) => { void fetchDirectory(path, true).catch((err) => {
console.error("Failed to open directory", err) log.error("Failed to open directory", err)
setError(err instanceof Error ? err.message : "Unable to open directory") setError(err instanceof Error ? err.message : "Unable to open directory")
}) })
} }

View File

@@ -1,6 +1,9 @@
import { Component, Show, For, createSignal, createEffect, onCleanup } from "solid-js" import { Component, Show, For, createSignal, createEffect, onCleanup } from "solid-js"
import type { Instance, RawMcpStatus } from "../types/instance" import type { Instance, RawMcpStatus } from "../types/instance"
import { fetchLspStatus, updateInstance } from "../stores/instances" import { fetchLspStatus, updateInstance } from "../stores/instances"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
interface InstanceInfoProps { interface InstanceInfoProps {
instance: Instance instance: Instance
@@ -113,7 +116,7 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
} catch (error) { } catch (error) {
if (!cancelled) { if (!cancelled) {
console.error("Failed to load instance metadata:", error) log.error("Failed to load instance metadata", error)
} }
} finally { } finally {
pendingMetadataRequests.delete(instanceId) pendingMetadataRequests.delete(instanceId)

View File

@@ -6,6 +6,9 @@ import KeyboardHint from "./keyboard-hint"
import Kbd from "./kbd" import Kbd from "./kbd"
import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry" import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry"
import { isMac } from "../lib/keyboard-utils" import { isMac } from "../lib/keyboard-utils"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
interface InstanceWelcomeViewProps { interface InstanceWelcomeViewProps {
@@ -189,7 +192,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
const session = await createSession(props.instance.id) const session = await createSession(props.instance.id)
setActiveParentSession(props.instance.id, session.id) setActiveParentSession(props.instance.id, session.id)
} catch (error) { } catch (error) {
console.error("Failed to create session:", error) log.error("Failed to create session:", error)
} finally { } finally {
setIsCreating(false) setIsCreating(false)
} }

View File

@@ -17,6 +17,9 @@ import CommandPalette from "../command-palette"
import Kbd from "../kbd" import Kbd from "../kbd"
import ContextUsagePanel from "../session/context-usage-panel" import ContextUsagePanel from "../session/context-usage-panel"
import SessionView from "../session/session-view" import SessionView from "../session/session-view"
import { getLogger } from "../../lib/logger"
const log = getLogger("session")
interface InstanceShellProps { interface InstanceShellProps {
instance: Instance instance: Instance
@@ -119,13 +122,13 @@ const InstanceShell: Component<InstanceShellProps> = (props) => {
onClose={(id) => { onClose={(id) => {
const result = props.onCloseSession(id) const result = props.onCloseSession(id)
if (result instanceof Promise) { if (result instanceof Promise) {
void result.catch((error) => console.error("Failed to close session:", error)) void result.catch((error) => log.error("Failed to close session:", error))
} }
}} }}
onNew={() => { onNew={() => {
const result = props.onNewSession() const result = props.onNewSession()
if (result instanceof Promise) { if (result instanceof Promise) {
void result.catch((error) => console.error("Failed to create session:", error)) void result.catch((error) => log.error("Failed to create session:", error))
} }
}} }}
showHeader showHeader

View File

@@ -1,6 +1,9 @@
import { createEffect, createSignal, onMount, onCleanup } from "solid-js" import { createEffect, createSignal, onMount, onCleanup } from "solid-js"
import { renderMarkdown, onLanguagesLoaded, initMarkdown, decodeHtmlEntities } from "../lib/markdown" import { renderMarkdown, onLanguagesLoaded, initMarkdown, decodeHtmlEntities } from "../lib/markdown"
import type { TextPart } from "../types/message" import type { TextPart } from "../types/message"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
interface MarkdownProps { interface MarkdownProps {
part: TextPart part: TextPart
@@ -43,7 +46,7 @@ export function Markdown(props: MarkdownProps) {
notifyRendered() notifyRendered()
} }
} catch (error) { } catch (error) {
console.error("Failed to render markdown:", error) log.error("Failed to render markdown:", error)
if (latestRequestedText === text) { if (latestRequestedText === text) {
setHtml(text) setHtml(text)
notifyRendered() notifyRendered()
@@ -68,7 +71,7 @@ export function Markdown(props: MarkdownProps) {
notifyRendered() notifyRendered()
} }
} catch (error) { } catch (error) {
console.error("Failed to render markdown:", error) log.error("Failed to render markdown:", error)
if (latestRequestedText === text) { if (latestRequestedText === text) {
setHtml(text) setHtml(text)
part.renderCache = { text, html: text, theme: themeKey } part.renderCache = { text, html: text, theme: themeKey }
@@ -124,7 +127,7 @@ export function Markdown(props: MarkdownProps) {
notifyRendered() notifyRendered()
} }
} catch (error) { } catch (error) {
console.error("Failed to re-render markdown after language load:", error) log.error("Failed to re-render markdown after language load:", error)
} }
}) })

View File

@@ -3,6 +3,9 @@ import { createEffect, createMemo, createSignal } from "solid-js"
import { providers, fetchProviders } from "../stores/sessions" import { providers, fetchProviders } from "../stores/sessions"
import { ChevronDown } from "lucide-solid" import { ChevronDown } from "lucide-solid"
import type { Model } from "../types/session" import type { Model } from "../types/session"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
interface ModelSelectorProps { interface ModelSelectorProps {
instanceId: string instanceId: string
@@ -25,7 +28,7 @@ export default function ModelSelector(props: ModelSelectorProps) {
createEffect(() => { createEffect(() => {
if (instanceProviders().length === 0) { if (instanceProviders().length === 0) {
fetchProviders(props.instanceId).catch(console.error) fetchProviders(props.instanceId).catch((error) => log.error("Failed to fetch providers", error))
} }
}) })

View File

@@ -4,6 +4,9 @@ import { useConfig } from "../stores/preferences"
import { serverApi } from "../lib/api-client" import { serverApi } from "../lib/api-client"
import FileSystemBrowserDialog from "./filesystem-browser-dialog" import FileSystemBrowserDialog from "./filesystem-browser-dialog"
import { openNativeFileDialog, supportsNativeDialogs } from "../lib/native/native-functions" import { openNativeFileDialog, supportsNativeDialogs } from "../lib/native/native-functions"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
interface BinaryOption { interface BinaryOption {
path: string path: string
@@ -83,7 +86,7 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
setTimeout(() => { setTimeout(() => {
pathsToValidate.forEach((path) => { pathsToValidate.forEach((path) => {
validateBinary(path).catch(console.error) validateBinary(path).catch((error) => log.error("Failed to validate binary", { path, error }))
}) })
}, 0) }, 0)
}) })

View File

@@ -10,6 +10,9 @@ import Kbd from "./kbd"
import { getActiveInstance } from "../stores/instances" import { getActiveInstance } from "../stores/instances"
import { agents, getSessionDraftPrompt, setSessionDraftPrompt, clearSessionDraftPrompt } from "../stores/sessions" import { agents, getSessionDraftPrompt, setSessionDraftPrompt, clearSessionDraftPrompt } from "../stores/sessions"
import { showAlertDialog } from "../stores/alerts" import { showAlertDialog } from "../stores/alerts"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
interface PromptInputProps { interface PromptInputProps {
instanceId: string instanceId: string
@@ -563,7 +566,7 @@ export default function PromptInput(props: PromptInputProps) {
}) })
setHistoryIndex(-1) setHistoryIndex(-1)
} catch (historyError) { } catch (historyError) {
console.error("Failed to update prompt history:", historyError) log.error("Failed to update prompt history:", historyError)
} }
} }
@@ -586,7 +589,7 @@ export default function PromptInput(props: PromptInputProps) {
} }
void refreshHistory() void refreshHistory()
} catch (error) { } catch (error) {
console.error("Failed to send message:", error) log.error("Failed to send message:", error)
showAlertDialog("Failed to send message", { showAlertDialog("Failed to send message", {
title: "Send failed", title: "Send failed",
detail: error instanceof Error ? error.message : String(error), detail: error instanceof Error ? error.message : String(error),

View File

@@ -8,6 +8,9 @@ import { serverApi } from "../lib/api-client"
import { restartCli } from "../lib/native/cli" import { restartCli } from "../lib/native/cli"
import { preferences, setListeningMode } from "../stores/preferences" import { preferences, setListeningMode } from "../stores/preferences"
import { showConfirmDialog } from "../stores/alerts" import { showConfirmDialog } from "../stores/alerts"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
interface RemoteAccessOverlayProps { interface RemoteAccessOverlayProps {
open: boolean open: boolean
@@ -62,7 +65,7 @@ export function RemoteAccessOverlay(props: RemoteAccessOverlayProps) {
const dataUrl = await toDataURL(url, { margin: 1, scale: 4 }) const dataUrl = await toDataURL(url, { margin: 1, scale: 4 })
setQrCodes((prev) => ({ ...prev, [url]: dataUrl })) setQrCodes((prev) => ({ ...prev, [url]: dataUrl }))
} catch (err) { } catch (err) {
console.error("Failed to generate QR code", err) log.error("Failed to generate QR code", err)
} }
} }
} }
@@ -101,7 +104,7 @@ export function RemoteAccessOverlay(props: RemoteAccessOverlayProps) {
try { try {
window.open(url, "_blank", "noopener,noreferrer") window.open(url, "_blank", "noopener,noreferrer")
} catch (err) { } catch (err) {
console.error("Failed to open URL", err) log.error("Failed to open URL", err)
} }
} }

View File

@@ -7,6 +7,9 @@ import Kbd from "./kbd"
import { keyboardRegistry } from "../lib/keyboard-registry" import { keyboardRegistry } from "../lib/keyboard-registry"
import { formatShortcut } from "../lib/keyboard-utils" import { formatShortcut } from "../lib/keyboard-utils"
import { showToastNotification } from "../lib/notifications" import { showToastNotification } from "../lib/notifications"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
interface SessionListProps { interface SessionListProps {
@@ -106,7 +109,7 @@ const SessionList: Component<SessionListProps> = (props) => {
await navigator.clipboard.writeText(sessionId) await navigator.clipboard.writeText(sessionId)
showToastNotification({ message: "Session ID copied", variant: "success" }) showToastNotification({ message: "Session ID copied", variant: "success" })
} catch (error) { } catch (error) {
console.error(`Failed to copy session ID ${sessionId}:`, error) log.error(`Failed to copy session ID ${sessionId}:`, error)
showToastNotification({ message: "Unable to copy session ID", variant: "error" }) showToastNotification({ message: "Unable to copy session ID", variant: "error" })
} }
} }

View File

@@ -4,6 +4,9 @@ import type { Session, Agent } from "../types/session"
import { getParentSessions, createSession, setActiveParentSession } from "../stores/sessions" import { getParentSessions, createSession, setActiveParentSession } from "../stores/sessions"
import { instances, stopInstance } from "../stores/instances" import { instances, stopInstance } from "../stores/instances"
import { agents } from "../stores/sessions" import { agents } from "../stores/sessions"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
interface SessionPickerProps { interface SessionPickerProps {
instanceId: string instanceId: string
@@ -55,7 +58,7 @@ const SessionPicker: Component<SessionPickerProps> = (props) => {
setActiveParentSession(props.instanceId, session.id) setActiveParentSession(props.instanceId, session.id)
props.onClose() props.onClose()
} catch (error) { } catch (error) {
console.error("Failed to create session:", error) log.error("Failed to create session:", error)
} finally { } finally {
setIsCreating(false) setIsCreating(false)
} }

View File

@@ -8,6 +8,9 @@ import PromptInput from "../prompt-input"
import { instances } from "../../stores/instances" import { instances } from "../../stores/instances"
import { loadMessages, sendMessage, forkSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand } from "../../stores/sessions" import { loadMessages, sendMessage, forkSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand } from "../../stores/sessions"
import { showAlertDialog } from "../../stores/alerts" import { showAlertDialog } from "../../stores/alerts"
import { getLogger } from "../../lib/logger"
const log = getLogger("session")
function isTextPart(part: ClientPart): part is ClientPart & { type: "text"; text: string } { function isTextPart(part: ClientPart): part is ClientPart & { type: "text"; text: string } {
return part?.type === "text" && typeof (part as any).text === "string" return part?.type === "text" && typeof (part as any).text === "string"
@@ -33,7 +36,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
const currentSession = session() const currentSession = session()
if (currentSession) { if (currentSession) {
loadMessages(props.instanceId, currentSession.id).catch(console.error) loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error))
} }
}) })
@@ -84,7 +87,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
} }
} }
} catch (error) { } catch (error) {
console.error("Failed to revert:", error) log.error("Failed to revert message", error)
showAlertDialog("Failed to revert to message", { showAlertDialog("Failed to revert to message", {
title: "Revert failed", title: "Revert failed",
variant: "error", variant: "error",
@@ -94,7 +97,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
async function handleFork(messageId?: string) { async function handleFork(messageId?: string) {
if (!messageId) { if (!messageId) {
console.warn("Fork requires a user message id") log.warn("Fork requires a user message id")
return return
} }
@@ -109,7 +112,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
setActiveSession(props.instanceId, forkedSession.id) setActiveSession(props.instanceId, forkedSession.id)
} }
await loadMessages(props.instanceId, forkedSession.id).catch(console.error) await loadMessages(props.instanceId, forkedSession.id).catch((error) => log.error("Failed to load forked session messages", error))
if (restoredText) { if (restoredText) {
const textarea = document.querySelector(".prompt-input") as HTMLTextAreaElement const textarea = document.querySelector(".prompt-input") as HTMLTextAreaElement
@@ -120,7 +123,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
} }
} }
} catch (error) { } catch (error) {
console.error("Failed to fork session:", error) log.error("Failed to fork session", error)
showAlertDialog("Failed to fork session", { showAlertDialog("Failed to fork session", {
title: "Fork failed", title: "Fork failed",
variant: "error", variant: "error",

View File

@@ -11,6 +11,9 @@ import type { TextPart, RenderCache } from "../types/message"
import { resolveToolRenderer } from "./tool-call/renderers" import { resolveToolRenderer } from "./tool-call/renderers"
import type { DiffPayload, DiffRenderOptions, MarkdownRenderOptions, ToolCallPart, ToolRendererContext } from "./tool-call/types" import type { DiffPayload, DiffRenderOptions, MarkdownRenderOptions, ToolCallPart, ToolRendererContext } from "./tool-call/types"
import { getRelativePath, getToolIcon, getToolName, isToolStateCompleted, isToolStateError, isToolStateRunning } from "./tool-call/utils" import { getRelativePath, getToolIcon, getToolName, isToolStateCompleted, isToolStateError, isToolStateRunning } from "./tool-call/utils"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
type ToolState = import("@opencode-ai/sdk").ToolState type ToolState = import("@opencode-ai/sdk").ToolState
@@ -540,7 +543,7 @@ export default function ToolCall(props: ToolCallProps) {
const sessionId = permission.sessionID || props.sessionId const sessionId = permission.sessionID || props.sessionId
await sendPermissionResponse(props.instanceId, sessionId, permission.id, response) await sendPermissionResponse(props.instanceId, sessionId, permission.id, response)
} catch (error) { } catch (error) {
console.error("Failed to send permission response:", error) log.error("Failed to send permission response", error)
setPermissionError(error instanceof Error ? error.message : "Unable to update permission") setPermissionError(error instanceof Error ? error.message : "Unable to update permission")
} finally { } finally {
setPermissionSubmitting(false) setPermissionSubmitting(false)

View File

@@ -2,6 +2,9 @@ import { isRenderableDiffText } from "../../lib/diff-utils"
import { getLanguageFromPath } from "../../lib/markdown" import { getLanguageFromPath } from "../../lib/markdown"
import type { ToolState } from "@opencode-ai/sdk" import type { ToolState } from "@opencode-ai/sdk"
import type { DiffPayload } from "./types" import type { DiffPayload } from "./types"
import { getLogger } from "../../lib/logger"
const log = getLogger("session")
export type ToolStateRunning = import("@opencode-ai/sdk").ToolStateRunning export type ToolStateRunning = import("@opencode-ai/sdk").ToolStateRunning
export type ToolStateCompleted = import("@opencode-ai/sdk").ToolStateCompleted export type ToolStateCompleted = import("@opencode-ai/sdk").ToolStateCompleted
@@ -134,7 +137,7 @@ export function formatUnknown(value: unknown): { text: string; language?: string
try { try {
return { text: JSON.stringify(value, null, 2), language: "json" } return { text: JSON.stringify(value, null, 2), language: "json" }
} catch (error) { } catch (error) {
console.error("Failed to stringify tool call output", error) log.error("Failed to stringify tool call output", error)
return { text: String(value) } return { text: String(value) }
} }
} }

View File

@@ -2,6 +2,9 @@ import { Component, createSignal, createEffect, For, Show, onCleanup } from "sol
import type { Agent } from "../types/session" import type { Agent } from "../types/session"
import type { OpencodeClient } from "@opencode-ai/sdk/client" import type { OpencodeClient } from "@opencode-ai/sdk/client"
import { serverApi } from "../lib/api-client" import { serverApi } from "../lib/api-client"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
const SEARCH_RESULT_LIMIT = 100 const SEARCH_RESULT_LIMIT = 100
const SEARCH_DEBOUNCE_MS = 200 const SEARCH_DEBOUNCE_MS = 200
@@ -124,7 +127,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
return snapshot return snapshot
}) })
.catch((error) => { .catch((error) => {
console.error(`[UnifiedPicker] Failed to load workspace files:`, error) log.error(`[UnifiedPicker] Failed to load workspace files:`, error)
setAllFiles([]) setAllFiles([])
setCachedWorkspaceId(null) setCachedWorkspaceId(null)
throw error throw error
@@ -178,7 +181,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
applyFileResults(mapEntriesToFileItems(results)) applyFileResults(mapEntriesToFileItems(results))
} catch (error) { } catch (error) {
if (workspaceId === props.workspaceId) { if (workspaceId === props.workspaceId) {
console.error(`[UnifiedPicker] Failed to fetch files:`, error) log.error(`[UnifiedPicker] Failed to fetch files:`, error)
if (shouldApplyResults(requestId, workspaceId)) { if (shouldApplyResults(requestId, workspaceId)) {
applyFileResults([]) applyFileResults([])
} }

View File

@@ -17,6 +17,7 @@ import type {
WorkspaceEventPayload, WorkspaceEventPayload,
WorkspaceEventType, WorkspaceEventType,
} from "../../../server/src/api-types" } from "../../../server/src/api-types"
import { getLogger } from "./logger"
const FALLBACK_API_BASE = "http://127.0.0.1:9898" const FALLBACK_API_BASE = "http://127.0.0.1:9898"
const RUNTIME_BASE = typeof window !== "undefined" ? window.location?.origin : undefined const RUNTIME_BASE = typeof window !== "undefined" ? window.location?.origin : undefined
@@ -38,15 +39,15 @@ function buildEventsUrl(base: string | undefined, path: string): string {
return path return path
} }
const HTTP_PREFIX = "[HTTP]" const httpLogger = getLogger("api")
const sseLogger = getLogger("sse")
function logHttp(message: string, context?: Record<string, unknown>) { function logHttp(message: string, context?: Record<string, unknown>) {
if (context) { if (context) {
console.log(`${HTTP_PREFIX} ${message}`, context) httpLogger.info(message, context)
return return
} }
console.log(`${HTTP_PREFIX} ${message}`) httpLogger.info(message)
} }
async function request<T>(path: string, init?: RequestInit): Promise<T> { async function request<T>(path: string, init?: RequestInit): Promise<T> {
@@ -186,18 +187,18 @@ export const serverApi = {
return request(`/api/storage/instances/${encodeURIComponent(id)}`, { method: "DELETE" }) return request(`/api/storage/instances/${encodeURIComponent(id)}`, { method: "DELETE" })
}, },
connectEvents(onEvent: (event: WorkspaceEventPayload) => void, onError?: () => void) { connectEvents(onEvent: (event: WorkspaceEventPayload) => void, onError?: () => void) {
console.log(`[SSE] Connecting to ${EVENTS_URL}`) sseLogger.info(`Connecting to ${EVENTS_URL}`)
const source = new EventSource(EVENTS_URL) const source = new EventSource(EVENTS_URL)
source.onmessage = (event) => { source.onmessage = (event) => {
try { try {
const payload = JSON.parse(event.data) as WorkspaceEventPayload const payload = JSON.parse(event.data) as WorkspaceEventPayload
onEvent(payload) onEvent(payload)
} catch (error) { } catch (error) {
console.error("[SSE] Failed to parse event", error) sseLogger.error("Failed to parse event", error)
} }
} }
source.onerror = () => { source.onerror = () => {
console.warn("[SSE] EventSource error, closing stream") sseLogger.warn("EventSource error, closing stream")
onError?.() onError?.()
} }
return source return source

View File

@@ -2,6 +2,9 @@ import type { Command } from "./commands"
import type { Command as SDKCommand } from "@opencode-ai/sdk" import type { Command as SDKCommand } from "@opencode-ai/sdk"
import { showAlertDialog } from "../stores/alerts" import { showAlertDialog } from "../stores/alerts"
import { activeSessionId, executeCustomCommand } from "../stores/sessions" import { activeSessionId, executeCustomCommand } from "../stores/sessions"
import { getLogger } from "./logger"
const log = getLogger("actions")
export function commandRequiresArguments(template?: string): boolean { export function commandRequiresArguments(template?: string): boolean {
if (!template) return false if (!template) return false
@@ -47,7 +50,7 @@ export function buildCustomCommandEntries(instanceId: string, commands: SDKComma
try { try {
await executeCustomCommand(instanceId, sessionId, cmd.name, args) await executeCustomCommand(instanceId, sessionId, cmd.name, args)
} catch (error) { } catch (error) {
console.error("Failed to run custom command:", error) log.error("Failed to run custom command", error)
showAlertDialog("Failed to run custom command. Check the console for details.", { showAlertDialog("Failed to run custom command. Check the console for details.", {
title: "Command failed", title: "Command failed",
variant: "error", variant: "error",

View File

@@ -8,6 +8,9 @@ import { keyboardRegistry } from "../keyboard-registry"
import { abortSession, getSessions, isSessionBusy } from "../../stores/sessions" import { abortSession, getSessions, isSessionBusy } from "../../stores/sessions"
import { showCommandPalette, hideCommandPalette } from "../../stores/command-palette" import { showCommandPalette, hideCommandPalette } from "../../stores/command-palette"
import type { Instance } from "../../types/instance" import type { Instance } from "../../types/instance"
import { getLogger } from "../logger"
const log = getLogger("actions")
interface UseAppLifecycleOptions { interface UseAppLifecycleOptions {
setEscapeInDebounce: (value: boolean) => void setEscapeInDebounce: (value: boolean) => void
@@ -115,9 +118,9 @@ export function useAppLifecycle(options: UseAppLifecycleOptions) {
try { try {
await abortSession(instance.id, sessionId) await abortSession(instance.id, sessionId)
console.log("Session aborted successfully") log.info("Session aborted successfully", { instanceId: instance.id, sessionId })
} catch (error) { } catch (error) {
console.error("Failed to abort session:", error) log.error("Failed to abort session", error)
} }
}, },
() => { () => {

View File

@@ -17,6 +17,9 @@ import type { Instance } from "../../types/instance"
import type { MessageRecord } from "../../stores/message-v2/types" import type { MessageRecord } from "../../stores/message-v2/types"
import { messageStoreBus } from "../../stores/message-v2/bus" import { messageStoreBus } from "../../stores/message-v2/bus"
import { cleanupBlankSessions } from "../../stores/session-state" import { cleanupBlankSessions } from "../../stores/session-state"
import { getLogger } from "../logger"
const log = getLogger("actions")
export interface UseCommandsOptions { export interface UseCommandsOptions {
preferences: Accessor<Preferences> preferences: Accessor<Preferences>
@@ -236,15 +239,16 @@ export function useCommands(options: UseCommandsOptions) {
modelID: session.model.modelId, modelID: session.model.modelId,
}, },
}) })
} catch (error: unknown) { } catch (error) {
setSessionCompactionState(instance.id, sessionId, false) setSessionCompactionState(instance.id, sessionId, false)
console.error("Failed to compact session:", error) log.error("Failed to compact session", error)
const message = error instanceof Error ? error.message : "Failed to compact session" const message = error instanceof Error ? error.message : "Failed to compact session"
showAlertDialog(`Compact failed: ${message}`, { showAlertDialog(`Compact failed: ${message}`, {
title: "Compact failed", title: "Compact failed",
variant: "error", variant: "error",
}) })
} }
}, },
}) })
@@ -322,12 +326,13 @@ export function useCommands(options: UseCommandsOptions) {
} }
} }
} catch (error) { } catch (error) {
console.error("Failed to revert message:", error) log.error("Failed to revert message", error)
showAlertDialog("Failed to revert message", { showAlertDialog("Failed to revert message", {
title: "Undo failed", title: "Undo failed",
variant: "error", variant: "error",
}) })
} }
}, },
}) })
@@ -503,7 +508,7 @@ export function useCommands(options: UseCommandsOptions) {
category: "System", category: "System",
keywords: ["/help", "shortcuts", "help"], keywords: ["/help", "shortcuts", "help"],
action: () => { action: () => {
console.log("Show help modal (not implemented)") log.info("Show help modal (not implemented)")
}, },
}) })
} }
@@ -513,11 +518,11 @@ export function useCommands(options: UseCommandsOptions) {
const result = command.action?.() const result = command.action?.()
if (result instanceof Promise) { if (result instanceof Promise) {
void result.catch((error) => { void result.catch((error) => {
console.error("Command execution failed:", error) log.error("Command execution failed", error)
}) })
} }
} catch (error) { } catch (error) {
console.error("Command execution failed:", error) log.error("Command execution failed", error)
} }
} }

View File

@@ -0,0 +1,128 @@
import debug from "debug"
export type LoggerNamespace = "sse" | "api" | "session" | "actions"
interface Logger {
log: (...args: unknown[]) => void
info: (...args: unknown[]) => void
warn: (...args: unknown[]) => void
error: (...args: unknown[]) => void
}
interface NamespaceState {
name: LoggerNamespace
enabled: boolean
}
const KNOWN_NAMESPACES: LoggerNamespace[] = ["sse", "api", "session", "actions"]
const STORAGE_KEY = "opencode:logger:namespaces"
const namespaceLoggers = new Map<LoggerNamespace, Logger>()
const enabledNamespaces = new Set<LoggerNamespace>()
const rawConsole = typeof globalThis !== "undefined" ? globalThis.console : undefined
function applyEnabledNamespaces(): void {
if (enabledNamespaces.size === 0) {
debug.disable()
} else {
debug.enable(Array.from(enabledNamespaces).join(","))
}
}
function persistEnabledNamespaces(): void {
if (typeof window === "undefined" || !window?.localStorage) return
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(Array.from(enabledNamespaces)))
} catch (error) {
rawConsole?.warn?.("Failed to persist logger namespaces", error)
}
}
function hydrateNamespacesFromStorage(): void {
if (typeof window === "undefined" || !window?.localStorage) return
try {
const stored = window.localStorage.getItem(STORAGE_KEY)
if (!stored) return
const parsed: unknown = JSON.parse(stored)
if (!Array.isArray(parsed)) return
for (const name of parsed) {
if (KNOWN_NAMESPACES.includes(name as LoggerNamespace)) {
enabledNamespaces.add(name as LoggerNamespace)
}
}
} catch (error) {
rawConsole?.warn?.("Failed to hydrate logger namespaces", error)
}
}
hydrateNamespacesFromStorage()
applyEnabledNamespaces()
function buildLogger(namespace: LoggerNamespace): Logger {
const base = debug(namespace)
const baseLogger: (...args: any[]) => void = base
const formatAndLog = (level: string, args: any[]) => {
baseLogger(level, ...args)
}
return {
log: (...args: any[]) => baseLogger(...args),
info: (...args: any[]) => baseLogger(...args),
warn: (...args: any[]) => formatAndLog("[warn]", args),
error: (...args: any[]) => formatAndLog("[error]", args),
}
}
function getLogger(namespace: LoggerNamespace): Logger {
if (!KNOWN_NAMESPACES.includes(namespace)) {
throw new Error(`Unknown logger namespace: ${namespace}`)
}
if (!namespaceLoggers.has(namespace)) {
namespaceLoggers.set(namespace, buildLogger(namespace))
}
return namespaceLoggers.get(namespace)!
}
function listLoggerNamespaces(): NamespaceState[] {
return KNOWN_NAMESPACES.map((name) => ({ name, enabled: enabledNamespaces.has(name) }))
}
function enableLogger(namespace: LoggerNamespace): void {
if (!KNOWN_NAMESPACES.includes(namespace)) {
throw new Error(`Unknown logger namespace: ${namespace}`)
}
if (enabledNamespaces.has(namespace)) return
enabledNamespaces.add(namespace)
persistEnabledNamespaces()
applyEnabledNamespaces()
}
function disableLogger(namespace: LoggerNamespace): void {
if (!KNOWN_NAMESPACES.includes(namespace)) {
throw new Error(`Unknown logger namespace: ${namespace}`)
}
if (!enabledNamespaces.has(namespace)) return
enabledNamespaces.delete(namespace)
persistEnabledNamespaces()
applyEnabledNamespaces()
}
function disableAllLoggers(): void {
enabledNamespaces.clear()
persistEnabledNamespaces()
applyEnabledNamespaces()
}
function enableAllLoggers(): void {
KNOWN_NAMESPACES.forEach((namespace) => enabledNamespaces.add(namespace))
persistEnabledNamespaces()
applyEnabledNamespaces()
}
export {
getLogger,
listLoggerNamespaces,
enableLogger,
disableLogger,
enableAllLoggers,
disableAllLoggers,
}

View File

@@ -1,5 +1,8 @@
import { marked } from "marked" import { marked } from "marked"
import { createHighlighter, type Highlighter, bundledLanguages } from "shiki/bundle/full" import { createHighlighter, type Highlighter, bundledLanguages } from "shiki/bundle/full"
import { getLogger } from "./logger"
const log = getLogger("actions")
let highlighter: Highlighter | null = null let highlighter: Highlighter | null = null
let highlighterPromise: Promise<Highlighter> | null = null let highlighterPromise: Promise<Highlighter> | null = null
@@ -71,7 +74,7 @@ function triggerLanguageListeners() {
try { try {
listener() listener()
} catch (error) { } catch (error) {
console.error("Error in language listener:", error) log.error("Error in language listener", error)
} }
} }
} }

View File

@@ -1,4 +1,7 @@
import { runtimeEnv } from "../runtime-env" import { runtimeEnv } from "../runtime-env"
import { getLogger } from "../logger"
const log = getLogger("actions")
export async function restartCli(): Promise<boolean> { export async function restartCli(): Promise<boolean> {
try { try {
@@ -20,7 +23,7 @@ export async function restartCli(): Promise<boolean> {
return false return false
} }
} catch (error) { } catch (error) {
console.error("Failed to restart CLI", error) log.error("Failed to restart CLI", error)
return false return false
} }

View File

@@ -1,4 +1,7 @@
import type { NativeDialogOptions } from "../native-functions" import type { NativeDialogOptions } from "../native-functions"
import { getLogger } from "../../logger"
const log = getLogger("actions")
interface ElectronDialogResult { interface ElectronDialogResult {
canceled?: boolean canceled?: boolean
@@ -33,7 +36,7 @@ export async function openElectronNativeDialog(options: NativeDialogOptions): Pr
const result = await api.openDialog(options) const result = await api.openDialog(options)
return coerceFirstPath(result) return coerceFirstPath(result)
} catch (error) { } catch (error) {
console.error("[native] electron dialog failed", error) log.error("[native] electron dialog failed", error)
return null return null
} }
} }

View File

@@ -1,4 +1,7 @@
import type { NativeDialogOptions } from "../native-functions" import type { NativeDialogOptions } from "../native-functions"
import { getLogger } from "../../logger"
const log = getLogger("actions")
interface TauriDialogModule { interface TauriDialogModule {
open?: ( open?: (
@@ -49,7 +52,7 @@ export async function openTauriNativeDialog(options: NativeDialogOptions): Promi
return response return response
} catch (error) { } catch (error) {
console.error("[native] tauri dialog failed", error) log.error("[native] tauri dialog failed", error)
return null return null
} }
} }

View File

@@ -1,3 +1,5 @@
import { getLogger } from "./logger"
export type HostRuntime = "electron" | "tauri" | "web" export type HostRuntime = "electron" | "tauri" | "web"
export type PlatformKind = "desktop" | "mobile" export type PlatformKind = "desktop" | "mobile"
@@ -61,6 +63,8 @@ function detectPlatform(): PlatformKind {
return "desktop" return "desktop"
} }
const log = getLogger("actions")
let cachedEnv: RuntimeEnvironment | null = null let cachedEnv: RuntimeEnvironment | null = null
export function detectRuntimeEnvironment(): RuntimeEnvironment { export function detectRuntimeEnvironment(): RuntimeEnvironment {
@@ -71,9 +75,8 @@ export function detectRuntimeEnvironment(): RuntimeEnvironment {
host: detectHost(), host: detectHost(),
platform: detectPlatform(), platform: detectPlatform(),
} }
if (typeof console !== "undefined") { if (typeof window !== "undefined") {
const message = `[runtime] host=${cachedEnv.host} platform=${cachedEnv.platform}` log.info(`[runtime] host=${cachedEnv.host} platform=${cachedEnv.platform}`)
console.info(message)
} }
return cachedEnv return cachedEnv
} }

View File

@@ -1,16 +1,17 @@
import type { WorkspaceEventPayload, WorkspaceEventType } from "../../../server/src/api-types" import type { WorkspaceEventPayload, WorkspaceEventType } from "../../../server/src/api-types"
import { serverApi } from "./api-client" import { serverApi } from "./api-client"
import { getLogger } from "./logger"
const RETRY_BASE_DELAY = 1000 const RETRY_BASE_DELAY = 1000
const RETRY_MAX_DELAY = 10000 const RETRY_MAX_DELAY = 10000
const SSE_PREFIX = "[SSE]" const log = getLogger("sse")
function logSse(message: string, context?: Record<string, unknown>) { function logSse(message: string, context?: Record<string, unknown>) {
if (context) { if (context) {
console.log(`${SSE_PREFIX} ${message}`, context) log.info(message, context)
return return
} }
console.log(`${SSE_PREFIX} ${message}`) log.info(message)
} }
class ServerEvents { class ServerEvents {

View File

@@ -20,6 +20,9 @@ import type {
InstanceStreamStatus, InstanceStreamStatus,
WorkspaceEventPayload, WorkspaceEventPayload,
} from "../../../server/src/api-types" } from "../../../server/src/api-types"
import { getLogger } from "./logger"
const log = getLogger("sse")
type InstanceEventPayload = Extract<WorkspaceEventPayload, { type: "instance.event" }> type InstanceEventPayload = Extract<WorkspaceEventPayload, { type: "instance.event" }>
type InstanceStatusPayload = Extract<WorkspaceEventPayload, { type: "instance.eventStatus" }> type InstanceStatusPayload = Extract<WorkspaceEventPayload, { type: "instance.eventStatus" }>
@@ -80,11 +83,11 @@ class SSEManager {
private handleEvent(instanceId: string, event: SSEEvent | InstanceStreamEvent): void { private handleEvent(instanceId: string, event: SSEEvent | InstanceStreamEvent): void {
if (!event || typeof event !== "object" || typeof (event as { type?: unknown }).type !== "string") { if (!event || typeof event !== "object" || typeof (event as { type?: unknown }).type !== "string") {
console.warn("[SSE] Dropping malformed event", event) log.warn("Dropping malformed event", event)
return return
} }
console.log("[SSE] Received event:", event.type, event) log.info("Received event", { type: event.type, event })
switch (event.type) { switch (event.type) {
case "message.updated": case "message.updated":
@@ -124,7 +127,7 @@ class SSEManager {
this.onLspUpdated?.(instanceId, event as EventLspUpdated) this.onLspUpdated?.(instanceId, event as EventLspUpdated)
break break
default: default:
console.warn("[SSE] Unknown event type:", event.type) log.warn("Unknown SSE event type", { type: event.type })
} }
} }

View File

@@ -1,6 +1,9 @@
import type { AppConfig, InstanceData } from "../../../server/src/api-types" import type { AppConfig, InstanceData } from "../../../server/src/api-types"
import { serverApi } from "./api-client" import { serverApi } from "./api-client"
import { serverEvents } from "./server-events" import { serverEvents } from "./server-events"
import { getLogger } from "./logger"
const log = getLogger("actions")
export type ConfigData = AppConfig export type ConfigData = AppConfig
@@ -19,7 +22,7 @@ function isDeepEqual(a: unknown, b: unknown): boolean {
try { try {
return JSON.stringify(a) === JSON.stringify(b) return JSON.stringify(a) === JSON.stringify(b)
} catch (error) { } catch (error) {
console.warn("Failed to compare config objects", error) log.warn("Failed to compare config objects", error)
} }
} }

View File

@@ -19,7 +19,8 @@
try { try {
document.documentElement.setAttribute('data-theme', 'dark') document.documentElement.setAttribute('data-theme', 'dark')
} catch (error) { } catch (error) {
console.warn('Failed to apply initial theme', error) const rawConsole = globalThis?.["console"]
rawConsole?.warn?.('Failed to apply initial theme', error)
} }
})() })()
</script> </script>

View File

@@ -9,7 +9,8 @@
try { try {
document.documentElement.setAttribute('data-theme', 'dark') document.documentElement.setAttribute('data-theme', 'dark')
} catch (error) { } catch (error) {
console.warn('Failed to apply initial theme', error) const rawConsole = globalThis?.["console"]
rawConsole?.warn?.('Failed to apply initial theme', error)
} }
})() })()
</script> </script>

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import { createInstanceMessageStore } from "./instance-store" import { createInstanceMessageStore } from "./instance-store"
import type { InstanceMessageStore } from "./instance-store" import type { InstanceMessageStore } from "./instance-store"
import { clearCacheForInstance } from "../../lib/global-cache" import { clearCacheForInstance } from "../../lib/global-cache"
import { getLogger } from "../../lib/logger"
const log = getLogger("session")
class MessageStoreBus { class MessageStoreBus {
private stores = new Map<string, InstanceMessageStore>() private stores = new Map<string, InstanceMessageStore>()
@@ -55,7 +58,7 @@ class MessageStoreBus {
try { try {
handler(instanceId) handler(instanceId)
} catch (error) { } 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, getInstanceConfig,
updateInstanceConfig as updateInstanceData, updateInstanceConfig as updateInstanceData,
} from "./instance-config" } from "./instance-config"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
type DeepReadonly<T> = T extends (...args: any[]) => unknown type DeepReadonly<T> = T extends (...args: any[]) => unknown
? T ? T
@@ -81,7 +84,7 @@ function deepEqual(a: unknown, b: unknown): boolean {
try { try {
return JSON.stringify(a) === JSON.stringify(b) return JSON.stringify(a) === JSON.stringify(b)
} catch (error) { } catch (error) {
console.warn("Failed to compare preference values", error) log.warn("Failed to compare preference values", error)
} }
} }
return false return false
@@ -148,11 +151,11 @@ async function syncConfig(source?: ConfigData): Promise<void> {
applyConfig(cleaned) applyConfig(cleaned)
if (migrated) { if (migrated) {
void storage.updateConfig(cleaned).catch((error: unknown) => { 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) { } catch (error) {
console.error("Failed to load config:", error) log.error("Failed to load config", error)
applyConfig(buildFallbackConfig()) applyConfig(buildFallbackConfig())
} }
} }
@@ -172,7 +175,7 @@ function logConfigDiff(previous: ConfigData, next: ConfigData) {
} }
const changes = diffObjects(previous, next) const changes = diffObjects(previous, next)
if (changes.length > 0) { 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 ensureConfigLoaded()
await storage.updateConfig(next) await storage.updateConfig(next)
} catch (error) { } catch (error) {
console.error("Failed to save config:", error) log.error("Failed to save config", error)
void syncConfig().catch((syncError: unknown) => { 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 { function toggleAutoCleanupBlankSessions(): void {
console.log("toggle auto cleanup") const nextValue = !preferences().autoCleanupBlankSessions
updatePreferences({ autoCleanupBlankSessions: !preferences().autoCleanupBlankSessions }) log.info("toggle auto cleanup", { value: nextValue })
updatePreferences({ autoCleanupBlankSessions: nextValue })
} }
function addRecentFolder(path: string): void { function addRecentFolder(path: string): void {
@@ -394,7 +398,7 @@ async function getAgentModelPreference(instanceId: string, agent: string): Promi
} }
void ensureConfigLoaded().catch((error: unknown) => { void ensureConfigLoaded().catch((error: unknown) => {
console.error("Failed to initialize config:", error) log.error("Failed to initialize config", error)
}) })
interface ConfigContextValue { interface ConfigContextValue {
@@ -466,12 +470,12 @@ const configContextValue: ConfigContextValue = {
const ConfigProvider: ParentComponent = (props) => { const ConfigProvider: ParentComponent = (props) => {
onMount(() => { onMount(() => {
ensureConfigLoaded().catch((error: unknown) => { ensureConfigLoaded().catch((error: unknown) => {
console.error("Failed to initialize config:", error) log.error("Failed to initialize config", error)
}) })
const unsubscribe = storage.onConfigChanged((config) => { const unsubscribe = storage.onConfigChanged((config) => {
syncConfig(config).catch((error: unknown) => { 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 { getDefaultModel, isModelValid } from "./session-models"
import { updateSessionInfo } from "./message-v2/session-info" import { updateSessionInfo } from "./message-v2/session-info"
import { messageStoreBus } from "./message-v2/bus" import { messageStoreBus } from "./message-v2/bus"
import { getLogger } from "../lib/logger"
const log = getLogger("actions")
const ID_LENGTH = 26 const ID_LENGTH = 26
const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" const BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
@@ -168,26 +171,27 @@ async function sendMessage(
}), }),
} }
console.log("[sendMessage] Sending prompt:", { log.info("sendMessage", {
instanceId,
sessionId, sessionId,
requestBody, requestBody,
}) })
try { try {
console.log(`[HTTP] POST /session.prompt_async for instance ${instanceId}`, { sessionId, requestBody }) log.info("session.prompt", { instanceId, sessionId, requestBody })
const response = await instance.client.session.promptAsync({ const response = await instance.client.session.prompt({
path: { id: sessionId }, path: { id: sessionId },
body: requestBody, body: requestBody,
}) })
console.log("[sendMessage] Response:", response) log.info("sendMessage response", response)
if (response.error) { 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") throw new Error(JSON.stringify(response.error) || "Failed to send message")
} }
} catch (error) { } catch (error) {
console.error("[sendMessage] Failed to send prompt:", error) log.error("Failed to send prompt", error)
throw error throw error
} }
} }
@@ -262,16 +266,16 @@ async function abortSession(instanceId: string, sessionId: string): Promise<void
throw new Error("Instance not ready") throw new Error("Instance not ready")
} }
console.log("[abortSession] Aborting session:", { instanceId, sessionId }) log.info("abortSession", { instanceId, sessionId })
try { try {
console.log(`[HTTP] POST /session.abort for instance ${instanceId}`, { sessionId }) log.info("session.abort", { instanceId, sessionId })
await instance.client.session.abort({ await instance.client.session.abort({
path: { id: sessionId }, path: { id: sessionId },
}) })
console.log("[abortSession] Session aborted successfully") log.info("abortSession complete", { instanceId, sessionId })
} catch (error) { } catch (error) {
console.error("[abortSession] Failed to abort session:", error) log.error("Failed to abort session", error)
throw error throw error
} }
} }
@@ -314,7 +318,7 @@ async function updateSessionModel(
} }
if (!isModelValid(instanceId, model)) { if (!isModelValid(instanceId, model)) {
console.warn("Invalid model selection", model) log.warn("Invalid model selection", model)
return return
} }

View File

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

View File

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

View File

@@ -6,6 +6,9 @@ import { showToastNotification } from "../lib/notifications"
import { messageStoreBus } from "./message-v2/bus" import { messageStoreBus } from "./message-v2/bus"
import { instances } from "./instances" import { instances } from "./instances"
import { showConfirmDialog } from "./alerts" import { showConfirmDialog } from "./alerts"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
export interface SessionInfo { export interface SessionInfo {
cost: number 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 } }) const response = await instance.client.session.messages({ path: { id: session.id } })
messages = response.data || [] messages = response.data || []
} catch (error) { } 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 return isFreshSession
} }
@@ -309,13 +312,13 @@ async function cleanupBlankSessions(instanceId: string, excludeSessionId?: strin
if (!isBlank) return false if (!isBlank) return false
await deleteSession(instanceId, sessionId).catch((error: Error) => { 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 return true
}) })
if (cleanupPromises.length > 0) { 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 deletionResults = await Promise.all(cleanupPromises)
const deletedCount = deletionResults.filter(Boolean).length const deletedCount = deletionResults.filter(Boolean).length