bots-garden/sidekickspublic Fork
⑂ feature/file-preview
Commits
⬇ Clone ▾
git clone https://git.rickub.com/bots-garden/sidekicks.git
git clone ssh://git@rickub.com/bots-garden/sidekicks.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
🛟 Updated. 6069387 · on feature/file-preview · k33g · 17h ago
agents-et-evenements.md · 231 lines · 20.2 KBmarkdown
Blame HistoryOpen raw

Référence : agents, channels et événements

États d'un agent

État Affiché (en · fr) Signification
stopped stopped · arrêté Aucun processus. L'état initial de chaque agent.
starting starting… · démarre… Le processus est lancé et la poignée de main ACP (initialize) est en cours.
ready ready · prêt En marche et inoccupé.
busy busy · occupé En marche et utilisé par au moins une conversation.
error error · erreur Le démarrage a échoué, ou le processus s'est arrêté seul (crash). Le statut porte la raison, suivie des dernières lignes stderr de l'agent en cas d'échec au démarrage.

outdated (affiché « restart to apply changes », ou « à redémarrer » en français) marque un agent en marche dont la définition a changé dans la configuration ; un redémarrage l'applique.

Transitions

Depuis Événement Vers
stopped, error démarrage demandé (▶ dans le panneau Agents, ou première utilisation) starting
starting la poignée de main réussit ready
starting la poignée de main échoue, protocole autre qu'ACP v1, délai dépassé (30 s) error
ready utilisé busy
busy le dernier utilisateur a fini ready
ready, busy arrêt demandé (■), fermeture de l'application, agent retiré de la configuration stopped
ready inutilisé depuis limits.stopIdleAgentsAfter (vérifié toutes les 30 s) stopped
ready un autre agent doit démarrer et celui-ci est l'agent inoccupé utilisé le moins récemment stopped
ready, busy le processus s'arrête seul error (jamais redémarré automatiquement)
starting arrêt demandé stopped dès la fin du démarrage

Quand limits.maxRunningAgents agents tournent et qu'ils sont tous busy, un démarrage est refusé avec le message too many agents running: N agent(s) busy (…), the limit is N.

Arrêter un agent ferme son entrée standard, attend 5 secondes, puis tue son groupe de processus (l'agent et ce qu'il a lancé, comme mm sous scripts/agents.sh).

Événements du Go vers l'interface

Événement Charge Envoyé quand
config:changed {path, valid, problems[]} Le fichier de configuration a été enregistré et rechargé (valide ou non).
agent:state {agent, state, error?} Un agent change d'état ou sa définition change.
agent:log {agent, line} Un agent écrit une ligne sur sa sortie d'erreur, ou une commande de sa sandbox en affiche une (préfixée par sandbox: ).
files:dropped {target, files[]} Des fichiers du système sont déposés sur un élément marqué data-file-drop-target ; target est son id, files leurs chemins absolus.

Les noms et types Go sont dans internal/events ; les constantes et types TypeScript sont générés dans frontend/bindings/events et frontend/bindings/…/internal/events.

Services appelés par l'interface

Service Méthode Effet
AgentService List() Tous les agents avec name, title, icon, description, state, error, outdated, runtime, canStopSandbox, canRemoveSandbox, dans l'ordre de la configuration.
Start(name), Stop(name), Restart(name) Comme dans les transitions ci-dessus ; une erreur est renvoyée à l'interface. Un démarrage recrée d'abord une sandbox absente si runtime.lifecycle.create est défini.
StopSandbox(name), RemoveSandbox(name) Arrête l'agent, puis lance runtime.lifecycle.stop ou remove (conteneur ou sandbox) ; la sortie va dans le journal de l'agent.
Logs(name) Les 200 dernières lignes stderr de l'agent.
ConfigService Current() path, source, created, valid, problems[], warnings[], theme, language, historyProblem.
SystemService Memory() level (normal, warning, critical), used et total en octets ; tout vide (total à 0) là où la mémoire ne peut pas être mesurée. macOS : le niveau de kern.memorystatus_vm_pressure_level, utilisée = mémoire des apps + résidente + compressée (vm_stat), comme dans Moniteur d'activité. Linux et Windows : déduit de la mémoire disponible, warning sous 20 %, critical sous 10 %. Le panneau Log l'appelle toutes les 5 secondes tant qu'il est ouvert (issue #61).

Channels (milestones M2 et M3)

Un channel contient une conversation entre l'utilisateur et un ou plusieurs agents. Une conversation directe est un channel à un seul membre, sans nom, dont direct vaut le nom de son agent. Les channels, les membres et les messages terminés sont gardés dans l'historique du fichier de configuration (ci-dessous) ; une réponse en cours est gardée quand elle se termine.

Channels

Champ Signification
id identifiant triable
name, description 1 à 64 caractères, unique sans tenir compte de la casse ; vide pour une conversation directe
direct l'agent d'une conversation directe ; vide pour un channel
members noms des agents, dans l'ordre d'arrivée

Qui répond à un message

