feat(terminals): reprise de conversation par cellule + fix ordre d'écriture
Permet de recharger la conversation CLI précédente de chaque cellule à la
réouverture du projet, de façon universelle (indépendant du modèle/CLI).
- profil AgentRuntime: bloc déclaratif optionnel `session { assignFlag, resumeFlag }`
- LeafCell: `conversationId` (persistant, distinct du SessionId PTY) + `agentWasRunning`
- runtime: SessionPlan (None/Assign/Resume) + composition pure des args
- LaunchAgent: décide Assign vs Resume, génère l'UUID, remonte l'id assigné
(persistance par l'appelant via setCellConversation — découplage SRP)
- close: SnapshotRunningAgents fige `agentWasRunning` avant le kill-all
(statut clot/en cours universel, sans parsing CLI)
- SessionInspector: port optionnel best-effort + adapter ClaudeTranscriptInspector
- popup de reprise par cellule (statut + sujet/tokens si dispo), intercalée
avant le Resume auto, jamais sur le chemin reattach
fix(terminals): sérialise les écritures PTY (file FIFO par handle) — corrige
les caractères mélangés/accents dus au réordonnancement des invoke Tauri concurrents
fix(layout): l'opération `move` préservait mal les champs du leaf (perdait `agent`)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -52,6 +52,18 @@ export interface CreateAgentInput {
|
||||
initialContent?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort enriched details about a CLI conversation (T7), used to enrich the
|
||||
* resume popup. Both fields are optional: a missing inspector or a missing
|
||||
* transcript yields an empty object — the popup degrades to the status alone.
|
||||
*/
|
||||
export interface ConversationDetails {
|
||||
/** A short, best-effort label for the conversation (last topic). */
|
||||
lastTopic?: string;
|
||||
/** A best-effort cumulative token count. */
|
||||
tokenCount?: number;
|
||||
}
|
||||
|
||||
/** Agents: create, list, read/update context, delete, launch (L6). */
|
||||
export interface AgentGateway {
|
||||
/** Lists all agents belonging to the given project. */
|
||||
@ -67,6 +79,13 @@ export interface AgentGateway {
|
||||
/**
|
||||
* Launches the agent: opens a PTY, spawns the CLI, and wires the output stream.
|
||||
* Mirrors {@link TerminalGateway.openTerminal} but invokes `launch_agent`.
|
||||
*
|
||||
* The returned handle carries an optional {@link TerminalHandle.assignedConversationId}:
|
||||
* when the agent's profile assigns a fresh CLI conversation id on this first
|
||||
* launch (the cell had none yet), it is surfaced here so the caller can persist
|
||||
* it on the hosting leaf (`setCellConversation`) and resume on the next open.
|
||||
* Pass the leaf's current `conversationId` via {@link OpenTerminalOptions} to
|
||||
* resume an existing conversation instead.
|
||||
*/
|
||||
launchAgent(
|
||||
projectId: string,
|
||||
@ -84,6 +103,17 @@ export interface AgentGateway {
|
||||
sessionId: string,
|
||||
onData: (bytes: Uint8Array) => void,
|
||||
): Promise<ReattachResult>;
|
||||
/**
|
||||
* Reads best-effort {@link ConversationDetails} for an agent's conversation
|
||||
* (T7), to enrich the resume popup with the last topic + a token indicator.
|
||||
* Best-effort by contract: a missing/unsupported inspector or a missing
|
||||
* transcript resolves to an empty object (never rejects for "no details").
|
||||
*/
|
||||
inspectConversation(
|
||||
projectId: string,
|
||||
agentId: string,
|
||||
conversationId: string,
|
||||
): Promise<ConversationDetails>;
|
||||
}
|
||||
|
||||
/** Options for opening a terminal. */
|
||||
@ -94,6 +124,13 @@ export interface OpenTerminalOptions {
|
||||
rows: number;
|
||||
/** Initial terminal width in columns. */
|
||||
cols: number;
|
||||
/**
|
||||
* Persistent CLI conversation id recorded on the hosting cell, if any. When
|
||||
* present, an agent launch **resumes** that conversation; when absent the
|
||||
* launch may *assign* a fresh id (surfaced on the returned handle). Only
|
||||
* meaningful for {@link AgentGateway.launchAgent}; ignored for plain terminals.
|
||||
*/
|
||||
conversationId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,6 +143,14 @@ export interface OpenTerminalOptions {
|
||||
export interface TerminalHandle {
|
||||
/** Stable session id (UUID) used by the backend for this PTY. */
|
||||
readonly sessionId: string;
|
||||
/**
|
||||
* Conversation id **assigned** by an agent launch when the profile minted a
|
||||
* fresh one (the cell had none yet). Present only on handles returned by
|
||||
* {@link AgentGateway.launchAgent}; `undefined` for plain terminals, resumes,
|
||||
* or profiles without a session block. The caller persists it on the hosting
|
||||
* leaf so the next open resumes instead of re-assigning.
|
||||
*/
|
||||
readonly assignedConversationId?: string;
|
||||
/** Sends bytes (xterm keystrokes) to the PTY. */
|
||||
write(data: Uint8Array): Promise<void>;
|
||||
/** Resizes the PTY. */
|
||||
@ -152,6 +197,17 @@ export interface TerminalGateway {
|
||||
sessionId: string,
|
||||
onData: (bytes: Uint8Array) => void,
|
||||
): Promise<ReattachResult>;
|
||||
/**
|
||||
* Kills a live PTY by its session id, independently of any view-held handle.
|
||||
* Used when a cell's agent changes: the old PTY must be torn down even though
|
||||
* the owning {@link TerminalHandle} is private to its (unmounting) view, whose
|
||||
* cleanup only ever {@link TerminalHandle.detach}es. Best-effort: resolves even
|
||||
* if the session is already gone.
|
||||
*
|
||||
* This is the only sanctioned way to kill a PTY outside
|
||||
* {@link TerminalHandle.close}.
|
||||
*/
|
||||
closeTerminal(sessionId: string): Promise<void>;
|
||||
}
|
||||
|
||||
/** The outcome of {@link TerminalGateway.reattach}. */
|
||||
|
||||
Reference in New Issue
Block a user