Lots 3 et 4 du ticket #69. Sans ça un agent CLI est indriveable depuis un téléphone : le clavier virtuel n'a ni Esc, ni Tab, ni Ctrl, ni flèches, donc on peut taper un prompt mais pas l'interrompre, compléter un chemin, sortir d'un éditeur ou rappeler l'historique. - `TerminalView` expose un `onReady(api)` optionnel (donc desktop inchangé, et inerte quand xterm ne monte pas). `api.send` passe par `term.input()` : le *même* chemin qu'une frappe réelle, donc le relais PTY, le comptage de lignes et la suspension du write-portal s'appliquent à l'identique. Écrire sur le handle aurait court-circuité le portal. - `TerminalKeyBar` (web-only) : Esc/Tab/Ctrl-C/Ctrl-D/flèches en chips tactiles 44px, scroll horizontal. Chaque tap annule le déplacement de focus et refocalise xterm — sinon le clavier virtuel se referme à chaque touche. - La cellule agent passe de `h-64` fixe (une letterbox d'~20 lignes) à `h-[60dvh]` sur téléphone, `sm:h-80` au-delà. - Tests : séquences d'octets émises, préservation du focus, état désactivé avant montage de xterm, et garde de non-régression sur la frontière — le client web ne monte aucun layout-grid/split/dock desktop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
3.6 KiB
TypeScript
94 lines
3.6 KiB
TypeScript
/**
|
|
* Web agent cell — ticket #13, lot F4. Thin wiring (no new terminal logic) that
|
|
* reuses the existing, transport-neutral {@link TerminalView} to render a CLI
|
|
* agent over the WebSocket in web mode.
|
|
*
|
|
* The agent's CLI runs server-side (B6); the browser is display-only. This
|
|
* component just supplies `TerminalView` with the DI **agent gateway** as the
|
|
* opener/reattacher:
|
|
* - `open` → `agent.launchAgent(projectId, agentId, …)` (frame `agent.launch`,
|
|
* unified `terminal.attached` ack; the minted conversation id is carried on the
|
|
* returned handle),
|
|
* - `reattach` → `agent.reattach(sessionId, …)` (frame `terminal.attach`, no
|
|
* relaunch, bounded scrollback repainted) — so a browser reload/reconnect
|
|
* resumes the surviving server-side PTY.
|
|
*
|
|
* The session id is persisted in component state so a re-mount re-attaches rather
|
|
* than relaunching, matching the desktop cell's lifecycle. A structured agent is
|
|
* refused server-side (`UNSUPPORTED`) and surfaced by `TerminalView`'s own error
|
|
* banner — no crash. It touches only gateways via DI (no `@tauri-apps/api`).
|
|
*/
|
|
|
|
import { useCallback, useState } from "react";
|
|
|
|
import type {
|
|
OpenTerminalOptions,
|
|
ReattachResult,
|
|
TerminalHandle,
|
|
} from "@/ports";
|
|
import { useGateways } from "@/app/di";
|
|
import { TerminalView, type TerminalInputApi } from "@/features/terminals";
|
|
import { TerminalKeyBar } from "./TerminalKeyBar";
|
|
|
|
interface WebAgentCellProps {
|
|
/** Owning project id (resolved server-side by `agent.launch`). */
|
|
projectId: string;
|
|
/** Agent to launch/attach. */
|
|
agentId: string;
|
|
/** Working directory for the cell (typically the project root). */
|
|
cwd: string;
|
|
/** Layout leaf id, when the caller tracks one (drives the singleton guard). */
|
|
nodeId?: string;
|
|
}
|
|
|
|
export function WebAgentCell({ projectId, agentId, cwd, nodeId }: WebAgentCellProps) {
|
|
const { agent } = useGateways();
|
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
// Input API of the mounted xterm (#69), used by the mobile key bar. Stays
|
|
// `null` in headless renders where xterm cannot mount — the bar then renders
|
|
// disabled rather than throwing.
|
|
const [inputApi, setInputApi] = useState<TerminalInputApi | null>(null);
|
|
|
|
const open = useCallback(
|
|
(options: OpenTerminalOptions, onData: (bytes: Uint8Array) => void): Promise<TerminalHandle> =>
|
|
agent.launchAgent(
|
|
projectId,
|
|
agentId,
|
|
nodeId ? { ...options, nodeId } : options,
|
|
onData,
|
|
),
|
|
[agent, projectId, agentId, nodeId],
|
|
);
|
|
|
|
const reattach = useCallback(
|
|
(sid: string, onData: (bytes: Uint8Array) => void): Promise<ReattachResult> =>
|
|
agent.reattach(sid, onData),
|
|
[agent],
|
|
);
|
|
|
|
return (
|
|
// #69 — the cell was a flat `h-64` (256px), which on a phone left the agent
|
|
// a letterbox roughly 20 lines tall. It now takes 60% of the *dynamic*
|
|
// viewport on phones (so the collapsing URL bar can't clip it) and keeps a
|
|
// fixed, desktop-like height from `sm` up. `min-h-0` on the terminal row
|
|
// lets it shrink inside the flex column instead of pushing the key bar off.
|
|
<div
|
|
data-testid="web-agent-cell"
|
|
className="flex h-[60dvh] min-h-64 w-full flex-col overflow-hidden rounded-md border border-border sm:h-80"
|
|
>
|
|
<div className="min-h-0 flex-1">
|
|
<TerminalView
|
|
cwd={cwd}
|
|
agentMode
|
|
open={open}
|
|
reattach={reattach}
|
|
sessionId={sessionId}
|
|
onSessionId={setSessionId}
|
|
onReady={setInputApi}
|
|
/>
|
|
</div>
|
|
<TerminalKeyBar api={inputApi} />
|
|
</div>
|
|
);
|
|
}
|