Command center: edit brain sections inline from the cards
ci / gates (push) Failing after 11s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped

Add PATCH /api/claws/{id}/brain (edit_brain): writes any of system_prompt / agent_md
/ persona / skills_md into the claw's .brain (best-effort) + persists system_prompt
to Postgres (authoritative) + commits a ClawSync revision.

Frontend: a reusable EditableSection (pencil → textarea → Save/Cancel → PATCH →
re-fetch brain). The SYSTEM PROMPT, HOW I OPERATE (AGENTS.md), and PERSONALITY cards
in the command center are now editable inline; saving writes back to the mapped
brain section. AGENTS.md card now always shows (so it can be authored when empty).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-26 13:30:58 -07:00
co-authored by Claude Opus 4.8
parent 9fca3f6676
commit 89c147b742
4 changed files with 112 additions and 12 deletions
@@ -5,10 +5,56 @@
// from Dashboard.tsx's in-file helpers.
import { useState, type CSSProperties } from "react";
import { ScrollText } from "lucide-react";
import { Check, Pencil, ScrollText, X } from "lucide-react";
export const mono = "'JetBrains Mono', ui-monospace, monospace";
/** A brain section: rendered read-only with a pencil; click to edit as raw text
* and PATCH it back to `/api/claws/{id}/brain`. `children` is the display;
* `value` is the editable source. `onSaved` should re-fetch the brain. */
export function EditableSection({ clawId, field, value, onSaved, children, placeholder }: {
clawId: string;
field: "system_prompt" | "agent_md" | "persona" | "skills_md";
value: string;
onSaved?: () => void;
children: React.ReactNode;
placeholder?: string;
}) {
const [editing, setEditing] = useState(false);
const [draft, setDraft] = useState(value);
const [saving, setSaving] = useState(false);
const save = async () => {
setSaving(true);
try {
await fetch(`/api/claws/${clawId}/brain`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ [field]: draft }) });
setEditing(false);
onSaved?.();
} catch {
/* keep the editor open on failure */
} finally {
setSaving(false);
}
};
if (editing) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<textarea value={draft} onChange={(e) => setDraft(e.target.value)} placeholder={placeholder} autoFocus
style={{ width: "100%", minHeight: 130, resize: "vertical", boxSizing: "border-box", borderRadius: 8, border: "1px solid rgba(255,255,255,.14)", background: "#0b0b0e", color: "#e6e6ea", padding: 10, fontFamily: mono, fontSize: 12.5, lineHeight: 1.55 }} />
<div style={{ display: "flex", gap: 8 }}>
<button type="button" onClick={save} disabled={saving} style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 12px", borderRadius: 7, border: 0, background: "#5fd08a", color: "#06140c", fontSize: 12, fontWeight: 700, cursor: "pointer" }}><Check size={13} />{saving ? "Saving…" : "Save"}</button>
<button type="button" onClick={() => setEditing(false)} style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 12px", borderRadius: 7, border: "1px solid rgba(255,255,255,.12)", background: "transparent", color: "#cfcfd5", fontSize: 12, cursor: "pointer" }}><X size={13} />Cancel</button>
</div>
</div>
);
}
return (
<div style={{ position: "relative" }}>
<button type="button" onClick={() => { setDraft(value); setEditing(true); }} aria-label="Edit" title="Edit" style={{ position: "absolute", top: -2, right: -2, zIndex: 2, display: "inline-flex", alignItems: "center", justifyContent: "center", width: 24, height: 24, borderRadius: 6, border: "1px solid rgba(255,255,255,.1)", background: "rgba(8,8,10,.55)", color: "#8a8a92", cursor: "pointer" }}><Pencil size={12} /></button>
{children}
</div>
);
}
// `GET /api/claws/{id}/brain` — the claw's .brain (cm-brain / ClawhDF5).
export type RawBrain = {
exists: boolean;
@@ -66,8 +112,9 @@ export function CollapseTick({ open, color, onClick }: { open: boolean; color: s
);
}
export function SystemPromptCard({ prompt }: { prompt: string }) {
export function SystemPromptCard({ prompt, clawId, onSaved }: { prompt: string; clawId?: string; onSaved?: () => void }) {
const [open, setOpen] = useState(true);
const display = prompt?.trim() ? <MarkdownText text={prompt} baseSize={14} /> : <p style={{ fontSize: 13, color: "#6a6a72", margin: 0 }}>No system prompt set.</p>;
return (
<div style={{ borderRadius: 12, background: "#0f0f13", border: "1px solid rgba(255,111,97,.3)", padding: "16px 18px", boxShadow: "0 8px 22px rgba(0,0,0,.4)" }}>
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: open ? 13 : 0 }}>
@@ -76,7 +123,11 @@ export function SystemPromptCard({ prompt }: { prompt: string }) {
<span style={{ flex: 1 }} />
<CollapseTick open={open} color="#ff8a7a" onClick={() => setOpen((o) => !o)} />
</div>
{open ? (prompt?.trim() ? <MarkdownText text={prompt} baseSize={14} /> : <p style={{ fontSize: 13, color: "#6a6a72", margin: 0 }}>No system prompt set.</p>) : null}
{open ? (
clawId ? (
<EditableSection clawId={clawId} field="system_prompt" value={prompt ?? ""} onSaved={onSaved} placeholder="System prompt / job description…">{display}</EditableSection>
) : display
) : null}
</div>
);
}