// opencode-plugin/index.ts — Consciousness integration for OpenCode. // // Bridges OpenCode events to the consciousness system: // - chat.message → forwards to poc-hook-opencode, appends output as text part // - tool.execute.after → signals response activity // - event → tracks session lifecycle (idle, compacted, etc.) // - shell.env → injects POC_SESSION_ID into subprocesses // // Install: copy this directory to your project's `plugin/` or `plugins/` dir, // or add to opencode.json: // "plugin": ["/home/kent/poc/consciousness-claude/opencode-plugin"] import type { Plugin, Hooks } from "@opencode-ai/plugin" import path from "path" import { $ } from "bun" import { $ } from "bun" // Find the poc-hook-opencode binary function findHookBinary(): string { const candidates = [ path.join(process.env.HOME || "", ".cargo/bin/poc-hook-opencode"), path.join(process.env.HOME || "", "poc/consciousness-claude/target/debug/poc-hook-opencode"), path.join(process.env.HOME || "", "poc/consciousness-claude/target/release/poc-hook-opencode"), ] for (const c of candidates) { try { const stat = Bun.file(c).statSync() if (stat?.isFile()) return c } catch {} } return "poc-hook-opencode" } const HOOK_BINARY = findHookBinary() // Generate a unique part ID (opencode uses ulid-like ascending IDs) let partCounter = 0 function nextPartId(): string { partCounter += 1 return `poc_part_${Date.now()}_${partCounter}` } export const ConsciousnessPlugin: Plugin = async (ctx) => { const hooks: Hooks = {} // Main hook: forward user messages to consciousness, inject context hooks["chat.message"] = async (input, output) => { const hookInput = JSON.stringify({ session_id: input.sessionID, hook_event: "UserPromptSubmit", }) try { const proc = Bun.spawn([HOOK_BINARY], { stdin: hookInput, stdout: "pipe", stderr: "pipe", }) const [stdout, stderr] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]) await proc.exited if (stdout && stdout.trim()) { // Append as a text part — must match MessageV2.TextPart schema: // { id, sessionID, messageID, type: "text", text, time?, synthetic?, ignored? } output.parts.push({ id: nextPartId(), sessionID: input.sessionID, messageID: output.message.id, type: "text", text: stdout, synthetic: true, }) } if (stderr && stderr.trim()) { console.error("[consciousness] hook stderr:", stderr.slice(0, 500)) } } catch (e) { console.error("[consciousness] hook error:", e) } } // Signal response after tool use hooks["tool.execute.after"] = async () => { try { await $`poc-daemon response`.quiet() } catch { // Daemon might not be running } } // Inject POC_SESSION_ID into all shell commands hooks["shell.env"] = async (input, output) => { if (input.sessionID) { output.env["POC_SESSION_ID"] = input.sessionID } } // Track session events hooks["event"] = async ({ event }) => { if (event.type === "session.compacted") { // Compaction detected — next hook invocation will detect via SQLite } if (event.type === "session.idle") { // Session went idle } } return hooks }