lazyshell

Sessions d'agents IA

Une session dont le process d'avant-plan est une CLI d'agent de code connue reçoit un marqueur indiquant son état détecté — au repos, en travail, bloquée (elle vous attend) ou terminée — au lieu du seul marqueur d'activité générique.

Le problème

Le marqueur d'activité générique () ne sait pas distinguer « ça a produit de la sortie » de « ça veut une réponse ». Avec trois agents ouverts, c'est exactement la distinction dont on a besoin : deux d'entre eux travaillent tranquillement, le troisième attend une autorisation depuis dix minutes.

MarqueurÉtatSignification
·idleAgent détecté, au repos.
workingUn tour est en cours.
blockedL'agent attend une réponse de votre part.
doneL'agent a fini son tour.

Détection sans configuration

Les CLI claude, codex et opencode sont reconnues sans aucun réglage : lazyshell confronte les manifestes intégrés (sous pkg/agent/manifests) à l'écran visible de la session et au titre de terminal qu'elle pose. Rien à installer, rien à déclarer.

Manifestes personnalisés

Déposez un fichier <nom-du-process>.yml dans ~/.config/lazyshell/agents/ (ou l'équivalent $XDG_CONFIG_HOME) pour surcharger un manifeste intégré, ou pour en ajouter un pour un autre agent : le même nom de fichier qu'un manifeste intégré le remplace purement et simplement, un nom différent s'ajoute au lot. Voir les manifestes intégrés pour le format.

Les manifestes sont locaux uniquement : lazyshell n'en télécharge jamais un depuis le réseau.

État autoritaire par hooks

La détection par manifeste est une déduction à partir de ce qui est affiché. Un second canal permet à l'agent de déclarer son état directement. Chaque session dispose de sa propre socket Unix, exposée au process qui tourne dedans sous $LAZYSHELL_SOCK (à côté de $LAZYSHELL_SESSION_ID), et lazyshell hook <état> — parmi idle, working, blocked et done — y écrit.

Ce n'est pas fait pour être tapé à la main, mais branché dans le mécanisme de hooks de l'agent :

$ lazyshell init --agents   # affiche la config à coller dans Claude Code / Codex

Claude Code

Un bloc hooks dans settings.json :

Codex

Une ligne notify dans config.toml. Codex n'a qu'un seul événement (agent-turn-complete), il ne peut donc jamais rapporter que done.

opencode

Pas encore branché : son signal le plus riche est un abonnement SSE plutôt que quelque chose qu'il pousse de lui-même — une forme d'intégration différente, laissée pour plus tard.

Dès qu'une session a reçu un seul événement de hook, la déduction par manifeste s'arrête définitivement pour cette session : le hook fait autorité à partir de là, et pas seulement jusqu'au prochain changement d'écran.

API de contrôle par les agents

Le canal de hooks ci-dessus ne permet à un agent que de se décrire. Lui permettre d'agir — créer des sessions, lire ce que les autres ont affiché, y taper, ou attendre qu'une session atteigne un état donné — est une fonctionnalité distincte, sur une socket distincte, désactivée sauf si control.enabled en décide autrement. C'est ce dont un agent « chef d'orchestre » a besoin.

lazyshell ctl list                                # id, nom, statut, état d'agent
lazyshell ctl read session-2 --tail 40            # texte brut, sans séquences d'échappement
lazyshell ctl new --name build --cwd ./api --command 'make test'
lazyshell ctl send build 'echo bonjour' --enter   # comme si c'était tapé
lazyshell ctl kill build
lazyshell ctl rename build tests

Les groupes (voir Groupes) sont lisibles et modifiables depuis ici, ce qui permet à un agent d'en orchestrer plusieurs autres comme un bloc :

lazyshell ctl list --group agents                  # seulement ce groupe
lazyshell ctl new --name w1 --group agents
lazyshell ctl group build agents                  # affecte une session à un groupe
lazyshell ctl ungroup build                       # l'en retire
lazyshell ctl group-send agents 'git pull' --enter
lazyshell ctl group-kill agents

Les deux verbes de diffusion affichent combien de sessions ils ont touchées. Un groupe vide ou inexistant est une erreur, jamais un « 0 session » silencieux : celui qui a fait une faute de frappe dans un nom de groupe ne doit pas s'entendre dire que son kill a réussi. group-send saute les sessions déjà terminées. Relancer un groupe n'est délibérément pas exposé — aucun verbe restart n'existe pour une session seule non plus ; cela reste la touche W, dans l'interface.

wait bloque jusqu'à ce qu'une session — ou, avec --group au lieu d'une cible, le premier membre de ce groupe — atteigne un état d'agent donné, au lieu de faire boucler un chef d'orchestre sur ctl list :

lazyshell ctl wait build --state blocked --timeout 300
lazyshell ctl wait --group agents --state blocked   # rend la main au premier qui bloque

--timeout est en secondes et optionnel (120 par défaut) ; un délai dépassé, une session/un groupe/un état inconnu, ou la session visée qui se termine avant d'atteindre l'état, sont tous rapportés comme un échec — la même sortie non nulle que toute autre erreur de ctl, jamais un blocage.

Une session se désigne par son id (session-2, la valeur de $LAZYSHELL_SESSION_ID) ou par son nom exact ; --json affiche la réponse brute. Contrairement à lazyshell hook, qui sort toujours en 0 pour ne jamais casser le tour d'un agent, ctl sort en code non nul au moindre échec — celui qui a demandé une session sans l'obtenir doit pouvoir le savoir. ctl new ne vole délibérément ni la sélection ni le clavier, contrairement à la touche n.

À lire avant d'activer. Il n'y a ni jeton ni permission par session : les permissions 0600 de la socket sont l'intégralité du contrôle d'accès. Activer signifie que tout processus tournant sous votre compte peut créer des sessions, y taper des commandes et lire leur sortie — pas seulement les agents que vous avez lancés dans lazyshell. Et ctl read rend le scrollback verbatim, secrets compris : le masquage de l'onglet environnement n'a pas d'équivalent ici, parce qu'un identifiant affiché dans un shell est indiscernable de n'importe quel autre texte une fois à l'écran.

Notifications

Une session qui passe blocked ou done déclenche une notification de bureau : OSC 9 et OSC 777 vers le terminal hôte par défaut — les deux sont envoyés sans condition, un terminal qui n'en comprend qu'un ignore l'autre — ou bien la commande de notify.fallback_command, avec le texte de la notification sur son entrée standard, pour un terminal qui en a besoin.

Au-delà de deux ou trois sessions d'agent ouvertes, B déplace la sélection directement vers la prochaine session blocked, en boucle : c'est le point du marqueur et de la notification réunis.

Une session sans agent notifie de la même façon quand sa dernière commande — via l'intégration shell OSC 133 — échoue, pour qu'un build ou un script long n'ait pas besoin d'être surveillé pour savoir qu'il a échoué. Une session d'agent ne déclenche jamais cette seconde notification en plus de la sienne.

Watchers de motifs

Les watchers de motifs généralisent l'idée à n'importe quelle session : un motif regex évalué sur chaque ligne de sortie, en plus de la détection par code de sortie et non à sa place — utile pour un serveur de dev qui logue une erreur sans pour autant se terminer. Déclarés dans un fichier de projet :

sessions:
  - name: api
    watch:
      - pattern: "ERR!"
        notify: true

ou armés à la volée sur la session sélectionnée avec v — un seul motif remplaçable par session, en plus de ce que le fichier de projet a déjà déclaré pour elle ; soumettre un motif vide le désarme. Dans les deux cas, une correspondance notifie par le même canal OSC/commande de secours que ci-dessus, au plus une fois toutes les 3 secondes par motif — un log qui répète la même correspondance 200 fois dans une rafale ne déclenche qu'une seule notification, pas 200. La correspondance se fait sur le texte visible (séquences d'échappement retirées) et se suspend tant qu'une application plein écran (vim, htop) tient la session, la même règle d'écran alterné que suit déjà OSC 133.

Durée de tour et statistiques

Une session en plein tour (working) affiche depuis combien de temps ce tour tourne, dans la liste des sessions — par exemple ⏱ 1m32s.

Renseigner agent_stats_command exécute cette commande pour la session sélectionnée seulement (au plus une fois toutes les 5 secondes : c'est prévu pour une recherche de coût ou de tokens, pas pour quelque chose d'assez léger pour tourner par session à chaque tick), avec $LAZYSHELL_SESSION_ID dans son environnement, et affiche sa première ligne de sortie à côté de la durée — la même forme « commande externe, on montre sa ligne » que le statusLine de Claude Code.

# ~/.config/lazyshell/config.yml
agent_stats_command: "~/bin/mon-compteur-de-tokens.sh"

Ce que lazyshell ne fait pas