chore: add Entire agent config (#402)

Entire-Checkpoint: 69bf47d06770
This commit is contained in:
KM Koushik
2026-05-18 10:38:23 +10:00
committed by GitHub
parent 04d0f4b123
commit dad2941971
9 changed files with 439 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
// Entire CLI plugin for OpenCode
// Auto-generated by `entire enable --agent opencode`
// Do not edit manually — changes will be overwritten on next install.
// Requires Bun runtime (used by OpenCode's plugin system for loading ESM plugins).
import type { Plugin } from "@opencode-ai/plugin"
export const EntirePlugin: Plugin = async ({ directory }) => {
const ENTIRE_CMD = 'entire'
// Track seen user messages to fire turn-start only once per message
const seenUserMessages = new Set<string>()
// Track current session ID for message events (which don't include sessionID)
let currentSessionID: string | null = null
// Track the model used by the most recent assistant message
let currentModel: string | null = null
// In-memory store for message metadata (role, tokens, etc.)
const messageStore = new Map<string, any>()
/**
* Build the shell command for a hook invocation.
* Uses sh -c so that shell command substitution in ENTIRE_CMD
* (e.g., $(git rev-parse --show-toplevel) for local-dev) is interpreted.
*/
function hookCmd(hookName: string): string[] {
if (ENTIRE_CMD !== "entire") {
return ["sh", "-c", `${ENTIRE_CMD} hooks opencode ${hookName}`]
}
return ["sh", "-c", `if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks opencode ${hookName}`]
}
/**
* Pipe JSON payload to an entire hooks command (async).
* Errors are logged but never thrown — plugin failures must not crash OpenCode.
*/
async function callHook(hookName: string, payload: Record<string, unknown>) {
try {
const json = JSON.stringify(payload)
const proc = Bun.spawn(hookCmd(hookName), {
cwd: directory,
stdin: new Blob([json + "\n"]),
stdout: "ignore",
stderr: "ignore",
})
await proc.exited
} catch {
// Silently ignore — plugin failures must not crash OpenCode
}
}
/**
* Synchronous variant for hooks that must complete before subsequent agent work
* or process exit. `turn-start` must finish initializing session state before a
* fast mid-turn commit can hit git hooks, and `turn-end` / `session-end` must
* finish before `opencode run` tears down its event loop.
*/
function callHookSync(hookName: string, payload: Record<string, unknown>) {
try {
const json = JSON.stringify(payload)
Bun.spawnSync(hookCmd(hookName), {
cwd: directory,
stdin: new TextEncoder().encode(json + "\n"),
stdout: "ignore",
stderr: "ignore",
})
} catch {
// Silently ignore — plugin failures must not crash OpenCode
}
}
function resetSessionTracking(sessionID: string) {
if (currentSessionID === sessionID) {
return false
}
seenUserMessages.clear()
messageStore.clear()
currentModel = null
currentSessionID = sessionID
return true
}
return {
event: async ({ event }) => {
try {
switch (event.type) {
case "session.created": {
const session = (event as any).properties?.info
if (!session?.id) break
// Reset per-session tracking state when switching sessions.
if (resetSessionTracking(session.id)) {
const json = JSON.stringify({
session_id: session.id,
})
const proc = Bun.spawn(hookCmd("session-start"), {
cwd: directory,
stdin: new Blob([json + "\n"]),
stdout: "ignore",
stderr: "ignore",
})
await proc.exited
}
break
}
case "message.updated": {
const msg = (event as any).properties?.info
if (!msg) break
if (msg.sessionID && resetSessionTracking(msg.sessionID)) {
callHookSync("session-start", {
session_id: msg.sessionID,
})
}
// Store message metadata (role, time, tokens, etc.)
messageStore.set(msg.id, msg)
// Track model from assistant messages
if (msg.role === "assistant" && msg.modelID) {
currentModel = msg.modelID
}
// Fallback: some opencode run flows commit before any message.part.updated
// event is delivered for the user's prompt. Start the turn from the
// user message itself so git hooks see an ACTIVE session in time.
if (msg.role === "user" && !seenUserMessages.has(msg.id)) {
seenUserMessages.add(msg.id)
const sessionID = msg.sessionID ?? currentSessionID
if (sessionID) {
callHookSync("turn-start", {
session_id: sessionID,
prompt: "",
model: currentModel ?? "",
})
}
}
break
}
case "message.part.updated": {
const part = (event as any).properties?.part
if (!part?.messageID) break
// Fire turn-start on the first text part of a new user message
const msg = messageStore.get(part.messageID)
if (msg?.role === "user" && part.type === "text" && !seenUserMessages.has(msg.id)) {
seenUserMessages.add(msg.id)
const sessionID = msg.sessionID ?? currentSessionID
if (sessionID) {
callHookSync("turn-start", {
session_id: sessionID,
prompt: part.text ?? "",
model: currentModel ?? "",
})
}
}
break
}
case "session.status": {
// session.status fires in both TUI and non-interactive (run) mode.
// session.idle is deprecated and not reliably emitted in run mode.
const props = (event as any).properties
if (props?.status?.type !== "idle") break
const sessionID = props?.sessionID ?? currentSessionID
if (!sessionID) break
// Use sync variant: `opencode run` exits on the same idle event,
// so an async hook would be killed before completing.
callHookSync("turn-end", {
session_id: sessionID,
model: currentModel ?? "",
})
break
}
case "session.compacted": {
const sessionID = (event as any).properties?.sessionID
if (!sessionID) break
await callHook("compaction", {
session_id: sessionID,
})
break
}
case "session.deleted": {
const session = (event as any).properties?.info
if (!session?.id) break
seenUserMessages.clear()
messageStore.clear()
currentSessionID = null
// Use sync variant: session-end may fire during shutdown.
callHookSync("session-end", {
session_id: session.id,
})
break
}
case "server.instance.disposed": {
// Fires when OpenCode shuts down (TUI close or `opencode run` exit).
// session.deleted only fires on explicit user deletion, not on quit,
// so this is the only reliable way to end sessions on exit.
if (!currentSessionID) break
const sessionID = currentSessionID
seenUserMessages.clear()
messageStore.clear()
currentSessionID = null
// Use sync variant: this is the last event before process exit.
callHookSync("session-end", {
session_id: sessionID,
})
break
}
}
} catch {
// Silently ignore — plugin failures must not crash OpenCode
}
},
}
}