metheus-governance-mcp-cli 0.2.261 → 0.2.262

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.
@@ -51,6 +51,38 @@ function normalizeMentionSelector(rawValue) {
51
51
  return String(rawValue || "").trim().replace(/^@+/, "").toLowerCase();
52
52
  }
53
53
 
54
+ function buildRunnerRouteOwnershipKey(routeRaw) {
55
+ const route = safeObject(routeRaw);
56
+ const name = String(route.name || "").trim() || "-";
57
+ const projectID = String(route.projectID || route.project_id || "").trim() || "-";
58
+ const provider = String(route.provider || "").trim().toLowerCase() || "-";
59
+ const role = String(route.role || route.bot_role || route.botRole || "").trim().toLowerCase() || "-";
60
+ const botID = String(
61
+ route.botID
62
+ || route.bot_id
63
+ || route.server_bot_id
64
+ || route.serverBotID
65
+ || "",
66
+ ).trim();
67
+ const botName = String(
68
+ route.botName
69
+ || route.bot_name
70
+ || route.server_bot_name
71
+ || route.serverBotName
72
+ || "",
73
+ ).trim();
74
+ const destinationID = String(route.destinationID || route.destination_id || "").trim();
75
+ const destinationLabel = String(route.destinationLabel || route.destination_label || "").trim();
76
+ return [
77
+ name,
78
+ projectID,
79
+ provider,
80
+ role,
81
+ botID || botName || "-",
82
+ destinationID || destinationLabel || "-",
83
+ ].join("::");
84
+ }
85
+
54
86
  function normalizeRunnerSharedInboxKey({ provider, bot, route }) {
55
87
  const normalizedProvider = String(provider || route?.provider || "").trim().toLowerCase() || "telegram";
56
88
  const botSelector = firstNonEmptyString([
@@ -98,6 +130,130 @@ function buildManagedConversationBotSelectors(managedConversationBots) {
98
130
  );
99
131
  }
100
132
 
133
+ function buildRunnerLocalInboundOwnerMap({
134
+ routeKey,
135
+ route,
136
+ bot,
137
+ managedConversationBots,
138
+ }) {
139
+ const owners = new Map();
140
+ const appendOwner = (selectorRaw, ownerRaw = {}) => {
141
+ const selector = normalizeMentionSelector(selectorRaw);
142
+ const ownerRouteKey = String(
143
+ ownerRaw.routeKey
144
+ || ownerRaw.route_key
145
+ || buildRunnerRouteOwnershipKey(ownerRaw.route),
146
+ ).trim();
147
+ const ownerBotUsername = normalizeMentionSelector(
148
+ ownerRaw.botUsername
149
+ || ownerRaw.bot_username
150
+ || ownerRaw.username
151
+ || safeObject(ownerRaw.bot).username
152
+ || safeObject(ownerRaw.bot).name
153
+ || safeObject(ownerRaw.route).botName
154
+ || safeObject(ownerRaw.route).bot_name
155
+ || safeObject(ownerRaw.route).serverBotName
156
+ || safeObject(ownerRaw.route).server_bot_name
157
+ || selector,
158
+ );
159
+ if (!selector || !ownerRouteKey) {
160
+ return;
161
+ }
162
+ owners.set(selector, {
163
+ selector,
164
+ routeKey: ownerRouteKey,
165
+ botUsername: ownerBotUsername || selector,
166
+ });
167
+ };
168
+
169
+ appendOwner(
170
+ bot?.username
171
+ || bot?.name
172
+ || route?.botName
173
+ || route?.bot_name
174
+ || route?.serverBotName
175
+ || route?.server_bot_name,
176
+ {
177
+ routeKey,
178
+ botUsername:
179
+ bot?.username
180
+ || bot?.name
181
+ || route?.botName
182
+ || route?.bot_name
183
+ || route?.serverBotName
184
+ || route?.server_bot_name,
185
+ },
186
+ );
187
+
188
+ for (const entryRaw of ensureArray(managedConversationBots)) {
189
+ const entry = safeObject(entryRaw);
190
+ appendOwner(
191
+ entry.username
192
+ || entry.display_name
193
+ || safeObject(entry.bot).username
194
+ || safeObject(entry.bot).name
195
+ || safeObject(entry.route).botName
196
+ || safeObject(entry.route).bot_name
197
+ || safeObject(entry.route).serverBotName
198
+ || safeObject(entry.route).server_bot_name,
199
+ entry,
200
+ );
201
+ }
202
+
203
+ return owners;
204
+ }
205
+
206
+ function resolveRunnerLocalInboundArtifactOwners({
207
+ update,
208
+ routeKey,
209
+ route,
210
+ bot,
211
+ managedConversationBots,
212
+ }) {
213
+ const normalizedUpdate = safeObject(update);
214
+ const ownersBySelector = buildRunnerLocalInboundOwnerMap({
215
+ routeKey,
216
+ route,
217
+ bot,
218
+ managedConversationBots,
219
+ });
220
+ const currentBotSelector = normalizeMentionSelector(
221
+ bot?.username
222
+ || bot?.name
223
+ || route?.botName
224
+ || route?.bot_name
225
+ || route?.serverBotName
226
+ || route?.server_bot_name,
227
+ );
228
+ const currentOwner = currentBotSelector ? ownersBySelector.get(currentBotSelector) : null;
229
+
230
+ const explicitMentions = Array.from(new Set(
231
+ ensureArray(normalizedUpdate.mentionUsernames)
232
+ .map((value) => normalizeMentionSelector(value))
233
+ .filter(Boolean),
234
+ ));
235
+ if (explicitMentions.length > 0) {
236
+ const explicitOwners = explicitMentions
237
+ .map((selector) => ownersBySelector.get(selector))
238
+ .filter((owner) => owner && owner.routeKey);
239
+ if (explicitOwners.length > 0) {
240
+ return explicitOwners;
241
+ }
242
+ return [];
243
+ }
244
+
245
+ const replyTargetSelector = normalizeMentionSelector(normalizedUpdate.replyToFromUsername);
246
+ if (
247
+ normalizedUpdate.replyToFromIsBot === true
248
+ && replyTargetSelector
249
+ && ownersBySelector.has(replyTargetSelector)
250
+ ) {
251
+ return [ownersBySelector.get(replyTargetSelector)];
252
+ }
253
+
254
+ return currentOwner ? [currentOwner] : [];
255
+ }
256
+
101
257
  function managedConversationBotTargetsCurrentRoute({
102
258
  update,
103
259
  bot,
@@ -327,49 +483,72 @@ function buildRunnerLocalInboundEnvelopeFromReceipt(rawReceipt) {
327
483
  });
328
484
  }
329
485
 
330
- function buildRunnerLocalInboundArtifacts(updates, routeKey, bot, destination) {
331
- const currentBotSelector = normalizeMentionSelector(
332
- bot?.username
333
- || bot?.name
334
- || "",
335
- );
486
+ function buildRunnerLocalInboundArtifacts(updates, routeKey, route, bot, destination, managedConversationBots = []) {
336
487
  const normalizedRouteKey = String(routeKey || "").trim();
337
488
  const destinationChatID = String(destination?.chatID || "").trim();
338
489
  return ensureArray(updates)
339
490
  .filter((update) => String(update.chatID || "").trim() === destinationChatID)
340
491
  .filter((update) => String(update.text || "").trim())
341
- .map((update) => {
342
- const chatID = String(update.chatID || "").trim();
343
- const messageID = intFromRawAllowZero(update.messageID, 0);
344
- const receiptKey = buildRunnerRecentLocalInboundReceiptKey(chatID, messageID);
345
- const receiptEntry = normalizeRunnerRecentLocalInboundReceipt({
346
- update_id: intFromRawAllowZero(update.updateID, 0),
347
- chat_id: chatID,
348
- chat_type: update.chatType,
349
- chat_title: update.chatTitle,
350
- message_id: messageID,
351
- message_thread_id: intFromRawAllowZero(update.messageThreadID, 0),
352
- reply_to_message_id: intFromRawAllowZero(update.replyToMessageID, 0),
353
- kind: update.fromIsBot ? "bot_reply" : "telegram_message",
354
- sender_id: update.fromID,
355
- sender: update.fromName,
356
- sender_username: update.fromUsername,
357
- sender_is_bot: update.fromIsBot === true,
358
- body: update.text,
359
- occurred_at: update.occurredAt,
360
- receipt_origin: "local_telegram_inbound",
361
- receipt_route_key: normalizedRouteKey,
362
- receipt_bot_username: currentBotSelector,
363
- received_at: firstNonEmptyString([update.occurredAt, new Date().toISOString()]),
364
- }, receiptKey);
365
- return { receiptEntry };
492
+ .flatMap((update) => {
493
+ const owners = resolveRunnerLocalInboundArtifactOwners({
494
+ update,
495
+ routeKey: normalizedRouteKey,
496
+ route,
497
+ bot,
498
+ managedConversationBots,
499
+ });
500
+ return owners.map((owner) => {
501
+ const chatID = String(update.chatID || "").trim();
502
+ const messageID = intFromRawAllowZero(update.messageID, 0);
503
+ const receiptKey = buildRunnerRecentLocalInboundReceiptKey(chatID, messageID);
504
+ const receiptEntry = normalizeRunnerRecentLocalInboundReceipt({
505
+ update_id: intFromRawAllowZero(update.updateID, 0),
506
+ chat_id: chatID,
507
+ chat_type: update.chatType,
508
+ chat_title: update.chatTitle,
509
+ message_id: messageID,
510
+ message_thread_id: intFromRawAllowZero(update.messageThreadID, 0),
511
+ reply_to_message_id: intFromRawAllowZero(update.replyToMessageID, 0),
512
+ kind: update.fromIsBot ? "bot_reply" : "telegram_message",
513
+ sender_id: update.fromID,
514
+ sender: update.fromName,
515
+ sender_username: update.fromUsername,
516
+ sender_is_bot: update.fromIsBot === true,
517
+ body: update.text,
518
+ occurred_at: update.occurredAt,
519
+ receipt_origin: "local_telegram_inbound",
520
+ receipt_route_key: owner.routeKey,
521
+ receipt_bot_username: owner.botUsername,
522
+ received_at: firstNonEmptyString([update.occurredAt, new Date().toISOString()]),
523
+ }, receiptKey);
524
+ return {
525
+ ownerRouteKey: String(owner.routeKey || "").trim(),
526
+ receiptEntry,
527
+ };
528
+ });
366
529
  })
367
530
  .filter((artifact) => {
368
531
  const normalizedEntry = ensureArray(safeObject(artifact).receiptEntry);
369
- return normalizedEntry.length === 2;
532
+ return normalizedEntry.length === 2 && String(safeObject(artifact).ownerRouteKey || "").trim();
370
533
  });
371
534
  }
372
535
 
536
+ function groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts) {
537
+ const grouped = new Map();
538
+ for (const artifactRaw of ensureArray(localInboundArtifacts)) {
539
+ const artifact = safeObject(artifactRaw);
540
+ const ownerRouteKey = String(artifact.ownerRouteKey || "").trim();
541
+ const normalizedEntry = ensureArray(artifact.receiptEntry);
542
+ if (!ownerRouteKey || normalizedEntry.length !== 2) {
543
+ continue;
544
+ }
545
+ const routeArtifacts = grouped.get(ownerRouteKey) || [];
546
+ routeArtifacts.push({ receiptEntry: normalizedEntry });
547
+ grouped.set(ownerRouteKey, routeArtifacts);
548
+ }
549
+ return Object.fromEntries(grouped.entries());
550
+ }
551
+
373
552
  function buildRunnerRecentLocalInboundEnvelopes(routeStateRaw, recentLocalInboundReceipts) {
374
553
  const routeState = safeObject(routeStateRaw);
375
554
  const relevantEnvelopes = Object.values(safeObject(recentLocalInboundReceipts))
@@ -904,12 +1083,15 @@ export async function archiveLocalTelegramMessagesForRoute({
904
1083
  const localInboundArtifacts = buildRunnerLocalInboundArtifacts(
905
1084
  updates,
906
1085
  routeKey,
1086
+ route,
907
1087
  bot,
908
1088
  destination,
1089
+ managedConversationBots,
909
1090
  );
1091
+ const localInboundArtifactsByRoute = groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts);
910
1092
  const recentLocalInboundReceipts = buildRunnerRecentLocalInboundReceipts(
911
1093
  routeState,
912
- localInboundArtifacts,
1094
+ ensureArray(localInboundArtifactsByRoute[routeKey]),
913
1095
  );
914
1096
  const recentLocalInboundEnvelopes = buildRunnerRecentLocalInboundEnvelopes(
915
1097
  routeState,
@@ -926,13 +1108,36 @@ export async function archiveLocalTelegramMessagesForRoute({
926
1108
  if (sharedInboxPatch && saveBotRunnerState) {
927
1109
  saveBotRunnerState(sharedInboxPatch);
928
1110
  }
929
- saveRunnerRouteState(
1111
+ const inputRouteKeys = Array.from(new Set([
930
1112
  routeKey,
931
- buildRunnerRouteInputStatePatch({
932
- recentLocalInboundEnvelopes,
933
- recentLocalInboundReceipts,
934
- }),
935
- );
1113
+ ...Object.keys(safeObject(localInboundArtifactsByRoute)),
1114
+ ]));
1115
+ for (const targetRouteKey of inputRouteKeys) {
1116
+ const fullState = loadBotRunnerState ? safeObject(loadBotRunnerState()) : {};
1117
+ const targetRouteState = safeObject(
1118
+ safeObject(fullState.routes)[targetRouteKey]
1119
+ || (targetRouteKey === routeKey ? routeState : {}),
1120
+ );
1121
+ const targetRecentLocalInboundReceipts = targetRouteKey === routeKey
1122
+ ? recentLocalInboundReceipts
1123
+ : buildRunnerRecentLocalInboundReceipts(
1124
+ targetRouteState,
1125
+ ensureArray(localInboundArtifactsByRoute[targetRouteKey]),
1126
+ );
1127
+ const targetRecentLocalInboundEnvelopes = targetRouteKey === routeKey
1128
+ ? recentLocalInboundEnvelopes
1129
+ : buildRunnerRecentLocalInboundEnvelopes(
1130
+ targetRouteState,
1131
+ targetRecentLocalInboundReceipts,
1132
+ );
1133
+ saveRunnerRouteState(
1134
+ targetRouteKey,
1135
+ buildRunnerRouteInputStatePatch({
1136
+ recentLocalInboundEnvelopes: targetRecentLocalInboundEnvelopes,
1137
+ recentLocalInboundReceipts: targetRecentLocalInboundReceipts,
1138
+ }),
1139
+ );
1140
+ }
936
1141
  saveRunnerRouteState(
937
1142
  routeKey,
938
1143
  buildRunnerRoutePollingStatePatch({
@@ -1436,6 +1436,113 @@ export async function runSelftestTelegramE2E(push, deps) {
1436
1436
  telegramE2EServer.state.comments.length === 0,
1437
1437
  `comments=${telegramE2EServer.state.comments.length}`,
1438
1438
  );
1439
+
1440
+ telegramE2EServer.state.comments = [];
1441
+ telegramE2EServer.state.updates = [
1442
+ {
1443
+ update_id: 403,
1444
+ message: {
1445
+ message_id: 83,
1446
+ date: Math.floor(Date.now() / 1000),
1447
+ chat: {
1448
+ id: Number(e2eDestination.chat_id),
1449
+ type: "supergroup",
1450
+ title: e2eDestination.label,
1451
+ },
1452
+ from: {
1453
+ id: 7001,
1454
+ is_bot: false,
1455
+ first_name: "Operator",
1456
+ username: "operator_user",
1457
+ },
1458
+ text: "@RyoAI2_bot 하이",
1459
+ entities: buildTelegramMentionEntities("@RyoAI2_bot 하이"),
1460
+ },
1461
+ },
1462
+ ];
1463
+ const routeRyoai1 = normalizeRunnerRoute({
1464
+ ...e2eRoute,
1465
+ name: "selftest-runner-e2e-ownership-ryoai1",
1466
+ server_bot_name: "RyoAI_bot",
1467
+ server_bot_id: "88888888-8888-4888-8888-888888888881",
1468
+ destination_id: "dest-telegram",
1469
+ destination_label: e2eDestination.label,
1470
+ });
1471
+ const routeRyoai2 = normalizeRunnerRoute({
1472
+ ...e2eRoute,
1473
+ name: "selftest-runner-e2e-ownership-ryoai2",
1474
+ server_bot_name: "RyoAI2_bot",
1475
+ server_bot_id: "88888888-8888-4888-8888-888888888882",
1476
+ destination_id: "dest-telegram",
1477
+ destination_label: e2eDestination.label,
1478
+ });
1479
+ const routeRyoai3 = normalizeRunnerRoute({
1480
+ ...e2eRoute,
1481
+ name: "selftest-runner-e2e-ownership-ryoai3",
1482
+ server_bot_name: "RyoAI3_bot",
1483
+ server_bot_id: "88888888-8888-4888-8888-888888888883",
1484
+ destination_id: "dest-telegram",
1485
+ destination_label: e2eDestination.label,
1486
+ });
1487
+ const routeRyoai2Key = runnerRouteKey(routeRyoai2);
1488
+ const routeRyoai3Key = runnerRouteKey(routeRyoai3);
1489
+ await archiveLocalTelegramMessagesForRoute({
1490
+ routeKey: routeRyoai3Key,
1491
+ route: routeRyoai3,
1492
+ routeState: {},
1493
+ runtime: {
1494
+ baseURL: telegramE2EServer.baseURL,
1495
+ timeoutSeconds: 10,
1496
+ token: e2eToken,
1497
+ actor: {
1498
+ user_id: e2eActorUserID,
1499
+ },
1500
+ },
1501
+ bot: {
1502
+ id: "88888888-8888-4888-8888-888888888883",
1503
+ name: "RyoAI3_bot",
1504
+ username: "RyoAI3_bot",
1505
+ role: "monitor",
1506
+ },
1507
+ destination: {
1508
+ chatID: e2eDestination.chat_id,
1509
+ },
1510
+ archiveThread: {
1511
+ threadID: e2eThreadID,
1512
+ },
1513
+ managedConversationBots: [
1514
+ {
1515
+ username: "RyoAI_bot",
1516
+ route: routeRyoai1,
1517
+ bot: { username: "RyoAI_bot", name: "RyoAI_bot" },
1518
+ },
1519
+ {
1520
+ username: "RyoAI2_bot",
1521
+ route: routeRyoai2,
1522
+ bot: { username: "RyoAI2_bot", name: "RyoAI2_bot" },
1523
+ },
1524
+ {
1525
+ username: "RyoAI3_bot",
1526
+ route: routeRyoai3,
1527
+ bot: { username: "RyoAI3_bot", name: "RyoAI3_bot" },
1528
+ },
1529
+ ],
1530
+ deps: buildRunnerRuntimeDeps(),
1531
+ });
1532
+ const ownershipState = loadBotRunnerState();
1533
+ const ryoai2Receipt = safeObject(
1534
+ safeObject(safeObject(ownershipState.routes)[routeRyoai2Key]).recent_local_inbound_receipts?.[`${String(e2eDestination.chat_id)}:83`],
1535
+ );
1536
+ const ryoai3Receipt = safeObject(
1537
+ safeObject(safeObject(ownershipState.routes)[routeRyoai3Key]).recent_local_inbound_receipts?.[`${String(e2eDestination.chat_id)}:83`],
1538
+ );
1539
+ push(
1540
+ "telegram_local_inbound_receipt_is_attributed_to_explicit_mention_owner_route",
1541
+ String(ryoai2Receipt.receipt_route_key || "") === routeRyoai2Key
1542
+ && String(ryoai2Receipt.receipt_bot_username || "") === "ryoai2_bot"
1543
+ && !String(ryoai3Receipt.receipt_route_key || "").trim(),
1544
+ `owner=${JSON.stringify(ryoai2Receipt)} foreign=${JSON.stringify(ryoai3Receipt)}`,
1545
+ );
1439
1546
  } catch (err) {
1440
1547
  push("telegram_runner_e2e_local_mock", false, String(err?.message || err));
1441
1548
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.261",
3
+ "version": "0.2.262",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [