@imenam/mcp-messenger 1.0.20 → 1.0.22
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 +12 -7
- package/dist/src/server/ui/app.js +80 -16
- package/dist/src/server/ui/index.html +11 -2
- package/dist/src/server/ui/styles.css +27 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -385,7 +385,8 @@ correspondants et l'état de lecture :
|
|
|
385
385
|
la colonne l'écrit donc ainsi. Chaque agent est **replié par défaut** et se déplie d'un
|
|
386
386
|
chevron sur ses boîtes ; une boîte que son agent n'a pas nommée s'affiche à son adresse,
|
|
387
387
|
la seule chose qui la distingue alors de ses sœurs. Cliquer l'agent filtre les fils sur
|
|
388
|
-
**toutes ses sessions**, cliquer une boîte sur **cette seule session**.
|
|
388
|
+
**toutes ses sessions**, cliquer une boîte sur **cette seule session**. Recliquer ne
|
|
389
|
+
relâche rien : on revient à la vue générale par « Tous les agents », en tête de colonne. La colonne se
|
|
389
390
|
replie sur les seuls avatars d'agents quand la place manque, son bouton de repli restant
|
|
390
391
|
en place : c'est lui qui la rouvre.
|
|
391
392
|
|
|
@@ -403,11 +404,15 @@ correspondants et l'état de lecture :
|
|
|
403
404
|
- **la liste des fils**, du plus récemment actif au plus ancien, chacun résumé par son
|
|
404
405
|
nom, ses correspondants, son dernier message et sa priorité la plus haute. Les deux
|
|
405
406
|
correspondants y sont d'abord **deux pastilles côte à côte**, séparées et non superposées
|
|
406
|
-
— leurs initiales doivent rester lisibles
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
407
|
+
— leurs initiales doivent rester lisibles. **Ces pastilles sont celles des agents, pas
|
|
408
|
+
des boîtes** : dans une liste, ce qu'on cherche du regard est le projet, et deux sessions
|
|
409
|
+
d'un même agent portent donc la même. Ce qui les distingue, le nom de la boîte, se lit au
|
|
410
|
+
survol, avec le projet, l'adresse et la mention de celle qui a écrit en dernier. Le
|
|
411
|
+
lecteur du fil suit la même règle, en tête de chaque message ;
|
|
412
|
+
- **un en-tête qui dit sur quoi la liste est filtrée** : sans filtre elle s'intitule
|
|
413
|
+
« Fils », et dès qu'un agent est choisi c'est lui le sujet — sa pastille en grand, son
|
|
414
|
+
nom en titre, et en dessous ce que le filtre recouvre, toutes ses sessions ou une seule ;
|
|
415
|
+
- **trois puces** au-dessus de la liste : tous, non lus, priorité haute ;
|
|
411
416
|
- **le lecteur du fil** : la conversation entière dans l'ordre, un bloc par message, avec
|
|
412
417
|
qui écrit à qui — en clair, pas en adresses — et une pastille disant si le destinataire
|
|
413
418
|
l'a lu. **Le corps d'un message est rendu comme du markdown** : titres, listes,
|
|
@@ -622,7 +627,7 @@ test/ # store, config, API HTTP, outils MCP, binaire réel
|
|
|
622
627
|
|
|
623
628
|
### Tests
|
|
624
629
|
|
|
625
|
-
|
|
630
|
+
273 tests couvrant : les suppressions de boîtes commandées depuis la console — une boîte
|
|
626
631
|
désignée à la main, les vides, celles sans activité depuis une date — avec leur aperçu,
|
|
627
632
|
leur double confirmation et le refus nominatif d'une boîte qui ne répond plus au critère, la reprise de la boîte d'une session interrompue (clé de session de
|
|
628
633
|
l'hôte, boîte orpheline, boîte d'une session vivante jamais reprise), le rendu markdown des messages et l'aplatissement des aperçus (dont
|
|
@@ -82,6 +82,8 @@ const el = {
|
|
|
82
82
|
agents: document.getElementById("agents"),
|
|
83
83
|
agentsFoot: document.getElementById("agents-foot"),
|
|
84
84
|
collapse: document.getElementById("collapse"),
|
|
85
|
+
threadsAvatar: document.getElementById("threads-avatar"),
|
|
86
|
+
threadsName: document.getElementById("threads-name"),
|
|
85
87
|
threadsScope: document.getElementById("threads-scope"),
|
|
86
88
|
// Le panneau, pas la liste qu'il contient : c'est lui que le séparateur redimensionne.
|
|
87
89
|
threadsPane: document.querySelector(".threads"),
|
|
@@ -235,6 +237,31 @@ function avatar(agentId, extraClass) {
|
|
|
235
237
|
return avatarFor(agentId, label, title, extraClass, !who.registered);
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Pastille de l'agent, et non de la boîte.
|
|
242
|
+
*
|
|
243
|
+
* Dans une liste, ce qui se cherche du regard est le projet : deux sessions d'un même
|
|
244
|
+
* agent portent donc la même pastille, et c'est voulu — ce qui les distingue, le nom de
|
|
245
|
+
* la boîte, se lit au survol. La pastille de boîte reste celle du lecteur et du volet,
|
|
246
|
+
* là où l'on regarde une session en particulier.
|
|
247
|
+
*/
|
|
248
|
+
function agentAvatar(agentId, extraClass, title) {
|
|
249
|
+
const who = party(agentId);
|
|
250
|
+
const seed = who.agentName || agentId;
|
|
251
|
+
const label = who.agentName || who.box || shortAddress(agentId);
|
|
252
|
+
return avatarFor(seed, label, title ?? boxTitle(agentId), extraClass, !who.registered);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Ce qu'une pastille dit au survol : le projet, la session, l'adresse. */
|
|
256
|
+
function boxTitle(agentId) {
|
|
257
|
+
const who = party(agentId);
|
|
258
|
+
return [
|
|
259
|
+
who.agentName || "agent sans nom",
|
|
260
|
+
who.box || (who.registered ? "boîte sans nom" : "boîte jamais connectée"),
|
|
261
|
+
agentId,
|
|
262
|
+
].join("\n");
|
|
263
|
+
}
|
|
264
|
+
|
|
238
265
|
// --- Dates -----------------------------------------------------------------
|
|
239
266
|
|
|
240
267
|
function formatDate(iso) {
|
|
@@ -419,9 +446,16 @@ function isSelected(kind, key) {
|
|
|
419
446
|
return state.selection.kind === kind && state.selection.key === key;
|
|
420
447
|
}
|
|
421
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Choisit ce que la liste des fils montre.
|
|
451
|
+
*
|
|
452
|
+
* Recliquer sur la sélection courante ne la relâche pas : cliquer un agent veut dire
|
|
453
|
+
* « montre-moi cet agent », et le redire ne peut pas vouloir dire le contraire. On revient
|
|
454
|
+
* à la vue générale par « Tous les agents », en tête de colonne, là où on l'attend.
|
|
455
|
+
*/
|
|
422
456
|
function select(kind, key) {
|
|
423
|
-
|
|
424
|
-
state.selection =
|
|
457
|
+
if (isSelected(kind, key)) return;
|
|
458
|
+
state.selection = { kind, key };
|
|
425
459
|
state.threadId = null;
|
|
426
460
|
state.messageId = null;
|
|
427
461
|
el.sheet.hidden = true;
|
|
@@ -672,7 +706,7 @@ function selectionLabel() {
|
|
|
672
706
|
|
|
673
707
|
function renderThreads(threads) {
|
|
674
708
|
state.threadCount = threads.length;
|
|
675
|
-
|
|
709
|
+
renderThreadsHead(threads.length);
|
|
676
710
|
|
|
677
711
|
el.threads.replaceChildren();
|
|
678
712
|
if (threads.length === 0) {
|
|
@@ -685,6 +719,43 @@ function renderThreads(threads) {
|
|
|
685
719
|
for (const thread of threads) el.threads.appendChild(threadRow(thread));
|
|
686
720
|
}
|
|
687
721
|
|
|
722
|
+
/**
|
|
723
|
+
* En-tête de la liste : ce sur quoi elle est filtrée, en grand.
|
|
724
|
+
*
|
|
725
|
+
* Sans filtre, elle s'intitule « Fils » et rien d'autre. Dès qu'un agent est choisi, c'est
|
|
726
|
+
* lui le sujet : sa pastille et son nom prennent la place du titre, et la ligne en dessous
|
|
727
|
+
* dit ce que le filtre recouvre — toutes ses sessions, ou une seule.
|
|
728
|
+
*/
|
|
729
|
+
function renderThreadsHead(count) {
|
|
730
|
+
const { kind, key } = state.selection;
|
|
731
|
+
const fils = plural(count, "fil");
|
|
732
|
+
|
|
733
|
+
if (kind === "all") {
|
|
734
|
+
el.threadsAvatar.hidden = true;
|
|
735
|
+
el.threadsAvatar.replaceChildren();
|
|
736
|
+
el.threadsName.textContent = "Fils";
|
|
737
|
+
el.threadsScope.textContent = `Toutes les boîtes · ${fils}`;
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Une boîte est d'abord une session de son agent : c'est donc l'agent qui s'affiche,
|
|
742
|
+
// et le nom de la boîte qui précise de quelle session il s'agit.
|
|
743
|
+
const address = kind === "box" ? key : (state.groups.find((g) => g.key === key)?.boxes[0] || {}).id;
|
|
744
|
+
const who = address ? party(address) : null;
|
|
745
|
+
const sessions = kind === "agent" ? (state.groups.find((g) => g.key === key)?.boxes.length ?? 0) : 0;
|
|
746
|
+
const box = kind === "box" ? state.directory.get(key) : null;
|
|
747
|
+
|
|
748
|
+
el.threadsAvatar.hidden = false;
|
|
749
|
+
el.threadsAvatar.replaceChildren(
|
|
750
|
+
address ? agentAvatar(address, "avatar--lg") : avatarFor(key, key, key, "avatar--lg", false)
|
|
751
|
+
);
|
|
752
|
+
el.threadsName.textContent = kind === "agent" ? key : who?.agentName || selectionLabel();
|
|
753
|
+
el.threadsScope.textContent =
|
|
754
|
+
kind === "agent"
|
|
755
|
+
? `${plural(sessions, "session")} · ${fils}`
|
|
756
|
+
: `${box ? boxLabel(box) : shortAddress(key)} · ${fils}`;
|
|
757
|
+
}
|
|
758
|
+
|
|
688
759
|
function emptyReason() {
|
|
689
760
|
if (state.search) return "Aucun fil ne correspond à cette recherche.";
|
|
690
761
|
if (state.filter === "unread") return "Tout est lu de ce côté.";
|
|
@@ -737,17 +808,8 @@ function threadParticipants(thread) {
|
|
|
737
808
|
|
|
738
809
|
const stack = node("div", "avatar-stack");
|
|
739
810
|
for (const id of thread.participants) {
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
badge.title = [
|
|
743
|
-
person.agentName || "agent sans nom",
|
|
744
|
-
person.box || (person.registered ? "boîte sans nom" : "boîte jamais connectée"),
|
|
745
|
-
id,
|
|
746
|
-
id === thread.last_message_from ? "a écrit le dernier message" : null,
|
|
747
|
-
]
|
|
748
|
-
.filter(Boolean)
|
|
749
|
-
.join("\n");
|
|
750
|
-
stack.appendChild(badge);
|
|
811
|
+
const last = id === thread.last_message_from ? "\na écrit le dernier message" : "";
|
|
812
|
+
stack.appendChild(agentAvatar(id, "avatar--sm avatar--stack", `${boxTitle(id)}${last}`));
|
|
751
813
|
}
|
|
752
814
|
who.appendChild(stack);
|
|
753
815
|
|
|
@@ -783,7 +845,9 @@ function renderThread(data) {
|
|
|
783
845
|
|
|
784
846
|
const meta = node("div", "reader-meta");
|
|
785
847
|
const stack = node("div", "avatar-stack");
|
|
786
|
-
|
|
848
|
+
// Le projet d'abord : deux sessions d'un même agent portent la même pastille, le nom
|
|
849
|
+
// de la boîte se lisant au survol.
|
|
850
|
+
for (const id of participants) stack.appendChild(agentAvatar(id, "avatar--sm avatar--stack"));
|
|
787
851
|
meta.appendChild(stack);
|
|
788
852
|
meta.appendChild(
|
|
789
853
|
node(
|
|
@@ -814,7 +878,7 @@ function messageRow(message, isLast) {
|
|
|
814
878
|
else if (isLast) classes.push("is-last");
|
|
815
879
|
const row = node("div", classes.join(" "));
|
|
816
880
|
|
|
817
|
-
row.appendChild(
|
|
881
|
+
row.appendChild(agentAvatar(message.from));
|
|
818
882
|
|
|
819
883
|
const column = node("div", "message-body-col");
|
|
820
884
|
const head = node("div", "message-head");
|
|
@@ -168,8 +168,17 @@
|
|
|
168
168
|
|
|
169
169
|
<section class="threads">
|
|
170
170
|
<div class="threads-head">
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
<!--
|
|
172
|
+
L'en-tête dit sur quoi la liste est filtrée. Quand c'est un agent, il y tient la
|
|
173
|
+
place principale, pastille comprise : c'est le sujet de ce qu'on lit en dessous.
|
|
174
|
+
-->
|
|
175
|
+
<div class="threads-title">
|
|
176
|
+
<span class="threads-avatar" id="threads-avatar" hidden></span>
|
|
177
|
+
<div class="threads-heading">
|
|
178
|
+
<h2 id="threads-name">Fils</h2>
|
|
179
|
+
<p class="muted" id="threads-scope"></p>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
173
182
|
<div class="chips" id="chips">
|
|
174
183
|
<button type="button" class="chip is-active" data-filter="all">Tous</button>
|
|
175
184
|
<button type="button" class="chip" data-filter="unread">Non lus</button>
|
|
@@ -1014,14 +1014,40 @@ body {
|
|
|
1014
1014
|
padding: 16px 20px 12px;
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
+
/* L'agent filtré est le sujet de la liste : sa pastille et son nom ouvrent le panneau. */
|
|
1018
|
+
.threads-title {
|
|
1019
|
+
display: flex;
|
|
1020
|
+
align-items: center;
|
|
1021
|
+
gap: 12px;
|
|
1022
|
+
min-width: 0;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
.threads-heading {
|
|
1026
|
+
min-width: 0;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1017
1029
|
.threads-head h2 {
|
|
1018
1030
|
margin: 0;
|
|
1019
|
-
font-size:
|
|
1031
|
+
font-size: 17px;
|
|
1032
|
+
line-height: 1.25;
|
|
1033
|
+
white-space: nowrap;
|
|
1034
|
+
overflow: hidden;
|
|
1035
|
+
text-overflow: ellipsis;
|
|
1020
1036
|
}
|
|
1021
1037
|
|
|
1022
1038
|
.threads-head p {
|
|
1023
1039
|
margin: 2px 0 0;
|
|
1024
1040
|
font-size: 12px;
|
|
1041
|
+
white-space: nowrap;
|
|
1042
|
+
overflow: hidden;
|
|
1043
|
+
text-overflow: ellipsis;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.avatar--lg {
|
|
1047
|
+
width: 40px;
|
|
1048
|
+
height: 40px;
|
|
1049
|
+
font-size: 15px;
|
|
1050
|
+
flex: 0 0 auto;
|
|
1025
1051
|
}
|
|
1026
1052
|
|
|
1027
1053
|
.chips {
|
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.22";
|
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.22";
|