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

bluebell.js
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

  1. Complete a short task without subtasks. Confirm that recent notifications shows one OpenCode event.
  2. Run a workflow that creates child sessions. Confirm that those child sessions do not produce notifications.
  3. Start a second turn in the same session. Confirm that the last assistant message ID changes.
  4. After verification, remove --local and check Mac remote delivery and real devices.

Sources

OpenCode official references