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",
|
"@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",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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([])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 { 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user