@imenam/mcp-messenger 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { type AgentConfig } from "../config.js";
2
- import type { Message } from "../core/types.js";
2
+ import type { LinkedMailbox, Message } from "../core/types.js";
3
3
  export interface WatchConfig {
4
4
  config: AgentConfig;
5
5
  mailbox: string;
@@ -16,4 +16,6 @@ export interface WatchConfig {
16
16
  export declare function resolveWatchConfig(mailbox: string | null, env?: NodeJS.ProcessEnv): WatchConfig;
17
17
  /** Ligne d'évènement : une seule ligne, lisible telle quelle dans la notification. */
18
18
  export declare function formatEvent(message: Message): string;
19
+ /** Ligne d'une liaison posée depuis la console sur la boîte surveillée. */
20
+ export declare function formatLinkEvent(link: LinkedMailbox): string;
19
21
  export declare function runWatchMode(mailbox: string | null): Promise<void>;
@@ -62,6 +62,13 @@ export function formatEvent(message) {
62
62
  const thread = message.thread_title ? ` · ${message.thread_title.replace(/\s+/g, " ").trim()}` : "";
63
63
  return `[messenger] ${flag}${message.from} — ${subject}${thread} (${message.message_id})`;
64
64
  }
65
+ /** Ligne d'une liaison posée depuis la console sur la boîte surveillée. */
66
+ export function formatLinkEvent(link) {
67
+ const who = link.correspondent;
68
+ const names = [who.agent_name, who.display_name].filter(Boolean).join(" · ") || "unnamed mailbox";
69
+ const note = link.note ? ` — ${link.note.replace(/\s+/g, " ").trim()}` : "";
70
+ return `[messenger] linked with ${names} (${who.id})${note} — call list_links`;
71
+ }
65
72
  export async function runWatchMode(mailbox) {
66
73
  const { config, mailbox: target, source } = resolveWatchConfig(mailbox);
67
74
  const client = new MessengerClient(config);
@@ -71,11 +78,14 @@ export async function runWatchMode(mailbox) {
71
78
  try {
72
79
  const result = await client.waitForMessage(POLL_SECONDS, undefined, { watcher: true });
73
80
  backoff = BACKOFF_MIN_MS;
74
- if (result.timed_out || !result.message)
81
+ if (result.timed_out)
75
82
  continue;
76
83
  // Une ligne, un réveil. On repart aussitôt en attente : les messages suivants
77
84
  // arrivés entre-temps sont récupérés au tour d'après.
78
- process.stdout.write(`${formatEvent(result.message)}\n`);
85
+ if (result.message)
86
+ process.stdout.write(`${formatEvent(result.message)}\n`);
87
+ else if (result.link)
88
+ process.stdout.write(`${formatLinkEvent(result.link)}\n`);
79
89
  }
80
90
  catch (error) {
81
91
  const detail = error instanceof Error ? error.message : String(error);
@@ -1,13 +1,26 @@
1
1
  import type { AgentConfig } from "../config.js";
2
- import type { Agent, AgentDirectoryEntry, FolderInfo, ListMessagesOptions, MailboxStats, Message, MessageSummary, Priority, SendMessageResult, ThreadInfo, ThreadSummary } from "../core/types.js";
2
+ import type { Agent, AgentDirectoryEntry, FolderInfo, LinkedMailbox, ListMessagesOptions, MailboxStats, Message, MessageSummary, Priority, SendMessageResult, ThreadInfo, ThreadSummary } from "../core/types.js";
3
3
  /** Échec d'un appel au serveur boîte aux lettres (réseau, authentification ou métier). */
4
4
  export declare class MessengerClientError extends Error {
5
5
  readonly status?: number | undefined;
6
6
  constructor(message: string, status?: number | undefined);
7
7
  }
8
+ /** Pièce jointe à l'envoi, contenu encodé en base64. */
9
+ export interface OutgoingAttachment {
10
+ filename: string;
11
+ mime_type: string | null;
12
+ content_base64: string;
13
+ }
14
+ export interface DownloadedAttachment {
15
+ filename: string;
16
+ mime_type: string;
17
+ data: Buffer;
18
+ }
8
19
  export interface WaitResult {
9
20
  timed_out: boolean;
10
21
  message: Message | null;
22
+ /** Liaison posée sur la boîte pendant l'attente ; seul un veilleur en reçoit. */
23
+ link?: LinkedMailbox | null;
11
24
  }
12
25
  /**
13
26
  * Client HTTP du serveur boîte aux lettres.
@@ -59,6 +72,8 @@ export declare class MessengerClient {
59
72
  }): Promise<Agent>;
60
73
  getAgent(agentId: string): Promise<Agent>;
61
74
  listAgents(): Promise<AgentDirectoryEntry[]>;
75
+ /** Liaisons de la boîte courante. Le serveur les note comme reçues par cet agent. */
76
+ listLinks(): Promise<LinkedMailbox[]>;
62
77
  stats(): Promise<MailboxStats>;
63
78
  sendMessage(input: {
64
79
  to: string;
@@ -68,9 +83,11 @@ export declare class MessengerClient {
68
83
  reply_to?: string | null;
69
84
  thread_title?: string | null;
70
85
  thread_description?: string | null;
86
+ attachments?: OutgoingAttachment[];
71
87
  }): Promise<SendMessageResult>;
72
88
  listMessages(options?: ListMessagesOptions): Promise<MessageSummary[]>;
73
89
  getMessage(messageId: string, markRead?: boolean): Promise<Message>;
90
+ downloadAttachment(attachmentId: string): Promise<DownloadedAttachment>;
74
91
  markMessages(messageIds: string[], state: {
75
92
  read?: boolean;
76
93
  starred?: boolean;
@@ -104,4 +121,7 @@ export declare class MessengerClient {
104
121
  watcher?: boolean;
105
122
  }): Promise<WaitResult>;
106
123
  private request;
124
+ private send;
125
+ /** Erreur lisible tirée d'une réponse en échec. */
126
+ private failure;
107
127
  }
@@ -1,3 +1,4 @@
1
+ import { filenameFromDisposition } from "../core/attachments.js";
1
2
  /** Échec d'un appel au serveur boîte aux lettres (réseau, authentification ou métier). */
2
3
  export class MessengerClientError extends Error {
3
4
  status;
@@ -88,6 +89,11 @@ export class MessengerClient {
88
89
  const { agents } = await this.request("GET", "/api/agents");
89
90
  return agents;
90
91
  }
92
+ /** Liaisons de la boîte courante. Le serveur les note comme reçues par cet agent. */
93
+ async listLinks() {
94
+ const { links } = await this.request("GET", "/api/links");
95
+ return links;
96
+ }
91
97
  async stats() {
92
98
  return this.request("GET", "/api/mailbox");
93
99
  }
@@ -105,6 +111,16 @@ export class MessengerClient {
105
111
  query: { mark_read: markRead },
106
112
  });
107
113
  }
114
+ async downloadAttachment(attachmentId) {
115
+ const response = await this.send("GET", `/api/attachments/${encodeURIComponent(attachmentId)}`);
116
+ if (!response.ok)
117
+ throw await this.failure(response);
118
+ return {
119
+ filename: filenameFromDisposition(response.headers.get("content-disposition")) ?? attachmentId,
120
+ mime_type: response.headers.get("content-type") ?? "application/octet-stream",
121
+ data: Buffer.from(await response.arrayBuffer()),
122
+ };
123
+ }
108
124
  async markMessages(messageIds, state) {
109
125
  const { updated } = await this.request("POST", "/api/messages/mark", {
110
126
  body: { message_ids: messageIds, ...state },
@@ -160,6 +176,13 @@ export class MessengerClient {
160
176
  });
161
177
  }
162
178
  async request(method, routePath, options = {}) {
179
+ const response = await this.send(method, routePath, options);
180
+ if (!response.ok)
181
+ throw await this.failure(response);
182
+ const text = await response.text();
183
+ return (text ? safeParse(text) : null);
184
+ }
185
+ async send(method, routePath, options = {}) {
163
186
  const url = new URL(this.config.serverUrl + routePath);
164
187
  for (const [key, value] of Object.entries(options.query ?? {})) {
165
188
  if (value === undefined || value === null || value === "")
@@ -171,9 +194,8 @@ export class MessengerClient {
171
194
  headers.authorization = `Bearer ${this.config.token}`;
172
195
  if (options.body !== undefined)
173
196
  headers["content-type"] = "application/json";
174
- let response;
175
197
  try {
176
- response = await fetch(url, {
198
+ return await fetch(url, {
177
199
  method,
178
200
  headers,
179
201
  body: options.body === undefined ? undefined : JSON.stringify(options.body),
@@ -184,15 +206,15 @@ export class MessengerClient {
184
206
  const reason = error instanceof Error ? error.message : String(error);
185
207
  throw new MessengerClientError(`cannot reach the messenger server at ${this.config.serverUrl}: ${reason}`);
186
208
  }
209
+ }
210
+ /** Erreur lisible tirée d'une réponse en échec. */
211
+ async failure(response) {
187
212
  const text = await response.text();
188
213
  const payload = text ? safeParse(text) : null;
189
- if (!response.ok) {
190
- const detail = payload && typeof payload === "object" && "error" in payload
191
- ? String(payload.error)
192
- : text.slice(0, 300) || response.statusText;
193
- throw new MessengerClientError(detail, response.status);
194
- }
195
- return payload;
214
+ const detail = payload && typeof payload === "object" && "error" in payload
215
+ ? String(payload.error)
216
+ : text.slice(0, 300) || response.statusText;
217
+ return new MessengerClientError(detail, response.status);
196
218
  }
197
219
  }
198
220
  function safeParse(text) {
@@ -0,0 +1,16 @@
1
+ /** Nombre de pièces jointes par message. */
2
+ export declare const MAX_ATTACHMENTS = 10;
3
+ /** Taille d'une pièce jointe, en octets. */
4
+ export declare const MAX_ATTACHMENT_BYTES: number;
5
+ /** Taille cumulée des pièces jointes d'un message, en octets. */
6
+ export declare const MAX_ATTACHMENTS_TOTAL_BYTES: number;
7
+ export declare function formatBytes(bytes: number): string;
8
+ /**
9
+ * En-tête `Content-Disposition` d'un téléchargement.
10
+ *
11
+ * Toujours `attachment` : servi en ligne, un fichier HTML ou SVG s'exécuterait dans
12
+ * l'origine de la console.
13
+ */
14
+ export declare function contentDisposition(filename: string): string;
15
+ /** Nom de fichier porté par un en-tête produit par `contentDisposition`. */
16
+ export declare function filenameFromDisposition(header: string | null): string | null;
@@ -0,0 +1,28 @@
1
+ /** Nombre de pièces jointes par message. */
2
+ export const MAX_ATTACHMENTS = 10;
3
+ /** Taille d'une pièce jointe, en octets. */
4
+ export const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
5
+ /** Taille cumulée des pièces jointes d'un message, en octets. */
6
+ export const MAX_ATTACHMENTS_TOTAL_BYTES = 25 * 1024 * 1024;
7
+ export function formatBytes(bytes) {
8
+ if (bytes < 1024)
9
+ return `${bytes} B`;
10
+ if (bytes < 1024 * 1024)
11
+ return `${(bytes / 1024).toFixed(1)} KB`;
12
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
13
+ }
14
+ /**
15
+ * En-tête `Content-Disposition` d'un téléchargement.
16
+ *
17
+ * Toujours `attachment` : servi en ligne, un fichier HTML ou SVG s'exécuterait dans
18
+ * l'origine de la console.
19
+ */
20
+ export function contentDisposition(filename) {
21
+ const ascii = filename.replace(/[^\x20-\x7e]|["\\]/g, "_");
22
+ return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
23
+ }
24
+ /** Nom de fichier porté par un en-tête produit par `contentDisposition`. */
25
+ export function filenameFromDisposition(header) {
26
+ const encoded = header?.match(/filename\*=UTF-8''([^;]+)/i)?.[1];
27
+ return encoded ? decodeURIComponent(encoded) : null;
28
+ }
@@ -1,10 +1,15 @@
1
1
  import { EventEmitter } from "node:events";
2
- import { type AdminThreadSummary, type Agent, type AgentDirectoryEntry, type DeliveryRole, type FolderInfo, type ListMessagesOptions, type ListThreadsOptions, type MailboxStats, type Message, type MessageSummary, type Priority, type SendMessageInput, type SendMessageResult, type ThreadInfo, type ThreadSummary } from "./types.js";
2
+ import { type AdminThreadSummary, type Agent, type AgentDirectoryEntry, type AttachmentInfo, type DeliveryRole, type FolderInfo, type LinkedMailbox, type ListMessagesOptions, type ListThreadsOptions, type MailboxLink, type MailboxStats, type Message, type MessageSummary, type Priority, type SendMessageInput, type SendMessageResult, type ThreadInfo, type ThreadSummary } from "./types.js";
3
3
  /** Erreur métier : traduite en 4xx par la couche HTTP, en message d'outil côté MCP. */
4
4
  export declare class MessengerError extends Error {
5
5
  readonly status: number;
6
6
  constructor(message: string, status?: number);
7
7
  }
8
+ /** Contenu d'une pièce jointe, avec ses métadonnées. */
9
+ export interface AttachmentContent extends AttachmentInfo {
10
+ message_id: string;
11
+ data: Buffer;
12
+ }
8
13
  export interface EnsureAgentOptions {
9
14
  registered?: boolean;
10
15
  /** Identité de l'agent : d'où il vient. Posée par la configuration. */
@@ -22,6 +27,11 @@ export interface StoreEvents {
22
27
  folder: string;
23
28
  role: DeliveryRole;
24
29
  }];
30
+ /** Émis pour chacune des deux boîtes d'une liaison qui vient d'être créée. */
31
+ link: [{
32
+ agent_id: string;
33
+ link_id: string;
34
+ }];
25
35
  }
26
36
  /**
27
37
  * Boîte aux lettres persistée en SQLite.
@@ -55,6 +65,8 @@ export declare class MessageStore extends EventEmitter<StoreEvents> {
55
65
  getMessage(agentId: string, messageId: string, markRead?: boolean): Message;
56
66
  /** Tous les messages d'un fil visibles par l'agent, du plus ancien au plus récent. */
57
67
  getThread(agentId: string, threadId: string): Message[];
68
+ /** Pièce jointe d'un message livré à cet agent, contenu compris. */
69
+ getAttachment(agentId: string, attachmentId: string): AttachmentContent;
58
70
  getThreadInfo(threadId: string): ThreadInfo | null;
59
71
  /**
60
72
  * Renomme un fil. Le titre est unique et partagé : les participants parlent du même
@@ -134,6 +146,23 @@ export declare class MessageStore extends EventEmitter<StoreEvents> {
134
146
  messages: number;
135
147
  threads: number;
136
148
  };
149
+ /**
150
+ * Lie deux boîtes existantes. La paire est stockée dans l'ordre des adresses : une
151
+ * liaison n'a pas de direction, et la même paire ne peut être liée deux fois.
152
+ */
153
+ createLink(first: string, second: string, note?: string | null): MailboxLink;
154
+ getLink(linkId: string): MailboxLink | null;
155
+ /** Toutes les liaisons du serveur, la plus récente en tête. */
156
+ listLinks(): MailboxLink[];
157
+ deleteLink(linkId: string): void;
158
+ /**
159
+ * Liaisons d'une boîte, la plus récente en tête, chacune avec la fiche de la boîte d'en
160
+ * face. `markSeen` note que l'agent de cette boîte en a pris connaissance.
161
+ */
162
+ linksFor(agentId: string, opts?: {
163
+ markSeen?: boolean;
164
+ linkId?: string;
165
+ }): LinkedMailbox[];
137
166
  /** Vue globale : tous les messages du serveur, indépendamment des boîtes. */
138
167
  listAllMessages(opts?: {
139
168
  search?: string;
@@ -183,6 +212,8 @@ export declare class MessageStore extends EventEmitter<StoreEvents> {
183
212
  read: boolean;
184
213
  }[];
185
214
  };
215
+ /** Pièce jointe vue du serveur, quelle que soit la boîte. */
216
+ getAttachmentAdmin(attachmentId: string): AttachmentContent;
186
217
  overview(): {
187
218
  messages: number;
188
219
  threads: number;
@@ -196,8 +227,8 @@ export declare class MessageStore extends EventEmitter<StoreEvents> {
196
227
  /** Filtre les ids fournis sur ceux réellement livrés à cet agent (isolation des boîtes). */
197
228
  private ownedMessageIds;
198
229
  private assertFolderExists;
199
- /** Destinataire de chaque message demandé, par identifiant de message. */
200
- private recipientsFor;
230
+ /** Destinataire et pièces jointes de chaque message demandé, par identifiant de message. */
231
+ private relatedFor;
201
232
  private toSummary;
202
233
  private toMessage;
203
234
  /** Un message sans aucune livraison n'est plus lisible par personne : on le supprime. */