import { ChatPanelWrapper, ChatHeader } from 'officerdev'; /** * A chat, sitting under the two lists. * * ── Why this exists instead of naming `officerdev/chat` in the layout ── * * It was tried, and it produced two Hosts panels. `PluginScreen` renders the workspace with * `appTypes={{ allowed, fallback: allowed[0] }}` where `allowed` is **this plugin's own panel keys**, so a * layout naming anything else silently falls back to the first one. That is deliberate and its comment * says why: a persisted layout must not be able to render another plugin's panel inside this screen. * * The registry being shared is a separate thing from the layout being allowed to name what is in it. Both * are true; only the second one decides this. * * So the panel is ours and the chat inside it is the platform's. `EmbeddableChat` is exported from * `officerdev` for precisely this — the whole composer, transcript, queue and model picker, with no second * implementation to fall behind the first. * * ── The prefix ── * * `promptPrefix` is described in `ChatPanelWrapper` as "a system prompt only this app understands". It is * per-panel rather than per-session-wide, so it costs nothing to anybody else's conversation. * * Kept to two sentences on purpose. The Proxy Manager SKILL carries the real knowledge and reaches every * conversation on the machine at a cost of two lines; this only says where the agent is standing, which is * the one thing the skill cannot know. Anything longer would be the skill written twice, and the copy here * would be the stale one. */ const PREFIX = [ 'This chat is a panel on the Proxy Manager screen, beside a list of this machine’s proxy hosts and a list', 'of its TLS certificates. Questions about domains, forwarding, whether a site is up, or when a certificate', 'expires are about THIS machine — read the Proxy Manager skill before answering one.', ].join(' '); /** * The wrapper is not decoration. `EmbeddableChat` is handed `flex-1 min-h-0`, which needs a flex COLUMN * with a real height above it — without one the composer floats mid-panel and the transcript overflows * below the fold, which is exactly what shipping it bare produced. `ChatPanelWrapper` has the same div for * the same reason. */ /** * The context this plugin's chats belong to. * * The platform turns it into `$DATA_PATH//plugins/proxy-manager/chats` — for the turn, and for the * header's session list — and the path is never sent from here. `docs/commander-directory.md` has why a * client does not choose a cwd; `cwdOf` in `api/chat/chat.ts` has the listing half. * * What the directory buys beyond tidiness: Claude Code reads a `CLAUDE.md` from its cwd at session start, * so anything these chats should know can live there instead of in every conversation on the machine. */ const CHAT_CONTEXT = { context: 'plugin', contextId: 'proxy-manager' } as const; /** * `ChatPanelWrapper`, not `EmbeddableChat` — one level up, and everything below it comes free. * * The wrapper already carries the session persistence (`usePanelSession`, so this panel stays on its own * conversation across a reload), the agent identity bar, the spawned-by bar, and the resume-the-newest * behaviour. Mounting the composer directly meant reimplementing all of that or going without it, which is * what this panel was doing. */ export const ChatPanel = ({ panelId }: { panelId: string }) => (
); /** * The panel's header: the conversation picker, scoped to this plugin's directory. * * A registry `header` is mounted with `{ panelId }` only, so the context is bound here rather than passed * — which is the whole reason this wrapper exists. */ export const ChatPanelHeader = ({ panelId }: { panelId: string }) => ( );