Le message Répondu par
/agent:commande arguments (agent est un agent de la configuration) cet agent, qui reçoit /commande arguments
contient des mentions @nom d'agents de la configuration les agents mentionnés, l'un après l'autre, dans l'ordre des mentions ; un agent mentionné qui n'est pas membre rejoint d'abord le channel
sans mention le dernier agent qui a répondu dans le channel ; avec un seul membre, ce membre
sans mention, personne n'a encore répondu, plusieurs membres personne : message système no-recipient
tout message d'une conversation directe son agent ; les mentions y sont du simple texte

Un message qui commence par / (éventuellement après des mentions) est une commande : il est envoyé seul. Tout autre message est envoyé avec les messages du channel que l'agent n'a pas encore vus (au plus les 20 derniers, chacun coupé à 1500 caractères). Un agent répond à un seul channel à la fois ; un channel qui l'attend indique à qui c'est le tour.

Fichiers joints

Un $chemin en début de mot joint le fichier chemin du dossier d'un agent (issue #53) : en conversation directe, celui de son agent ; dans un channel, celui du premier agent mentionné avec @, sinon celui du premier membre. Un $ dans du code, après un autre caractère (5$), échappé (\$) ou suivi d'aucune lettre ($5) reste du texte ; la ponctuation qui termine une phrase ne fait pas partie du chemin. Un chemin s'arrête au premier espace.

Chaque agent qui répond reçoit les fichiers avec le message (pas avec une commande slash), sous forme de blocs ACP choisis selon ce qu'il a annoncé :

