// 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') }, } }