summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-29 20:54:33 -0600
committerAdam <[email protected]>2025-12-30 04:57:35 -0600
commitfa1ac7bc957f3b8b13b97e85eac40729f16b510b (patch)
tree0e65db62401b8057019222a8d05f6b9de452bffb /packages/app/src/context
parentc82ab649e2307237b480a94dbb7df6d77a8bf71a (diff)
downloadopencode-fa1ac7bc957f3b8b13b97e85eac40729f16b510b.tar.gz
opencode-fa1ac7bc957f3b8b13b97e85eac40729f16b510b.zip
feat(desktop): system notifications
Diffstat (limited to 'packages/app/src/context')
-rw-r--r--packages/app/src/context/notification.tsx25
-rw-r--r--packages/app/src/context/platform.tsx3
2 files changed, 19 insertions, 9 deletions
diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx
index 2b258ebd6..33d72d4f2 100644
--- a/packages/app/src/context/notification.tsx
+++ b/packages/app/src/context/notification.tsx
@@ -2,7 +2,9 @@ import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useGlobalSDK } from "./global-sdk"
import { useGlobalSync } from "./global-sync"
+import { usePlatform } from "@/context/platform"
import { Binary } from "@opencode-ai/util/binary"
+import { base64Encode } from "@opencode-ai/util/encode"
import { EventSessionError } from "@opencode-ai/sdk/v2"
import { makeAudioPlayer } from "@solid-primitives/audio"
import idleSound from "@opencode-ai/ui/audio/staplebops-01.aac"
@@ -43,6 +45,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const globalSDK = useGlobalSDK()
const globalSync = useGlobalSync()
+ const platform = usePlatform()
const [store, setStore, _, ready] = persisted(
"notification.v1",
@@ -64,8 +67,8 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
const sessionID = event.properties.sessionID
const [syncStore] = globalSync.child(directory)
const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
- const isChild = match.found && syncStore.session[match.index].parentID
- if (isChild) break
+ const session = match.found ? syncStore.session[match.index] : undefined
+ if (session?.parentID) break
try {
idlePlayer?.play()
} catch {}
@@ -74,25 +77,29 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
type: "turn-complete",
session: sessionID,
})
+ const href = `/${base64Encode(directory)}/session/${sessionID}`
+ void platform.notify("Response ready", session?.title ?? sessionID, href)
break
}
case "session.error": {
const sessionID = event.properties.sessionID
- if (sessionID) {
- const [syncStore] = globalSync.child(directory)
- const match = Binary.search(syncStore.session, sessionID, (s) => s.id)
- const isChild = match.found && syncStore.session[match.index].parentID
- if (isChild) break
- }
+ const [syncStore] = globalSync.child(directory)
+ const match = sessionID ? Binary.search(syncStore.session, sessionID, (s) => s.id) : undefined
+ const session = sessionID && match?.found ? syncStore.session[match.index] : undefined
+ if (session?.parentID) break
try {
errorPlayer?.play()
} catch {}
+ const error = "error" in event.properties ? event.properties.error : undefined
setStore("list", store.list.length, {
...base,
type: "error",
session: sessionID ?? "global",
- error: "error" in event.properties ? event.properties.error : undefined,
+ error,
})
+ const description = session?.title ?? (typeof error === "string" ? error : "An error occurred")
+ const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
+ void platform.notify("Session error", description, href)
break
}
}
diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx
index 2b710e6f2..85afd1e1f 100644
--- a/packages/app/src/context/platform.tsx
+++ b/packages/app/src/context/platform.tsx
@@ -14,6 +14,9 @@ export type Platform = {
/** Restart the app */
restart(): Promise<void>
+ /** Send a system notification (optional deep link) */
+ notify(title: string, description?: string, href?: string): Promise<void>
+
/** Open native directory picker dialog (Tauri only) */
openDirectoryPickerDialog?(opts?: { title?: string; multiple?: boolean }): Promise<string | string[] | null>