Fichier promptCapabilities Envoyé comme
image (image/*) image bloc image (base64), avec son URI
texte, 1 Mo au plus embeddedContext bloc resource avec le texte
tout autre texte ou image — resource_link : URI file:// + le chemin tel que l'agent le voit, nom, type, taille
ni texte ni image — non envoyé : attachment-unavailable

Un fichier est lu quand vient le tour de l'agent (5 Mo au plus) ; un fichier illisible donne attachment-unavailable, et l'agent répond sans lui.

Messages

Champ Signification
id, channel, createdAt identité
agent l'auteur d'un message d'agent, ou l'agent dont parle un message système
role user, agent ou system
parts text (Markdown), thought (raisonnement), tool (un appel d'outil), plan (étapes d'un plan)
done faux pendant qu'un message d'agent s'affiche
queued un message de l'utilisateur qui attend la fin des réponses en cours
stopReason comment un message d'agent s'est terminé : end_turn, cancelled, max_tokens, max_turn_requests, refusal
error pourquoi un message d'agent s'est terminé sans raison d'arrêt (l'explication de l'agent), ou cancelled pour un message en attente abandonné par Stop
code messages système : agent-joined, agent-left, no-recipient, agent-unavailable (impossible de le démarrer ou de le joindre ; error dit pourquoi), attachment-unavailable (un fichier joint n'a pas été envoyé à agent ; error le nomme et dit pourquoi)
attachments messages de l'utilisateur : les fichiers joints avec $chemin, sous la forme {agent, path} (l'agent dont le dossier le contient). Seule la référence est gardée, jamais le contenu.

Un appel d'outil a id, title, kind (read, edit, delete, move, search, execute, think, fetch, other), status (pending, in_progress, completed, failed), input (JSON compact), output, locations, diffs (path, oldText, newText, newFile).

Événements

Événement Contenu Envoyé quand
channels:changed {channels[]} un channel est créé, renommé ou supprimé, ou ses membres changent
message:updated {channel, message} un message est ajouté, ou terminé
message:delta {channel, messageId, index, kind, text?, part?} text s'ajoute à la partie index (texte, raisonnement), ou part la remplace (outil, plan)
turn:changed {channel, busy, queued, answering} le channel commence ou arrête de répondre, ou le tour d'un autre agent commence ; queued messages attendent
permission:requested {id, channel, agent, messageId, toolCall, options[], rejections} un agent demande à utiliser un outil ; rejections compte les refus déjà donnés pendant ce tour
permission:resolved {id, channel} la demande a reçu une réponse, ou a été annulée par Stop
usage:changed {channel, agent, usage: {used, size}} un agent a indiqué l'occupation de sa fenêtre de contexte (size vaut 0 si elle est inconnue)
commands:changed {agent, commands[]} un agent a annoncé ses commandes slash (name, description, hint)

ChannelService

Méthode Effet
List() Tous les channels et conversations directes, du plus ancien au plus récent.
Create(name, description) Crée un channel. Un nom déjà pris ou vide est une erreur.
Rename(id, name, description) Renomme un channel (pas une conversation directe).
Delete(id) Arrête ses réponses, puis supprime le channel et tout son historique.
Clear(id) Efface tous les messages d'un channel ou d'une conversation directe et garde son nom, sa description et ses membres. Les sessions des membres sont oubliées (sessionId et lastSeen vidés) : leur prochain tour ouvre une nouvelle session, qui ne sait rien de ce qui a été effacé ; l'usage est remis à zéro. Refusé tant que le channel répond ou a des messages en attente (an agent is answering in this channel…). Envoie channels:changed.
AddMember(id, agent), RemoveMember(id, agent) Invite ou retire un agent ; le channel affiche agent-joined ou agent-left.
Direct(agent) La conversation directe avec agent, créée la première fois.
Send(id, text) Ajoute un message de l'utilisateur et renvoie son id ; les agents qui répondent sont démarrés si besoin ; le message attend si le channel répond déjà. Un texte vide ou un channel inconnu sont des erreurs. Chaque $chemin joint un fichier du dossier d'un agent (voir Fichiers joints) ; plus de 10 est une erreur.
Cancel(id) Arrête la réponse en cours (session/cancel), les agents qui devaient encore répondre et les messages en attente ; répond « cancelled » aux demandes de permission en attente.
AnswerPermission(id, optionId) Répond à une demande en attente avec l'une de ses options.
Conversation(id) channel, messages (les 100 derniers), more, busy, queued, answering, usage{agent: …}, permissions.
Older(id, beforeId) {messages, more} : les 100 messages précédant beforeId.
Commands(agent) Les dernières commandes annoncées par l'agent, gardées d'un lancement à l'autre ; vide s'il n'en a jamais annoncé.
Prepare(id) Démarre les membres du channel qui n'ont jamais annoncé leurs commandes et ouvre leur session pour ce channel (aucun modèle n'est chargé) ; rend la main une fois les sessions ouvertes, les commandes arrivent par commands:changed. Un membre qui répond ailleurs est ignoré. La session sert la première réponse du membre, à qui l'on donne quand même le passé du channel.

Historique

Emplacement <dossier de config utilisateur>/sidekicks/data/<16 caractères hexadécimaux>/ : une base Pebble par fichier de configuration (l'empreinte de son chemin absolu). Le dossier de config utilisateur est ~/Library/Application Support sous macOS, $XDG_CONFIG_HOME ou ~/.config sous Linux.
Gardé les channels, les membres (avec l'id de session ACP de chaque agent et le dernier message qu'il a vu), les messages terminés, les dernières commandes de chaque agent
Non gardé les demandes de permission en attente, les messages en attente, une réponse interrompue par la fermeture
Indisponible si la base ne peut pas être ouverte (un autre Sidekicks utilise le même fichier), l'historique vit en mémoire jusqu'à la fermeture, et ConfigService.Current().historyProblem dit pourquoi
Après un redémarrage un agent qui sait faire session/load reprend sa session ; sinon, son premier prompt dans un channel contient les derniers messages de ce channel

Workspaces (milestone M4)

Le workspace d'un agent est son dossier workspace, atteint selon runtime.type.

Runtime Fichiers Changements suivis Terminal Suppression
local directement notifications du système de fichiers (pas dans .git, node_modules), une demi-seconde après le dernier changement $SHELL -l (sinon bash, sinon sh) dans un pseudo-terminal vers la corbeille du système (~/.Trash, ou la corbeille freedesktop.org) ; définitive si la corbeille est sur un autre disque
container docker exec dans workspacePath (par défaut : le même chemin que workspace) dates et tailles comparées toutes les 2 s docker exec -it : bash s'il existe, sinon sh définitive
sbx sbx exec dans workspacePath (par défaut : le même chemin que workspace, où sbx monte le dossier de l'hôte) dates et tailles comparées toutes les 2 s sbx exec -it : bash s'il existe, sinon sh définitive

Les chemins sont relatifs au dossier et utilisent /. Un chemin qui sort du dossier (.., absolu, lien symbolique vers l'extérieur) est refusé. Le dossier lui-même ne peut pas être supprimé.

Fichiers

Champ Signification
name, path le nom de l'entrée, et son chemin depuis le dossier
dir un dossier
size, modTime taille en octets, dernière modification

Un fichier ouvert pour modification a path, content, version ; version identifie le contenu.

WorkspaceService

Méthode Effet
Info(agent) {location, problem} : où est le dossier (local · /chemin, container nom · /chemin), ou pourquoi il ne peut pas être affiché.
List(agent, dir) Les entrées de dir ("" est le dossier), dossiers d'abord, puis par nom.
Read(agent, path) Un fichier texte de 1 Mo au plus ; un fichier binaire ou plus gros est une erreur.
Save(agent, path, content, version) Écrit le fichier s'il a toujours version ; sinon {conflict: true} et rien n'est écrit. Une version vide écrase. Le fichier est écrit à côté puis déplacé à sa place.
Create(agent, dir, name) Crée un fichier vide ; refusé si le nom existe ou contient /.
Remove(agent, path) Supprime un fichier ou un dossier avec son contenu (voir le tableau ci-dessus).
Attach(agent, hostPath) Joint un fichier de cet ordinateur à un message : renvoie son chemin dans le dossier de l'agent, à écrire après $. Un fichier déjà dans le dossier est utilisé sur place ; tout autre est copié dans attachments/ (nom libre, espaces remplacés par -, 5 Mo au plus).
ChooseAttachment() Affiche le sélecteur de fichier natif, depuis le dossier personnel ; renvoie le chemin choisi, ou "".
Image(agent, path) Une image du dossier (.png, .jpg, .gif, .webp, .svg…, 5 Mo au plus) sous forme d'URL data:, pour l'aperçu. Tout autre fichier est une erreur.
Watch(agent), Unwatch() Suit les changements du dossier d'un agent, annoncés par workspace:changed.
Link(agent, href) {path, revealable} : le fichier du dossier de l'agent auquel mène un lien de sa réponse — un chemin relatif, un chemin absolu dans le dossier (tel que l'agent ou cet ordinateur le voit), ou une URL file:// ; l'ancre et les paramètres sont ignorés, les %xx décodés. Le fichier peut ne pas exister encore. revealable s'il est sur cet ordinateur. Tout autre lien, ou un chemin qui sort du dossier, est une erreur (this link is not a file of the agent's folder). Issue #65.
Reveal(agent, path) Montre le fichier dans le gestionnaire de fichiers : open -R (macOS), explorer /select, (Windows), xdg-open de son dossier (Linux). Seulement pour un fichier du dossier présent sur cet ordinateur.

TerminalService

Méthode Effet
Open(agent, cols, rows) Démarre un shell dans le dossier de l'agent ; renvoie son id.
Write(id, data) Envoie des frappes.
Resize(id, cols, rows) Change la taille, en caractères.
Close(id) Raccroche le shell et tout ce qu'il a lancé (les groupes de processus de ses descendants), tâches de fond comprises, puis tue ce qui reste au bout d'une seconde ; dans un conteneur, à l'intérieur du conteneur aussi.

Événements

Événement Contenu Envoyé quand
workspace:changed {agent} des fichiers ont changé dans le dossier suivi
terminal:output {id, data} le terminal a affiché quelque chose ; regroupé toutes les 16 ms ou 32 Ko, sans jamais couper un caractère
terminal:exit {id} le shell s'est terminé

Méthodes Settings de ConfigService

Méthode Effet
Content() Le texte du fichier de configuration.
Validate(text) Les problèmes et avertissements de text, avec line, column, message, warning, sans enregistrer.
Save(text) Écrit text s'il est valide ; sinon renvoie ses problèmes et n'écrit rien.
Schema() Le JSON Schema de la configuration.
Open(path) Arrête les réponses et tous les agents, ferme l'historique, charge et surveille path avec son propre historique, le mémorise.
Choose() Affiche le sélecteur de fichier natif et ouvre le fichier choisi ; renvoie "" en cas d'annulation.
Recent() Les fichiers de configuration ouverts depuis l'application, du plus récent au plus ancien (8 au plus).
ReadSandboxEnv(path) Lit un fichier d'environnement sbx pour l'assistant : name, workspace, sandboxFolder, createCommand, stopCommand, removeCommand (commandes sur une ligne). Un fichier qui n'en est pas un est une erreur.
ChooseSandboxEnv() Affiche le sélecteur de fichier natif, depuis le dossier personnel, pour un fichier d'environnement sbx ; renvoie son chemin, ou "" en cas d'annulation.
ChooseAgentConfig() De même, pour le fichier de configuration d'un agent (YAML).
ReadCompose(path) Lit un fichier Docker Compose pour l'assistant : folder, et pour chaque service name, container, createCommand, stopCommand, removeCommand. Un fichier sans services est une erreur.
ChooseCompose() Le sélecteur de fichier natif, pour un fichier Compose.
Reset(choice) La Danger zone : settings (configuration par défaut, préférences oubliées), history (celui de cette configuration), allHistory (celui de toutes les configurations) ou everything. Chaque élément réinitialisé est d'abord gardé en copie datée ; renvoie {copies[]}. L'historique est rouvert vide et les channels déclarés sont recréés ; puis config:changed est envoyé.

Arrêt d'un agent

L'entrée standard de l'agent est fermée ; après 5 s, l'agent et ses enfants reçoivent SIGTERM (le temps pour scripts/agents.sh de décharger le modèle) ; 3 s plus tard, SIGKILL.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# Référence : agents, channels et événements

## États d'un agent

| État | Affiché (`en` · `fr`) | Signification |
| --- | --- | --- |
| `stopped` | stopped · arrêté | Aucun processus. L'état initial de chaque agent. |
| `starting` | starting… · démarre… | Le processus est lancé et la poignée de main ACP (`initialize`) est en cours. |
| `ready` | ready · prêt | En marche et inoccupé. |
| `busy` | busy · occupé | En marche et utilisé par au moins une conversation. |
| `error` | error · erreur | Le démarrage a échoué, ou le processus s'est arrêté seul (crash). Le statut porte la raison, suivie des dernières lignes stderr de l'agent en cas d'échec au démarrage. |

`outdated` (affiché « restart to apply changes », ou « à redémarrer » en français) marque un agent en marche dont la définition a changé dans la configuration ; un redémarrage l'applique.

## Transitions

| Depuis | Événement | Vers |
| --- | --- | --- |
| `stopped`, `error` | démarrage demandé (▶ dans le panneau Agents, ou première utilisation) | `starting` |
| `starting` | la poignée de main réussit | `ready` |
| `starting` | la poignée de main échoue, protocole autre qu'ACP v1, délai dépassé (30 s) | `error` |
| `ready` | utilisé | `busy` |
| `busy` | le dernier utilisateur a fini | `ready` |
| `ready`, `busy` | arrêt demandé (■), fermeture de l'application, agent retiré de la configuration | `stopped` |
| `ready` | inutilisé depuis `limits.stopIdleAgentsAfter` (vérifié toutes les 30 s) | `stopped` |
| `ready` | un autre agent doit démarrer et celui-ci est l'agent inoccupé utilisé le moins récemment | `stopped` |
| `ready`, `busy` | le processus s'arrête seul | `error` (jamais redémarré automatiquement) |
| `starting` | arrêt demandé | `stopped` dès la fin du démarrage |

Quand `limits.maxRunningAgents` agents tournent et qu'ils sont tous `busy`, un démarrage est refusé avec le message `too many agents running: N agent(s) busy (…), the limit is N`.

Arrêter un agent ferme son entrée standard, attend 5 secondes, puis tue son groupe de processus (l'agent et ce qu'il a lancé, comme `mm` sous `scripts/agents.sh`).

## Événements du Go vers l'interface

| Événement | Charge | Envoyé quand |
| --- | --- | --- |
| `config:changed` | `{path, valid, problems[]}` | Le fichier de configuration a été enregistré et rechargé (valide ou non). |
| `agent:state` | `{agent, state, error?}` | Un agent change d'état ou sa définition change. |
| `agent:log` | `{agent, line}` | Un agent écrit une ligne sur sa sortie d'erreur, ou une commande de sa sandbox en affiche une (préfixée par `sandbox: `). |
| `files:dropped` | `{target, files[]}` | Des fichiers du système sont déposés sur un élément marqué `data-file-drop-target` ; `target` est son `id`, `files` leurs chemins absolus. |

Les noms et types Go sont dans `internal/events` ; les constantes et types TypeScript sont générés dans `frontend/bindings/events` et `frontend/bindings/…/internal/events`.

## Services appelés par l'interface

| Service | Méthode | Effet |
| --- | --- | --- |
| `AgentService` | `List()` | Tous les agents avec `name, title, icon, description, state, error, outdated, runtime, canStopSandbox, canRemoveSandbox`, dans l'ordre de la configuration. |
| | `Start(name)`, `Stop(name)`, `Restart(name)` | Comme dans les transitions ci-dessus ; une erreur est renvoyée à l'interface. Un démarrage recrée d'abord une sandbox absente si `runtime.lifecycle.create` est défini. |
| | `StopSandbox(name)`, `RemoveSandbox(name)` | Arrête l'agent, puis lance `runtime.lifecycle.stop` ou `remove` (conteneur ou sandbox) ; la sortie va dans le journal de l'agent. |
| | `Logs(name)` | Les 200 dernières lignes stderr de l'agent. |
| `ConfigService` | `Current()` | `path, source, created, valid, problems[], warnings[], theme, language, historyProblem`. |
| `SystemService` | `Memory()` | `level` (`normal`, `warning`, `critical`), `used` et `total` en octets ; tout vide (`total` à 0) là où la mémoire ne peut pas être mesurée. macOS : le niveau de `kern.memorystatus_vm_pressure_level`, utilisée = mémoire des apps + résidente + compressée (`vm_stat`), comme dans Moniteur d'activité. Linux et Windows : déduit de la mémoire disponible, `warning` sous 20 %, `critical` sous 10 %. Le panneau Log l'appelle toutes les 5 secondes tant qu'il est ouvert (issue #61). |

## Channels (milestones M2 et M3)

Un channel contient une conversation entre l'utilisateur et un ou plusieurs agents. Une conversation directe est un channel à un seul membre, sans nom, dont `direct` vaut le nom de son agent. Les channels, les membres et les messages terminés sont gardés dans l'historique du fichier de configuration (ci-dessous) ; une réponse en cours est gardée quand elle se termine.

### Channels

| Champ | Signification |
| --- | --- |
| `id` | identifiant triable |
| `name`, `description` | 1 à 64 caractères, unique sans tenir compte de la casse ; vide pour une conversation directe |
| `direct` | l'agent d'une conversation directe ; vide pour un channel |
| `members` | noms des agents, dans l'ordre d'arrivée |

### Qui répond à un message

| Le message | Répondu par |
| --- | --- |
| `/agent:commande arguments` (`agent` est un agent de la configuration) | cet agent, qui reçoit `/commande arguments` |
| contient des mentions `@nom` d'agents de la configuration | les agents mentionnés, l'un après l'autre, dans l'ordre des mentions ; un agent mentionné qui n'est pas membre rejoint d'abord le channel |
| sans mention | le dernier agent qui a répondu dans le channel ; avec un seul membre, ce membre |
| sans mention, personne n'a encore répondu, plusieurs membres | personne : message système `no-recipient` |
| tout message d'une conversation directe | son agent ; les mentions y sont du simple texte |

Un message qui commence par `/` (éventuellement après des mentions) est une commande : il est envoyé seul. Tout autre message est envoyé avec les messages du channel que l'agent n'a pas encore vus (au plus les 20 derniers, chacun coupé à 1500 caractères). Un agent répond à un seul channel à la fois ; un channel qui l'attend indique à qui c'est le tour.

### Fichiers joints

Un `$chemin` en début de mot joint le fichier `chemin` du dossier d'un agent (issue #53) : en conversation directe, celui de son agent ; dans un channel, celui du premier agent mentionné avec `@`, sinon celui du premier membre. Un `$` dans du code, après un autre caractère (`5$`), échappé (`\$`) ou suivi d'aucune lettre (`$5`) reste du texte ; la ponctuation qui termine une phrase ne fait pas partie du chemin. Un chemin s'arrête au premier espace.

Chaque agent qui répond reçoit les fichiers avec le message (pas avec une commande slash), sous forme de blocs ACP choisis selon ce qu'il a annoncé :

| Fichier | `promptCapabilities` | Envoyé comme |
| --- | --- | --- |
| image (`image/*`) | `image` | bloc `image` (base64), avec son URI |
| texte, 1 Mo au plus | `embeddedContext` | bloc `resource` avec le texte |
| tout autre texte ou image | — | `resource_link` : URI `file://` + le chemin tel que l'agent le voit, nom, type, taille |
| ni texte ni image | — | non envoyé : `attachment-unavailable` |

Un fichier est lu quand vient le tour de l'agent (5 Mo au plus) ; un fichier illisible donne `attachment-unavailable`, et l'agent répond sans lui.

### Messages

| Champ | Signification |
| --- | --- |
| `id`, `channel`, `createdAt` | identité |
| `agent` | l'auteur d'un message d'agent, ou l'agent dont parle un message système |
| `role` | `user`, `agent` ou `system` |
| `parts` | `text` (Markdown), `thought` (raisonnement), `tool` (un appel d'outil), `plan` (étapes d'un plan) |
| `done` | faux pendant qu'un message d'agent s'affiche |
| `queued` | un message de l'utilisateur qui attend la fin des réponses en cours |
| `stopReason` | comment un message d'agent s'est terminé : `end_turn`, `cancelled`, `max_tokens`, `max_turn_requests`, `refusal` |
| `error` | pourquoi un message d'agent s'est terminé sans raison d'arrêt (l'explication de l'agent), ou `cancelled` pour un message en attente abandonné par Stop |
| `code` | messages système : `agent-joined`, `agent-left`, `no-recipient`, `agent-unavailable` (impossible de le démarrer ou de le joindre ; `error` dit pourquoi), `attachment-unavailable` (un fichier joint n'a pas été envoyé à `agent` ; `error` le nomme et dit pourquoi) |
| `attachments` | messages de l'utilisateur : les fichiers joints avec `$chemin`, sous la forme `{agent, path}` (l'agent dont le dossier le contient). Seule la référence est gardée, jamais le contenu. |

Un appel d'outil a `id, title, kind` (`read, edit, delete, move, search, execute, think, fetch, other`), `status` (`pending, in_progress, completed, failed`), `input` (JSON compact), `output`, `locations`, `diffs` (`path, oldText, newText, newFile`).

### Événements

| Événement | Contenu | Envoyé quand |
| --- | --- | --- |
| `channels:changed` | `{channels[]}` | un channel est créé, renommé ou supprimé, ou ses membres changent |
| `message:updated` | `{channel, message}` | un message est ajouté, ou terminé |
| `message:delta` | `{channel, messageId, index, kind, text?, part?}` | `text` s'ajoute à la partie `index` (texte, raisonnement), ou `part` la remplace (outil, plan) |
| `turn:changed` | `{channel, busy, queued, answering}` | le channel commence ou arrête de répondre, ou le tour d'un autre agent commence ; `queued` messages attendent |
| `permission:requested` | `{id, channel, agent, messageId, toolCall, options[], rejections}` | un agent demande à utiliser un outil ; `rejections` compte les refus déjà donnés pendant ce tour |
| `permission:resolved` | `{id, channel}` | la demande a reçu une réponse, ou a été annulée par Stop |
| `usage:changed` | `{channel, agent, usage: {used, size}}` | un agent a indiqué l'occupation de sa fenêtre de contexte (`size` vaut 0 si elle est inconnue) |
| `commands:changed` | `{agent, commands[]}` | un agent a annoncé ses commandes slash (`name, description, hint`) |

### `ChannelService`

| Méthode | Effet |
| --- | --- |
| `List()` | Tous les channels et conversations directes, du plus ancien au plus récent. |
| `Create(name, description)` | Crée un channel. Un nom déjà pris ou vide est une erreur. |
| `Rename(id, name, description)` | Renomme un channel (pas une conversation directe). |
| `Delete(id)` | Arrête ses réponses, puis supprime le channel et tout son historique. |
| `Clear(id)` | Efface tous les messages d'un channel ou d'une conversation directe et garde son nom, sa description et ses membres. Les sessions des membres sont oubliées (`sessionId` et `lastSeen` vidés) : leur prochain tour ouvre une nouvelle session, qui ne sait rien de ce qui a été effacé ; l'usage est remis à zéro. Refusé tant que le channel répond ou a des messages en attente (`an agent is answering in this channel…`). Envoie `channels:changed`. |
| `AddMember(id, agent)`, `RemoveMember(id, agent)` | Invite ou retire un agent ; le channel affiche `agent-joined` ou `agent-left`. |
| `Direct(agent)` | La conversation directe avec `agent`, créée la première fois. |
| `Send(id, text)` | Ajoute un message de l'utilisateur et renvoie son id ; les agents qui répondent sont démarrés si besoin ; le message attend si le channel répond déjà. Un texte vide ou un channel inconnu sont des erreurs. Chaque `$chemin` joint un fichier du dossier d'un agent (voir [Fichiers joints](#fichiers-joints)) ; plus de 10 est une erreur. |
| `Cancel(id)` | Arrête la réponse en cours (`session/cancel`), les agents qui devaient encore répondre et les messages en attente ; répond « cancelled » aux demandes de permission en attente. |
| `AnswerPermission(id, optionId)` | Répond à une demande en attente avec l'une de ses options. |
| `Conversation(id)` | `channel, messages` (les 100 derniers), `more, busy, queued, answering, usage{agent: …}, permissions`. |
| `Older(id, beforeId)` | `{messages, more}` : les 100 messages précédant `beforeId`. |
| `Commands(agent)` | Les dernières commandes annoncées par l'agent, gardées d'un lancement à l'autre ; vide s'il n'en a jamais annoncé. |
| `Prepare(id)` | Démarre les membres du channel qui n'ont jamais annoncé leurs commandes et ouvre leur session pour ce channel (aucun modèle n'est chargé) ; rend la main une fois les sessions ouvertes, les commandes arrivent par `commands:changed`. Un membre qui répond ailleurs est ignoré. La session sert la première réponse du membre, à qui l'on donne quand même le passé du channel. |

### Historique

| | |
| --- | --- |
| Emplacement | `<dossier de config utilisateur>/sidekicks/data/<16 caractères hexadécimaux>/` : une base Pebble par fichier de configuration (l'empreinte de son chemin absolu). Le dossier de config utilisateur est `~/Library/Application Support` sous macOS, `$XDG_CONFIG_HOME` ou `~/.config` sous Linux. |
| Gardé | les channels, les membres (avec l'id de session ACP de chaque agent et le dernier message qu'il a vu), les messages terminés, les dernières commandes de chaque agent |
| Non gardé | les demandes de permission en attente, les messages en attente, une réponse interrompue par la fermeture |
| Indisponible | si la base ne peut pas être ouverte (un autre Sidekicks utilise le même fichier), l'historique vit en mémoire jusqu'à la fermeture, et `ConfigService.Current().historyProblem` dit pourquoi |
| Après un redémarrage | un agent qui sait faire `session/load` reprend sa session ; sinon, son premier prompt dans un channel contient les derniers messages de ce channel |

## Workspaces (milestone M4)

Le workspace d'un agent est son dossier `workspace`, atteint selon `runtime.type`.

| Runtime | Fichiers | Changements suivis | Terminal | Suppression |
| --- | --- | --- | --- | --- |
| `local` | directement | notifications du système de fichiers (pas dans `.git`, `node_modules`), une demi-seconde après le dernier changement | `$SHELL -l` (sinon bash, sinon sh) dans un pseudo-terminal | vers la corbeille du système (`~/.Trash`, ou la corbeille freedesktop.org) ; définitive si la corbeille est sur un autre disque |
| `container` | `docker exec` dans `workspacePath` (par défaut : le même chemin que `workspace`) | dates et tailles comparées toutes les 2 s | `docker exec -it` : bash s'il existe, sinon sh | définitive |
| `sbx` | `sbx exec` dans `workspacePath` (par défaut : le même chemin que `workspace`, où sbx monte le dossier de l'hôte) | dates et tailles comparées toutes les 2 s | `sbx exec -it` : bash s'il existe, sinon sh | définitive |

Les chemins sont relatifs au dossier et utilisent `/`. Un chemin qui sort du dossier (`..`, absolu, lien symbolique vers l'extérieur) est refusé. Le dossier lui-même ne peut pas être supprimé.

### Fichiers

| Champ | Signification |
| --- | --- |
| `name`, `path` | le nom de l'entrée, et son chemin depuis le dossier |
| `dir` | un dossier |
| `size`, `modTime` | taille en octets, dernière modification |

Un fichier ouvert pour modification a `path, content, version` ; `version` identifie le contenu.

### `WorkspaceService`

| Méthode | Effet |
| --- | --- |
| `Info(agent)` | `{location, problem}` : où est le dossier (`local · /chemin`, `container nom · /chemin`), ou pourquoi il ne peut pas être affiché. |
| `List(agent, dir)` | Les entrées de `dir` (`""` est le dossier), dossiers d'abord, puis par nom. |
| `Read(agent, path)` | Un fichier texte de 1 Mo au plus ; un fichier binaire ou plus gros est une erreur. |
| `Save(agent, path, content, version)` | Écrit le fichier s'il a toujours `version` ; sinon `{conflict: true}` et rien n'est écrit. Une `version` vide écrase. Le fichier est écrit à côté puis déplacé à sa place. |
| `Create(agent, dir, name)` | Crée un fichier vide ; refusé si le nom existe ou contient `/`. |
| `Remove(agent, path)` | Supprime un fichier ou un dossier avec son contenu (voir le tableau ci-dessus). |
| `Attach(agent, hostPath)` | Joint un fichier de cet ordinateur à un message : renvoie son chemin dans le dossier de l'agent, à écrire après `$`. Un fichier déjà dans le dossier est utilisé sur place ; tout autre est copié dans `attachments/` (nom libre, espaces remplacés par `-`, 5 Mo au plus). |
| `ChooseAttachment()` | Affiche le sélecteur de fichier natif, depuis le dossier personnel ; renvoie le chemin choisi, ou `""`. |
| `Image(agent, path)` | Une image du dossier (`.png`, `.jpg`, `.gif`, `.webp`, `.svg`…, 5 Mo au plus) sous forme d'URL `data:`, pour l'aperçu. Tout autre fichier est une erreur. |
| `Watch(agent)`, `Unwatch()` | Suit les changements du dossier d'un agent, annoncés par `workspace:changed`. |
| `Link(agent, href)` | `{path, revealable}` : le fichier du dossier de l'agent auquel mène un lien de sa réponse — un chemin relatif, un chemin absolu dans le dossier (tel que l'agent ou cet ordinateur le voit), ou une URL `file://` ; l'ancre et les paramètres sont ignorés, les `%xx` décodés. Le fichier peut ne pas exister encore. `revealable` s'il est sur cet ordinateur. Tout autre lien, ou un chemin qui sort du dossier, est une erreur (`this link is not a file of the agent's folder`). Issue #65. |
| `Reveal(agent, path)` | Montre le fichier dans le gestionnaire de fichiers : `open -R` (macOS), `explorer /select,` (Windows), `xdg-open` de son dossier (Linux). Seulement pour un fichier du dossier présent sur cet ordinateur. |

### `TerminalService`

| Méthode | Effet |
| --- | --- |
| `Open(agent, cols, rows)` | Démarre un shell dans le dossier de l'agent ; renvoie son id. |
| `Write(id, data)` | Envoie des frappes. |
| `Resize(id, cols, rows)` | Change la taille, en caractères. |
| `Close(id)` | Raccroche le shell et tout ce qu'il a lancé (les groupes de processus de ses descendants), tâches de fond comprises, puis tue ce qui reste au bout d'une seconde ; dans un conteneur, à l'intérieur du conteneur aussi. |

### Événements

| Événement | Contenu | Envoyé quand |
| --- | --- | --- |
| `workspace:changed` | `{agent}` | des fichiers ont changé dans le dossier suivi |
| `terminal:output` | `{id, data}` | le terminal a affiché quelque chose ; regroupé toutes les 16 ms ou 32 Ko, sans jamais couper un caractère |
| `terminal:exit` | `{id}` | le shell s'est terminé |

## Méthodes Settings de `ConfigService`

| Méthode | Effet |
| --- | --- |
| `Content()` | Le texte du fichier de configuration. |
| `Validate(text)` | Les problèmes et avertissements de `text`, avec `line, column, message, warning`, sans enregistrer. |
| `Save(text)` | Écrit `text` s'il est valide ; sinon renvoie ses problèmes et n'écrit rien. |
| `Schema()` | Le JSON Schema de la configuration. |
| `Open(path)` | Arrête les réponses et tous les agents, ferme l'historique, charge et surveille `path` avec son propre historique, le mémorise. |
| `Choose()` | Affiche le sélecteur de fichier natif et ouvre le fichier choisi ; renvoie `""` en cas d'annulation. |
| `Recent()` | Les fichiers de configuration ouverts depuis l'application, du plus récent au plus ancien (8 au plus). |
| `ReadSandboxEnv(path)` | Lit un fichier d'environnement sbx pour l'assistant : `name, workspace, sandboxFolder, createCommand, stopCommand, removeCommand` (commandes sur une ligne). Un fichier qui n'en est pas un est une erreur. |
| `ChooseSandboxEnv()` | Affiche le sélecteur de fichier natif, depuis le dossier personnel, pour un fichier d'environnement sbx ; renvoie son chemin, ou `""` en cas d'annulation. |
| `ChooseAgentConfig()` | De même, pour le fichier de configuration d'un agent (YAML). |
| `ReadCompose(path)` | Lit un fichier Docker Compose pour l'assistant : `folder`, et pour chaque service `name, container, createCommand, stopCommand, removeCommand`. Un fichier sans services est une erreur. |
| `ChooseCompose()` | Le sélecteur de fichier natif, pour un fichier Compose. |
| `Reset(choice)` | La Danger zone : `settings` (configuration par défaut, préférences oubliées), `history` (celui de cette configuration), `allHistory` (celui de toutes les configurations) ou `everything`. Chaque élément réinitialisé est d'abord gardé en copie datée ; renvoie `{copies[]}`. L'historique est rouvert vide et les channels déclarés sont recréés ; puis `config:changed` est envoyé. |

## Arrêt d'un agent

L'entrée standard de l'agent est fermée ; après 5 s, l'agent et ses enfants reçoivent SIGTERM (le temps pour `scripts/agents.sh` de décharger le modèle) ; 3 s plus tard, SIGKILL.