diff --git a/plugins/opencode/osc99-notify.js b/plugins/opencode/osc99-notify.js new file mode 100644 index 0000000..e6fa7e3 --- /dev/null +++ b/plugins/opencode/osc99-notify.js @@ -0,0 +1,70 @@ +// OSC 99 Desktop Notifications for OpenCode +// +// Sends desktop notifications via the OSC 99 escape sequence protocol +// when OpenCode needs your attention. Notifications are only shown when +// the terminal window is unfocused (o=unfocused). +// +// Tested with: Kitty terminal +// May also work with other terminals that support the OSC 99 protocol. +// +// Features: +// - Notifies when a session goes idle (waiting for your input) +// - Notifies when a question is asked (e.g. multiple choice) +// - Notifies when a permission prompt is raised +// - Notifies when a session error occurs +// - Includes the project directory name in the notification title +// - Suppresses notifications from subagent sessions (e.g. Explore Task) +// - Suppresses notifications when the terminal window is focused +// - Uses distinct sounds per notification type (system, question, error) +// +// Installation: +// Copy this file to ~/.config/opencode/plugins/osc99-notify.js +// +// Notification types: +// Idle: title -> OpenCode (project-name) +// body -> Waiting for your input +// sound -> system +// +// Question: title -> OpenCode (project-name) +// body -> Has a question for you +// sound -> question +// +// Permission: title -> OpenCode (project-name) +// body -> Needs your permission +// sound -> question +// +// Error: title -> OpenCode (project-name) +// body -> Encountered an error +// sound -> error + +export const OSC99Notify = async ({ client }) => { + const path = require('path') + const project = path.basename(process.cwd()) + let id = 0 + + const notify = (icon, message, sound = 'system') => { + id++ + process.stderr.write(`\x1b]99;i=${id}:d=0:s=${sound}:o=unfocused;${icon} OpenCode (${project})\x1b\\`) + process.stderr.write(`\x1b]99;i=${id}:p=body;${message}\x1b\\`) + } + + return { + event: async ({ event }) => { + if (event.type === "message.part.updated" && event.properties.part.type === "tool" && event.properties.part.tool === "question" && event.properties.part.state?.status === "pending") + notify('❓', 'Has a question for you', 'question') + + if (event.type === "session.idle") { + const session = await client.session.get({ path: { id: event.properties.sessionID } }) + if (!session.data?.parentID) + notify('🤖', 'Waiting for your input') + } + if (event.type === "session.error") { + const session = await client.session.get({ path: { id: event.properties.sessionID } }) + if (!session.data?.parentID) + notify('🚨', 'Encountered an error', 'error') + } + if (event.type === "permission.updated") + notify('🔐', 'Needs your permission', 'question') + }, + } +}