@imenam/mcp-messenger 1.0.26 → 1.0.28
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 +701 -666
- package/dist/src/cli/setup-mcp.js +22 -17
- package/dist/src/cli/watch.js +0 -1
- package/dist/src/config.d.ts +10 -12
- package/dist/src/config.js +33 -16
- package/dist/src/core/store.d.ts +11 -0
- package/dist/src/core/store.js +22 -0
- package/dist/src/mcp/mailbox-registry.d.ts +15 -1
- package/dist/src/mcp/mailbox-registry.js +0 -0
- package/dist/src/mcp/resume-state.d.ts +11 -5
- package/dist/src/mcp/start.d.ts +9 -8
- package/dist/src/mcp/start.js +41 -45
- package/dist/src/mcp/tools.js +10 -5
- package/dist/src/server/app.js +46 -10
- package/dist/src/server/start.js +7 -0
- package/dist/src/server/ui/app.js +32 -7
- package/dist/src/server/ui/index.html +11 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/dist/src/server/app.js
CHANGED
|
@@ -102,21 +102,24 @@ const isoDate = z
|
|
|
102
102
|
.regex(/^\d{4}-\d{2}-\d{2}(T[\d:.]+Z?)?$/, "expected a date such as 2026-08-01")
|
|
103
103
|
.refine((value) => !Number.isNaN(Date.parse(value)), "not a real date");
|
|
104
104
|
const staleSchema = z.object({ before: isoDate });
|
|
105
|
+
const createdBeforeSchema = z.object({ created_before: isoDate });
|
|
105
106
|
/**
|
|
106
107
|
* Une suppression désigne ses boîtes, et rappelle à quel titre elles sont supprimables :
|
|
107
|
-
* `before`, elles n'ont plus d'activité depuis cette date ; `
|
|
108
|
-
*
|
|
109
|
-
* doit donc savoir lequel
|
|
108
|
+
* `before`, elles n'ont plus d'activité depuis cette date ; `created_before`, elles ont
|
|
109
|
+
* été ouvertes avant cette date ; `empty`, rien n'y est jamais passé. Les trois critères
|
|
110
|
+
* s'excluent — le serveur revérifie celui qu'on lui donne, il doit donc savoir lequel
|
|
111
|
+
* c'est, et les deux critères datés ne désignent pas les mêmes boîtes.
|
|
110
112
|
*/
|
|
111
113
|
const purgeSchema = z
|
|
112
114
|
.object({
|
|
113
115
|
before: isoDate.optional(),
|
|
116
|
+
created_before: isoDate.optional(),
|
|
114
117
|
empty: z.literal(true).optional(),
|
|
115
118
|
/** Les adresses vues dans l'aperçu : on ne supprime jamais autre chose que ce qui a été montré. */
|
|
116
119
|
mailboxes: z.array(z.string().trim().min(1)).min(1).max(500),
|
|
117
120
|
})
|
|
118
|
-
.refine((input) =>
|
|
119
|
-
message: "provide
|
|
121
|
+
.refine((input) => [input.before, input.created_before, input.empty].filter((value) => value !== undefined).length === 1, {
|
|
122
|
+
message: "provide exactly one of `before` (inactive since that date), `created_before` (created before that date) or `empty: true`",
|
|
120
123
|
});
|
|
121
124
|
const adminListSchema = z.object({
|
|
122
125
|
search: z.string().trim().min(1).optional(),
|
|
@@ -130,7 +133,10 @@ export function createApp({ store, config }) {
|
|
|
130
133
|
// Présence des veilleurs : état de connexion, donc en mémoire du processus.
|
|
131
134
|
const watchers = new WatcherRegistry();
|
|
132
135
|
app.disable("x-powered-by");
|
|
133
|
-
|
|
136
|
+
// Le serveur n'écoute qu'en loopback : ce qui l'atteint de l'extérieur passe par un
|
|
137
|
+
// reverse proxy. Sans ce réglage, toutes les requêtes se présentent comme `127.0.0.1`
|
|
138
|
+
// et le quota par adresse n'en distingue aucune.
|
|
139
|
+
app.set("trust proxy", config.trustProxy);
|
|
134
140
|
app.use(helmet({
|
|
135
141
|
contentSecurityPolicy: {
|
|
136
142
|
directives: {
|
|
@@ -160,7 +166,9 @@ export function createApp({ store, config }) {
|
|
|
160
166
|
const cookieOptions = (req) => ({
|
|
161
167
|
httpOnly: true,
|
|
162
168
|
sameSite: "strict",
|
|
163
|
-
//
|
|
169
|
+
// Vrai dès que le proxy annonce `X-Forwarded-Proto: https`, ce que `trust proxy` (posé
|
|
170
|
+
// plus haut depuis la configuration) autorise Express à lire. Reste faux en clair sur
|
|
171
|
+
// la machine, où le cookie doit justement circuler.
|
|
164
172
|
secure: req.secure,
|
|
165
173
|
path: "/",
|
|
166
174
|
});
|
|
@@ -236,6 +244,16 @@ export function createApp({ store, config }) {
|
|
|
236
244
|
const { before } = staleSchema.parse(req.query);
|
|
237
245
|
res.json({ before, mailboxes: store.staleMailboxes(before) });
|
|
238
246
|
});
|
|
247
|
+
/**
|
|
248
|
+
* Boîtes ouvertes avant une date : ce que la purge par ancienneté emporterait.
|
|
249
|
+
*
|
|
250
|
+
* Même aperçu obligatoire que les autres nettoyages, mais un critère différent — ici
|
|
251
|
+
* l'activité ne protège rien, seule compte la date d'ouverture de la boîte.
|
|
252
|
+
*/
|
|
253
|
+
admin.get("/mailboxes/created-before", (req, res) => {
|
|
254
|
+
const { created_before: createdBefore } = createdBeforeSchema.parse(req.query);
|
|
255
|
+
res.json({ created_before: createdBefore, mailboxes: store.mailboxesCreatedBefore(createdBefore) });
|
|
256
|
+
});
|
|
239
257
|
/**
|
|
240
258
|
* Boîtes par lesquelles rien n'est jamais passé. Sans date : une boîte vide l'est
|
|
241
259
|
* indépendamment de son âge, et un agent dont toutes les boîtes sont vides disparaît
|
|
@@ -254,15 +272,29 @@ export function createApp({ store, config }) {
|
|
|
254
272
|
*/
|
|
255
273
|
admin.post("/mailboxes/purge", (req, res) => {
|
|
256
274
|
const input = purgeSchema.parse(req.body ?? {});
|
|
257
|
-
const reason = input.empty
|
|
258
|
-
|
|
275
|
+
const reason = input.empty
|
|
276
|
+
? "empty"
|
|
277
|
+
: input.created_before
|
|
278
|
+
? `older than ${input.created_before}`
|
|
279
|
+
: `inactive since ${input.before}`;
|
|
280
|
+
const candidates = input.empty
|
|
281
|
+
? store.emptyMailboxes()
|
|
282
|
+
: input.created_before
|
|
283
|
+
? store.mailboxesCreatedBefore(input.created_before)
|
|
284
|
+
: store.staleMailboxes(input.before);
|
|
285
|
+
const eligible = new Set(candidates.map((mailbox) => mailbox.id));
|
|
259
286
|
const changed = input.mailboxes.filter((id) => !eligible.has(id));
|
|
260
287
|
if (changed.length > 0) {
|
|
261
288
|
throw new MessengerError(`these mailboxes are no longer ${reason}, nothing was deleted: ${changed.join(", ")}`, 409);
|
|
262
289
|
}
|
|
263
290
|
const removed = store.deleteMailboxes(input.mailboxes);
|
|
264
291
|
logger.warn(`admin purge: ${removed.mailboxes} mailbox(es) deleted, ${reason}`);
|
|
265
|
-
res.json({
|
|
292
|
+
res.json({
|
|
293
|
+
before: input.before ?? null,
|
|
294
|
+
created_before: input.created_before ?? null,
|
|
295
|
+
empty: input.empty ?? false,
|
|
296
|
+
...removed,
|
|
297
|
+
});
|
|
266
298
|
});
|
|
267
299
|
/**
|
|
268
300
|
* Suppression d'une boîte désignée à la main, quel que soit son état.
|
|
@@ -502,6 +534,10 @@ function authenticateAgent(config) {
|
|
|
502
534
|
res.status(401).json({ error: "invalid or missing bearer token" });
|
|
503
535
|
return;
|
|
504
536
|
}
|
|
537
|
+
// Le jeton partagé atteste l'appartenance à la flotte, pas une adresse : `X-Agent-Id`
|
|
538
|
+
// n'est adossé à rien ici, et rien ne cloisonne les boîtes entre elles. C'est le mode
|
|
539
|
+
// « cercle de confiance », annoncé comme tel au démarrage et dans le README ;
|
|
540
|
+
// MESSENGER_AGENT_TOKENS est ce qui lie une adresse à un jeton.
|
|
505
541
|
}
|
|
506
542
|
req.agentId = agentId;
|
|
507
543
|
next();
|
package/dist/src/server/start.js
CHANGED
|
@@ -43,6 +43,13 @@ export async function runServerMode() {
|
|
|
43
43
|
logger.info(`mcp-messenger v${APP_VERSION} listening on ${running.url} (loopback only)`);
|
|
44
44
|
logger.info(`database: ${config.dbPath}`);
|
|
45
45
|
logger.info(`auth: ${authMode} — required on every request`);
|
|
46
|
+
// Le jeton partagé prouve qu'on fait partie de la flotte, pas qu'on est telle boîte :
|
|
47
|
+
// `X-Agent-Id` n'est alors adossé à rien. Le dire ici, sinon la ligne au-dessus se lit
|
|
48
|
+
// comme une garantie de cloisonnement qu'elle ne donne pas.
|
|
49
|
+
if (config.agentTokens.size === 0) {
|
|
50
|
+
logger.warn("shared token: no isolation between mailboxes — any holder can act as any address");
|
|
51
|
+
logger.warn("set MESSENGER_AGENT_TOKENS (one token per agent) to compartmentalize");
|
|
52
|
+
}
|
|
46
53
|
logger.info(`web console: ${running.url}/ui/ (asks for MESSENGER_ADMIN_TOKEN once)`);
|
|
47
54
|
let shuttingDown = false;
|
|
48
55
|
const shutdown = (signal) => {
|
|
@@ -101,7 +101,9 @@ const el = {
|
|
|
101
101
|
purgeClose: document.getElementById("purge-close"),
|
|
102
102
|
purgeCancel: document.getElementById("purge-cancel"),
|
|
103
103
|
purgeBefore: document.getElementById("purge-before"),
|
|
104
|
+
purgeCreatedBefore: document.getElementById("purge-created-before"),
|
|
104
105
|
purgePreview: document.getElementById("purge-preview"),
|
|
106
|
+
purgePreviewCreated: document.getElementById("purge-preview-created"),
|
|
105
107
|
purgePreviewEmpty: document.getElementById("purge-preview-empty"),
|
|
106
108
|
purgeResult: document.getElementById("purge-result"),
|
|
107
109
|
purgeConfirm: document.getElementById("purge-confirm"),
|
|
@@ -1303,8 +1305,11 @@ const purge = { mode: null, before: null, mailboxes: [], selected: new Set(), ar
|
|
|
1303
1305
|
|
|
1304
1306
|
function openPurge() {
|
|
1305
1307
|
const month = new Date(Date.now() - 30 * 86_400_000);
|
|
1308
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1306
1309
|
el.purgeBefore.value = month.toISOString().slice(0, 10);
|
|
1307
|
-
el.purgeBefore.max =
|
|
1310
|
+
el.purgeBefore.max = today;
|
|
1311
|
+
el.purgeCreatedBefore.value = month.toISOString().slice(0, 10);
|
|
1312
|
+
el.purgeCreatedBefore.max = today;
|
|
1308
1313
|
resetPurge();
|
|
1309
1314
|
el.purge.showModal();
|
|
1310
1315
|
}
|
|
@@ -1356,6 +1361,13 @@ async function previewStale() {
|
|
|
1356
1361
|
await previewPurge("stale", () => api("/admin/mailboxes/stale", { before }), before);
|
|
1357
1362
|
}
|
|
1358
1363
|
|
|
1364
|
+
/** Boîtes ouvertes avant la date choisie, qu'elles servent encore ou non. */
|
|
1365
|
+
async function previewCreated() {
|
|
1366
|
+
const before = el.purgeCreatedBefore.value;
|
|
1367
|
+
if (!before) return;
|
|
1368
|
+
await previewPurge("created", () => api("/admin/mailboxes/created-before", { created_before: before }), before);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1359
1371
|
async function previewPurge(mode, load, before = null) {
|
|
1360
1372
|
resetPurge();
|
|
1361
1373
|
try {
|
|
@@ -1373,10 +1385,15 @@ async function previewPurge(mode, load, before = null) {
|
|
|
1373
1385
|
function renderPurgePreview() {
|
|
1374
1386
|
const container = document.createDocumentFragment();
|
|
1375
1387
|
const count = purge.mailboxes.length;
|
|
1376
|
-
// Le critère est rappelé sous les yeux de qui va confirmer : les
|
|
1377
|
-
// remplissent la même liste, et elles ne portent pas sur les mêmes boîtes.
|
|
1378
|
-
|
|
1379
|
-
|
|
1388
|
+
// Le critère est rappelé sous les yeux de qui va confirmer : les quatre suppressions
|
|
1389
|
+
// remplissent la même liste, et elles ne portent pas sur les mêmes boîtes. Les deux
|
|
1390
|
+
// critères datés se distinguent par leur seule formulation : « depuis » regarde
|
|
1391
|
+
// l'activité, « créée avant » regarde l'ouverture de la boîte.
|
|
1392
|
+
const dated =
|
|
1393
|
+
purge.mode === "created"
|
|
1394
|
+
? ` créée avant le ${formatDay(purge.before)}`
|
|
1395
|
+
: ` sans activité depuis le ${formatDay(purge.before)}`;
|
|
1396
|
+
const criterion = purge.mode === "empty" ? "vide" : purge.mode === "one" ? "" : dated;
|
|
1380
1397
|
|
|
1381
1398
|
if (count === 0) {
|
|
1382
1399
|
container.appendChild(node("p", "purge-empty", `Aucune boîte ${criterion.trim()}.`));
|
|
@@ -1392,7 +1409,9 @@ function renderPurgePreview() {
|
|
|
1392
1409
|
? "Cette boîte, et tout ce que personne d'autre ne pourrait plus lire :"
|
|
1393
1410
|
: purge.mode === "empty"
|
|
1394
1411
|
? `${plural(count, "boîte vide", "boîtes vides")}.`
|
|
1395
|
-
:
|
|
1412
|
+
: purge.mode === "created"
|
|
1413
|
+
? `${plural(count, "boîte créée", "boîtes créées")} avant le ${formatDay(purge.before)}.`
|
|
1414
|
+
: `${plural(count, "boîte")}${dated}.`
|
|
1396
1415
|
)
|
|
1397
1416
|
);
|
|
1398
1417
|
|
|
@@ -1457,7 +1476,11 @@ async function confirmPurge() {
|
|
|
1457
1476
|
? await remove(`/admin/mailboxes/${encodeURIComponent(deleted[0])}`)
|
|
1458
1477
|
: await post("/admin/mailboxes/purge", {
|
|
1459
1478
|
// Le critère repart avec les adresses : c'est lui que le serveur revérifie.
|
|
1460
|
-
...(purge.mode === "empty"
|
|
1479
|
+
...(purge.mode === "empty"
|
|
1480
|
+
? { empty: true }
|
|
1481
|
+
: purge.mode === "created"
|
|
1482
|
+
? { created_before: purge.before }
|
|
1483
|
+
: { before: purge.before }),
|
|
1461
1484
|
mailboxes: deleted,
|
|
1462
1485
|
});
|
|
1463
1486
|
resetPurge();
|
|
@@ -1488,7 +1511,9 @@ el.purgeOpen.addEventListener("click", openPurge);
|
|
|
1488
1511
|
el.purgeClose.addEventListener("click", () => el.purge.close());
|
|
1489
1512
|
el.purgeCancel.addEventListener("click", () => el.purge.close());
|
|
1490
1513
|
el.purgeBefore.addEventListener("change", resetPurge);
|
|
1514
|
+
el.purgeCreatedBefore.addEventListener("change", resetPurge);
|
|
1491
1515
|
el.purgePreview.addEventListener("click", previewStale);
|
|
1516
|
+
el.purgePreviewCreated.addEventListener("click", previewCreated);
|
|
1492
1517
|
el.purgePreviewEmpty.addEventListener("click", previewEmpty);
|
|
1493
1518
|
el.purgeConfirm.addEventListener("click", confirmPurge);
|
|
1494
1519
|
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
</button>
|
|
71
71
|
</div>
|
|
72
72
|
<p class="modal-sub">
|
|
73
|
-
|
|
73
|
+
Trois nettoyages, trois critères. La suppression est définitive dans tous les cas, et emporte les messages que
|
|
74
74
|
plus personne ne pourrait lire ; ce que le correspondant a reçu de son côté reste intact. Un agent dont toutes
|
|
75
75
|
les boîtes disparaissent quitte l'annuaire avec elles.
|
|
76
76
|
</p>
|
|
@@ -94,6 +94,16 @@
|
|
|
94
94
|
<button type="button" class="btn" id="purge-preview">Prévisualiser</button>
|
|
95
95
|
</div>
|
|
96
96
|
|
|
97
|
+
<!-- Troisième critère : l'âge de la boîte, que son activité ne rachète pas. -->
|
|
98
|
+
<div class="purge-controls">
|
|
99
|
+
<label class="field">
|
|
100
|
+
<span class="field-label">Boîtes créées avant le</span>
|
|
101
|
+
<span class="field-input"><input type="date" id="purge-created-before" /></span>
|
|
102
|
+
<span class="purge-hint">Sur la seule date d'ouverture : une boîte encore active en fait partie.</span>
|
|
103
|
+
</label>
|
|
104
|
+
<button type="button" class="btn" id="purge-preview-created">Prévisualiser</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
97
107
|
<div class="purge-result" id="purge-result"></div>
|
|
98
108
|
|
|
99
109
|
<div class="modal-foot">
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const APP_VERSION = "1.0.
|
|
1
|
+
export declare const APP_VERSION = "1.0.28";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Généré par scripts/generate-version.js — ne pas modifier à la main.
|
|
2
|
-
export const APP_VERSION = "1.0.
|
|
2
|
+
export const APP_VERSION = "1.0.28";
|