Skip to content

Modo interactivo TUI

源码版本v0.73.1

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

  1. Ensamblaje UI: en init() se cuelgan contenedores header / chatContainer / pendingMessagesContainer / statusContainer / editorContainer / footer al TUI, se enfoca el editor y se arranca ui.start(). Ver packages/coding-agent/src/modes/interactive/interactive-mode.ts:559-650.
  2. Dispatch de envío del editor: defaultEditor.onSubmit es la entrada principal de UI, reconoce comandos slash (/settings, /model, /export, /import, /fork, /new, /compact, etc.); el texto que no es comando va a session.prompt. Ver packages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2625.
  3. Render de AgentSessionEvent: handleEvent con un switch procesa agent_start, queue_update, assistant_message, tool_call, tool_result, compaction, error y una decena de tipos más, actualizando sincrónicamente los componentes UI. Ver packages/coding-agent/src/modes/interactive/interactive-mode.ts:2627-2980.
  4. Keybindings: setupKeyHandlers registra Ctrl+C, Ctrl+D, Ctrl+Z, Alt+Enter (followUp), Ctrl+P (cycle model), Ctrl+T (cycle thinking), etc. Ver packages/coding-agent/src/modes/interactive/interactive-mode.ts:2352-2417.
  5. Modo bash: el prefijo ! dispara handleBashCommand, el resultado se renderiza con BashExecutionComponent; el prefijo !! fija excludeFromContext: true. Ver packages/coding-agent/src/modes/interactive/interactive-mode.ts:5364-5450.
  6. Integración de UI de extensiones: rebindSession vuelve a llamar session.bindExtensions para proporcionar uiContext, permitiendo a las extensiones inyectar widget, dialog, status. Ver packages/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

defaultEditor.onSubmit es el núcleo del dispatch, con muchos if (text === "/xxx") en cadena:

typescript
// 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:

typescript
// 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

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.