plugins: Add OSC 99 desktop notification plugin for OpenCode

This commit is contained in:
Jeena 2026-04-03 09:36:59 +00:00
parent a3db357b6f
commit eef9a66d2d

View file

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