feat(frontend): surface agent web sur WebSocket (cellule agent) (#13)

Lot F4 du chantier server/client mode : le client web expose une cellule
agent branchée sur le PTY WebSocket, en face de agent.launch (B6).

- wsLiveClient.ts : launchAgent sur le transport WebSocket.
- streamGateways.ts : gateway agent alignée sur le contrat serveur B6.
- WebAgentCell.tsx : cellule agent web, câblée dans WebWorkspace et index.
- Tests : agentGateway.test.ts, WebApp.test.tsx.

Validé : frontend 749 tests verts, contrat B6↔F4 aligné (aucun écart de
frame), desktop non régressé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 23:17:24 +02:00
parent 6391d1c75a
commit 5254f16095
7 changed files with 359 additions and 32 deletions

View File

@ -340,6 +340,38 @@ export class WsLiveClient {
);
}
/**
* Launches a CLI agent (`agent.launch`) over the same PTY channel and tracks
* it exactly like a terminal (B6): the ack is a unified `terminal.attached`
* carrying `sessionId` + `assignedConversationId`, then output/input/resize and
* reconnection replay reuse the terminal mechanics. Structured agents are
* refused server-side with `UNSUPPORTED` (this channel is PTY-only); the
* rejected `send` surfaces that error to the caller unchanged.
*/
launchAgent(params: {
projectId: string;
agentId: string;
rows: number;
cols: number;
nodeId?: string | null;
conversationId?: string | null;
onData: (bytes: Uint8Array) => void;
onStatus?: (status: string, exitCode?: number | null) => void;
}): Promise<TerminalAttachResult> {
return this.attachInternal(
"agent.launch",
{
projectId: params.projectId,
agentId: params.agentId,
nodeId: params.nodeId ?? null,
rows: params.rows,
cols: params.cols,
conversationId: params.conversationId ?? null,
},
{ onData: params.onData, onStatus: params.onStatus, lastSeq: 0 },
);
}
/** Re-attaches to an existing terminal (`terminal.attach`) and tracks it. */
attachTerminal(params: {
sessionId: string;