@khalilgharbaoui/opencode-claude-code-plugin 0.53.5 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tui.ts","../src/subagent-sidebar.ts","../src/spawn-record-store.ts"],"sourcesContent":["/**\n * The TUI half of the package: a `Subagents` section at the bottom of\n * opencode's session sidebar listing the current session's subagents, running\n * first, each with a spinner (or ✓ / ✗), its agent type, its task description,\n * a `bg` marker for background ones, and a muted line saying what it really\n * runs as (`sonnet-5.5 · high · alpha`). Clicking a row opens that subagent's\n * session; opencode's own `up` key goes back to the parent.\n *\n * Always on: there is no option. One module serves both majors (h #g234):\n *\n * - opencode 1.x loads TUI plugins from `tui.json`'s `plugin` list, never\n * from `opencode.json`'s, resolves a package to `exports[\"./tui\"]`, and\n * wants a default export `{ id, tui }` with no `server`.\n * - opencode 2.x resolves the SAME `plugins` entry it already has\n * (`<repo>/dist`) to `<dir>/tui`, and wants `{ id, setup }`. 1.x ignores a\n * `setup` key and 2.x a `tui` key, so the default export carries both.\n *\n * Everything that decides WHAT to show lives in `src/subagent-sidebar.ts`\n * (pure, tested). Each major gets a thin adapter that gathers that module's\n * inputs from its own TUI state and supplies its own theme colours; the\n * section itself is drawn by one renderer. Nothing in here may throw into the\n * host or log: on the TUI's own thread stderr is the screen (h #g193), so every\n * failure degrades to drawing less.\n */\nimport { RGBA } from \"@opentui/core\"\nimport { createElement, createTextNode, effect, insert, insertNode, setProp } from \"@opentui/solid\"\nimport { createMemo, createSignal, onCleanup } from \"solid-js\"\nimport {\n SIDEBAR_ROW_WIDTH,\n SPINNER_FRAMES,\n collapsedSummary,\n completionsIn,\n deriveSubagentRows,\n layoutRow,\n partChildID,\n type ChildRunState,\n type ChildSessionInput,\n type CompletionInput,\n type Segment,\n type SidebarInput,\n type SubagentRow,\n type TaskPartInput,\n} from \"./subagent-sidebar.js\"\nimport { createSpawnRecordReader, type SpawnRecord } from \"./spawn-record-store.js\"\n\nexport const SUBAGENTS_TUI_PLUGIN_ID = \"opencode-claude-code-plugin.subagents\"\n\n/** After opencode 1.x's own sidebar sections (files is 500). */\nconst SECTION_ORDER = 900\nconst SPINNER_INTERVAL_MS = 100\n/** How often the spawn records and the finished rows' fade are rechecked. */\nconst REFRESH_INTERVAL_MS = 1500\n\n/** The tool names a subagent dispatch has: `task` on 1.x, `subagent` on 2.x. */\nconst TASK_TOOLS = new Set([\"task\", \"subagent\"])\n\ntype Color = RGBA\n\n/** The colours the section paints with, whatever theme shape the host has. */\nexport interface SidebarColors {\n text: Color\n muted: Color\n spinner: Color\n done: Color\n error: Color\n hover: Color\n}\n\n/** What one major has to supply; the renderer below does everything else. */\ninterface SidebarHost {\n colors(): SidebarColors\n /** The inputs for one parent session, minus the clock and the records. */\n gather(parentID: string): Pick<SidebarInput, \"children\" | \"parts\" | \"completions\" | \"status\" | \"stoppedAt\">\n navigate(sessionID: string): void\n}\n\nfunction attempt<T>(fn: () => T, fallback: T): T {\n try {\n return fn()\n } catch {\n return fallback\n }\n}\n\n/**\n * `a` moved `weight` of the way to `b`, so a finished glyph reads as muted.\n * Works in whatever scale the host's RGBA uses, because it only ever hands\n * the class back values it read from the same class.\n */\nfunction mix(a: Color, b: Color, weight: number): Color {\n return attempt(\n () =>\n RGBA.fromValues(\n a.r + (b.r - a.r) * weight,\n a.g + (b.g - a.g) * weight,\n a.b + (b.b - a.b) * weight,\n a.a,\n ),\n a,\n )\n}\n\nfunction roleColor(colors: SidebarColors, role: Segment[\"role\"]): Color {\n switch (role) {\n case \"spinner\":\n return colors.spinner\n case \"done\":\n return colors.done\n case \"error\":\n return colors.error\n case \"agent\":\n case \"description\":\n return colors.text\n default:\n return colors.muted\n }\n}\n\n/**\n * The section, its clocks and the spawn-record reader, for one host. Returns\n * the slot renderer and a disposer.\n */\nfunction createSidebar(host: SidebarHost) {\n const [frame, setFrame] = createSignal(0)\n const [now, setNow] = createSignal(Date.now())\n const records = createSpawnRecordReader()\n let recordMap: Map<string, SpawnRecord> = attempt(() => records.read(), new Map())\n const [recordVersion, setRecordVersion] = createSignal(0)\n\n // One spinner clock and one refresh clock for every mounted section, each\n // running only while something needs it: nothing ticks while no subagent is\n // listed, and the spinner stops the moment the last one finishes.\n const sectionsRunning = new Set<symbol>()\n const sectionsShowing = new Set<symbol>()\n let spinner: ReturnType<typeof setInterval> | undefined\n let refresh: ReturnType<typeof setInterval> | undefined\n const syncClocks = () => {\n if (sectionsRunning.size > 0 && !spinner) {\n spinner = setInterval(() => setFrame((value) => (value + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS)\n } else if (sectionsRunning.size === 0 && spinner) {\n clearInterval(spinner)\n spinner = undefined\n }\n if (sectionsShowing.size > 0 && !refresh) {\n refresh = setInterval(() => {\n const next = attempt(() => records.read(), recordMap)\n if (next !== recordMap) {\n recordMap = next\n setRecordVersion((value) => value + 1)\n }\n setNow(Date.now())\n }, REFRESH_INTERVAL_MS)\n } else if (sectionsShowing.size === 0 && refresh) {\n clearInterval(refresh)\n refresh = undefined\n }\n }\n\n const deriveRows = (parentID: string): SubagentRow[] => {\n recordVersion()\n return deriveSubagentRows({\n ...host.gather(parentID),\n record: (id) => recordMap.get(id),\n now: now(),\n })\n }\n\n const buildRow = (row: SubagentRow): OpentuiNode => {\n const box = createElement(\"box\")\n const layout = layoutRow(row, SIDEBAR_ROW_WIDTH)\n const first = createElement(\"text\")\n setProp(first, \"wrapMode\", \"none\")\n layout.first.forEach((segment, index) => {\n const span = createElement(\"span\")\n if (index === 0 && row.state === \"running\") {\n insert(span, () => SPINNER_FRAMES[frame() % SPINNER_FRAMES.length])\n } else {\n insertNode(span, createTextNode(segment.text))\n }\n effect(() => setProp(span, \"style\", { fg: roleColor(host.colors(), segment.role) }))\n insertNode(first, span)\n })\n insertNode(box, first)\n for (const segment of layout.second) {\n const line = createElement(\"text\")\n insertNode(line, createTextNode(segment.text))\n setProp(line, \"wrapMode\", \"none\")\n effect(() => setProp(line, \"fg\", roleColor(host.colors(), segment.role)))\n insertNode(box, line)\n }\n if (row.sessionID) {\n const sessionID = row.sessionID\n setProp(box, \"onMouseDown\", () => attempt(() => host.navigate(sessionID), undefined))\n setProp(box, \"onMouseOver\", () =>\n attempt(() => setProp(box, \"backgroundColor\", host.colors().hover), undefined),\n )\n setProp(box, \"onMouseOut\", () =>\n attempt(() => setProp(box, \"backgroundColor\", \"transparent\"), undefined),\n )\n }\n return box\n }\n\n const buildSection = (\n list: SubagentRow[],\n expanded: () => boolean,\n setExpanded: (next: (value: boolean) => boolean) => boolean,\n ): OpentuiNode => {\n const section = createElement(\"box\")\n // Heading, styled like opencode 1.x's own MCP and Todo sections: a bold\n // title, and with more than two rows a caret that folds the list away\n // behind a one-line summary.\n const header = createElement(\"box\")\n setProp(header, \"flexDirection\", \"row\")\n setProp(header, \"gap\", 1)\n setProp(header, \"onMouseDown\", () => list.length > 2 && setExpanded((value) => !value))\n if (list.length > 2) {\n const caret = createElement(\"text\")\n insert(caret, () => (expanded() ? \"▼\" : \"▶\"))\n effect(() => setProp(caret, \"fg\", host.colors().text))\n insertNode(header, caret)\n }\n const title = createElement(\"text\")\n const bold = createElement(\"b\")\n insertNode(bold, createTextNode(\"Subagents\"))\n insertNode(title, bold)\n const summary = createElement(\"span\")\n insert(summary, () => (expanded() ? \"\" : ` (${collapsedSummary(list)})`))\n effect(() => setProp(summary, \"style\", { fg: host.colors().muted }))\n insertNode(title, summary)\n effect(() => setProp(title, \"fg\", host.colors().text))\n insertNode(header, title)\n insertNode(section, header)\n\n const body = createElement(\"box\")\n insert(body, () => (expanded() || list.length <= 2 ? list.map(buildRow) : null))\n insertNode(section, body)\n return section\n }\n\n const render = (sessionID: () => string | undefined): OpentuiNode => {\n const root = createElement(\"box\")\n const key = Symbol(\"subagents-section\")\n const [expanded, setExpanded] = createSignal(true)\n const rows = createMemo(() => {\n const parentID = sessionID()\n if (!parentID) return [] as SubagentRow[]\n return attempt(() => deriveRows(parentID), [] as SubagentRow[])\n })\n effect(() => {\n const list = rows()\n if (list.some((row) => row.state === \"running\")) sectionsRunning.add(key)\n else sectionsRunning.delete(key)\n if (list.length > 0) sectionsShowing.add(key)\n else sectionsShowing.delete(key)\n syncClocks()\n })\n onCleanup(() => {\n sectionsRunning.delete(key)\n sectionsShowing.delete(key)\n syncClocks()\n })\n insert(root, () => {\n const list = rows()\n if (list.length === 0) return null\n return attempt(() => buildSection(list, expanded, setExpanded), null)\n })\n return root\n }\n\n const dispose = () => {\n sectionsRunning.clear()\n sectionsShowing.clear()\n syncClocks()\n }\n return { render, dispose }\n}\n\n// ---------------------------------------------------------------------------\n// opencode 1.x\n// ---------------------------------------------------------------------------\n\n// The structural slice of opencode 1.x's `TuiPluginApi` this module reads.\n// Hand-written, like `src/opencode-types.ts`, so nothing is imported from\n// `@opencode-ai/plugin`.\ninterface V1SessionInfo {\n id: string\n parentID?: string\n title?: string\n agent?: string\n time?: { created?: number; updated?: number }\n}\ninterface V1Part {\n type: string\n tool?: string\n synthetic?: boolean\n text?: string\n state?: {\n status?: string\n input?: Record<string, unknown>\n metadata?: Record<string, unknown>\n title?: string\n time?: { start?: number; end?: number }\n }\n}\ninterface V1Api {\n state: {\n session: {\n get(sessionID: string): V1SessionInfo | undefined\n messages(sessionID: string): ReadonlyArray<{ id: string; role: string; time?: { created?: number } }> | undefined\n status(sessionID: string): { type?: string } | undefined\n }\n part(messageID: string): ReadonlyArray<V1Part> | undefined\n }\n theme: {\n current: { text: Color; textMuted: Color; accent: Color; success: Color; error: Color; backgroundElement: Color }\n }\n route: { navigate(name: string, params?: Record<string, unknown>): void }\n event: { on(type: string, handler: (event: { properties?: any }) => void): () => void }\n client?: { session?: { children?: (input: { sessionID: string }) => Promise<{ data?: V1SessionInfo[] }> } }\n slots: { register(plugin: { order?: number; slots: Record<string, (ctx: unknown, props: any) => OpentuiNode> }): string }\n lifecycle?: { onDispose(fn: () => void): () => void }\n}\n\nexport function registerV1(api: V1Api): void {\n // Child sessions seen per parent. 1.x has no session list in its TUI state,\n // so they come from its events, from a one-off `children` fetch the first\n // time a parent is shown (a TUI started after the dispatch still lists\n // them), and from finished parts' metadata.\n const childrenByParent = new Map<string, Map<string, ChildSessionInput>>()\n const fetched = new Set<string>()\n const stoppedAt = new Map<string, number>()\n const lastStatus = new Map<string, string>()\n const [version, setVersion] = createSignal(0)\n const bump = () => setVersion((value) => value + 1)\n\n const remember = (info: V1SessionInfo | undefined) => {\n if (!info?.id || !info.parentID) return false\n let children = childrenByParent.get(info.parentID)\n if (!children) {\n children = new Map()\n childrenByParent.set(info.parentID, children)\n }\n children.set(info.id, {\n id: info.id,\n title: info.title,\n agent: info.agent,\n created: info.time?.created,\n updated: info.time?.updated,\n })\n return true\n }\n\n const unsubscribe: Array<() => void> = []\n const listen = (type: string, handler: (properties: any) => void) => {\n const off = attempt(\n () => api.event.on(type, (event) => attempt(() => handler(event?.properties ?? {}), undefined)),\n undefined,\n )\n if (off) unsubscribe.push(off)\n }\n listen(\"session.created\", (properties) => remember(properties.info) && bump())\n listen(\"session.updated\", (properties) => remember(properties.info) && bump())\n listen(\"session.deleted\", (properties) => {\n const info = properties.info as V1SessionInfo | undefined\n if (info?.parentID && childrenByParent.get(info.parentID)?.delete(info.id)) bump()\n })\n listen(\"session.status\", (properties) => {\n const id = properties.sessionID as string | undefined\n const type = properties.status?.type as string | undefined\n if (!id || !type) return\n const previous = lastStatus.get(id)\n lastStatus.set(id, type)\n if (type === \"idle\" && previous && previous !== \"idle\") stoppedAt.set(id, Date.now())\n if (type !== \"idle\") stoppedAt.delete(id)\n })\n\n const fetchChildren = (parentID: string) => {\n if (fetched.has(parentID)) return\n fetched.add(parentID)\n const session = api.client?.session\n if (!session?.children) return\n void Promise.resolve()\n .then(() => session.children!.call(session, { sessionID: parentID }))\n .then((response) => {\n let changed = false\n for (const info of response?.data ?? []) changed = remember(info) || changed\n if (changed) bump()\n })\n .catch(() => undefined)\n }\n\n const host: SidebarHost = {\n colors() {\n const theme = api.theme.current\n return {\n text: theme.text,\n muted: theme.textMuted,\n spinner: theme.accent,\n done: mix(theme.success, theme.textMuted, 0.45),\n error: mix(theme.error, theme.textMuted, 0.35),\n hover: theme.backgroundElement,\n }\n },\n gather(parentID) {\n version()\n fetchChildren(parentID)\n const parts: TaskPartInput[] = []\n const completions: CompletionInput[] = []\n for (const message of api.state.session.messages(parentID) ?? []) {\n const messageParts = api.state.part(message.id) ?? []\n if (message.role === \"assistant\") {\n for (const part of messageParts) {\n if (part.type !== \"tool\" || !TASK_TOOLS.has(part.tool ?? \"\")) continue\n const state = part.state ?? {}\n parts.push({\n status: state.status,\n input: state.input,\n metadata: state.metadata,\n title: state.title,\n start: state.time?.start,\n end: state.time?.end,\n })\n }\n } else if (message.role === \"user\") {\n // A background child's end is a synthetic user part in the parent.\n for (const part of messageParts) {\n if (part.type !== \"text\" || part.synthetic !== true || !part.text) continue\n completions.push(...completionsIn(part.text, message.time?.created))\n }\n }\n }\n const known = new Map(childrenByParent.get(parentID) ?? [])\n for (const part of parts) {\n const id = partChildID(part)\n if (!id || known.has(id)) continue\n const info = attempt(() => api.state.session.get(id), undefined)\n known.set(id, {\n id,\n title: info?.title,\n agent: info?.agent,\n created: info?.time?.created ?? part.start,\n updated: info?.time?.updated,\n })\n }\n return {\n children: [...known.values()],\n parts,\n completions,\n status: (id: string): ChildRunState => {\n const type = attempt(() => api.state.session.status(id)?.type, undefined)\n if (type === \"busy\" || type === \"retry\") return \"busy\"\n if (type === \"idle\") return \"idle\"\n return \"unknown\"\n },\n stoppedAt: (id: string) => stoppedAt.get(id),\n }\n },\n navigate(sessionID) {\n api.route.navigate(\"session\", { sessionID })\n },\n }\n\n const sidebar = createSidebar(host)\n api.slots.register({\n order: SECTION_ORDER,\n slots: {\n sidebar_content(_ctx: unknown, props: { session_id: string }) {\n return sidebar.render(() => props.session_id)\n },\n },\n })\n api.lifecycle?.onDispose(() => {\n for (const off of unsubscribe) attempt(off, undefined)\n sidebar.dispose()\n })\n}\n\n// ---------------------------------------------------------------------------\n// opencode 2.x\n// ---------------------------------------------------------------------------\n\n// The structural slice of opencode 2.x's TUI `Context` this module reads,\n// measured on 2.0.22 (h #g234). Its sessions carry `parentID`, its status is\n// `\"running\" | \"idle\"`, an assistant message holds its tool calls inline as\n// `content[]` items named `subagent` (with `metadata.sessionID` set while the\n// call still runs, unlike 1.x), and a background child's end is a message of\n// type `synthetic` whose text is `<subagent sessionID=\"...\" state=\"...\">`.\ninterface V2Session {\n id: string\n parentID?: string\n title?: string\n agent?: string\n time?: { created?: number; updated?: number }\n}\ninterface V2Message {\n type?: string\n text?: string\n time?: { created?: number }\n content?: Array<{\n type?: string\n name?: string\n time?: { created?: number; completed?: number }\n state?: { status?: string; input?: Record<string, unknown>; metadata?: Record<string, unknown> }\n }>\n}\ntype V2Shade = Record<string, Color>\ninterface V2Context {\n data: {\n session: {\n list(): V2Session[] | undefined\n status(sessionID: string): string\n message: { list(sessionID: string): V2Message[] | undefined }\n }\n }\n theme: {\n text: { base: Color; muted: Color; feedback: { success: V2Shade; error: V2Shade } }\n background: { raised: { base: Color } }\n hue: { accent: V2Shade }\n }\n themeMode?: \"dark\" | \"light\"\n ui: {\n router: { navigate(destination: { type: \"session\"; sessionID: string }): void }\n slot(claim: { append: string; render: (input: { sessionID: string }) => OpentuiNode }): () => void\n }\n}\n\nexport function setupV2(context: V2Context): () => void {\n const host: SidebarHost = {\n colors() {\n const theme = context.theme\n const muted = theme.text.muted\n // The accent hue at the shade that reads on this mode's background.\n const accent = theme.hue.accent[context.themeMode === \"light\" ? \"500\" : \"200\"] ?? theme.text.base\n return {\n text: theme.text.base,\n muted,\n spinner: accent,\n done: mix(theme.text.feedback.success.muted ?? theme.text.feedback.success.base, muted, 0.45),\n error: mix(theme.text.feedback.error.muted ?? theme.text.feedback.error.base, muted, 0.35),\n hover: theme.background.raised.base,\n }\n },\n gather(parentID) {\n const children: ChildSessionInput[] = (context.data.session.list() ?? [])\n .filter((session) => session.parentID === parentID)\n .map((session) => ({\n id: session.id,\n title: session.title,\n agent: session.agent,\n created: session.time?.created,\n updated: session.time?.updated,\n }))\n const parts: TaskPartInput[] = []\n const completions: CompletionInput[] = []\n for (const message of context.data.session.message.list(parentID) ?? []) {\n if (message.type === \"assistant\") {\n for (const item of message.content ?? []) {\n if (item.type !== \"tool\" || !TASK_TOOLS.has(item.name ?? \"\")) continue\n parts.push({\n status: item.state?.status,\n input: item.state?.input,\n metadata: item.state?.metadata,\n start: item.time?.created,\n end: item.time?.completed,\n })\n }\n } else if (message.type === \"synthetic\" && message.text) {\n completions.push(...completionsIn(message.text, message.time?.created))\n }\n }\n return {\n children,\n parts,\n completions,\n status: (id: string): ChildRunState => {\n const status = attempt(() => context.data.session.status(id), undefined)\n if (status === \"running\") return \"busy\"\n if (status === \"idle\") return \"idle\"\n return \"unknown\"\n },\n stoppedAt: () => undefined,\n }\n },\n navigate(sessionID) {\n context.ui.router.navigate({ type: \"session\", sessionID })\n },\n }\n\n const sidebar = createSidebar(host)\n const release = attempt(\n () =>\n context.ui.slot({\n append: \"sidebar.content\",\n render: (input) => sidebar.render(() => input.sessionID),\n }),\n undefined,\n )\n return () => {\n if (release) attempt(release, undefined)\n sidebar.dispose()\n }\n}\n\nconst tui = async (api: V1Api): Promise<void> => {\n attempt(() => registerV1(api), undefined)\n}\n\nconst setup = (context: V2Context): (() => void) | void =>\n attempt(() => setupV2(context), undefined)\n\nexport default { id: SUBAGENTS_TUI_PLUGIN_ID, tui, setup }\n","/**\n * The model behind the sidebar's `Subagents` section (`src/tui.ts`): which\n * subagents of one opencode session to show, in what state, in what order, and\n * the exact text of each row.\n *\n * Pure on purpose. It imports nothing, so the TUI bundle carries no logger (a\n * logger on the TUI's main thread writes to the terminal it is drawing on,\n * h #g193) and every rule here is testable without a terminal. The TUI half\n * only gathers the inputs and paints the segments this module returns.\n *\n * Measured on opencode 1.18.35 (h #g234), and each one shapes a rule below:\n *\n * - A RUNNING `task` part carries no `state.metadata` in the TUI's store, so\n * the child session id is not on the part until it completes. Rows are\n * built from the child SESSIONS (`parentID` is the current session) and a\n * part is matched to its child by the title opencode gives the child,\n * `<description> (@<agent> subagent)`.\n * - A background dispatch's part completes at once with a `<task id\n * state=\"running\">` envelope; the child's end arrives later as a synthetic\n * user part `<task id=\"ses_...\" state=\"completed\">` in the parent (opencode\n * 2 writes `<subagent sessionID=\"...\" state=\"...\">`). That envelope is the\n * authoritative end of a background child.\n * - `metadata.model` on a finished part is what opencode ASKED for, not what\n * ran: an agent's `forceModel`, its `reasoningEffort`, the dispatch form and\n * the fallback chain all change the spawn without telling opencode. So the\n * label prefers the spawn record the server half writes\n * (`src/spawn-record-store.ts`) and falls back to the part.\n */\n\nexport type SubagentState = \"running\" | \"done\" | \"error\"\n\n/** One child session as either major reports it. */\nexport interface ChildSessionInput {\n id: string\n title?: string\n agent?: string\n created?: number\n updated?: number\n}\n\n/** A `task` / `subagent` tool part in the parent, as either major reports it. */\nexport interface TaskPartInput {\n status?: string\n input?: Record<string, unknown>\n metadata?: Record<string, unknown>\n title?: string\n start?: number\n end?: number\n}\n\n/** A background child's end, read out of the parent's synthetic user parts. */\nexport interface CompletionInput {\n sessionID: string\n state: string\n at?: number\n}\n\n/** What the server half recorded about a session's real spawn. */\nexport interface SpawnRecordInput {\n model?: string\n effort?: string\n account?: string\n at?: number\n}\n\nexport type ChildRunState = \"busy\" | \"idle\" | \"unknown\"\n\nexport interface SidebarInput {\n children: ChildSessionInput[]\n parts: TaskPartInput[]\n completions: CompletionInput[]\n status: (sessionID: string) => ChildRunState\n record: (sessionID: string) => SpawnRecordInput | undefined\n /** When the TUI first saw a child stop, by session id. */\n stoppedAt: (sessionID: string) => number | undefined\n now: number\n}\n\nexport interface SubagentRow {\n /** The child session, absent while a dispatch has not created one yet. */\n sessionID?: string\n agent: string\n description: string\n background: boolean\n state: SubagentState\n /** `sonnet-5.5 · high · alpha`, empty when nothing is known. */\n label: string\n started: number\n finished?: number\n}\n\n/** How long a finished subagent stays in the list, and how many of them. */\nexport const FINISHED_TTL_MS = 10 * 60_000\nexport const FINISHED_KEEP = 3\n/**\n * A background child that reads idle with no envelope yet is still starting\n * for this long: opencode creates the session before it marks it busy.\n */\nexport const STARTING_GRACE_MS = 10_000\n/** The usable width of a row in opencode 1.18.35's 42-column sidebar. */\nexport const SIDEBAR_ROW_WIDTH = 36\nexport const SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"]\n\nconst TITLE_SUFFIX = /\\s*\\(@([^)\\s]+) subagent\\)\\s*$/\n\n/** `Fix the form (@implementor subagent)` -> description and agent. */\nexport function parseChildTitle(title: string | undefined): { description: string; agent?: string } {\n const text = (title ?? \"\").trim()\n const match = TITLE_SUFFIX.exec(text)\n if (!match) return { description: text }\n return { description: text.slice(0, match.index).trim(), agent: match[1] }\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined\n}\n\n/** The child session a part names, on either major's metadata. */\nexport function partChildID(part: TaskPartInput): string | undefined {\n const metadata = part.metadata ?? {}\n return text(metadata.sessionId) ?? text(metadata.sessionID)\n}\n\nfunction partAgent(part: TaskPartInput): string | undefined {\n const input = part.input ?? {}\n return text(input.subagent_type) ?? text(input.agent)\n}\n\nfunction partDescription(part: TaskPartInput): string | undefined {\n return text(part.input?.description) ?? text(part.title)\n}\n\nfunction partBackground(part: TaskPartInput): boolean {\n return part.input?.background === true || part.metadata?.background === true\n}\n\nconst COMPLETION_ENVELOPE =\n /<(?:task\\s+id|subagent\\s+sessionID)=\"(ses_[A-Za-z0-9]+)\"\\s+state=\"([a-z_]+)\"/g\n\n/**\n * Every background end a text names, in order. Both majors' envelopes:\n * `<task id=\"ses_...\" state=\"completed\">` (1.x) and\n * `<subagent sessionID=\"ses_...\" state=\"completed\">` (2.x). `running` is the\n * dispatch's own answer, not an end, so it is skipped.\n */\nexport function completionsIn(value: string, at?: number): CompletionInput[] {\n const found: CompletionInput[] = []\n for (const match of value.matchAll(COMPLETION_ENVELOPE)) {\n if (match[2] === \"running\") continue\n found.push({ sessionID: match[1], state: match[2], at })\n }\n return found\n}\n\nfunction endState(state: string): SubagentState {\n return state === \"completed\" ? \"done\" : \"error\"\n}\n\n/**\n * `claude-opus-5-5` -> `opus-5.5`, `claude-sonnet-4-5-20250929@alpha` ->\n * `sonnet-4.5`, `claude-opus-5-5-fast` -> `opus-5.5 fast`,\n * `claude-opus-5[1m]` -> `opus-5 1m`. Anything that is not a Claude id is\n * returned without its account marker and otherwise untouched.\n */\nexport function shortModelName(modelID: string | undefined): string | undefined {\n let id = text(modelID)\n if (!id) return undefined\n const at = id.indexOf(\"@\")\n if (at > 0) id = id.slice(0, at)\n let suffix = \"\"\n const window = /\\[(\\d+[km])\\]$/i.exec(id)\n if (window) {\n suffix += ` ${window[1].toLowerCase()}`\n id = id.slice(0, window.index)\n }\n if (id.endsWith(\"-fast\")) {\n suffix = ` fast${suffix}`\n id = id.slice(0, -\"-fast\".length)\n }\n if (!id.startsWith(\"claude-\")) return `${id}${suffix}`\n id = id.slice(\"claude-\".length).replace(/-\\d{8}$/, \"\")\n const match = /^([a-z]+)-(\\d+)(?:-(\\d+))?$/.exec(id)\n if (!match) return `${id}${suffix}`\n const version = match[3] === undefined ? match[2] : `${match[2]}.${match[3]}`\n return `${match[1]}-${version}${suffix}`\n}\n\n/**\n * The account a provider id names on a multi-account install\n * (`claude-code-alpha` -> `alpha`). A bare `claude-code` (one account) and a\n * provider that is not this plugin's name none: there is nothing to tell apart.\n */\nexport function accountFromProvider(providerID: string | undefined): string | undefined {\n const id = text(providerID)\n if (!id?.startsWith(\"claude-code-\")) return undefined\n return id.slice(\"claude-code-\".length) || undefined\n}\n\n/** `sonnet-5.5 · high · alpha`; pieces that are not known are left out. */\nexport function subagentLabel(\n record: SpawnRecordInput | undefined,\n part: TaskPartInput | undefined,\n): string {\n const asked = part?.metadata?.model as { modelID?: unknown; providerID?: unknown } | undefined\n const askedModel = typeof asked?.modelID === \"string\" ? asked.modelID : undefined\n const askedProvider = typeof asked?.providerID === \"string\" ? asked.providerID : undefined\n const model = shortModelName(record?.model ?? askedModel)\n const effort = text(record?.effort)\n const account = record?.model ? text(record.account) : accountFromProvider(askedProvider)\n return [model, effort, account].filter(Boolean).join(\" · \")\n}\n\n/**\n * Cut `value` to `width` cells with a trailing ellipsis. Counts code points,\n * which is a cell each for the text a task description holds; a wide glyph\n * can overrun by one cell and is left to the renderer's own clipping.\n */\nexport function truncate(value: string, width: number): string {\n const chars = Array.from(value.replace(/\\s+/g, \" \").trim())\n if (width <= 0) return \"\"\n if (chars.length <= width) return chars.join(\"\")\n if (width === 1) return \"…\"\n return `${chars.slice(0, width - 1).join(\"\").trimEnd()}…`\n}\n\n/**\n * The rows for one session: running first (oldest first), then finished\n * (newest first), at most `FINISHED_KEEP` of those and none older than\n * `FINISHED_TTL_MS`. Empty when nothing is worth showing, which is what makes\n * the section disappear.\n */\nexport function deriveSubagentRows(input: SidebarInput): SubagentRow[] {\n const completions = new Map<string, CompletionInput>()\n for (const completion of input.completions) completions.set(completion.sessionID, completion)\n\n const byChild = new Map<string, TaskPartInput>()\n const unmatched: TaskPartInput[] = []\n for (const part of input.parts) {\n const child = partChildID(part)\n if (child) byChild.set(child, part)\n else unmatched.push(part)\n }\n\n const rows: SubagentRow[] = []\n const seen = new Set<string>()\n for (const child of input.children) {\n if (seen.has(child.id)) continue\n seen.add(child.id)\n const parsed = parseChildTitle(child.title)\n let part = byChild.get(child.id)\n if (!part) {\n // A running part has no child id yet: match it on the title opencode\n // gave the child, agent and description both.\n const index = unmatched.findIndex(\n (candidate) =>\n partDescription(candidate) === parsed.description &&\n (parsed.agent === undefined || partAgent(candidate) === parsed.agent),\n )\n if (index >= 0) part = unmatched.splice(index, 1)[0]\n }\n rows.push(rowFor(input, child, parsed, part, completions.get(child.id)))\n }\n // A dispatch opencode has not created a session for yet: shown, not clickable.\n for (const part of unmatched) {\n if (part.status !== \"pending\" && part.status !== \"running\") continue\n const description = partDescription(part)\n const agent = partAgent(part)\n if (!description && !agent) continue\n rows.push({\n agent: agent ?? \"subagent\",\n description: description ?? \"\",\n background: partBackground(part),\n state: \"running\",\n label: subagentLabel(undefined, part),\n started: part.start ?? input.now,\n })\n }\n\n const running = rows\n .filter((row) => row.state === \"running\")\n .sort((a, b) => a.started - b.started)\n const finished = rows\n .filter((row) => row.state !== \"running\")\n .filter((row) => input.now - (row.finished ?? row.started) <= FINISHED_TTL_MS)\n .sort((a, b) => (b.finished ?? b.started) - (a.finished ?? a.started))\n .slice(0, FINISHED_KEEP)\n return [...running, ...finished]\n}\n\nfunction rowFor(\n input: SidebarInput,\n child: ChildSessionInput,\n parsed: { description: string; agent?: string },\n part: TaskPartInput | undefined,\n completion: CompletionInput | undefined,\n): SubagentRow {\n const background = part ? partBackground(part) : false\n const started = part?.start ?? child.created ?? input.now\n const status = input.status(child.id)\n let state: SubagentState\n let finished: number | undefined\n if (part && !background && part.status !== \"pending\" && part.status !== \"running\") {\n state = part.status === \"completed\" ? \"done\" : \"error\"\n finished = part.end\n } else if (completion) {\n state = endState(completion.state)\n finished = completion.at\n } else if (status === \"busy\") {\n state = \"running\"\n } else if (part && !background) {\n // pending or running: the parent is still waiting on this child.\n state = \"running\"\n } else if (status === \"idle\" && input.now - started < STARTING_GRACE_MS) {\n state = \"running\"\n } else if (status === \"unknown\" && background) {\n // Nothing says it stopped: a background child keeps its spinner until\n // its envelope arrives or opencode reports it idle.\n state = \"running\"\n } else {\n state = \"done\"\n }\n if (state !== \"running\" && finished === undefined) {\n finished = input.stoppedAt(child.id) ?? child.updated ?? started\n }\n return {\n sessionID: child.id,\n agent: parsed.agent ?? (part ? partAgent(part) : undefined) ?? text(child.agent) ?? \"subagent\",\n description: (part ? partDescription(part) : undefined) ?? parsed.description,\n background,\n state,\n label: subagentLabel(input.record(child.id), part),\n started,\n finished,\n }\n}\n\n/** A run of text with the role the TUI maps onto a theme colour. */\nexport interface Segment {\n text: string\n role: \"spinner\" | \"done\" | \"error\" | \"agent\" | \"description\" | \"muted\"\n}\n\n/**\n * The two lines of one row, laid out for `width` cells:\n *\n * ⠋ implementor Fix the dispatch f… bg\n * sonnet-5.5 · high · alpha\n *\n * The `bg` marker is right-aligned, the description takes what is left, and a\n * finished row is drawn in the muted role throughout apart from its glyph.\n */\nexport function layoutRow(\n row: SubagentRow,\n width: number = SIDEBAR_ROW_WIDTH,\n frame = 0,\n): { first: Segment[]; second: Segment[] } {\n const finished = row.state !== \"running\"\n const glyph =\n row.state === \"running\"\n ? SPINNER_FRAMES[frame % SPINNER_FRAMES.length]\n : row.state === \"done\"\n ? \"✓\"\n : \"✗\"\n const marker = row.background ? \"bg\" : \"\"\n const reserved = marker ? marker.length + 1 : 0\n const agentWidth = Math.max(1, Math.min(Array.from(row.agent).length, width - 2 - reserved - 6))\n const agent = truncate(row.agent, agentWidth)\n const room = width - 2 - Array.from(agent).length - 1 - reserved\n const description = room > 0 ? truncate(row.description, room) : \"\"\n const used = 2 + Array.from(agent).length + (description ? 1 + Array.from(description).length : 0)\n const first: Segment[] = [\n { text: glyph, role: row.state === \"running\" ? \"spinner\" : row.state === \"done\" ? \"done\" : \"error\" },\n { text: \" \", role: \"muted\" },\n { text: agent, role: finished ? \"muted\" : \"agent\" },\n ]\n if (description) {\n first.push({ text: \" \", role: \"muted\" })\n first.push({ text: description, role: finished ? \"muted\" : \"description\" })\n }\n if (marker) {\n first.push({ text: \" \".repeat(Math.max(1, width - used - marker.length)), role: \"muted\" })\n first.push({ text: marker, role: \"muted\" })\n }\n const second: Segment[] = row.label\n ? [{ text: ` ${truncate(row.label, width - 2)}`, role: \"muted\" }]\n : []\n return { first, second }\n}\n\n/** The collapsed heading's count: `2 running` or `3 done`. */\nexport function collapsedSummary(rows: SubagentRow[]): string {\n const running = rows.filter((row) => row.state === \"running\").length\n return running > 0 ? `${running} running` : `${rows.length} done`\n}\n","/**\n * What each opencode session's `claude` process REALLY spawned as: model,\n * effort and account. Written by the server half on every turn that reaches\n * the CLI, read by the TUI half's `Subagents` sidebar section (`src/tui.ts`).\n *\n * ## Why a file, and why the TUI cannot read this anywhere else\n *\n * Measured on opencode 1.18.35 (h #g234): an `implementor` subagent whose\n * agent file says `forceModel: claude-sonnet-5-5` and `reasoningEffort: high`\n * spawned as exactly that, while the finished `task` part's `metadata.model`\n * said `claude-code-default/claude-opus-5-5` and no message carried an effort\n * at all. Everything that changes a spawn on the plugin side (an agent's\n * `forceModel` and `reasoningEffort`, `defaultSubagentModel`, the dispatch form\n * of h #g227, the fallback chain of h #g162, an account failover) is invisible\n * to opencode by construction, so showing opencode's own view would be showing\n * the wrong model exactly when the operator configured it not to be. The two\n * halves do not share memory on either major (1.x runs the server half in a\n * worker thread, 2.x in a subprocess), so the record goes through the state\n * directory the resume and dispatch stores already use.\n *\n * ## Shape, and what is never written\n *\n * ```json\n * { \"version\": 1, \"sessions\": {\n * \"ses_abc\": { \"model\": \"claude-sonnet-5-5\", \"effort\": \"high\", \"account\": \"alpha\", \"at\": 1760000000000 } } }\n * ```\n *\n * A model id, an effort level and an account NAME: the same three strings the\n * dispatch store keeps (h #g228), none secret. No prompt, no path, no session\n * key. `account` is written only on a multi-account install, where it tells\n * two configured accounts apart; on a single-account install there is nothing\n * to tell apart and it stays out.\n *\n * ## Cost and failure\n *\n * One write per session per process, and another only when what it spawns as\n * changes: `recordSessionSpawn` compares against what this process last wrote\n * and returns before touching the disk otherwise. Same discipline as the\n * resume store (h #g197): merge-on-write so two opencode processes do not erase\n * each other, atomic rename, `0600` in the `0700` directory, bounded at\n * `MAX_SPAWN_RECORDS` newest sessions and `SPAWN_RECORD_MAX_AGE_MS`. A missing,\n * unreadable or half-written file is an empty store, which costs the sidebar\n * its effort and falls back to the model opencode asked for. Two writers racing\n * can drop one entry; the next turn of that session writes it again, and the\n * only thing it ever costs is a label.\n *\n * Imports nothing that logs, because the TUI bundle reads it on the terminal's\n * own thread (h #g193): every function here is silent and never throws.\n */\nimport { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nexport interface SpawnRecord {\n model?: string\n effort?: string\n account?: string\n at: number\n}\n\n/** Sessions worth remembering at once. A subagent fan-out names a handful. */\nexport const MAX_SPAWN_RECORDS = 256\n\n/** A session not spawned this long ago is not one the sidebar is showing. */\nexport const SPAWN_RECORD_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\nlet pathOverride: string | null = null\nconst lastWritten = new Map<string, string>()\n\nexport function spawnRecordStorePath(): string {\n if (pathOverride) return pathOverride\n const stateRoot = process.env.XDG_STATE_HOME || join(homedir(), \".local\", \"state\")\n return join(stateRoot, \"opencode-claude-code-plugin\", \"session-spawns.json\")\n}\n\nfunction isRecord(value: unknown): value is SpawnRecord {\n const record = value as SpawnRecord | null\n if (!record || typeof record !== \"object\" || Array.isArray(record)) return false\n if (typeof record.at !== \"number\" || !Number.isFinite(record.at)) return false\n const ok = (field: unknown) => field === undefined || typeof field === \"string\"\n if (!ok(record.model) || !ok(record.effort) || !ok(record.account)) return false\n return !!record.model\n}\n\nfunction readEntries(file: string): Map<string, SpawnRecord> {\n const out = new Map<string, SpawnRecord>()\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\"))\n const entries = parsed && typeof parsed === \"object\" ? parsed.sessions : null\n if (!entries || typeof entries !== \"object\") return out\n const cutoff = Date.now() - SPAWN_RECORD_MAX_AGE_MS\n const valid = Object.entries(entries as Record<string, unknown>)\n .filter((entry): entry is [string, SpawnRecord] => isRecord(entry[1]))\n .filter(([, record]) => record.at >= cutoff)\n .sort((a, b) => a[1].at - b[1].at)\n for (const [key, record] of valid) {\n out.set(key, {\n model: record.model,\n ...(record.effort ? { effort: record.effort } : {}),\n ...(record.account ? { account: record.account } : {}),\n at: record.at,\n })\n }\n } catch {\n // ENOENT before the first spawn, or a file another process is renaming\n // into place: the empty store is the answer.\n }\n return out\n}\n\n/**\n * Record what `sessionID` spawned as. Returns whether the disk was written,\n * which is false for an unchanged record, an unusable id and a failed write\n * alike: the caller has nothing to do about any of them.\n */\nexport function recordSessionSpawn(\n sessionID: string,\n spawn: { model: string; effort?: string; account?: string },\n now: number = Date.now(),\n): boolean {\n if (!sessionID || sessionID === \"default\" || !spawn.model) return false\n const record: SpawnRecord = {\n model: spawn.model,\n ...(spawn.effort ? { effort: spawn.effort } : {}),\n ...(spawn.account ? { account: spawn.account } : {}),\n at: now,\n }\n const fingerprint = `${record.model}\\n${record.effort ?? \"\"}\\n${record.account ?? \"\"}`\n if (lastWritten.get(sessionID) === fingerprint) return false\n const file = spawnRecordStorePath()\n const merged = readEntries(file)\n merged.delete(sessionID)\n merged.set(sessionID, record)\n const cutoff = now - SPAWN_RECORD_MAX_AGE_MS\n const kept = [...merged.entries()]\n .filter(([, entry]) => entry.at >= cutoff)\n .sort((a, b) => a[1].at - b[1].at)\n .slice(-MAX_SPAWN_RECORDS)\n try {\n mkdirSync(join(file, \"..\"), { recursive: true, mode: 0o700 })\n const tmp = `${file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify({ version: 1, sessions: Object.fromEntries(kept) }), {\n mode: 0o600,\n })\n renameSync(tmp, file)\n } catch {\n return false\n }\n if (lastWritten.size >= MAX_SPAWN_RECORDS) lastWritten.clear()\n lastWritten.set(sessionID, fingerprint)\n return true\n}\n\n/**\n * A reader that re-parses the file only when it changed. The TUI calls `read()`\n * on every refresh; an unchanged file costs one `stat`.\n */\nexport function createSpawnRecordReader(file: () => string = spawnRecordStorePath) {\n let identity = \"\"\n let cached = new Map<string, SpawnRecord>()\n return {\n read(): Map<string, SpawnRecord> {\n const path = file()\n let next = \"\"\n try {\n const stats = statSync(path)\n next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`\n } catch {\n next = \"missing\"\n }\n if (next !== identity) {\n identity = next\n cached = next === \"missing\" ? new Map() : readEntries(path)\n }\n return cached\n },\n }\n}\n\nexport function _setSpawnRecordStorePathForTests(path: string | null): void {\n pathOverride = path\n lastWritten.clear()\n}\n"],"mappings":"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","names":["text"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khalilgharbaoui/opencode-claude-code-plugin",
3
- "version": "0.53.5",
3
+ "version": "0.54.0",
4
4
  "description": "Claude Code CLI provider plugin for opencode",
5
5
  "homepage": "https://opencode-claude-code-plugin.dev/",
6
6
  "funding": {
@@ -16,6 +16,9 @@
16
16
  ".": {
17
17
  "import": "./dist/index.js",
18
18
  "types": "./dist/index.d.ts"
19
+ },
20
+ "./tui": {
21
+ "import": "./dist/tui.js"
19
22
  }
20
23
  },
21
24
  "files": [
@@ -258,6 +258,18 @@ Everything else is optional. Models appear in the picker without extra config. T
258
258
  `plugin` key is read by both opencode majors, so this block needs no edit after an
259
259
  opencode 2 upgrade.
260
260
 
261
+ On opencode 1.x, the sidebar's **Subagents** section (below) also needs the same spec in
262
+ `~/.config/opencode/tui.json`, because 1.x loads TUI plugins only from that file's
263
+ `plugin` array, never from `opencode.json`'s:
264
+
265
+ ```json
266
+ { "plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"] }
267
+ ```
268
+
269
+ Same spec as in `opencode.json`, a `file://` checkout path included (1.x reads the
270
+ package's `exports["./tui"]`). opencode 2 needs nothing: it finds `<dir>/tui` through the
271
+ `plugins` entry it already has.
272
+
261
273
  ### opencode 2
262
274
 
263
275
  Same package, same config. 2.x's native key is `plugins` (plural), but it still reads 1.x's `plugin` key, so an existing install needs no edit:
@@ -860,11 +872,27 @@ usage window. It counts only background dispatches Claude made through this open
860
872
  process, uses `task_status`'s own running test, and never collects a result.
861
873
  Claude is told the same number: an accepted background dispatch's result gets one
862
874
  trailing line, `Background subagents running now: N (including this one).` (one line per
863
- `task_batch`, `(including the 2 just started)`). Foreground results and every other tool
875
+ `task_batch`, `(including the 2 just started)`, also when one of its subagents failed and
876
+ the batch is reported as an error). Foreground results and every other tool
864
877
  are unchanged. The lookup is bounded (four at a time, 500 ms in all) and the line is
865
878
  simply left out when it runs over or a child cannot be read, so a missing line is not a
866
879
  fault. There is no option for it.
867
880
 
881
+ ### Subagents in the sidebar
882
+
883
+ Always on, no option. While the viewed session has subagents, the sidebar ends with a
884
+ `Subagents` section: one row each, running first (spinner in the theme accent), then up
885
+ to three finished ones (✓ or ✗, greyed out, gone after ten minutes), with the agent type,
886
+ the task description, a dim `bg` for background dispatches, and a muted second line
887
+ `model · effort · account` (account only on a multi-account install). That line is what
888
+ the plugin really spawned, read from `$XDG_STATE_HOME/opencode-claude-code-plugin/session-spawns.json`,
889
+ so `forceModel`, an agent's `reasoningEffort`, a dispatch-form answer, the fallback chain
890
+ and a failover all show there, while opencode's own footer shows the model it asked for.
891
+ Clicking a row opens the subagent's session; `up` returns to the parent. More than two
892
+ rows fold behind a `▼` heading. Setup: nothing on opencode 2; on opencode 1 the
893
+ `tui.json` line under Minimum install. If the section never appears on 1.x, that line is
894
+ missing or opencode was not fully restarted.
895
+
868
896
  ### Let Claude load the user's opencode skills
869
897
 
870
898
  ```json