chat/CLAUDE.md replaces the promptPrefix. the platform copies it into the chats directory on every chat that starts there. it says more than the prefix could afford to, and the part worth having is the rule the prefix had no room for: a refusal is an answer, not an obstacle to route around. no hunting for another credential, no reaching NPM another way, no deciding the refusal is a bug and working around it while investigating. that rule is written rather than enforced, and the file says why: the agent runs with a shell, so most of those would work. stepping over the line is worse than the thing it was asked to do, because the person ends up with neither the outcome nor an accurate account of what happened. prompted by a real turn today: a 403 on a delete, followed by a hunt for a broader-scoped credential, ended by the owner interrupting it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
55 lines
2.7 KiB
TypeScript
55 lines
2.7 KiB
TypeScript
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.
|
|
*
|
|
* ── What these chats are told, and where it lives ──
|
|
*
|
|
* `chat/CLAUDE.md`, in this plugin. The platform copies it into the chats directory whenever a chat starts
|
|
* there, and Claude Code reads it from its own cwd at session start.
|
|
*
|
|
* There WAS a `promptPrefix` here and it is gone. A prefix rides every message: paid per turn forever,
|
|
* invisible as a design artefact because it lives in a TypeScript constant, and visible in the one place
|
|
* it should not be — the owner sent three words and watched a paragraph of preamble go out attached to
|
|
* them. The file costs nothing per turn and can be opened by the person it is about.
|
|
*/
|
|
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 }) => (
|
|
<div className="flex h-full min-h-0 flex-col">
|
|
<ChatPanelWrapper panelId={panelId} chatContext={CHAT_CONTEXT} />
|
|
</div>
|
|
);
|
|
|
|
/**
|
|
* 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 }) => (
|
|
<ChatHeader panelId={panelId} chatContext={CHAT_CONTEXT} />
|
|
);
|