// Task detail drawer — right-side panel that streams the agent's live conversation
function TaskDrawer({ taskId, onClose }) {
if (!taskId) return null;
const stream = window.Gastronaut?.useTaskStream?.(taskId);
const task = stream?.task;
const messages = stream?.messages || [];
const status = task?.status || "running";
const agentColor = task?.agentColor === "gold" ? "var(--gold)" : "var(--accent)";
const bodyRef = React.useRef(null);
React.useEffect(() => {
if (bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
}, [messages.length]);
React.useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
const statusPill = {
running: { bg: "rgba(6,182,212,0.15)", c: "var(--accent)", label: "running" },
queued: { bg: "rgba(255,255,255,0.06)", c: "var(--ink-2)", label: "queued" },
done: { bg: "rgba(34,197,94,0.15)", c: "var(--good)", label: "done" },
error: { bg: "rgba(239,68,68,0.15)", c: "var(--danger)", label: "error" },
}[status] || { bg: "rgba(255,255,255,0.06)", c: "var(--ink-2)", label: status };
return (
e.stopPropagation()}
style={{
width: "min(620px, 100%)",
height: "100%",
borderRadius: 0,
borderLeft: "1px solid var(--line-2)",
display: "flex", flexDirection: "column",
animation: "slideIn 220ms cubic-bezier(0.2,0.8,0.2,1)",
}}
>
{/* Header */}
{task?.agentName || "Agent"}
·
{statusPill.label}
{task?.prompt || "…"}
{taskId} {task?.createdAt ? "· " + new Date(task.createdAt).toLocaleTimeString() : ""}
{/* Messages */}
{messages.length === 0 && (
waiting for agent…
)}
{messages.map((m, i) =>
)}
{status === "running" && (
agent is thinking…
)}
{/* Footer */}
{messages.length} message{messages.length === 1 ? "" : "s"}
{task?.finishedAt ? " · " + Math.round((new Date(task.finishedAt) - new Date(task.createdAt))/1000) + "s" : ""}
esc to close
);
}
function DrawerMessage({ msg, color }) {
if (msg.role === "user") {
return (
{msg.text}
);
}
if (msg.role === "assistant") {
return (
);
}
if (msg.role === "tool_use") {
const input = msg.input ? (typeof msg.input === "string" ? msg.input : JSON.stringify(msg.input, null, 2)) : "";
return (
→ tool · {msg.tool}
{input &&
{input.slice(0, 1200)}}
);
}
if (msg.role === "tool_result") {
return (
← {msg.isError ? "tool error" : "tool result"}
{msg.text &&
{msg.text}}
);
}
if (msg.role === "error") {
return (
{msg.text}
);
}
return null;
}
// New Task modal — chat-style composer with agent assignment
function NewTaskModal({ open, onClose, onSubmitted }) {
const [text, setText] = React.useState("");
const [agentId, setAgentId] = React.useState("auto");
const [priority, setPriority] = React.useState("normal");
React.useEffect(() => {
if (!open) return;
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [open, onClose]);
if (!open) return null;
const agents = [
{ id: "auto", name: "Auto-route", role: "Prime decides", color: "cyan" },
{ id: "prime", name: "Prime", role: "Orchestrator", color: "cyan" },
{ id: "market", name: "Market Sentry", role: "Markets & risk", color: "gold" },
{ id: "creative", name: "Creative Director", role: "Brand & copy", color: "cyan" },
{ id: "research", name: "Research Specialist", role: "Primary research", color: "cyan" },
{ id: "ops", name: "Ops Steward", role: "Infra & secrets", color: "cyan" },
{ id: "scribe", name: "Scribe", role: "Git & docs", color: "cyan" },
{ id: "tape", name: "Tape Reader", role: "Tick ingest", color: "gold" },
];
const chosen = agents.find(a => a.id === agentId) || agents[0];
const chosenColor = chosen.color === "gold" ? "var(--gold)" : "var(--accent)";
const [sending, setSending] = React.useState(false);
const submit = async () => {
if (!text.trim() || sending) return;
setSending(true);
let taskId = null;
try {
if (window.Gastronaut?.submitTask) {
const r = await window.Gastronaut.submitTask({ prompt: text, agentId, priority });
taskId = r?.id || null;
}
} catch (e) {
console.error("[new-task] submit failed", e);
} finally {
setSending(false);
onClose();
setText("");
setAgentId("auto");
if (taskId && onSubmitted) onSubmitted(taskId);
}
};
return (
e.stopPropagation()}
style={{
width: "min(720px, 100%)",
padding: 0,
borderRadius: 22,
overflow: "hidden",
animation: "riseIn 240ms cubic-bezier(0.2, 0.8, 0.2, 1)",
}}
>
{/* Header */}
New task
What should the team do?
{/* Chat-style composer */}
{/* Assign strip */}
Assign to
{agents.map(a => {
const on = a.id === agentId;
const c = a.color === "gold" ? "var(--gold)" : "var(--accent)";
return (
);
})}
{/* Footer: priority + assign button */}
{["low", "normal", "high"].map(p => (
))}
⌘ ↵ to assign
);
}
function PillButton({ icon, label }) {
return (
);
}
// ---- Terminal view ----
function TerminalView() {
const live = !!window.Gastronaut?.live;
const liveRef = React.useRef(null);
const sendLive = window.Gastronaut?.useLiveTerminal?.(liveRef);
const [lines, setLines] = React.useState(live ? [] : [
{ k: "sys", t: "Connected to vps-03.gastronaut.internal · NYC-03" },
{ k: "sys", t: "claude-code v0.9.2 · node v20.11.1 · 8 vCPU / 16 GB" },
{ k: "in", t: "claude status --all" },
{ k: "out", t: "prime live Sonnet 4.6 13d 4h load 0.52" },
{ k: "out", t: "market-sentry live Haiku 4.5 13d 4h load 0.68" },
{ k: "out", t: "creative-dir working Sonnet 4.6 6d 12h load 0.42" },
{ k: "out", t: "research-spec live Sonnet 4.6 6d 12h load 0.31" },
{ k: "out", t: "ops-steward idle Haiku 4.5 13d 4h load 0.08" },
{ k: "out", t: "scribe working Haiku 4.5 3d 2h load 0.22" },
{ k: "out", t: "tape-reader live Haiku 4.5 13d 4h load 0.81" },
]);
const [input, setInput] = React.useState("");
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current) ref.current.scrollTop = ref.current.scrollHeight;
if (liveRef.current) liveRef.current.scrollTop = liveRef.current.scrollHeight;
}, [lines, input]);
const run = () => {
if (!input.trim()) return;
const cmd = input;
setInput("");
if (live && sendLive) {
sendLive(cmd + "\n");
return;
}
const out = mockRun(cmd);
setLines(l => [...l, { k: "in", t: cmd }, ...out.map(t => ({ k: "out", t }))]);
};
return (
}
/>
{/* Window chrome */}
claude@w8h8: ~ — bash
SSH · ed25519
{live ? (
) : (
lines.map((l, i) => {
if (l.k === "sys") return
{"# "}{l.t}
;
if (l.k === "in") return
➜ ~/gastronaut {l.t}
;
return
{l.t}
;
})
)}
{/* Prompt line */}
➜
~/gastronaut
setInput(e.target.value)}
onKeyDown={(e) => { if (e.key === "Enter") run(); }}
style={{
flex: 1,
background: "transparent",
border: "none",
outline: "none",
color: "var(--ink-1)",
fontFamily: "inherit",
fontSize: "inherit",
}}
placeholder="type a command — claude, ls, tail, trade…"
autoFocus
/>
{/* Status bar */}
● online
user: claude
pwd: /home/claude
host: w8h8.com
CPU 42% · MEM 3.1G · DISK 28%
{/* Snippets */}
Common commands
{[
{ c: "claude status --all", d: "Roster snapshot" },
{ c: "claude ask prime 'summarize today'", d: "Talk to Prime" },
{ c: "tail -f events.log", d: "Live stream" },
{ c: "gastronaut budgets", d: "Spend today" },
].map(s => (
{s.c}
{s.d}
))}
Active sessions
{[
{ u: "sefa", h: "this window", n: "zsh" },
{ u: "prime", h: "agent shell", n: "claude-code" },
{ u: "scribe", h: "git worker", n: "node" },
].map((s, i) => (
{s.u}@{s.h}
{s.n}
))}
Safety
Destructive commands (rm -rf, dd, force-push) require a second confirmation from Prime. Real-money trade tools are not installed in this shell.
);
}
function mockRun(cmd) {
const c = cmd.trim().toLowerCase();
if (c.startsWith("ls")) return ["agents/ charter.yaml events.log secrets/ tools/"];
if (c.startsWith("claude ask prime")) return ["prime: on it. summary pending.", "(response streamed in ~14s)"];
if (c.startsWith("gastronaut budgets")) return ["daily cap $120.00 used $41.20 (34%)", "per-agent $30.00 / 24h"];
if (c === "whoami") return ["sefa"];
if (c === "date") return ["Tue Apr 21 14:03:12 UTC 2026"];
if (c.startsWith("claude status")) return [
"prime live load 0.52",
"market-sentry live load 0.68",
"creative-dir working load 0.42",
"research-spec live load 0.31",
"ops-steward idle load 0.08",
];
return [`zsh: command not found: ${cmd}`];
}
window.NewTaskModal = NewTaskModal;
window.TerminalView = TerminalView;
window.TaskDrawer = TaskDrawer;