/** @jsxImportSource @opentui/solid */ import { createSignal, onCleanup } from 'solid-js'; import type { TuiPlugin, TuiPluginModule } from '@opencode-ai/plugin/tui'; import type fs from 'fs'; import type path from 'path'; interface StateJson { currentPhase?: string; workflowName?: string; } interface MessagePartUpdatedEvent { properties?: { part?: { sessionID?: string; type?: string; tool?: string; }; }; } function readLatestState( sessionDir: string, ): { phase: string; workflow: string } | null { try { // require() is intentional: top-level ESM imports of Node built-ins are not // supported in the Bun plugin runtime. See AGENTS.md for rationale. // eslint-disable-next-line @typescript-eslint/no-require-imports const fsSync = require('fs') as typeof fs; // eslint-disable-next-line @typescript-eslint/no-require-imports const pathSync = require('path') as typeof path; const vibeDir = pathSync.join(sessionDir, '.vibe', 'conversations'); const dirs = fsSync.readdirSync(vibeDir); let latest: { mtime: number; file: string } | null = null; for (const dir of dirs) { const file = pathSync.join(vibeDir, dir, 'state.json'); try { const stat = fsSync.statSync(file); if (!latest || stat.mtimeMs > latest.mtime) { latest = { mtime: stat.mtimeMs, file }; } } catch { // unreadable entry — skip silently } } if (!latest) return null; const state = JSON.parse( fsSync.readFileSync(latest.file, 'utf8'), ) as StateJson; if (!state.currentPhase && !state.workflowName) return null; return { phase: state.currentPhase ?? '—', workflow: state.workflowName ?? '—', }; } catch { return null; } } // eslint-disable-next-line @typescript-eslint/require-await -- TuiPlugin signature requires Promise; plugin body is synchronous const tui: TuiPlugin = async (api) => { api.slots.register({ order: 5, slots: { sidebar_content(_ctx, props) { const theme = () => api.theme.current; const [state, setState] = createSignal<{ phase: string; workflow: string; } | null>(null); const offPart = api.event.on('message.part.updated', (e) => { const ev = e as MessagePartUpdatedEvent; const part = ev.properties?.part; if (!part) return; if (part.sessionID !== props.session_id) return; if (part.type !== 'tool') return; if (!part.tool?.startsWith('workflows_')) return; const dir = api.state.path.directory; if (!dir) return; setState(readLatestState(dir)); }); onCleanup(offPart); // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- JSX element typed as `error` by @opentui/solid's JSX types; safe at runtime return ( Workflow {state()?.workflow}:{' '} {/* eslint-disable-next-line solid/style-prop -- `fg` is an OpenTUI-specific style prop, not a standard CSS property */} {state()?.phase} ); }, }, }); }; const plugin: TuiPluginModule & { id: string } = { id: 'workflows-phase', tui }; export default plugin;