Skip to content

TUI interaktiver Modus

源码版本v0.73.1

InteractiveMode ist pi's default-Laufmodus - ein vollbild-TUI, das im Terminal Dutzende Komponenten rendert: Konversation, Werkzeugausführung, Streaming-Output, Statusleiste, Editor, Skill-Auswahl, Modellauswahl. Diese Datei hat 5493 Zeilen und ist die längste Einzeldatei in pi-mono, weil sie die gesamte UI-Interaktionslogik (Keybindings, Befehls-Verteilung, Event-Rendering, Extension-UI-Integration, auto-compaction, auto-retry, Bild-Paste, File-Drag-Drop) in einer Klasse konzentriert. Dieser Text behandelt nur Eingang und Render-Hauptstamm, die detaillierte Befehlsverarbeitung liegt in den jeweiligen handle*Command-Methoden.

Verantwortung

  1. UI-Zusammenbau: In init() werden header / chatContainer / pendingMessagesContainer / statusContainer / editorContainer / footer als Container ans TUI gehängt, der Fokus auf editor gesetzt, ui.start() gestartet. Siehe packages/coding-agent/src/modes/interactive/interactive-mode.ts:559-650.
  2. Editor-Submit-Verteilung: defaultEditor.onSubmit ist der UI-Haupteingang, erkennt /-Slash-Befehle (/settings, /model, /export, /import, /fork, /new, /compact usw.), nicht-Befehls-Text geht an session.prompt. Siehe packages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2625.
  3. AgentSessionEvent-Rendering: handleEvent switch verarbeitet agent_start, queue_update, assistant_message, tool_call, tool_result, compaction, error und mehr als zehn Event-Typen und aktualisiert synchron UI-Komponenten. Siehe packages/coding-agent/src/modes/interactive/interactive-mode.ts:2627-2980.
  4. Keybindings: setupKeyHandlers registriert Ctrl+C, Ctrl+D, Ctrl+Z, Alt+Enter (followUp), Ctrl+P (cycle model), Ctrl+T (cycle thinking) usw., siehe packages/coding-agent/src/modes/interactive/interactive-mode.ts:2352-2417.
  5. bash-Modus: !-Präfix triggert handleBashCommand, das Ergebnis wird über BashExecutionComponent gerendert; !!-Präfix setzt excludeFromContext: true. Siehe packages/coding-agent/src/modes/interactive/interactive-mode.ts:5364-5450.
  6. Extension-UI-Integration: rebindSession ruft neu session.bindExtensions auf und stellt uiContext bereit, Extensions können widget, dialog, status injizieren, siehe packages/coding-agent/src/modes/interactive/interactive-mode.ts:1812-1870.

Entwurfsmotivation

Warum 5493 Zeilen nicht aufteilen? Weil diese Klasse das Zentrum der Zustandsmaschine ist - Dutzende Felder (streamingComponent, pendingTools, autoCompactionLoader, retryLoader, retryCountdown, pendingBashComponents, skillCommands) beeinflussen sich gegenseitig im selben Event-Stream; aufgespalten in mehrere kleine Klassen würde sich der Zustand verstreuen, Synchronisation über Klassengrenzen würde schwerer. pi entscheidet sich dafür, den gesamten UI-Zustand in einer Klasse zu konzentrieren und über das große switch handleEvent einheitlich zu verarbeiten - der Code wird lang, aber die Zustandsflüsse bleiben klar.

Die kleine Hilfsfunktion isExpandable (packages/coding-agent/src/modes/interactive/interactive-mode.ts:142-144) ist eine Duck-Typing-Prüfung der Render-Schicht: Jede Komponente mit einer setExpanded-Methode kann ein-/ausgeklappt werden. So teilen sich ExpandableText, header und Werkzeug-Output dieselbe Aufklapp-Logik, ohne von einer gemeinsamen abstrakten Basisklasse zu erben.

Die Unterscheidung zwischen defaultEditor.onSubmit und this.editor.onSubmit: defaultEditor ist die feste Instanz, this.editor ist der aktuell aktive Editor (eventuell ein von einer Extension injizierter Custom-Editor). setupEditorSubmitHandler hängt den Handler nur am defaultEditor ein, aber der Handler liest intern this.editor für den aktuellen Text, damit ein eingewechselter Custom-Editor dieselbe Befehlsverteilung weiter nutzt.

Wichtige Dateien

defaultEditor.onSubmit ist der Kern der Befehlsverteilung, viele if (text === "/xxx") in Reihe:

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;
	}
	// ... weitere Dutzend Befehlszweige ...

Alt+Enter reiht beim Streaming followUp ein, außerhalb wird es zum normalen Submit:

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);
}

Datenfluss

Bidirektionaler Fluss von UI-Eingabe zu Event-Rendering:

Grenzen und Fehler

Zusammenfassung

InteractiveMode ist das TUI-Zentrum von pi, 5493 Zeilen konzentrieren den gesamten UI-Zustand und die Befehlsverteilung. defaultEditor.onSubmit ist der Eingang, handleEvent der Render-Hauptstamm. Wie die Runtime zusammengebaut wird, siehe Session switch/fork/import; die Event-Quelle AgentSession siehe AgentSession Orchestrierungsschicht; die nicht-interaktiven Modi siehe print und rpc Modi.