Mode interactif TUI
InteractiveMode est le mode d'exécution par défaut de pi — un TUI plein écran qui rend la conversation, l'exécution des outils, le streaming, la barre d'état, l'éditeur, le sélecteur de skills, le sélecteur de modèle et des dizaines d'autres composants dans le terminal. Ce fichier fait 5493 lignes, le plus long fichier unique de tout pi-mono, parce qu'il concentre toute la logique d'interaction UI (key bindings, dispatch de commandes, rendu d'événements, intégration UI des extensions, auto-compaction, auto-retry, collage d'image, drag-and-drop de fichiers) dans une seule classe. Cet article ne couvre que l'entrée et le tronc de rendu ; le traitement détaillé des commandes est dans les méthodes handle*Command respectives.
Responsabilités
- Assemblage UI : dans
init(), on attache les conteneurs header / chatContainer / pendingMessagesContainer / statusContainer / editorContainer / footer auTUI, on met le focus sureditor, puis on lanceui.start(). Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:559-650. - Dispatch des soumissions de l'éditeur :
defaultEditor.onSubmitest l'entrée principale de l'UI, il reconnaît les slash commandes/(/settings,/model,/export,/import,/fork,/new,/compact…), et les textes hors commande partent sursession.prompt. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2625. - Rendu des AgentSessionEvent :
handleEventfait un switch sur une dizaine de types d'événements (agent_start,queue_update,assistant_message,tool_call,tool_result,compaction,error…), met à jour les composants UI en sync. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2627-2980. - Key bindings :
setupKeyHandlersenregistre Ctrl+C, Ctrl+D, Ctrl+Z, Alt+Enter (followUp), Ctrl+P (cycle model), Ctrl+T (cycle thinking), etc. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2352-2417. - Mode bash : le préfixe
!déclenchehandleBashCommand, le résultat est rendu viaBashExecutionComponent; le préfixe!!metexcludeFromContext: true. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:5364-5450. - Intégration UI des extensions :
rebindSessionrappellesession.bindExtensionsen fournissantuiContext, les extensions peuvent injecter widget, dialog, status. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:1812-1870.
Motifs de conception
Pourquoi ne pas découper les 5493 lignes ? Parce que cette classe est le centre de la machine à états — des dizaines de champs (streamingComponent, pendingTools, autoCompactionLoader, retryLoader, retryCountdown, pendingBashComponents, skillCommands) s'influencent mutuellement dans le même flux d'événements ; éclater en plusieurs petites classes éparpillerait l'état et la synchro inter-classes deviendrait plus dure. Le choix de pi est de concentrer tout l'état UI dans une seule classe et de tout traiter via le gros switch de handleEvent : le code est long mais les transitions d'état restent lisibles.
isExpandable, cette petite fonction utilitaire (packages/coding-agent/src/modes/interactive/interactive-mode.ts:142-144), est un check duck typing de la couche de rendu : tout composant qui a une méthode setExpanded peut être replié/déplié. Cela permet à ExpandableText, au header et aux sorties d'outil de partager une même logique de repli sans hériter d'une même classe abstraite.
La distinction defaultEditor.onSubmit vs this.editor.onSubmit : defaultEditor est l'instance fixe, this.editor est l'éditeur actuellement actif (potentiellement un éditeur custom injecté par une extension). setupEditorSubmitHandler ne monte le handler que sur defaultEditor, mais le handler lit this.editor pour obtenir le texte courant — ainsi un éditeur custom qui prend la main réutilise le même dispatch de commandes.
Fichiers clés
packages/coding-agent/src/modes/interactive/interactive-mode.ts:142-160—isExpandablecheck duck typing et composant de texte repliableExpandableText.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— déclaration des champs declass InteractiveMode: streamingComponent, pendingTools, autoCompactionLoader, retryLoader, etc.packages/coding-agent/src/modes/interactive/interactive-mode.ts:559-650— méthodeinit: charge fd/rg, monte les conteneurs UI, setupEditorSubmitHandler,ui.start().packages/coding-agent/src/modes/interactive/interactive-mode.ts:692-730— méthoderun: init + checks async de version/maj paquet/tmux + affichage des warnings de démarrage.packages/coding-agent/src/modes/interactive/interactive-mode.ts:2441-2540— première moitié 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— début dehandleEventet branchesagent_start,queue_update.packages/coding-agent/src/modes/interactive/interactive-mode.ts:3334-3364—handleFollowUp: Alt+Enter en streaming part surstreamingBehavior: "followUp", hors streaming dégrade en onSubmit ordinaire.
defaultEditor.onSubmit est le cœur du dispatch de commandes, avec une longue série de if (text === "/xxx") :
// 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;
}
// ... des dizaines d'autres branches de commande ...Alt+Enter met en file d'attente un followUp pendant le streaming, hors streaming dégrade en submit ordinaire :
// 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);
}Flux de données
Flux bidirectionnel entre entrée UI et rendu d'événements :
Limites et cas d'échec
- Événement avant initialisation : le début de
handleEventvérifieisInitialized; si non initialisé, il faitawait this.init()pour empêcher un événement d'arriver avant l'assemblage UI. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2628-2630. - Détection de terminal mort :
isDeadTerminalErrorvérifie les codes d'erreurEIO/EPIPE/ENOTCONNet cesse alors de tenter un rendu pour éviter une avalanche. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:167-175. - Warning auth Anthropic subscription : quand une clé API préfixée
sk-ant-oatest détectée, on affiche une fois un warning « la facturation par auth subscription est différente », gardé via le flaganthropicSubscriptionWarningShown. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:177-182. - Échappement de l'auto-retry : l'événement
agent_startnettoie l'escapeHandler et le countdownLoader du retry précédent, pour que l'état de retry ne fuie pas vers le tour suivant. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2641-2653. - Bascule d'éditeur custom :
this.editorpeut basculer dedefaultEditorvers un éditeur fourni par extension, mais les callbacksonSubmit/onChangepointent toujours vers l'implémentation dedefaultEditorpour garantir un comportement cohérent. Voirpackages/coding-agent/src/modes/interactive/interactive-mode.ts:2182-2227.
Synthèse
InteractiveMode est le centre TUI de pi, 5493 lignes qui concentrent tout l'état UI et le dispatch de commandes. defaultEditor.onSubmit est l'entrée, handleEvent est le tronc de rendu. Le runtime qui l'assemble est dans switch/fork/import de session, la source d'événements AgentSession est dans couche d'orchestration AgentSession, et les modes non interactifs sont dans modes print et rpc.