Entrée et dispatch CLI
main.ts est le point d'entrée du processus pour la commande pi. Il parse les arguments en AppMode, assemble AgentSessionRuntime, puis dispatch vers l'un des trois modes d'exécution : TUI interactif, print one-shot, ou JSON-RPC. Au-delà du dispatch de mode, il gère aussi les migrations, la résolution de session, le choix du modèle et le pré-traitement des arguments de type fichier. C'est en quelque sorte la couche frontale qui « traduit l'invocation shell en appel runtime ».
Responsabilités
- Parsing et dispatch :
parseArgsproduitArgs,resolveAppModecalculeAppMode, et on choisit entrerunRpcMode/InteractiveMode/runPrintMode. Voirpackages/coding-agent/src/main.ts:98-109etpackages/coding-agent/src/main.ts:673-726. - Résolution de session : gestion des conflits entre
--continue,--resume,--session <id|path>,--fork <id>,--no-session, et résolution du chemin pour localiser le.jsonlsur disque. Voirpackages/coding-agent/src/main.ts:188-212etpackages/coding-agent/src/main.ts:147-172. - Assemblage des services : appelle
createAgentSessionServices+createAgentSessionRuntimepour relier cwd, agentDir, authStorage, et les chemins d'extensions/skills/thèmes. Voirpackages/coding-agent/src/main.ts:522-560. - Court-circuit pré-exécution :
pi --version,pi --export,pi package ...,pi config ...ne passent pas par le runtime, ils font directementprocess.exit. Voirpackages/coding-agent/src/main.ts:431-478.
Motivation de design
Pourquoi isoler l'entrée dans un fichier dédié ? Parce que le parsing CLI, le choix de session et les migrations n'arrivent qu'une seule fois, au démarrage du processus. Mettre tout ça dans createAgentSession (l'entrée SDK) polluerait sa réutilisabilité — un appelant SDK a généralement déjà un context runtime et n'a pas besoin de parser argv ni de prendre le contrôle de stdout au niveau processus. main.ts ne gère donc que les « trivialités de niveau processus » : prise en main de stdout, traitement des signaux, migrations, vérification de version, validation des forks ; tout le reste est délégué au SDK et à la factory runtime.
Fichiers clés
packages/coding-agent/src/main.ts:54-71—readPipedStdin, lit stdin en pipe hors TTY, utilisé comme message initial du mode print.packages/coding-agent/src/main.ts:98-109—resolveAppMode, calcule l'un des quatreAppModeà partir deArgs.mode/Args.printet destdin.isTTY.packages/coding-agent/src/main.ts:423-478— début de la fonctionmain: migrations, offline, court-circuit package/config, diagnostics d'arguments.packages/coding-agent/src/main.ts:522-560— closure factorycreateRuntime, capture les chemins d'extensions/skills/thèmes issus du parsing CLI pour les passer àcreateAgentSessionServices.packages/coding-agent/src/main.ts:673-726— le triptyque de dispatch :runRpcMode/new InteractiveMode/runPrintMode.packages/coding-agent/src/cli/— répertoire du sous-module CLI, contientargs.ts,file-processor.ts,initial-message.ts,session-picker.ts,list-models.ts.
resolveAppMode regarde d'abord le flag --mode, puis retombe sur la détection TTY de stdin, de sorte que l'usage en pipe echo x | pi bascule automatiquement en mode print :
// packages/coding-agent/src/main.ts:98-109
function resolveAppMode(parsed: Args, stdinIsTTY: boolean): AppMode {
if (parsed.mode === "rpc") {
return "rpc";
}
if (parsed.mode === "json") {
return "json";
}
if (parsed.print || !stdinIsTTY) {
return "print";
}
return "interactive";
}Au point de dispatch triptyque, on choisit la branche directement depuis appMode ; print et json partagent runPrintMode, la différence se fait via toPrintOutputMode(appMode) :
// packages/coding-agent/src/main.ts:673-726
if (appMode === "rpc") {
printTimings();
await runRpcMode(runtime);
} else if (appMode === "interactive") {
// ...
const interactiveMode = new InteractiveMode(runtime, {
migratedProviders,
modelFallbackMessage,
initialMessage,
initialImages,
initialMessages: parsed.messages,
verbose: parsed.verbose,
});
// ...
await interactiveMode.run();
} else {
printTimings();
const exitCode = await runPrintMode(runtime, {
mode: toPrintOutputMode(appMode),
messages: parsed.messages,
initialMessage,
initialImages,
});
// ...
}Flux de données
Le chemin de dispatch une fois argv arrivé :
Limites et échecs
- Conflit de fork :
--forkne peut pas être combiné avec--session,--continue,--resume,--no-session. Voirpackages/coding-agent/src/main.ts:188-202. - Prise en main de stdout : en mode non interactif, on appelle
takeOverStdout()pour redirigerconsole.logvers un pipe interne, afin que les séquences d'échappement du TUI ne polluent pas la sortie print/rpc ;restoreStdoutest appelé à la sortie. - cwd de session absent : quand
--sessionpointe vers un autre projet, le mode interactif ouvre un prompt pour laisser l'utilisateur choisir, tandis que print/rpc quittent en erreur. Voirpackages/coding-agent/src/main.ts:502-514. - Mode benchmark : en
startupBenchmark, on ne fait qu'appelerinteractiveMode.init()puisstop()immédiatement, sans entrer dans la boucle principale.
Résumé
main.ts est une coquille fine : il traduit argv et stdin en une closure de factory runtime, puis confie le reste à l'un des trois modes. Pour le détail de l'assemblage interne de la factory runtime, voir Assemblage createAgentSession ; pour le mode interactif, voir Mode interactif TUI ; pour les modes non interactifs, voir Modes print et rpc.