feat(ui): add runtime logger and replace console usage
This commit is contained in:
1
package-lock.json
generated
1
package-lock.json
generated
@@ -8895,6 +8895,7 @@
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@opencode-ai/sdk": "^1.0.133",
|
||||
"@solidjs/router": "^0.13.0",
|
||||
"debug": "^4.4.3",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"lucide-solid": "^0.300.0",
|
||||
"marked": "^12.0.0",
|
||||
|
||||
@@ -26,8 +26,30 @@ This starts the Vite dev server at `http://localhost:3000`.
|
||||
|
||||
To build the production assets:
|
||||
|
||||
```bash
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@opencode-ai/sdk": "^1.0.133",
|
||||
"@solidjs/router": "^0.13.0",
|
||||
"debug": "^4.4.3",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"lucide-solid": "^0.300.0",
|
||||
"marked": "^12.0.0",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { initMarkdown } from "./lib/markdown"
|
||||
import { useTheme } from "./lib/theme"
|
||||
import { useCommands } from "./lib/hooks/use-commands"
|
||||
import { useAppLifecycle } from "./lib/hooks/use-app-lifecycle"
|
||||
import { getLogger } from "./lib/logger"
|
||||
import {
|
||||
hasInstances,
|
||||
isSelectingFolder,
|
||||
@@ -42,6 +43,8 @@ import {
|
||||
updateSessionModel,
|
||||
} from "./stores/sessions"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
const App: Component = () => {
|
||||
const { isDark } = useTheme()
|
||||
const {
|
||||
@@ -61,7 +64,7 @@ const App: Component = () => {
|
||||
const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false)
|
||||
|
||||
createEffect(() => {
|
||||
void initMarkdown(isDark()).catch(console.error)
|
||||
void initMarkdown(isDark()).catch((error) => log.error("Failed to initialize markdown", error))
|
||||
})
|
||||
|
||||
const activeInstance = createMemo(() => getActiveInstance())
|
||||
@@ -106,13 +109,16 @@ const App: Component = () => {
|
||||
setShowFolderSelection(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) {
|
||||
clearLaunchError()
|
||||
if (isMissingBinaryError(error)) {
|
||||
setLaunchErrorBinary(selectedBinary)
|
||||
}
|
||||
console.error("Failed to create instance:", error)
|
||||
log.error("Failed to create instance", error)
|
||||
} finally {
|
||||
setIsSelectingFolder(false)
|
||||
}
|
||||
@@ -137,7 +143,7 @@ const App: Component = () => {
|
||||
try {
|
||||
await acknowledgeDisconnectedInstance()
|
||||
} 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)
|
||||
setActiveParentSession(instanceId, session.id)
|
||||
} catch (error) {
|
||||
console.error("Failed to create session:", error)
|
||||
log.error("Failed to create session", error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +195,7 @@ const App: Component = () => {
|
||||
try {
|
||||
await fetchSessions(instanceId)
|
||||
} catch (error) {
|
||||
console.error("Failed to refresh sessions after closing:", error)
|
||||
log.error("Failed to refresh sessions after closing", error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ import { For, Show, createEffect, createMemo } from "solid-js"
|
||||
import { agents, fetchAgents, sessions } from "../stores/sessions"
|
||||
import { ChevronDown } from "lucide-solid"
|
||||
import type { Agent } from "../types/session"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
interface AgentSelectorProps {
|
||||
instanceId: string
|
||||
@@ -49,10 +52,11 @@ export default function AgentSelector(props: AgentSelectorProps) {
|
||||
|
||||
createEffect(() => {
|
||||
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) => {
|
||||
if (value && value.name !== props.currentAgent) {
|
||||
await props.onAgentChange(value.name)
|
||||
|
||||
@@ -8,6 +8,9 @@ import { normalizeDiffText } from "../lib/diff-utils"
|
||||
import { setCacheEntry } from "../lib/global-cache"
|
||||
import type { CacheEntryParams } from "../lib/global-cache"
|
||||
import type { DiffViewMode } from "../stores/preferences"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
disableCache()
|
||||
|
||||
@@ -110,7 +113,7 @@ export function ToolCallDiffViewer(props: ToolCallDiffViewerProps) {
|
||||
>
|
||||
{(data) => (
|
||||
<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>
|
||||
}}>
|
||||
<DiffView
|
||||
|
||||
@@ -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 type { FileSystemEntry, FileSystemListingMetadata } from "../../../server/src/api-types"
|
||||
import { serverApi } from "../lib/api-client"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
const MAX_RESULTS = 200
|
||||
|
||||
@@ -172,7 +175,7 @@ const FileSystemBrowserDialog: Component<FileSystemBrowserDialogProps> = (props)
|
||||
|
||||
function handleNavigateTo(path: string) {
|
||||
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")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Component, Show, For, createSignal, createEffect, onCleanup } from "solid-js"
|
||||
import type { Instance, RawMcpStatus } from "../types/instance"
|
||||
import { fetchLspStatus, updateInstance } from "../stores/instances"
|
||||
import { getLogger } from "../lib/logger"
|
||||
|
||||
const log = getLogger("session")
|
||||
|
||||
interface InstanceInfoProps {
|
||||
instance: Instance
|
||||
@@ -113,7 +116,7 @@ const InstanceInfo: Component<InstanceInfoProps> = (props) => {
|
||||
|
||||
} catch (error) {
|
||||
if (!cancelled) {
|
||||
console.error("Failed to load instance metadata:", error)
|
||||
log.error("Failed to load instance metadata", error)
|
||||
}
|
||||
} finally {
|
||||
pendingMetadataRequests.delete(instanceId)
|
||||
|
||||
@@ -6,6 +6,9 @@ import KeyboardHint from "./keyboard-hint"
|
||||
import Kbd from "./kbd"
|
||||
import { keyboardRegistry, type KeyboardShortcut } from "../lib/keyboard-registry"
|
||||
import { isMac } from "../lib/keyboard-utils"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
|
||||
interface InstanceWelcomeViewProps {
|
||||
@@ -189,7 +192,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
||||
const session = await createSession(props.instance.id)
|
||||
setActiveParentSession(props.instance.id, session.id)
|
||||
} catch (error) {
|
||||
console.error("Failed to create session:", error)
|
||||
log.error("Failed to create session:", error)
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ import CommandPalette from "../command-palette"
|
||||
import Kbd from "../kbd"
|
||||
import ContextUsagePanel from "../session/context-usage-panel"
|
||||
import SessionView from "../session/session-view"
|
||||
import { getLogger } from "../../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
interface InstanceShellProps {
|
||||
instance: Instance
|
||||
@@ -119,13 +122,13 @@ const InstanceShell: Component<InstanceShellProps> = (props) => {
|
||||
onClose={(id) => {
|
||||
const result = props.onCloseSession(id)
|
||||
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={() => {
|
||||
const result = props.onNewSession()
|
||||
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
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { createEffect, createSignal, onMount, onCleanup } from "solid-js"
|
||||
import { renderMarkdown, onLanguagesLoaded, initMarkdown, decodeHtmlEntities } from "../lib/markdown"
|
||||
import type { TextPart } from "../types/message"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
interface MarkdownProps {
|
||||
part: TextPart
|
||||
@@ -43,7 +46,7 @@ export function Markdown(props: MarkdownProps) {
|
||||
notifyRendered()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to render markdown:", error)
|
||||
log.error("Failed to render markdown:", error)
|
||||
if (latestRequestedText === text) {
|
||||
setHtml(text)
|
||||
notifyRendered()
|
||||
@@ -68,7 +71,7 @@ export function Markdown(props: MarkdownProps) {
|
||||
notifyRendered()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to render markdown:", error)
|
||||
log.error("Failed to render markdown:", error)
|
||||
if (latestRequestedText === text) {
|
||||
setHtml(text)
|
||||
part.renderCache = { text, html: text, theme: themeKey }
|
||||
@@ -124,7 +127,7 @@ export function Markdown(props: MarkdownProps) {
|
||||
notifyRendered()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to re-render markdown after language load:", error)
|
||||
log.error("Failed to re-render markdown after language load:", error)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ import { createEffect, createMemo, createSignal } from "solid-js"
|
||||
import { providers, fetchProviders } from "../stores/sessions"
|
||||
import { ChevronDown } from "lucide-solid"
|
||||
import type { Model } from "../types/session"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
interface ModelSelectorProps {
|
||||
instanceId: string
|
||||
@@ -25,7 +28,7 @@ export default function ModelSelector(props: ModelSelectorProps) {
|
||||
|
||||
createEffect(() => {
|
||||
if (instanceProviders().length === 0) {
|
||||
fetchProviders(props.instanceId).catch(console.error)
|
||||
fetchProviders(props.instanceId).catch((error) => log.error("Failed to fetch providers", error))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import { useConfig } from "../stores/preferences"
|
||||
import { serverApi } from "../lib/api-client"
|
||||
import FileSystemBrowserDialog from "./filesystem-browser-dialog"
|
||||
import { openNativeFileDialog, supportsNativeDialogs } from "../lib/native/native-functions"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
interface BinaryOption {
|
||||
path: string
|
||||
@@ -83,7 +86,7 @@ const OpenCodeBinarySelector: Component<OpenCodeBinarySelectorProps> = (props) =
|
||||
|
||||
setTimeout(() => {
|
||||
pathsToValidate.forEach((path) => {
|
||||
validateBinary(path).catch(console.error)
|
||||
validateBinary(path).catch((error) => log.error("Failed to validate binary", { path, error }))
|
||||
})
|
||||
}, 0)
|
||||
})
|
||||
|
||||
@@ -10,6 +10,9 @@ import Kbd from "./kbd"
|
||||
import { getActiveInstance } from "../stores/instances"
|
||||
import { agents, getSessionDraftPrompt, setSessionDraftPrompt, clearSessionDraftPrompt } from "../stores/sessions"
|
||||
import { showAlertDialog } from "../stores/alerts"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
interface PromptInputProps {
|
||||
instanceId: string
|
||||
@@ -563,7 +566,7 @@ export default function PromptInput(props: PromptInputProps) {
|
||||
})
|
||||
setHistoryIndex(-1)
|
||||
} 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()
|
||||
} catch (error) {
|
||||
console.error("Failed to send message:", error)
|
||||
log.error("Failed to send message:", error)
|
||||
showAlertDialog("Failed to send message", {
|
||||
title: "Send failed",
|
||||
detail: error instanceof Error ? error.message : String(error),
|
||||
|
||||
@@ -8,6 +8,9 @@ import { serverApi } from "../lib/api-client"
|
||||
import { restartCli } from "../lib/native/cli"
|
||||
import { preferences, setListeningMode } from "../stores/preferences"
|
||||
import { showConfirmDialog } from "../stores/alerts"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
interface RemoteAccessOverlayProps {
|
||||
open: boolean
|
||||
@@ -62,7 +65,7 @@ export function RemoteAccessOverlay(props: RemoteAccessOverlayProps) {
|
||||
const dataUrl = await toDataURL(url, { margin: 1, scale: 4 })
|
||||
setQrCodes((prev) => ({ ...prev, [url]: dataUrl }))
|
||||
} 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 {
|
||||
window.open(url, "_blank", "noopener,noreferrer")
|
||||
} catch (err) {
|
||||
console.error("Failed to open URL", err)
|
||||
log.error("Failed to open URL", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import Kbd from "./kbd"
|
||||
import { keyboardRegistry } from "../lib/keyboard-registry"
|
||||
import { formatShortcut } from "../lib/keyboard-utils"
|
||||
import { showToastNotification } from "../lib/notifications"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
|
||||
interface SessionListProps {
|
||||
@@ -106,7 +109,7 @@ const SessionList: Component<SessionListProps> = (props) => {
|
||||
await navigator.clipboard.writeText(sessionId)
|
||||
showToastNotification({ message: "Session ID copied", variant: "success" })
|
||||
} 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" })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import type { Session, Agent } from "../types/session"
|
||||
import { getParentSessions, createSession, setActiveParentSession } from "../stores/sessions"
|
||||
import { instances, stopInstance } from "../stores/instances"
|
||||
import { agents } from "../stores/sessions"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
interface SessionPickerProps {
|
||||
instanceId: string
|
||||
@@ -55,7 +58,7 @@ const SessionPicker: Component<SessionPickerProps> = (props) => {
|
||||
setActiveParentSession(props.instanceId, session.id)
|
||||
props.onClose()
|
||||
} catch (error) {
|
||||
console.error("Failed to create session:", error)
|
||||
log.error("Failed to create session:", error)
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import PromptInput from "../prompt-input"
|
||||
import { instances } from "../../stores/instances"
|
||||
import { loadMessages, sendMessage, forkSession, isSessionMessagesLoading, setActiveParentSession, setActiveSession, runShellCommand } from "../../stores/sessions"
|
||||
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 } {
|
||||
return part?.type === "text" && typeof (part as any).text === "string"
|
||||
@@ -33,7 +36,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
||||
|
||||
const currentSession = session()
|
||||
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) {
|
||||
console.error("Failed to revert:", error)
|
||||
log.error("Failed to revert message", error)
|
||||
showAlertDialog("Failed to revert to message", {
|
||||
title: "Revert failed",
|
||||
variant: "error",
|
||||
@@ -94,7 +97,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
||||
|
||||
async function handleFork(messageId?: string) {
|
||||
if (!messageId) {
|
||||
console.warn("Fork requires a user message id")
|
||||
log.warn("Fork requires a user message id")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,7 +112,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
||||
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) {
|
||||
const textarea = document.querySelector(".prompt-input") as HTMLTextAreaElement
|
||||
@@ -120,7 +123,7 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fork session:", error)
|
||||
log.error("Failed to fork session", error)
|
||||
showAlertDialog("Failed to fork session", {
|
||||
title: "Fork failed",
|
||||
variant: "error",
|
||||
|
||||
@@ -11,6 +11,9 @@ import type { TextPart, RenderCache } from "../types/message"
|
||||
import { resolveToolRenderer } from "./tool-call/renderers"
|
||||
import type { DiffPayload, DiffRenderOptions, MarkdownRenderOptions, ToolCallPart, ToolRendererContext } from "./tool-call/types"
|
||||
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
|
||||
|
||||
@@ -540,7 +543,7 @@ export default function ToolCall(props: ToolCallProps) {
|
||||
const sessionId = permission.sessionID || props.sessionId
|
||||
await sendPermissionResponse(props.instanceId, sessionId, permission.id, response)
|
||||
} 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")
|
||||
} finally {
|
||||
setPermissionSubmitting(false)
|
||||
|
||||
@@ -2,6 +2,9 @@ import { isRenderableDiffText } from "../../lib/diff-utils"
|
||||
import { getLanguageFromPath } from "../../lib/markdown"
|
||||
import type { ToolState } from "@opencode-ai/sdk"
|
||||
import type { DiffPayload } from "./types"
|
||||
import { getLogger } from "../../lib/logger"
|
||||
const log = getLogger("session")
|
||||
|
||||
|
||||
export type ToolStateRunning = import("@opencode-ai/sdk").ToolStateRunning
|
||||
export type ToolStateCompleted = import("@opencode-ai/sdk").ToolStateCompleted
|
||||
@@ -134,7 +137,7 @@ export function formatUnknown(value: unknown): { text: string; language?: string
|
||||
try {
|
||||
return { text: JSON.stringify(value, null, 2), language: "json" }
|
||||
} catch (error) {
|
||||
console.error("Failed to stringify tool call output", error)
|
||||
log.error("Failed to stringify tool call output", error)
|
||||
return { text: String(value) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { Component, createSignal, createEffect, For, Show, onCleanup } from "sol
|
||||
import type { Agent } from "../types/session"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk/client"
|
||||
import { serverApi } from "../lib/api-client"
|
||||
import { getLogger } from "../lib/logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
const SEARCH_RESULT_LIMIT = 100
|
||||
const SEARCH_DEBOUNCE_MS = 200
|
||||
@@ -124,7 +127,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
||||
return snapshot
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`[UnifiedPicker] Failed to load workspace files:`, error)
|
||||
log.error(`[UnifiedPicker] Failed to load workspace files:`, error)
|
||||
setAllFiles([])
|
||||
setCachedWorkspaceId(null)
|
||||
throw error
|
||||
@@ -178,7 +181,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
|
||||
applyFileResults(mapEntriesToFileItems(results))
|
||||
} catch (error) {
|
||||
if (workspaceId === props.workspaceId) {
|
||||
console.error(`[UnifiedPicker] Failed to fetch files:`, error)
|
||||
log.error(`[UnifiedPicker] Failed to fetch files:`, error)
|
||||
if (shouldApplyResults(requestId, workspaceId)) {
|
||||
applyFileResults([])
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
WorkspaceEventPayload,
|
||||
WorkspaceEventType,
|
||||
} from "../../../server/src/api-types"
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
const FALLBACK_API_BASE = "http://127.0.0.1:9898"
|
||||
const RUNTIME_BASE = typeof window !== "undefined" ? window.location?.origin : undefined
|
||||
@@ -38,15 +39,15 @@ function buildEventsUrl(base: string | undefined, path: string): string {
|
||||
return path
|
||||
}
|
||||
|
||||
const HTTP_PREFIX = "[HTTP]"
|
||||
const httpLogger = getLogger("api")
|
||||
const sseLogger = getLogger("sse")
|
||||
|
||||
function logHttp(message: string, context?: Record<string, unknown>) {
|
||||
|
||||
if (context) {
|
||||
console.log(`${HTTP_PREFIX} ${message}`, context)
|
||||
httpLogger.info(message, context)
|
||||
return
|
||||
}
|
||||
console.log(`${HTTP_PREFIX} ${message}`)
|
||||
httpLogger.info(message)
|
||||
}
|
||||
|
||||
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" })
|
||||
},
|
||||
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)
|
||||
source.onmessage = (event) => {
|
||||
try {
|
||||
const payload = JSON.parse(event.data) as WorkspaceEventPayload
|
||||
onEvent(payload)
|
||||
} catch (error) {
|
||||
console.error("[SSE] Failed to parse event", error)
|
||||
sseLogger.error("Failed to parse event", error)
|
||||
}
|
||||
}
|
||||
source.onerror = () => {
|
||||
console.warn("[SSE] EventSource error, closing stream")
|
||||
sseLogger.warn("EventSource error, closing stream")
|
||||
onError?.()
|
||||
}
|
||||
return source
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { Command } from "./commands"
|
||||
import type { Command as SDKCommand } from "@opencode-ai/sdk"
|
||||
import { showAlertDialog } from "../stores/alerts"
|
||||
import { activeSessionId, executeCustomCommand } from "../stores/sessions"
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
export function commandRequiresArguments(template?: string): boolean {
|
||||
if (!template) return false
|
||||
@@ -47,7 +50,7 @@ export function buildCustomCommandEntries(instanceId: string, commands: SDKComma
|
||||
try {
|
||||
await executeCustomCommand(instanceId, sessionId, cmd.name, args)
|
||||
} 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.", {
|
||||
title: "Command failed",
|
||||
variant: "error",
|
||||
|
||||
@@ -8,6 +8,9 @@ import { keyboardRegistry } from "../keyboard-registry"
|
||||
import { abortSession, getSessions, isSessionBusy } from "../../stores/sessions"
|
||||
import { showCommandPalette, hideCommandPalette } from "../../stores/command-palette"
|
||||
import type { Instance } from "../../types/instance"
|
||||
import { getLogger } from "../logger"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
interface UseAppLifecycleOptions {
|
||||
setEscapeInDebounce: (value: boolean) => void
|
||||
@@ -115,9 +118,9 @@ export function useAppLifecycle(options: UseAppLifecycleOptions) {
|
||||
|
||||
try {
|
||||
await abortSession(instance.id, sessionId)
|
||||
console.log("Session aborted successfully")
|
||||
log.info("Session aborted successfully", { instanceId: instance.id, sessionId })
|
||||
} catch (error) {
|
||||
console.error("Failed to abort session:", error)
|
||||
log.error("Failed to abort session", error)
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
||||
@@ -17,6 +17,9 @@ import type { Instance } from "../../types/instance"
|
||||
import type { MessageRecord } from "../../stores/message-v2/types"
|
||||
import { messageStoreBus } from "../../stores/message-v2/bus"
|
||||
import { cleanupBlankSessions } from "../../stores/session-state"
|
||||
import { getLogger } from "../logger"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
export interface UseCommandsOptions {
|
||||
preferences: Accessor<Preferences>
|
||||
@@ -236,15 +239,16 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
modelID: session.model.modelId,
|
||||
},
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
} catch (error) {
|
||||
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"
|
||||
showAlertDialog(`Compact failed: ${message}`, {
|
||||
title: "Compact failed",
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
@@ -322,12 +326,13 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to revert message:", error)
|
||||
log.error("Failed to revert message", error)
|
||||
showAlertDialog("Failed to revert message", {
|
||||
title: "Undo failed",
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
@@ -503,7 +508,7 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
category: "System",
|
||||
keywords: ["/help", "shortcuts", "help"],
|
||||
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?.()
|
||||
if (result instanceof Promise) {
|
||||
void result.catch((error) => {
|
||||
console.error("Command execution failed:", error)
|
||||
log.error("Command execution failed", error)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Command execution failed:", error)
|
||||
log.error("Command execution failed", error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
128
packages/ui/src/lib/logger.ts
Normal file
128
packages/ui/src/lib/logger.ts
Normal 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,
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { marked } from "marked"
|
||||
import { createHighlighter, type Highlighter, bundledLanguages } from "shiki/bundle/full"
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
let highlighter: Highlighter | null = null
|
||||
let highlighterPromise: Promise<Highlighter> | null = null
|
||||
@@ -71,7 +74,7 @@ function triggerLanguageListeners() {
|
||||
try {
|
||||
listener()
|
||||
} catch (error) {
|
||||
console.error("Error in language listener:", error)
|
||||
log.error("Error in language listener", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { runtimeEnv } from "../runtime-env"
|
||||
import { getLogger } from "../logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
export async function restartCli(): Promise<boolean> {
|
||||
try {
|
||||
@@ -20,7 +23,7 @@ export async function restartCli(): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to restart CLI", error)
|
||||
log.error("Failed to restart CLI", error)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { NativeDialogOptions } from "../native-functions"
|
||||
import { getLogger } from "../../logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
interface ElectronDialogResult {
|
||||
canceled?: boolean
|
||||
@@ -33,7 +36,7 @@ export async function openElectronNativeDialog(options: NativeDialogOptions): Pr
|
||||
const result = await api.openDialog(options)
|
||||
return coerceFirstPath(result)
|
||||
} catch (error) {
|
||||
console.error("[native] electron dialog failed", error)
|
||||
log.error("[native] electron dialog failed", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { NativeDialogOptions } from "../native-functions"
|
||||
import { getLogger } from "../../logger"
|
||||
const log = getLogger("actions")
|
||||
|
||||
|
||||
interface TauriDialogModule {
|
||||
open?: (
|
||||
@@ -49,7 +52,7 @@ export async function openTauriNativeDialog(options: NativeDialogOptions): Promi
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error("[native] tauri dialog failed", error)
|
||||
log.error("[native] tauri dialog failed", error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
export type HostRuntime = "electron" | "tauri" | "web"
|
||||
export type PlatformKind = "desktop" | "mobile"
|
||||
|
||||
@@ -61,6 +63,8 @@ function detectPlatform(): PlatformKind {
|
||||
return "desktop"
|
||||
}
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
let cachedEnv: RuntimeEnvironment | null = null
|
||||
|
||||
export function detectRuntimeEnvironment(): RuntimeEnvironment {
|
||||
@@ -71,9 +75,8 @@ export function detectRuntimeEnvironment(): RuntimeEnvironment {
|
||||
host: detectHost(),
|
||||
platform: detectPlatform(),
|
||||
}
|
||||
if (typeof console !== "undefined") {
|
||||
const message = `[runtime] host=${cachedEnv.host} platform=${cachedEnv.platform}`
|
||||
console.info(message)
|
||||
if (typeof window !== "undefined") {
|
||||
log.info(`[runtime] host=${cachedEnv.host} platform=${cachedEnv.platform}`)
|
||||
}
|
||||
return cachedEnv
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { WorkspaceEventPayload, WorkspaceEventType } from "../../../server/src/api-types"
|
||||
import { serverApi } from "./api-client"
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
const RETRY_BASE_DELAY = 1000
|
||||
const RETRY_MAX_DELAY = 10000
|
||||
const SSE_PREFIX = "[SSE]"
|
||||
const log = getLogger("sse")
|
||||
|
||||
function logSse(message: string, context?: Record<string, unknown>) {
|
||||
if (context) {
|
||||
console.log(`${SSE_PREFIX} ${message}`, context)
|
||||
log.info(message, context)
|
||||
return
|
||||
}
|
||||
console.log(`${SSE_PREFIX} ${message}`)
|
||||
log.info(message)
|
||||
}
|
||||
|
||||
class ServerEvents {
|
||||
|
||||
@@ -20,6 +20,9 @@ import type {
|
||||
InstanceStreamStatus,
|
||||
WorkspaceEventPayload,
|
||||
} from "../../../server/src/api-types"
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
const log = getLogger("sse")
|
||||
|
||||
type InstanceEventPayload = Extract<WorkspaceEventPayload, { type: "instance.event" }>
|
||||
type InstanceStatusPayload = Extract<WorkspaceEventPayload, { type: "instance.eventStatus" }>
|
||||
@@ -80,11 +83,11 @@ class SSEManager {
|
||||
|
||||
private handleEvent(instanceId: string, event: SSEEvent | InstanceStreamEvent): void {
|
||||
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
|
||||
}
|
||||
|
||||
console.log("[SSE] Received event:", event.type, event)
|
||||
log.info("Received event", { type: event.type, event })
|
||||
|
||||
switch (event.type) {
|
||||
case "message.updated":
|
||||
@@ -124,7 +127,7 @@ class SSEManager {
|
||||
this.onLspUpdated?.(instanceId, event as EventLspUpdated)
|
||||
break
|
||||
default:
|
||||
console.warn("[SSE] Unknown event type:", event.type)
|
||||
log.warn("Unknown SSE event type", { type: event.type })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { AppConfig, InstanceData } from "../../../server/src/api-types"
|
||||
import { serverApi } from "./api-client"
|
||||
import { serverEvents } from "./server-events"
|
||||
import { getLogger } from "./logger"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
export type ConfigData = AppConfig
|
||||
|
||||
@@ -19,7 +22,7 @@ function isDeepEqual(a: unknown, b: unknown): boolean {
|
||||
try {
|
||||
return JSON.stringify(a) === JSON.stringify(b)
|
||||
} catch (error) {
|
||||
console.warn("Failed to compare config objects", error)
|
||||
log.warn("Failed to compare config objects", error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
try {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
} catch (error) {
|
||||
console.warn('Failed to apply initial theme', error)
|
||||
const rawConsole = globalThis?.["console"]
|
||||
rawConsole?.warn?.('Failed to apply initial theme', error)
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
try {
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
} catch (error) {
|
||||
console.warn('Failed to apply initial theme', error)
|
||||
const rawConsole = globalThis?.["console"]
|
||||
rawConsole?.warn?.('Failed to apply initial theme', error)
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user