Replace embedded README bodies with links to central ia-dev-* docs. Update agent references from projects/README to docs/repo path.
8.0 KiB
Format JSON du spooler tickets (projects//data/issues/)
Les mails et réponses sont stockés dans projects//data/issues/ (sous ia_dev, par projet). Il n'y a toujours qu'un seul fichier de spooler par message reçu — pas un fichier par état : le même fichier change d'extension et de contenu quand on passe de « à traiter » à « répondu ». Seuls les messages à partir du 10 mars 2026 (ou la date MAIL_SINCE_DATE en env) sont récupérés. Filtrage par expéditeurs autorisés (conf.json → tickets.authorized_emails), sans s'appuyer sur le statut « non lu ». Le traitement se base sur l'extension du fichier (.pending = à traiter ; après réponse, le fichier est renommé en .response et son contenu mis à jour avec la question reçue et la réponse envoyée).
Emplacement et nommage des fichiers
Un seul fichier de spooler par message reçu : il n'y a pas un fichier par état. Un même message est représenté par un seul fichier ; quand le statut change (pending → response), on renomme ce fichier (changement d'extension) et on met à jour son contenu ; on ne crée pas de second fichier.
- Racine :
ia_dev/projects/<id>/data/issues/(id résolu par MAIL_TO ou AI_AGENT_TOKEN ; voirdocs/repo/ia-dev-project-conf-schema.mdà la racine du monorepo smart_ide) - Format des noms :
<date>.<id>.<from>.<status>(un seul fichier par message ; le statut change en renommant l’extension et en mettant à jour le contenu).date:YYYY-MM-DDTHHmmss(ex.2026-03-14T094530)id: identifiant unique généré à la réception (déterministe à partir du message, ex. 8 caractères hexadécimaux). Même message → mêmeid.from: adresse expéditeur rendue sûre pour le système de fichiers (ex.user_example.compouruser@example.com)status: extension du fichier :pending(à traiter) ouresponse(réponse envoyée ; le fichier.pendingest alors supprimé)
- Messages entrants :
<date>.<id>.<from>.pending. Créés partickets-fetch-inbox.sh. À traiter = fichier.pending(il n'existe alors pas encore de.responsepour ce message). - Changement de statut : on ne modifie pas
<date>.<id>.<from>. Après envoi réussi, le scriptwrite-response-spooler.shrenomme le fichier en.responseet écrit le nouveau contenu (question reçue + réponse envoyée) ; l'ancien.pendingest supprimé. Il reste donc un seul fichier par message. - Après réponse : le fichier s'appelle
<date>.<id>.<from>.responseet contient la question reçue et la réponse envoyée (voir schéma response ci-dessous). - Pièces jointes (pj) : pour un message de base
<base>(ex.2026-03-14T094530.a1b2c3d4.laurence_lecoffre.io), les pièces jointes sont dans<base>.d/. Nommage des fichiers :<index>_<nom_fichier_sanitifé>. Le JSON du message contient le tableauattachmentsavec pour chaque entrée le chemin relatif àdata/issues/, le type MIME et la taille.
Exemples :
2026-03-14T094530.a1b2c3d4.laurence_lecoffre.io.pending— message entrant en attente2026-03-14T094530.a1b2c3d4.laurence_lecoffre.io.response— réponse envoyée (question reçue + réponse dans le JSON) ; le.pendingassocié a été supprimé
Schéma JSON — message entrant (incoming)
Fichiers .pending.
{
"version": 1,
"type": "incoming",
"id": "a1b2c3d4",
"message_id": "<original.Message-ID@host>",
"from": "laurence@lecoffre.io",
"to": ["ai.support.lecoffreio@4nkweb.com"],
"subject": "Demande d'information",
"date": "Fri, 14 Mar 2026 09:45:30 +0100",
"body": "Texte brut du corps du mail.",
"references": ["<id1@host>", "<id2@host>"],
"in_reply_to": "<id2@host>",
"uid": "42",
"created_at": "2026-03-14T09:45:35Z",
"issue_number": null,
"status": "pending",
"attachments": [
{
"filename": "document.pdf",
"path": "2026-03-14T094530.a1b2c3d4.laurence_lecoffre.io.d/0_document.pdf",
"content_type": "application/pdf",
"size": 12345
}
]
}
| Champ | Type | Description |
|---|---|---|
version |
number | Version du schéma (1) |
type |
string | "incoming" |
id |
string | Identifiant court du message (même que dans le nom de fichier) |
message_id |
string | En-tête Message-ID du mail |
from |
string | Adresse de l'expéditeur |
to |
string[] | Adresses destinataires |
subject |
string | Sujet |
date |
string | Date d'envoi (RFC2822 ou ISO8601) |
body |
string | Corps du message (texte brut) |
references |
string[] | En-tête References (liste de Message-IDs) |
in_reply_to |
string | null | En-tête In-Reply-To |
uid |
string | UID IMAP (pour référence, pas pour marquer lu) |
created_at |
string | Date d'écriture dans le spool (ISO8601) |
issue_number |
number | null | Numéro d'issue Gitea si une issue a été créée |
status |
string | pending |
attachments |
array | Pièces jointes : tableau d'objets (voir ci-dessous). path est relatif à data/issues/ ; les fichiers sont dans <base>.d/. L'agent qui traite les tickets peut les lire depuis projects/<id>/data/issues/<path>. |
Objet pièce jointe : filename (nom d'origine), path (chemin relatif sous projects/<id>/data/issues/), content_type (MIME), size (octets).
Schéma JSON — réponse envoyée (response)
Fichiers .response. Contiennent la question reçue et la réponse envoyée.
{
"version": 1,
"type": "response",
"received_question": {
"subject": "Demande d'information",
"body": "Texte brut du corps du mail reçu."
},
"sent_response": {
"in_reply_to_message_id": "<original.Message-ID@host>",
"to": "laurence@lecoffre.io",
"subject": "Re: Demande d'information",
"body": "Texte de la réponse envoyée par l'agent.",
"sent_at": "2026-03-14T10:12:00Z"
},
"created_at": "2026-03-14T10:12:00Z"
}
| Champ | Type | Description |
|---|---|---|
version |
number | Version du schéma (1) |
type |
string | "response" |
received_question |
object | Question reçue : subject, body |
sent_response |
object | Réponse envoyée : in_reply_to_message_id, to, subject, body, sent_at |
created_at |
string | Date de création du fichier (ISO8601) |
Configuration projet (conf.json)
- ticketing_url et la config ticketing sont sous la clé
tickets(et non plus sousgit). ticketscontient :ticketing_url: URL des issues Giteaauthorized_emails:to: récupérer uniquement les mails envoyés à cette adresse (destinataire).from: liste des expéditeurs autorisés — un élément par adresse (tableau de chaînes). Ne pas mettre plusieurs adresses dans une seule chaîne.
Exemple :
"tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/lecoffre_ng/issues",
"authorized_emails": {
"to": "ai.support.lecoffreio@4nkweb.com",
"from": [
"ai.support.lecoffreio@4nkweb.com",
"laurence@lecoffre.io",
"nicolas.cantu@pm.me"
]
}
}
La récupération ne prend que les messages envoyés à authorized_emails.to et expédités par une des adresses de authorized_emails.from. Aucun marquage « lu / non lu ». Lorsqu'une réponse est enregistrée, le fichier .pending est supprimé et remplacé par le fichier .response (même base <date>.<id>.<from>).
IMAP : le script se connecte à la boîte IMAP configurée (ex. .secrets/gitea-issues/imap-bridge.env). Les messages sont filtrés selon les en-têtes To, Delivered-To, X-Original-To, etc. Pour que des messages « envoyés à » l'alias soient trouvés, la boîte IMAP doit être celle qui reçoit le courrier pour cette adresse (celle de authorized_emails.to). Si l'IMAP pointe vers une autre boîte (ex. une adresse personnelle qui reçoit tout), aucun message ne contiendra l'alias dans To/Delivered-To et tout sera exclu par not_to_alias. Il faut alors utiliser les identifiants IMAP de la boîte qui reçoit les mails envoyés à l'alias.