Coding tools · 04
OpenCode
Use an OpenCode plugin to listen for session.idle, read the main session’s last assistant message ID, and use that as a stable ID for the turn.
Current official events
session.idle is a completion candidate
An OpenCode plugin’s event callback can receive session.idle. This means a session has become idle; it does not guarantee that it is the top-level user task you want to track. Complex workflows may include child sessions.
Step 1
Create a bluebell plugin
export const BluebellPlugin = async ({ client, directory }) => {
const cli = "/Applications/bluebellMac.app/Contents/Helpers/bluebellctl"
return {
event: async ({ event }) => {
if (event.type !== "session.idle") return
const sessionID = event.properties.sessionID
const sessionResult = await client.session.get({ path: { id: sessionID } })
const session = sessionResult.data
if (!session || session.parentID) return
const messageResult = await client.session.messages({ path: { id: sessionID } })
const messages = messageResult.data || []
const lastAssistant = [...messages].reverse().find(
(item) => item.info && item.info.role === "assistant"
)
const messageID = lastAssistant && lastAssistant.info.id
if (!messageID) return
const workspace = directory.split("/").filter(Boolean).pop() || "OpenCode"
const process = Bun.spawn([
cli, "emit",
"--source", "OpenCode",
"--workspace-name", workspace,
"--session-id", sessionID,
"--turn-id", messageID,
"--dedupe-key", "opencode:" + sessionID + ":" + messageID,
"--local",
], { stdout: "ignore", stderr: "ignore" })
await process.exited
},
}
} The example uses the OpenCode SDK to read session and message lists. It sends only IDs and the workspace display name, never message bodies, to bluebell. Keep --local.
Step 2
Choose project-level or global installation
| Location | Scope |
|---|---|
.opencode/plugins/bluebell.js | Only the current project; a good place for initial testing. |
~/.config/opencode/plugins/bluebell.js | All local OpenCode projects. |
Do not install the same plugin at both project and global levels: one idle event could invoke the command twice. Even if dedupeKey merges the events, the extra calls are unnecessary.
Step 3
Test root sessions and subtasks separately
- Complete a short task without subtasks. Confirm that recent notifications shows one OpenCode event.
- Run a workflow that creates child sessions. Confirm that those child sessions do not produce notifications.
- Start a second turn in the same session. Confirm that the last assistant message ID changes.
- After verification, remove
--localand check Mac remote delivery and real devices.
Sources
OpenCode official references
- OpenCode plugins Plugin locations, event callbacks, and session.idle
- OpenCode SDK session and messages APIs