@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.
- package/README.md +764 -707
- package/dist/src/cli/watch.d.ts +3 -1
- package/dist/src/cli/watch.js +12 -2
- package/dist/src/client/messenger-client.d.ts +21 -1
- package/dist/src/client/messenger-client.js +31 -9
- package/dist/src/core/attachments.d.ts +16 -0
- package/dist/src/core/attachments.js +28 -0
- package/dist/src/core/store.d.ts +34 -3
- package/dist/src/core/store.js +404 -216
- package/dist/src/core/types.d.ts +41 -0
- package/dist/src/mcp/start.js +25 -22
- package/dist/src/mcp/tools.js +186 -9
- package/dist/src/server/app.js +89 -12
- package/dist/src/server/ui/app.js +867 -7
- package/dist/src/server/ui/index.html +59 -0
- package/dist/src/server/ui/styles.css +751 -20
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +16 -2
- package/package.json +2 -2
package/dist/src/cli/watch.d.ts
CHANGED
|
@@ -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>;
|
package/dist/src/cli/watch.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
package/dist/src/core/store.d.ts
CHANGED
|
@@ -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
|
|
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. */
|