hive-agent: the per-agent page has no message box; its send route and the broker's operator message are removed
The page's composer (its input, slash commands and completion), POST /send in hive-agent, the OperatorMsg request of the agent socket and its handler in hive-c0re, and the broker-error helper only the send route used are deleted: writing to an agent is the swarm UI's terminal. cancel turn, new session and log out stay in the state badge's menu; POST /api/compact stays, with nothing on the page calling it. The request-line limit keeps its size, and its comment says it is a generous bound now. The docs describe the actions in place of the footer and the prompt.
This commit is contained in:
parent
e149ff3315
commit
23f4f1823b
2 changed files with 77 additions and 505 deletions
|
|
@ -33,24 +33,21 @@ import { Row } from "@hive/shared/term-row.js";
|
|||
const NEAR_BOTTOM_PX = 48;
|
||||
const LOAD_MORE_SCROLL_PX = 80;
|
||||
|
||||
/** Imperative escape hatch for TermInput's local-only slash commands
|
||||
* (`/help`, `/clear`) — same shape as app.js's old `termAPI` object,
|
||||
* kept as a ref handle rather than lifting the whole row array up to
|
||||
* Root so LiveStream/useLiveStream still own their state privately. */
|
||||
/** Imperative escape hatch for the page's local-only notes (the result of
|
||||
* a state-badge action) — kept as a ref handle rather than lifting the whole
|
||||
* row array up to Root so LiveStream/useLiveStream still own their state
|
||||
* privately. */
|
||||
export interface LiveStreamHandle {
|
||||
pushNote: (text: string) => void;
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
export const LiveStream = forwardRef<LiveStreamHandle, object>(
|
||||
function LiveStream(_props, ref) {
|
||||
const { rows, hasMore, loadingMore, loadMore, pushLocalNote, clearLocal } =
|
||||
const { rows, hasMore, loadingMore, loadMore, pushLocalNote } =
|
||||
useLiveStream();
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({ pushNote: pushLocalNote, clear: clearLocal }),
|
||||
[pushLocalNote, clearLocal],
|
||||
);
|
||||
useImperativeHandle(ref, () => ({ pushNote: pushLocalNote }), [
|
||||
pushLocalNote,
|
||||
]);
|
||||
const logRef = useRef<HTMLDivElement>(null);
|
||||
const [stickToBottom, setStickToBottom] = useState(true);
|
||||
const [unseen, setUnseen] = useState(0);
|
||||
|
|
|
|||
|
|
@ -1,187 +0,0 @@
|
|||
// <TermInput> — the fixed composer at the bottom of the page. Sends a
|
||||
// plain message to this agent's own turn loop (`POST send`), or
|
||||
// intercepts a `/slash` command locally (never sent to the agent).
|
||||
// Ported from app.js's `renderTermInput`/`handleSlashCommand`/
|
||||
// `completeSlash`, with one deliberate UX change: `/new-session` and
|
||||
// `/logout` used to pop the old shadow-DOM `themedConfirm` modal
|
||||
// (@hive/shared/modal.js) before firing — this rewrite's destructive
|
||||
// actions all avoid that widget family (see SidePanel.tsx's/
|
||||
// useConfirmClick.ts's file comments for why), and a modal doesn't fit
|
||||
// a text-input flow anyway. Instead: typing the command once arms it
|
||||
// (a local note explains what confirming does), typing it again within
|
||||
// the same "armed" state fires it — a keyboard-native two-step confirm.
|
||||
import { useRef, useState } from "preact/hooks";
|
||||
import {
|
||||
postCancelTurn,
|
||||
postCompact,
|
||||
postNewSession,
|
||||
postLogout,
|
||||
postSend,
|
||||
} from "../lib/termActions.js";
|
||||
|
||||
export interface TermInputProps {
|
||||
label: string;
|
||||
online: boolean;
|
||||
onLocalNote: (text: string) => void;
|
||||
onClear: () => void;
|
||||
}
|
||||
|
||||
const SLASH_COMMANDS: { name: string; desc: string }[] = [
|
||||
{ name: "/help", desc: "list slash commands" },
|
||||
{ name: "/clear", desc: "wipe the terminal panel (local-only)" },
|
||||
{ name: "/cancel", desc: "SIGINT the in-flight claude turn" },
|
||||
{ name: "/compact", desc: "compact the persistent claude session" },
|
||||
{
|
||||
name: "/new-session",
|
||||
desc: "fresh claude session next turn (type twice to confirm)",
|
||||
},
|
||||
{
|
||||
name: "/logout",
|
||||
desc: "rotate OAuth creds + park in needs-login (type twice to confirm)",
|
||||
},
|
||||
];
|
||||
|
||||
const MAX_PX = 12 * 16; // ~8 lines @ 1.5 line-height, 1em base
|
||||
|
||||
function completeSlash(prefix: string): string | null {
|
||||
const matches = SLASH_COMMANDS.filter((c) => c.name.startsWith(prefix));
|
||||
if (!matches.length) return null;
|
||||
const idx = matches.findIndex((c) => c.name === prefix);
|
||||
return matches[(idx + 1) % matches.length].name;
|
||||
}
|
||||
|
||||
export function TermInput({
|
||||
label,
|
||||
online,
|
||||
onLocalNote,
|
||||
onClear,
|
||||
}: TermInputProps) {
|
||||
const [value, setValue] = useState("");
|
||||
const taRef = useRef<HTMLTextAreaElement>(null);
|
||||
// Which destructive command is currently "armed" (typed once, waiting
|
||||
// for the confirming repeat) — any other command clears it.
|
||||
const armedRef = useRef<string | null>(null);
|
||||
|
||||
function grow() {
|
||||
const ta = taRef.current;
|
||||
if (!ta) return;
|
||||
ta.style.height = "auto";
|
||||
ta.style.height = Math.min(ta.scrollHeight, MAX_PX) + "px";
|
||||
}
|
||||
|
||||
function reportFailure(label_: string, detail?: string) {
|
||||
onLocalNote(`✗ ${label_} failed${detail ? ": " + detail : ""}`);
|
||||
}
|
||||
|
||||
function handleSlash(line: string): boolean {
|
||||
const trimmed = line.trim();
|
||||
const [cmd] = trimmed.split(/\s+/);
|
||||
const wasArmed = armedRef.current === cmd;
|
||||
armedRef.current = null;
|
||||
|
||||
switch (cmd) {
|
||||
case "/help":
|
||||
onLocalNote("/help");
|
||||
for (const c of SLASH_COMMANDS)
|
||||
onLocalNote(` ${c.name.padEnd(13)} — ${c.desc}`);
|
||||
return true;
|
||||
case "/clear":
|
||||
onClear();
|
||||
onLocalNote(
|
||||
"· terminal cleared (local view only — server history kept)",
|
||||
);
|
||||
return true;
|
||||
case "/cancel":
|
||||
postCancelTurn().then(
|
||||
(r) => !r.ok && reportFailure("/cancel", r.detail),
|
||||
);
|
||||
return true;
|
||||
case "/compact":
|
||||
postCompact().then((r) => !r.ok && reportFailure("/compact", r.detail));
|
||||
return true;
|
||||
case "/new-session":
|
||||
if (wasArmed) {
|
||||
postNewSession().then(
|
||||
(r) => !r.ok && reportFailure("/new-session", r.detail),
|
||||
);
|
||||
} else {
|
||||
armedRef.current = "/new-session";
|
||||
onLocalNote(
|
||||
"⚠ drops all prior --continue context. type /new-session again to confirm.",
|
||||
);
|
||||
}
|
||||
return true;
|
||||
case "/logout":
|
||||
if (wasArmed) {
|
||||
postLogout().then((r) => !r.ok && reportFailure("/logout", r.detail));
|
||||
} else {
|
||||
armedRef.current = "/logout";
|
||||
onLocalNote(
|
||||
"⚠ SIGINTs the current turn + rotates OAuth credentials. type /logout again to confirm.",
|
||||
);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
onLocalNote(`✗ unknown slash command: ${cmd} — try /help`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const line = value;
|
||||
if (!line.trim()) return;
|
||||
setValue("");
|
||||
requestAnimationFrame(grow);
|
||||
if (line.trim().startsWith("/")) {
|
||||
handleSlash(line);
|
||||
return;
|
||||
}
|
||||
armedRef.current = null;
|
||||
postSend(line).then((r) => !r.ok && reportFailure("send", r.detail));
|
||||
}
|
||||
|
||||
function onInput(e: Event) {
|
||||
setValue((e.currentTarget as HTMLTextAreaElement).value);
|
||||
grow();
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === "Tab" && value.startsWith("/") && !value.includes(" ")) {
|
||||
const next = completeSlash(value);
|
||||
if (next) {
|
||||
e.preventDefault();
|
||||
setValue(next);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={`term-input${online ? "" : " disabled"}`}>
|
||||
<form
|
||||
class="sendform-term"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}}
|
||||
>
|
||||
<span class="prompt">operator@{label} ▸</span>
|
||||
<textarea
|
||||
ref={taRef}
|
||||
value={value}
|
||||
placeholder={`message ${label}…`}
|
||||
rows={1}
|
||||
disabled={!online}
|
||||
autocomplete="off"
|
||||
onInput={onInput}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<span class="submit-hint">↵ send · ⇧↵ newline · /help</span>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue