ia_dev/projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md
Nicolas Cantu 11da4329e4 docs: point conf schema and README stubs to smart_ide docs/repo
Replace embedded README bodies with links to central ia-dev-* docs.
Update agent references from projects/README to docs/repo path.
2026-04-03 18:20:13 +02:00

8.0 KiB
Raw Blame History

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 ; voir docs/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 lextension 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ême id.
    • from : adresse expéditeur rendue sûre pour le système de fichiers (ex. user_example.com pour user@example.com)
    • status : extension du fichier : pending (à traiter) ou response (réponse envoyée ; le fichier .pending est alors supprimé)
  • Messages entrants : <date>.<id>.<from>.pending. Créés par tickets-fetch-inbox.sh. À traiter = fichier .pending (il n'existe alors pas encore de .response pour ce message).
  • Changement de statut : on ne modifie pas <date>.<id>.<from>. Après envoi réussi, le script write-response-spooler.sh renomme le fichier en .response et écrit le nouveau contenu (question reçue + réponse envoyée) ; l'ancien .pending est supprimé. Il reste donc un seul fichier par message.
  • Après réponse : le fichier s'appelle <date>.<id>.<from>.response et 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 tableau attachments avec 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 attente
  • 2026-03-14T094530.a1b2c3d4.laurence_lecoffre.io.response — réponse envoyée (question reçue + réponse dans le JSON) ; le .pending associé 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 sous git).
  • tickets contient :
    • ticketing_url : URL des issues Gitea
    • authorized_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.