the brief moves into a file the owner can read

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
This commit is contained in:
2026-08-25 18:50:02 +00:00
co-authored by Claude Opus 5
parent 23f6d06632
commit 65c98a49dd
2 changed files with 56 additions and 30 deletions
+8 -30
View File
@@ -17,37 +17,15 @@ import { ChatPanelWrapper, ChatHeader } from 'officerdev';
* `officerdev` for precisely this — the whole composer, transcript, queue and model picker, with no second
* implementation to fall behind the first.
*
* ── The prefix ──
* ── What these chats are told, and where it lives ──
*
* `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.
* `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.
*
* 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/<email>/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.
* 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;
@@ -61,7 +39,7 @@ const CHAT_CONTEXT = { context: 'plugin', contextId: 'proxy-manager' } as const;
*/
export const ChatPanel = ({ panelId }: { panelId: string }) => (
<div className="flex h-full min-h-0 flex-col">
<ChatPanelWrapper panelId={panelId} promptPrefix={PREFIX} chatContext={CHAT_CONTEXT} />
<ChatPanelWrapper panelId={panelId} chatContext={CHAT_CONTEXT} />
</div>
);