chore(wip): checkpoint P8/C avant chantier Codex inter-agents
Sauvegarde de l'arbre de travail en cours (persistance P8, conversations C-series, write-portal frontend, médiation d'entrée) avant d'attaquer le support de la délégation inter-agents pour les profils Codex. Le round-trip inter-agent question/réponse est couvert sans tokens par les tests loopback existants (state::mcp_e2e_loopback_tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -3,11 +3,14 @@
|
||||
* {@link TerminalGateway} port (or a custom opener), and fits it to its container:
|
||||
*
|
||||
* - PTY output (gateway `onData`) → `term.write(bytes)`.
|
||||
* - xterm `onData` (keystrokes) → `handle.write(bytes)` **only for a plain
|
||||
* (non-agent) cell** (a raw shell). In **agent mode** (`agentMode`, lot F2,
|
||||
* cadrage §4.2) keystrokes are NOT written to the PTY: input is mediated by
|
||||
* IdeA through {@link MediatedInput}. The PTY **output** path stays live and
|
||||
* INCHANGED in both modes (xterm is always the raw output view).
|
||||
* - xterm `onData` (keystrokes) → `handle.write(bytes)` in **both** modes: the
|
||||
* agent cell is a **native terminal** (ARCHITECTURE §20), so human keystrokes
|
||||
* (Enter included) reach the PTY unconditionally, exactly like a raw shell.
|
||||
* In agent mode the keystrokes are additionally reported to the write-portal
|
||||
* (a {@link WritePortal} supplied via `portal`) which (a) keeps a *human line*
|
||||
* counter (+1 per printable, reset on Enter/Ctrl-C) and (b) can momentarily
|
||||
* **suspend** the keystroke relay while it injects a delegation. The PTY
|
||||
* **output** path stays live and unchanged in both modes.
|
||||
* - container resize (fit addon) → `handle.resize(rows, cols)`.
|
||||
*
|
||||
* Pure presentation: it only knows the port, never `invoke()`/`Channel`
|
||||
@ -40,6 +43,7 @@ import type {
|
||||
OpenTerminalOptions,
|
||||
ReattachResult,
|
||||
TerminalHandle,
|
||||
WritePortal,
|
||||
} from "@/ports";
|
||||
|
||||
interface TerminalViewProps {
|
||||
@ -76,14 +80,21 @@ interface TerminalViewProps {
|
||||
*/
|
||||
onSessionId?: (sessionId: string) => void;
|
||||
/**
|
||||
* Agent mode (lot F2, cadrage §4.2). When `true` the cell hosts an agent, so
|
||||
* keystrokes (`term.onData`) are **not** forwarded to the PTY — input is
|
||||
* mediated by IdeA through {@link MediatedInput} rendered under the terminal.
|
||||
* The PTY **output** stays live and unchanged (xterm remains the raw output
|
||||
* view). When `false`/absent the cell is a plain shell: keystrokes go straight
|
||||
* to the PTY (current behaviour). Defaults to `false`.
|
||||
* Agent mode (ARCHITECTURE §20). When `true` the cell hosts an agent and the
|
||||
* terminal is **native**: keystrokes (`term.onData`) reach the PTY exactly
|
||||
* like a plain shell, AND are reported to the {@link WritePortal} (`portal`)
|
||||
* for line counting / suspension. When `false`/absent the cell is a plain
|
||||
* shell with no portal. Defaults to `false`.
|
||||
*/
|
||||
agentMode?: boolean;
|
||||
/**
|
||||
* The write-portal for this agent cell (ARCHITECTURE §20). Only meaningful in
|
||||
* agent mode: it receives keystroke reports, gates the relay via
|
||||
* {@link WritePortal.isSuspended}, and is given the live handle so it can
|
||||
* inject delegations through the same single PTY writer. Absent for plain
|
||||
* cells.
|
||||
*/
|
||||
portal?: WritePortal;
|
||||
}
|
||||
|
||||
export function TerminalView({
|
||||
@ -93,6 +104,7 @@ export function TerminalView({
|
||||
sessionId,
|
||||
onSessionId,
|
||||
agentMode = false,
|
||||
portal,
|
||||
}: TerminalViewProps) {
|
||||
const { terminal } = useGateways();
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -116,6 +128,8 @@ export function TerminalView({
|
||||
terminalRef.current = terminal;
|
||||
const agentModeRef = useRef(agentMode);
|
||||
agentModeRef.current = agentMode;
|
||||
const portalRef = useRef(portal);
|
||||
portalRef.current = portal;
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
@ -151,14 +165,22 @@ export function TerminalView({
|
||||
let handle: TerminalHandle | null = null;
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Keystroke → PTY path. In **agent mode** (F2) keystrokes are mediated by
|
||||
// IdeA (MediatedInput) and must NOT reach the PTY here; we drop them so the
|
||||
// raw terminal is output-only for the human. In **plain mode** keystrokes go
|
||||
// straight to the PTY (raw shell), buffering any that arrive before the PTY
|
||||
// finished opening. The output path below is unchanged in both modes.
|
||||
// Keystroke → PTY path. The agent cell is a **native terminal**
|
||||
// (ARCHITECTURE §20): keystrokes reach the PTY exactly like a plain shell.
|
||||
// In agent mode we additionally (1) report the keystroke to the write-portal
|
||||
// for line counting, and (2) honour the portal's `isSuspended()` flag, which
|
||||
// is raised while the portal injects a delegation so the human keystroke does
|
||||
// not race the injected text. Keystrokes arriving before the PTY opened are
|
||||
// buffered. The output path below is unchanged in both modes.
|
||||
let pending = "";
|
||||
const onKey = term.onData((data) => {
|
||||
if (agentModeRef.current) return;
|
||||
const portal = portalRef.current;
|
||||
if (agentModeRef.current && portal) {
|
||||
// Always report for counting (even while suspended — the portal decides
|
||||
// what counts), then drop the relay while the portal is injecting.
|
||||
portal.onHumanData(data);
|
||||
if (portal.isSuspended()) return;
|
||||
}
|
||||
if (handle) void handle.write(encoder.encode(data));
|
||||
else pending += data;
|
||||
});
|
||||
@ -176,6 +198,9 @@ export function TerminalView({
|
||||
return;
|
||||
}
|
||||
handle = h;
|
||||
// Hand the live handle to the write-portal so it can inject delegations
|
||||
// through the SAME single PTY writer (no second physical writer).
|
||||
if (agentModeRef.current) portalRef.current?.bindHandle(h);
|
||||
if (pending) {
|
||||
void h.write(encoder.encode(pending));
|
||||
pending = "";
|
||||
@ -268,6 +293,7 @@ export function TerminalView({
|
||||
if (rafId) cancelAnimationFrame(rafId);
|
||||
ro.disconnect();
|
||||
onKey.dispose();
|
||||
portalRef.current?.unbindHandle();
|
||||
// DETACH, never close: tearing the view down (navigation / layout change)
|
||||
// must leave the backend PTY running so the AI isn't cut off. Killing the
|
||||
// PTY is an explicit user action handled elsewhere.
|
||||
|
||||
Reference in New Issue
Block a user