import type { LayoutNode } from 'officerdev'; import { WorkspaceView, ChatPanelWrapper } from 'officerdev'; import { useParams } from 'react-router'; import { useAuthoredLayout } from 'state/useAuthoredLayout'; import { defaultLayout } from './defaultLayout'; import { BotsList } from './BotsList'; /** * `/bots` — one sentence arms a watch, and the conversation stays open to widen it. * * ── The same shape as Command HQ ── * * A narrow list on the left, the conversation on the right, and the selection in the URL as a route pair: * `/bots` is the new-bot state, `/bots/:sessionId` is one of them. That is the convention * `docs/navigation-audit.md` sets and the one Command HQ already follows, so a bot is linkable and a * reload keeps the one you were reading. * * ── Why the panels are supplied here rather than registered ── * * Every bot runs in one shared directory, which the server resolves from `context: 'bots'`. The registry's * `officerdev/chat` derives its context from the workspace and would land on none of it — it would list * and resume the caller's HOME, which is exactly what this screen did on its first outing. `components` is * the prop for a screen that supplies its own chat panel, and it carries the header, so the picker lists * bots rather than every chat on the machine. * * The instructions a bot runs under are `$DATA_PATH//bots/CLAUDE.md`, written by the server on * every turn — `api/bots/brief.ts`. */ const CHAT_CONTEXT = { context: 'bots' } as const; const BotsChat = ({ panelId }: { panelId: string }) => { const { sessionId } = useParams(); return (
{/* `sessionId ?? null` — null is "the screen chose a new chat", which is not the same as having no opinion. On `/bots` with no id that is exactly right: an empty composer waiting for the sentence. */}
); }; export const BotsScreen = () => { const workspace = useAuthoredLayout('screens/bots', defaultLayout as LayoutNode); return ( ); };