Modo interactivo TUI
InteractiveMode es el modo por defecto de pi: una TUI a pantalla completa que renderiza en la terminal la conversación, la ejecución de herramientas, el streaming, la barra de estado, el editor, el selector de skills, el selector de modelos y decenas de componentes más. Este archivo tiene 5493 líneas, el archivo individual más largo de todo pi-mono, porque concentra en una sola clase toda la lógica de interacción UI (keybindings, dispatch de comandos, render de eventos, integración de UI de extensiones, auto-compaction, auto-retry, pegado de imágenes, drag&drop de archivos). Esta página sólo cubre la entrada y el tronco de render; el detalle del procesamiento de comandos está en sus respectivos métodos handle*Command.
Responsabilidades
- Ensamblaje UI: en
init()se cuelgan contenedores header / chatContainer / pendingMessagesContainer / statusContainer / editorContainer / footer alTUI, se enfoca eleditory se arrancaui.start(). Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:559-650. - Dispatch de envío del editor:
defaultEditor.onSubmites la entrada principal de UI, reconoce comandos slash (/settings,/model,/export,/import,/fork,/new,/compact, etc.); el texto que no es comando va asession.prompt. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2625. - Render de AgentSessionEvent:
handleEventcon un switch procesaagent_start,queue_update,assistant_message,tool_call,tool_result,compaction,errory una decena de tipos más, actualizando sincrónicamente los componentes UI. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2627-2980. - Keybindings:
setupKeyHandlersregistra Ctrl+C, Ctrl+D, Ctrl+Z, Alt+Enter (followUp), Ctrl+P (cycle model), Ctrl+T (cycle thinking), etc. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2352-2417. - Modo bash: el prefijo
!disparahandleBashCommand, el resultado se renderiza conBashExecutionComponent; el prefijo!!fijaexcludeFromContext: true. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:5364-5450. - Integración de UI de extensiones:
rebindSessionvuelve a llamarsession.bindExtensionspara proporcionaruiContext, permitiendo a las extensiones inyectar widget, dialog, status. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:1812-1870.
Motivación de diseño
¿Por qué no partir las 5493 líneas? Porque esta clase es el centro de la máquina de estados: decenas de campos (streamingComponent, pendingTools, autoCompactionLoader, retryLoader, retryCountdown, pendingBashComponents, skillCommands) se influyen mutuamente en el mismo flujo de eventos; si se divide en clases pequeñas, el estado se dispersa y la sincronización entre clases se vuelve más difícil. La elección de pi es concentrar todo el estado UI en una clase y procesarlo uniformemente con el switch grande de handleEvent: el código queda largo pero el flujo de estado es claro.
isExpandable, una pequeña utilidad (packages/coding-agent/src/modes/interactive/interactive-mode.ts:142-144), es la comprobación duck typing del render: cualquier componente con método setExpanded puede plegarse/desplegarse. Esto deja que ExpandableText, el header y la salida de herramientas compartan la misma lógica sin heredar de una misma base abstracta.
La distinción entre defaultEditor.onSubmit y this.editor.onSubmit: defaultEditor es la instancia fija; this.editor es el editor actualmente activo (puede ser uno custom inyectado por extensión). setupEditorSubmitHandler cuelga el handler sólo en defaultEditor, pero el handler internamente lee this.editor para obtener el texto actual, de modo que un editor custom entrante también reutiliza el mismo dispatch de comandos.
Archivos clave
packages/coding-agent/src/modes/interactive/interactive-mode.ts:142-160—isExpandableduck typing y el componente de texto plegableExpandableText.packages/coding-agent/src/modes/interactive/interactive-mode.ts:213-226—InteractiveModeOptions:migratedProviders,modelFallbackMessage,initialMessage,initialImages,initialMessages,verbose.packages/coding-agent/src/modes/interactive/interactive-mode.ts:228-360— Declaración de campos declass InteractiveMode, incluyendo streamingComponent, pendingTools, autoCompactionLoader, retryLoader, etc.packages/coding-agent/src/modes/interactive/interactive-mode.ts:559-650—init: carga fd/rg, cuelga contenedores UI, setupEditorSubmitHandler,ui.start().packages/coding-agent/src/modes/interactive/interactive-mode.ts:692-730—run: init + comprobaciones async de versión/paquete/tmux + muestra advertencias de arranque.packages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2540— Primera mitad dedefaultEditor.onSubmit:/settings,/model,/export,/import,/share,/copy,/name,/session,/fork,/clone,/tree,/login,/logout,/new,/compact.packages/coding-agent/src/modes/interactive/interactive-mode.ts:2627-2665— Inicio dehandleEventy ramasagent_start,queue_update.packages/coding-agent/src/modes/interactive/interactive-mode.ts:3334-3364—handleFollowUp: Alt+Enter en streaming va porstreamingBehavior: "followUp"; fuera de streaming degenera a onSubmit normal.
defaultEditor.onSubmit es el núcleo del dispatch, con muchos if (text === "/xxx") en cadena:
// packages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2470
this.defaultEditor.onSubmit = async (text: string) => {
text = text.trim();
if (!text) return;
// Handle commands
if (text === "/settings") {
this.showSettingsSelector();
this.editor.setText("");
return;
}
if (text === "/scoped-models") {
this.editor.setText("");
await this.showModelsSelector();
return;
}
if (text === "/model" || text.startsWith("/model ")) {
const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
this.editor.setText("");
await this.handleModelCommand(searchTerm);
return;
}
// ... decenas de ramas de comando ...Alt+Enter encola followUp en streaming y degenera a Enter normal fuera de streaming:
// packages/coding-agent/src/modes/interactive/interactive-mode.ts:3352-3363
if (this.session.isStreaming) {
this.editor.addToHistory?.(text);
this.editor.setText("");
await this.session.prompt(text, { streamingBehavior: "followUp" });
this.updatePendingMessagesDisplay();
this.ui.requestRender();
}
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
else if (this.editor.onSubmit) {
this.editor.setText("");
this.editor.onSubmit(text);
}Flujo de datos
Flujo bidireccional de entrada UI a render de eventos:
Límites y fallos
- Evento antes de inicializar:
handleEventcompruebaisInitializedal inicio; si no,await this.init(), previniendo que eventos lleguen antes que el ensamblaje UI. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2628-2630. - Detección de terminal muerta:
isDeadTerminalErrorcomprueba códigosEIO/EPIPE/ENOTCONN; al capturarse no se vuelve a intentar render, evitando avalancha. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:167-175. - Aviso de auth de suscripción Anthropic: si detecta API key con prefijo
sk-ant-oatmuestra un aviso "la facturación de auth por suscripción es distinta", con flaganthropicSubscriptionWarningShownpara no repetir. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:177-182. - Limpieza de escape de auto-retry: en el evento
agent_startse limpian el escapeHandler y el countdownLoader del retry anterior, para que el estado de retry no se filtre al siguiente turno. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2641-2653. - Cambio de editor custom:
this.editorpuede pasar dedefaultEditora uno proporcionado por extensión, pero los callbacksonSubmit/onChangesiguen apuntando a la implementación dedefaultEditor, garantizando comportamiento consistente. Verpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2182-2227.
Resumen
InteractiveMode es el centro TUI de pi: 5493 líneas que gestionan todo el estado UI y el dispatch de comandos. defaultEditor.onSubmit es la entrada; handleEvent es el tronco de render. El runtime que lo ensambla en sesión switch/fork/import; la fuente de eventos AgentSession en capa de orquestación AgentSession; los modos no interactivos en modos print y rpc.