Skip to content

Instantly share code, notes, and snippets.

@rstacruz
Last active August 6, 2025 16:52
Show Gist options
  • Save rstacruz/024a1d798c315c7f1c4607c7cf433a4e to your computer and use it in GitHub Desktop.
Save rstacruz/024a1d798c315c7f1c4607c7cf433a4e to your computer and use it in GitHub Desktop.

OpenCode notification plugin

Shows desktop notifications with a summary of what the agent last said:

Screenshot

  • Place AGENTS.md in ~/.config/opencode/AGENTS.md
  • Place notification.ts in ~/.config/opencode/plugin/notification.ts

Also see https://opencode.ai/docs/plugins/

Idle notifications

At the end of responses, when user input is needed:

  • Reply with a paragraph in the format of *Summary:* <summary>, where 'summary' is a summary of your response.
  • Avoid summaries like "awaiting user input" or "waiting for your response." Instead, summarise your response.
  • Limit summary to 10 words.
  • If a question was answered, summarise the answer. Example: *Summary:* Yes, Bun supports TypeScript..
/*
* Notification idle plugin
*/
export const NotificationPlugin = async ({ client, $ }) => {
let lastMessage: { messageID: string | null; text: string | null } = {
messageID: null,
text: null,
};
return {
event: async ({ event }) => {
// Save message text for idle summary
if (event.type === "message.part.updated") {
if (event.properties.part.type === "text") {
const { messageID, text } = event.properties.part;
lastMessage = { messageID, text };
}
}
if (event.type === "session.idle") {
const summary = getIdleSummary(lastMessage?.text) ?? "Idle";
if (process.platform === "darwin") {
await $`osascript -e 'do shell script "afplay /System/Library/Sounds/Frog.aiff"'`;
await $`osascript -e 'display notification ${JSON.stringify(summary)} with title "opencode"'`;
} else {
await $`canberra-gtk-play --id=message`;
await $`notify-send 'opencode' '${summary.replace(/'/g, "'\\''")}'`;
}
}
},
};
};
/**
* Extract a last `*Summary:* ...` line at the end of the text
*/
function getIdleSummary(text: string | null) {
if (!text) return;
const idleMatch = text.match(/[_\*]Summary:[_\*]? (.*)[_\*]?$/m);
if (idleMatch && idleMatch[1]) {
return idleMatch[1].trim();
}
if (text.length > 80) {
return text.slice(0, 80) + "...";
}
return text;
}
// Also see:
// https://opencode.ai/docs/plugins/
// https://github.com/sst/opencode/blob/857a3cd52221b820dbfd34dae8ff1d42bbb8c108/packages/sdk/js/src/gen/types.gen.ts#L1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment