Forked from OpenCode v1.17.4 with multi-agent system: - 5 agents: aircoding, scheduler, worker, architect, reviewer - Deterministic DAG scheduling engine (coordinator_tick) - Tool whitelists as hard enforcement - AirCoding validation plugin - V1 requirements: C4 docs, ADR, AGENTS.md, debug-log.md - Design documents in docs/
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
|
import type { BuiltinTuiPlugin } from "../builtins"
|
|
import { createMemo, For, Match, Show, Switch, createSignal } from "solid-js"
|
|
|
|
const id = "internal:sidebar-mcp"
|
|
|
|
function View(props: { api: TuiPluginApi }) {
|
|
const [open, setOpen] = createSignal(true)
|
|
const theme = () => props.api.theme.current
|
|
const list = createMemo(() => props.api.state.mcp())
|
|
const on = createMemo(() => list().filter((item) => item.status === "connected").length)
|
|
const bad = createMemo(
|
|
() =>
|
|
list().filter(
|
|
(item) =>
|
|
item.status === "failed" || item.status === "needs_auth" || item.status === "needs_client_registration",
|
|
).length,
|
|
)
|
|
|
|
const dot = (status: string) => {
|
|
if (status === "connected") return theme().success
|
|
if (status === "failed") return theme().error
|
|
if (status === "disabled") return theme().textMuted
|
|
if (status === "needs_auth") return theme().warning
|
|
if (status === "needs_client_registration") return theme().error
|
|
return theme().textMuted
|
|
}
|
|
|
|
return (
|
|
<Show when={list().length > 0}>
|
|
<box>
|
|
<box flexDirection="row" gap={1} onMouseDown={() => list().length > 2 && setOpen((x) => !x)}>
|
|
<Show when={list().length > 2}>
|
|
<text fg={theme().text}>{open() ? "▼" : "▶"}</text>
|
|
</Show>
|
|
<text fg={theme().text}>
|
|
<b>MCP</b>
|
|
<Show when={!open()}>
|
|
<span style={{ fg: theme().textMuted }}>
|
|
{" "}
|
|
({on()} active{bad() > 0 ? `, ${bad()} error${bad() > 1 ? "s" : ""}` : ""})
|
|
</span>
|
|
</Show>
|
|
</text>
|
|
</box>
|
|
<Show when={list().length <= 2 || open()}>
|
|
<For each={list()}>
|
|
{(item) => (
|
|
<box flexDirection="row" gap={1}>
|
|
<text
|
|
flexShrink={0}
|
|
style={{
|
|
fg: dot(item.status),
|
|
}}
|
|
>
|
|
•
|
|
</text>
|
|
<text fg={theme().text} wrapMode="word">
|
|
{item.name}{" "}
|
|
<span style={{ fg: theme().textMuted }}>
|
|
<Switch fallback={item.status}>
|
|
<Match when={item.status === "connected"}>Connected</Match>
|
|
<Match when={item.status === "failed"}>
|
|
<i>{item.error}</i>
|
|
</Match>
|
|
<Match when={item.status === "disabled"}>Disabled</Match>
|
|
<Match when={item.status === "needs_auth"}>Needs auth</Match>
|
|
<Match when={item.status === "needs_client_registration"}>Needs client ID</Match>
|
|
</Switch>
|
|
</span>
|
|
</text>
|
|
</box>
|
|
)}
|
|
</For>
|
|
</Show>
|
|
</box>
|
|
</Show>
|
|
)
|
|
}
|
|
|
|
const tui: TuiPlugin = async (api) => {
|
|
api.slots.register({
|
|
order: 200,
|
|
slots: {
|
|
sidebar_content() {
|
|
return <View api={api} />
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
const plugin: BuiltinTuiPlugin = {
|
|
id,
|
|
tui,
|
|
}
|
|
|
|
export default plugin
|