agent: TermInput — composer + slash commands

Ported from app.js's `renderTermInput`/`handleSlashCommand`/
`completeSlash`: prompt + auto-growing textarea, Enter sends (Shift+Enter
newline), Tab cycles slash-command completion. Same command set
(/help, /clear, /cancel, /compact, /model, /effort, /new-session,
/logout), same routes (`api/cancel`, `api/compact`, `api/new-session`,
`api/logout`, `send`) via new `lib/termActions.ts` (same `{ok, detail}`
shape as modelEffort.ts's POST helper).

One deliberate UX change: `/new-session` and `/logout` used to pop the
old shadow-DOM `themedConfirm` modal before firing — this rewrite's
destructive actions all avoid that widget family already (SidePanel,
useConfirmClick), and a modal doesn't fit a text-input flow anyway.
Typing the command once arms it (a local note explains what confirming
does); typing it again fires it — a keyboard-native two-step confirm.

`/help`/`/clear` need to reach into LiveStream's row list (local-only
echo rows, never sent anywhere) without lifting that state up to Root —
`useLiveStream` gained `pushLocalNote`/`clearLocal`, exposed off
`LiveStream` via `forwardRef`+`useImperativeHandle` (preact/compat),
same shape as app.js's old `termAPI` object but scoped as a ref handle
instead of a module-level variable.

Screenshot-verified end to end: typed "/help" + Enter into a real
mounted composer, confirmed the textarea clears and the local note rows
(command list) append to the live pane.
This commit is contained in:
iris 2026-08-28 02:53:03 +02:00
commit d9249a36bf
5 changed files with 249 additions and 9 deletions

View file

@ -35,6 +35,13 @@ export interface UseLiveStreamResult {
hasMore: boolean;
loadingMore: boolean;
loadMore: () => void;
/** Append a local-only note row (TermInput's `/help` output, error
* feedback on a failed slash-command POST) never sent anywhere,
* purely a client-side echo. */
pushLocalNote: (text: string) => void;
/** Wipe the local view only (server-side history is untouched)
* TermInput's `/clear`. */
clearLocal: () => void;
}
function appendRow(rows: StreamRow[], row: StreamRow): StreamRow[] {
@ -179,5 +186,14 @@ export function useLiveStream(opts: UseLiveStreamOptions = {}): UseLiveStreamRes
}
}
return { rows, hasMore, loadingMore, loadMore };
const localKeyRef = useRef(0);
function pushLocalNote(text: string) {
localKeyRef.current += 1;
setRows((prev) => appendRow(prev, { key: `local-${localKeyRef.current}`, cssClass: 'note', fromHistory: false, text }));
}
function clearLocal() {
setRows([]);
}
return { rows, hasMore, loadingMore, loadMore, pushLocalNote, clearLocal };
}