metheus-governance-mcp-cli 0.2.274 → 0.2.276

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/cli.mjs CHANGED
@@ -133,15 +133,16 @@ import {
133
133
  tryRegister,
134
134
  withWorkspaceDirArg,
135
135
  } from "./lib/client-registration.mjs";
136
- import {
137
- applyPendingAgeSelection,
138
- buildTelegramBotReplyEnvelope,
139
- buildTelegramMessageEnvelopeFromParsedArchive as buildRunnerTelegramMessageEnvelopeFromParsedArchive,
140
- buildRunnerRouteStateFromComment,
141
- buildProcessableArchiveLogicalKey,
142
- findEarlierProcessableArchiveDuplicate,
143
- findRecentTelegramMessageEnvelope,
144
- isTelegramLocalInboundEnvelopeForRoute,
136
+ import {
137
+ applyPendingAgeSelection,
138
+ buildTelegramBotReplyEnvelope,
139
+ buildTelegramMessageEnvelopeFromParsedArchive as buildRunnerTelegramMessageEnvelopeFromParsedArchive,
140
+ buildRunnerRouteDuplicateStateFromComment,
141
+ buildRunnerRouteStateFromComment,
142
+ buildProcessableArchiveLogicalKey,
143
+ findEarlierProcessableArchiveDuplicate,
144
+ findRecentTelegramMessageEnvelope,
145
+ isTelegramLocalInboundEnvelopeForRoute,
145
146
  isInboundArchiveKind,
146
147
  normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
147
148
  normalizeArchiveCommentRecord,
@@ -2100,12 +2101,15 @@ function mergeRunnerStateRecords(preferred, fallback) {
2100
2101
  }
2101
2102
  return safeObject(secondary[key]);
2102
2103
  };
2103
- return {
2104
- last_processed_comment_id: pickStringField("last_processed_comment_id"),
2105
- last_processed_created_at: pickStringField("last_processed_created_at"),
2106
- last_source_message_id: pickNumberField("last_source_message_id", { allowUndefined: true }),
2107
- last_source_kind: pickStringField("last_source_kind"),
2108
- last_error: pickStringField("last_error", { allowBlank: true }),
2104
+ return {
2105
+ last_processed_comment_id: pickStringField("last_processed_comment_id"),
2106
+ last_processed_created_at: pickStringField("last_processed_created_at"),
2107
+ last_dedupe_comment_id: pickStringField("last_dedupe_comment_id"),
2108
+ last_source_message_id: pickNumberField("last_source_message_id", { allowUndefined: true }),
2109
+ last_source_kind: pickStringField("last_source_kind"),
2110
+ last_dedupe_source_message_id: pickNumberField("last_dedupe_source_message_id", { allowUndefined: true }),
2111
+ last_dedupe_source_kind: pickStringField("last_dedupe_source_kind"),
2112
+ last_error: pickStringField("last_error", { allowBlank: true }),
2109
2113
  updated_at: firstNonEmptyString([primary.updated_at, secondary.updated_at, new Date().toISOString()]),
2110
2114
  last_action: pickStringField("last_action"),
2111
2115
  last_reason: pickStringField("last_reason"),
@@ -5856,11 +5860,11 @@ function maybeBuildDuplicateArchivedSkipForRoute({
5856
5860
  };
5857
5861
  }
5858
5862
  const duplicateState = safeObject(routeStateForDuplicate);
5859
- const lastSourceMessageID = intFromRawAllowZero(duplicateState.last_source_message_id, 0);
5860
- const lastSourceKind = String(duplicateState.last_source_kind || "").trim().toLowerCase();
5863
+ const lastSourceMessageID = intFromRawAllowZero(duplicateState.last_dedupe_source_message_id, 0);
5864
+ const lastSourceKind = String(duplicateState.last_dedupe_source_kind || "").trim().toLowerCase();
5861
5865
  const selectedMessageID = intFromRawAllowZero(selectedParsed.messageID, 0);
5862
5866
  const selectedKind = String(selectedParsed.kind || "").trim().toLowerCase();
5863
- const lastCommentID = String(duplicateState.last_processed_comment_id || "").trim();
5867
+ const lastCommentID = String(duplicateState.last_dedupe_comment_id || "").trim();
5864
5868
  if (
5865
5869
  selectedMessageID > 0
5866
5870
  && lastSourceMessageID > 0
@@ -8591,9 +8595,11 @@ function normalizeLocalTelegramUpdate(rawUpdate) {
8591
8595
  };
8592
8596
  }
8593
8597
 
8594
- function buildArchivedInboundMessageKey(chatID, messageID) {
8595
- return `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
8596
- }
8598
+ function buildArchivedInboundMessageKey(chatID, messageID, sourceBotUsername = "") {
8599
+ const baseKey = `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
8600
+ const normalizedBotUsername = normalizeTelegramMentionUsername(sourceBotUsername);
8601
+ return normalizedBotUsername ? `${baseKey}::${normalizedBotUsername}` : baseKey;
8602
+ }
8597
8603
 
8598
8604
  function formatTelegramInboundArchiveComment(normalized) {
8599
8605
  const archiveSourceOrigin = String(normalized.archiveSourceOrigin || normalized.sourceOrigin || "").trim();
@@ -10575,6 +10581,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
10575
10581
  }),
10576
10582
  requestKey: requestClaim.requestKey,
10577
10583
  claimedRequest,
10584
+ dedupeStatePatch: buildRunnerRouteDuplicateStateFromComment(selectedRecord),
10578
10585
  visibilityStatePatch,
10579
10586
  });
10580
10587
  saveRunnerRouteState(routeKey, acceptedExecutionRoutePatch);
@@ -10739,6 +10746,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
10739
10746
  }),
10740
10747
  requestKey: requestClaim.requestKey,
10741
10748
  claimedRequest,
10749
+ dedupeStatePatch: buildRunnerRouteDuplicateStateFromComment(selectedRecord),
10742
10750
  visibilityStatePatch,
10743
10751
  });
10744
10752
  saveRunnerRouteState(routeKey, acceptedExecutionRoutePatch);
@@ -534,6 +534,16 @@ export function buildRunnerRouteStateFromComment(record, patch = {}) {
534
534
  };
535
535
  }
536
536
 
537
+ export function buildRunnerRouteDuplicateStateFromComment(record, patch = {}) {
538
+ const parsed = safeObject(record?.parsedArchive);
539
+ return {
540
+ last_dedupe_comment_id: String(record?.id || "").trim(),
541
+ last_dedupe_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
542
+ last_dedupe_source_kind: String(parsed.kind || "").trim(),
543
+ ...safeObject(patch),
544
+ };
545
+ }
546
+
537
547
  export function normalizeArchiveCommentRecord(rawComment, parseArchivedChatComment) {
538
548
  const comment = safeObject(rawComment);
539
549
  const body = String(comment.body || "").trim();
@@ -51,6 +51,56 @@ function intFromRawAllowZero(raw, fallback = 0) {
51
51
  return Number.isFinite(parsed) ? parsed : fallback;
52
52
  }
53
53
 
54
+ function escapeRegex(text) {
55
+ return String(text || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
56
+ }
57
+
58
+ function normalizeMentionSelector(value) {
59
+ return String(value || "").trim().replace(/^@+/, "").toLowerCase();
60
+ }
61
+
62
+ function buildRunnerInboundTargetSelectorSuffix(selector) {
63
+ const normalizedSelector = normalizeMentionSelector(selector);
64
+ return normalizedSelector ? `::${normalizedSelector}` : "";
65
+ }
66
+
67
+ function resolveRunnerArchiveSourceTargetSelector(recordRaw) {
68
+ const parsed = safeObject(safeObject(recordRaw).parsedArchive);
69
+ return normalizeMentionSelector(parsed.sourceBotUsername || parsed.source_bot_username);
70
+ }
71
+
72
+ function resolveRunnerLocalInboundReceiptTargetSelector(receiptRaw) {
73
+ const receipt = safeObject(receiptRaw);
74
+ const receiptBotSelector = normalizeMentionSelector(
75
+ receipt.receipt_bot_username
76
+ || receipt.receiptBotUsername
77
+ || receipt.source_bot_username
78
+ || receipt.sourceBotUsername,
79
+ );
80
+ if (!receiptBotSelector) {
81
+ return "";
82
+ }
83
+ const replyTargetSelector = normalizeMentionSelector(
84
+ receipt.reply_to_from_username
85
+ || receipt.replyToFromUsername
86
+ || receipt.reply_to_username
87
+ || receipt.replyToUsername,
88
+ );
89
+ const replyTargetIsBot = receipt.reply_to_from_is_bot === true
90
+ || receipt.replyToFromIsBot === true
91
+ || receipt.reply_to_sender_is_bot === true
92
+ || receipt.replyToSenderIsBot === true;
93
+ if (replyTargetIsBot && replyTargetSelector && replyTargetSelector === receiptBotSelector) {
94
+ return receiptBotSelector;
95
+ }
96
+ const body = String(receipt.body || receipt.text || "").trim();
97
+ if (!body) {
98
+ return "";
99
+ }
100
+ const mentionPattern = new RegExp(`(^|\\s)@${escapeRegex(receiptBotSelector)}(?=\\b|\\s|$)`, "i");
101
+ return mentionPattern.test(body) ? receiptBotSelector : "";
102
+ }
103
+
54
104
  function buildRunnerArchiveSourceMessageKey(recordRaw) {
55
105
  const parsed = safeObject(safeObject(recordRaw).parsedArchive);
56
106
  const chatID = String(parsed.chatID || parsed.chatId || "").trim();
@@ -58,7 +108,7 @@ function buildRunnerArchiveSourceMessageKey(recordRaw) {
58
108
  if (!chatID || !(messageID > 0)) {
59
109
  return "";
60
110
  }
61
- return `${chatID}:${messageID}`;
111
+ return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(resolveRunnerArchiveSourceTargetSelector(recordRaw))}`;
62
112
  }
63
113
 
64
114
  function buildRunnerLocalInboundReceiptKey(receiptRaw) {
@@ -68,7 +118,7 @@ function buildRunnerLocalInboundReceiptKey(receiptRaw) {
68
118
  if (!chatID || !(messageID > 0)) {
69
119
  return "";
70
120
  }
71
- return `${chatID}:${messageID}`;
121
+ return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(resolveRunnerLocalInboundReceiptTargetSelector(receiptRaw))}`;
72
122
  }
73
123
 
74
124
  function buildRunnerReceiptReplaySortTime(receiptRaw) {
@@ -157,10 +207,6 @@ function buildContextSpeakerType(parsedArchiveRaw) {
157
207
  return parsed.senderIsBot === true ? "bot" : "system";
158
208
  }
159
209
 
160
- function normalizeMentionSelector(value) {
161
- return String(value || "").trim().replace(/^@+/, "").toLowerCase();
162
- }
163
-
164
210
  function uniqueOrdered(values) {
165
211
  const seen = new Set();
166
212
  const output = [];
@@ -10,6 +10,7 @@ export function buildRunnerAcceptedExecutionRoutePatch({
10
10
  requestKey = "",
11
11
  claimedRequest = {},
12
12
  visibilityStatePatch = {},
13
+ dedupeStatePatch = {},
13
14
  }) {
14
15
  const normalizedActiveExecutionPatch = safeObject(activeExecutionPatch);
15
16
  const normalizedClaimedRequest = safeObject(claimedRequest);
@@ -19,6 +20,7 @@ export function buildRunnerAcceptedExecutionRoutePatch({
19
20
  last_root_work_item_id: String(normalizedClaimedRequest.root_work_item_id || "").trim(),
20
21
  last_root_work_item_title: String(normalizedClaimedRequest.root_work_item_title || "").trim(),
21
22
  last_root_work_item_status: String(normalizedClaimedRequest.root_work_item_status || "").trim(),
23
+ ...safeObject(dedupeStatePatch),
22
24
  ...safeObject(visibilityStatePatch),
23
25
  };
24
26
  }
@@ -280,6 +280,34 @@ function currentRouteOwnsRunnerInboundUpdate({
280
280
  }).some((owner) => String(safeObject(owner).routeKey || "").trim() === normalizedRouteKey);
281
281
  }
282
282
 
283
+ function resolveRunnerInboundArchiveSourceBotUsername({
284
+ update,
285
+ route,
286
+ bot,
287
+ }) {
288
+ const normalizedUpdate = safeObject(update);
289
+ const currentBotSelectors = buildRouteBotUsernameCandidates(bot, route);
290
+ const currentBotSelector = ensureArray(currentBotSelectors)[0] || "";
291
+ if (!currentBotSelector) {
292
+ return "";
293
+ }
294
+ const explicitMentions = ensureArray(normalizedUpdate.mentionUsernames)
295
+ .map((value) => normalizeMentionSelector(value))
296
+ .filter(Boolean);
297
+ if (explicitMentions.includes(currentBotSelector)) {
298
+ return currentBotSelector;
299
+ }
300
+ const replyTargetSelector = normalizeMentionSelector(normalizedUpdate.replyToFromUsername);
301
+ if (
302
+ normalizedUpdate.replyToFromIsBot === true
303
+ && replyTargetSelector
304
+ && currentBotSelectors.includes(replyTargetSelector)
305
+ ) {
306
+ return replyTargetSelector;
307
+ }
308
+ return "";
309
+ }
310
+
283
311
  function managedConversationBotTargetsCurrentRoute({
284
312
  update,
285
313
  bot,
@@ -480,6 +508,18 @@ function normalizeRunnerRecentLocalInboundReceipt(rawReceipt, fallbackKey = "")
480
508
  if (replyToMessageID > 0) {
481
509
  normalized.reply_to_message_id = replyToMessageID;
482
510
  }
511
+ const replyToFromUsername = firstNonEmptyString([
512
+ receipt.reply_to_from_username,
513
+ receipt.replyToFromUsername,
514
+ receipt.reply_to_username,
515
+ receipt.replyToUsername,
516
+ ]);
517
+ if (replyToFromUsername) {
518
+ normalized.reply_to_from_username = replyToFromUsername;
519
+ }
520
+ if (receipt.reply_to_from_is_bot === true || receipt.replyToFromIsBot === true) {
521
+ normalized.reply_to_from_is_bot = true;
522
+ }
483
523
  const chatType = firstNonEmptyString([receipt.chat_type, receipt.chatType]);
484
524
  if (chatType) {
485
525
  normalized.chat_type = chatType;
@@ -535,6 +575,8 @@ function buildRunnerLocalInboundArtifacts(updates, routeKey, route, bot, destina
535
575
  message_id: messageID,
536
576
  message_thread_id: intFromRawAllowZero(update.messageThreadID, 0),
537
577
  reply_to_message_id: intFromRawAllowZero(update.replyToMessageID, 0),
578
+ reply_to_from_username: update.replyToFromUsername,
579
+ reply_to_from_is_bot: update.replyToFromIsBot === true,
538
580
  kind: update.fromIsBot ? "bot_reply" : "telegram_message",
539
581
  sender_id: update.fromID,
540
582
  sender: update.fromName,
@@ -628,14 +670,16 @@ function buildRunnerRecentLocalInboundReceipts(routeStateRaw, localInboundArtifa
628
670
  const RUNNER_INBOUND_ARCHIVE_RESERVATION_TTL_MS = 10 * 60 * 1000;
629
671
  const runnerInboundArchiveReservations = new Map();
630
672
 
631
- function buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID) {
673
+ function buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID, sourceBotUsername = "") {
632
674
  const normalizedThreadID = String(threadID || "").trim();
633
675
  const normalizedChatID = String(chatID || "").trim();
634
676
  const normalizedMessageID = intFromRawAllowZero(messageID, 0);
677
+ const normalizedSourceBotUsername = normalizeMentionSelector(sourceBotUsername);
635
678
  if (!normalizedThreadID || !normalizedChatID || !(normalizedMessageID > 0)) {
636
679
  return "";
637
680
  }
638
- return `${normalizedThreadID}::${normalizedChatID}:${normalizedMessageID}`;
681
+ const baseKey = `${normalizedThreadID}::${normalizedChatID}:${normalizedMessageID}`;
682
+ return normalizedSourceBotUsername ? `${baseKey}::${normalizedSourceBotUsername}` : baseKey;
639
683
  }
640
684
 
641
685
  function cleanupRunnerInboundArchiveReservations(nowMs = Date.now()) {
@@ -646,8 +690,8 @@ function cleanupRunnerInboundArchiveReservations(nowMs = Date.now()) {
646
690
  }
647
691
  }
648
692
 
649
- function reserveRunnerInboundArchiveMessage(threadID, chatID, messageID) {
650
- const reservationKey = buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID);
693
+ function reserveRunnerInboundArchiveMessage(threadID, chatID, messageID, sourceBotUsername = "") {
694
+ const reservationKey = buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID, sourceBotUsername);
651
695
  if (!reservationKey) {
652
696
  return {
653
697
  ok: false,
@@ -777,7 +821,7 @@ async function loadRunnerExistingInboundArchiveKeys({
777
821
  .map((record) => normalizeArchiveCommentRecord(record, parseArchivedChatComment))
778
822
  .map((record) => record.parsedArchive)
779
823
  .filter((parsed) => parsed && isInboundArchiveKind(parsed.kind) && parsed.chatID)
780
- .map((parsed) => buildArchivedInboundMessageKey(parsed.chatID, parsed.messageID)),
824
+ .map((parsed) => buildArchivedInboundMessageKey(parsed.chatID, parsed.messageID, parsed.sourceBotUsername)),
781
825
  );
782
826
  }
783
827
 
@@ -835,12 +879,22 @@ async function archiveRunnerTelegramInboundUpdates({
835
879
  ) {
836
880
  continue;
837
881
  }
838
- const dedupeKey = buildArchivedInboundMessageKey(update.chatID, update.messageID);
882
+ const archiveSourceBotUsername = resolveRunnerInboundArchiveSourceBotUsername({
883
+ update,
884
+ route,
885
+ bot,
886
+ });
887
+ const dedupeKey = buildArchivedInboundMessageKey(update.chatID, update.messageID, archiveSourceBotUsername);
839
888
  if (boolFromRaw(archivePolicy.dedupeInbound, true) && existingKeys.has(dedupeKey)) {
840
889
  continue;
841
890
  }
842
891
  const reservation = boolFromRaw(archivePolicy.dedupeInbound, true)
843
- ? reserveRunnerInboundArchiveMessage(archiveThread?.threadID, update.chatID, update.messageID)
892
+ ? reserveRunnerInboundArchiveMessage(
893
+ archiveThread?.threadID,
894
+ update.chatID,
895
+ update.messageID,
896
+ archiveSourceBotUsername,
897
+ )
844
898
  : { ok: true, reservationKey: "" };
845
899
  if (!reservation.ok) {
846
900
  continue;
@@ -851,6 +905,8 @@ async function archiveRunnerTelegramInboundUpdates({
851
905
  archiveBody = formatTelegramInboundArchiveComment({
852
906
  ...update,
853
907
  archiveSourceOrigin: "telegram_archive_context",
908
+ archiveSourceRouteKey: archiveSourceBotUsername ? String(routeKey || "").trim() : "",
909
+ archiveSourceBotUsername,
854
910
  });
855
911
  createdComment = await createThreadComment({
856
912
  siteBaseURL: runtime.baseURL,
@@ -88,6 +88,10 @@ import {
88
88
  import {
89
89
  resolveCodexRawTextProcessResult,
90
90
  } from "./local-ai-adapters.mjs";
91
+ import {
92
+ buildRunnerRouteDuplicateStateFromComment,
93
+ buildRunnerRouteStateFromComment,
94
+ } from "./runner-helpers.mjs";
91
95
  import {
92
96
  buildRunnerInheritedRootReferenceRecorderResult,
93
97
  buildRunnerRootThreadRecorderFailure,
@@ -17735,6 +17739,17 @@ export async function runSelftestRunnerScenarios(push, deps) {
17735
17739
  }
17736
17740
 
17737
17741
  try {
17742
+ const foreignSkipState = buildRunnerRouteStateFromComment({
17743
+ id: "comment-foreign-skip-1",
17744
+ createdAt: "2026-04-01T00:00:00.000Z",
17745
+ parsedArchive: {
17746
+ kind: "telegram_message",
17747
+ messageID: 1178,
17748
+ },
17749
+ }, {
17750
+ last_action: "trigger_skipped",
17751
+ last_reason: "group message mentioned a different bot",
17752
+ });
17738
17753
  const acceptedRoutePatch = buildRunnerAcceptedExecutionRoutePatch({
17739
17754
  activeExecutionPatch: {
17740
17755
  active_comment_id: "comment-active-handoff-1",
@@ -17747,6 +17762,13 @@ export async function runSelftestRunnerScenarios(push, deps) {
17747
17762
  root_work_item_title: "Runner Accepted Handoff",
17748
17763
  root_work_item_status: "doing",
17749
17764
  },
17765
+ dedupeStatePatch: buildRunnerRouteDuplicateStateFromComment({
17766
+ id: "comment-active-handoff-1",
17767
+ parsedArchive: {
17768
+ kind: "telegram_message",
17769
+ messageID: 1180,
17770
+ },
17771
+ }),
17750
17772
  visibilityStatePatch: {
17751
17773
  last_visibility_mode: "reply",
17752
17774
  last_visibility_status: "visible",
@@ -17757,11 +17779,15 @@ export async function runSelftestRunnerScenarios(push, deps) {
17757
17779
  String(acceptedRoutePatch.active_comment_id || "") === "comment-active-handoff-1"
17758
17780
  && String(acceptedRoutePatch.last_request_key || "") === "request-active-handoff-1"
17759
17781
  && String(acceptedRoutePatch.last_root_work_item_title || "") === "Runner Accepted Handoff"
17782
+ && String(acceptedRoutePatch.last_dedupe_comment_id || "") === "comment-active-handoff-1"
17783
+ && String(acceptedRoutePatch.last_dedupe_source_kind || "") === "telegram_message"
17784
+ && Number(acceptedRoutePatch.last_dedupe_source_message_id || 0) === 1180
17785
+ && String(foreignSkipState.last_dedupe_comment_id || "") === ""
17760
17786
  && String(acceptedRoutePatch.last_visibility_status || "") === "visible"
17761
17787
  && !Object.prototype.hasOwnProperty.call(acceptedRoutePatch, "text")
17762
17788
  && !Object.prototype.hasOwnProperty.call(acceptedRoutePatch, "archiveConversation")
17763
17789
  && !Object.prototype.hasOwnProperty.call(acceptedRoutePatch, "delivery"),
17764
- `request=${String(acceptedRoutePatch.last_request_key || "(none)")} root=${String(acceptedRoutePatch.last_root_work_item_id || "(none)")} visibility=${String(acceptedRoutePatch.last_visibility_status || "(none)")}`,
17790
+ `request=${String(acceptedRoutePatch.last_request_key || "(none)")} root=${String(acceptedRoutePatch.last_root_work_item_id || "(none)")} dedupe=${String(acceptedRoutePatch.last_dedupe_source_message_id || "(none)")} foreign_skip_dedupe=${String(foreignSkipState.last_dedupe_comment_id || "(none)")} visibility=${String(acceptedRoutePatch.last_visibility_status || "(none)")}`,
17765
17791
  );
17766
17792
  } catch (err) {
17767
17793
  push("runner_recorder_active_execution_handoff_stays_within_module_5_boundary", false, String(err?.message || err));
@@ -19691,6 +19717,66 @@ export async function runSelftestRunnerScenarios(push, deps) {
19691
19717
  push("runner_entrypoint_pending_selection_stays_within_module_4_7_boundary", false, String(err?.message || err));
19692
19718
  }
19693
19719
 
19720
+ try {
19721
+ const foreignArchiveComment = {
19722
+ id: "comment-foreign-owner-1",
19723
+ createdAt: "2026-03-30T00:00:00.000Z",
19724
+ updatedAt: "2026-03-30T00:00:00.000Z",
19725
+ body: "@woobn_bot hi",
19726
+ parsedArchive: {
19727
+ kind: "telegram_message",
19728
+ messageID: 280,
19729
+ chatID: "-100999",
19730
+ chatType: "supergroup",
19731
+ sender: "tester",
19732
+ senderIsBot: false,
19733
+ body: "@woobn_bot hi",
19734
+ sourceBotUsername: "woobn_bot",
19735
+ },
19736
+ };
19737
+ const pendingWork = selectRunnerPendingWorkEntrypoint({
19738
+ comments: [foreignArchiveComment],
19739
+ importOutcome: {
19740
+ importedCommentIDs: [],
19741
+ importedComments: [],
19742
+ currentPollLocalInboundReceipts: [
19743
+ {
19744
+ chat_id: "-100999",
19745
+ message_id: 280,
19746
+ body: "@RyoAI3_bot 하이",
19747
+ receipt_bot_username: "ryoai3_bot",
19748
+ occurred_at: "2026-03-31T00:10:00.000Z",
19749
+ },
19750
+ ],
19751
+ },
19752
+ refreshedState: {
19753
+ last_processed_comment_id: "comment-foreign-owner-1",
19754
+ last_processed_created_at: "2026-03-30T00:00:00.000Z",
19755
+ },
19756
+ mode: "continue",
19757
+ parseArchivedChatComment,
19758
+ deps: {
19759
+ normalizeArchiveCommentRecord: (record) => ({
19760
+ id: String(record?.id || "").trim(),
19761
+ body: String(record?.body || "").trim(),
19762
+ createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
19763
+ updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
19764
+ parsedArchive: safeObject(record?.parsedArchive),
19765
+ }),
19766
+ applyPendingAgeSelection: (selection) => selection,
19767
+ },
19768
+ pendingSelectionOptions: {},
19769
+ });
19770
+ push(
19771
+ "runner_entrypoint_receipt_replay_skips_foreign_bot_archive_collision",
19772
+ ensureArray(pendingWork.receiptBackedPending).length === 0
19773
+ && ensureArray(safeObject(pendingWork.pending).pending).length === 0,
19774
+ `replay=${String(ensureArray(pendingWork.receiptBackedPending).length)} pending=${String(ensureArray(safeObject(pendingWork.pending).pending).length)}`,
19775
+ );
19776
+ } catch (err) {
19777
+ push("runner_entrypoint_receipt_replay_skips_foreign_bot_archive_collision", false, String(err?.message || err));
19778
+ }
19779
+
19694
19780
  try {
19695
19781
  const deliveryContext = await prepareLocalBotDeliveryContext({
19696
19782
  siteBaseURL: "https://example.test",
@@ -923,10 +923,10 @@ export async function runSelftestTelegramE2E(push, deps) {
923
923
  `envelope=${JSON.stringify(progressEnvelope)}`,
924
924
  );
925
925
  push(
926
- "telegram_archive_comment_is_context_only_without_route_ownership_fields",
926
+ "telegram_archive_comment_preserves_route_ownership_fields_for_explicit_targeting",
927
927
  progressCommentBody.includes("archive_source_origin: telegram_archive_context")
928
- && !progressCommentBody.includes("archive_source_route_key:")
929
- && !progressCommentBody.includes("archive_source_bot_username:"),
928
+ && progressCommentBody.includes("archive_source_route_key:")
929
+ && progressCommentBody.includes("archive_source_bot_username:"),
930
930
  `body=${progressCommentBody}`,
931
931
  );
932
932
  const progressParsedArchive = parseArchivedChatComment(progressCommentBody);
@@ -1657,6 +1657,93 @@ export async function runSelftestTelegramE2E(push, deps) {
1657
1657
  && ownershipBodies.length === 0,
1658
1658
  `owner=${JSON.stringify(ryoai2Receipt)} foreign=${JSON.stringify(ryoai3Receipt)} comments=${ownershipBodies.join(" || ")}`,
1659
1659
  );
1660
+
1661
+ telegramE2EServer.state.comments = [
1662
+ {
1663
+ id: "foreign-owner-archive-280",
1664
+ body: buildRunnerRuntimeDeps().formatTelegramInboundArchiveComment({
1665
+ eventName: "telegram.message.created",
1666
+ chatID: e2eDestination.chat_id,
1667
+ chatType: "supergroup",
1668
+ messageID: 280,
1669
+ fromID: "7001",
1670
+ fromName: "Operator",
1671
+ fromUsername: "operator_user",
1672
+ fromIsBot: false,
1673
+ mentionUsernames: ["woobn_bot"],
1674
+ text: "@WooBN_bot 하이",
1675
+ occurredAt: "2026-03-31T00:00:00.000Z",
1676
+ archiveSourceOrigin: "telegram_archive_context",
1677
+ archiveSourceRouteKey: "telegram-monitor-woobn-bot::project::telegram::monitor::dest::actor",
1678
+ archiveSourceBotUsername: "woobn_bot",
1679
+ }),
1680
+ created_at: "2026-03-31T00:00:01.000Z",
1681
+ updated_at: "2026-03-31T00:00:01.000Z",
1682
+ author_user_id: e2eActorUserID,
1683
+ },
1684
+ ];
1685
+ telegramE2EServer.state.updates = [
1686
+ {
1687
+ update_id: 404,
1688
+ message: {
1689
+ message_id: 280,
1690
+ date: Math.floor(Date.now() / 1000),
1691
+ chat: {
1692
+ id: Number(e2eDestination.chat_id),
1693
+ type: "supergroup",
1694
+ title: e2eDestination.label,
1695
+ },
1696
+ from: {
1697
+ id: 7002,
1698
+ is_bot: false,
1699
+ first_name: "Operator",
1700
+ username: "operator_user",
1701
+ },
1702
+ text: "@RyoAI3_bot 하이",
1703
+ entities: buildTelegramMentionEntities("@RyoAI3_bot 하이"),
1704
+ },
1705
+ },
1706
+ ];
1707
+ await archiveLocalTelegramMessagesForRoute({
1708
+ routeKey: routeRyoai3Key,
1709
+ route: routeRyoai3,
1710
+ routeState: {},
1711
+ runtime: {
1712
+ baseURL: telegramE2EServer.baseURL,
1713
+ timeoutSeconds: 10,
1714
+ token: e2eToken,
1715
+ actor: {
1716
+ user_id: e2eActorUserID,
1717
+ },
1718
+ },
1719
+ bot: {
1720
+ id: "88888888-8888-4888-8888-888888888883",
1721
+ name: "RyoAI3_bot",
1722
+ username: "RyoAI3_bot",
1723
+ role: "monitor",
1724
+ },
1725
+ destination: {
1726
+ chatID: e2eDestination.chat_id,
1727
+ },
1728
+ archiveThread: {
1729
+ threadID: e2eThreadID,
1730
+ },
1731
+ managedConversationBots: [
1732
+ {
1733
+ username: "RyoAI3_bot",
1734
+ route: routeRyoai3,
1735
+ bot: { username: "RyoAI3_bot", name: "RyoAI3_bot" },
1736
+ },
1737
+ ],
1738
+ deps: buildRunnerRuntimeDeps(),
1739
+ });
1740
+ const foreignCollisionBodies = telegramE2EServer.state.comments.map((item) => String(item.body || ""));
1741
+ push(
1742
+ "telegram_explicit_self_mention_reimports_when_same_message_id_exists_for_foreign_bot_archive",
1743
+ foreignCollisionBodies.filter((item) => item.includes("message_id: 280")).length === 2
1744
+ && foreignCollisionBodies.some((item) => item.includes("archive_source_bot_username: @ryoai3_bot")),
1745
+ `count=${foreignCollisionBodies.filter((item) => item.includes("message_id: 280")).length} bodies=${foreignCollisionBodies.join(" || ")}`,
1746
+ );
1660
1747
  } catch (err) {
1661
1748
  push("telegram_runner_e2e_local_mock", false, String(err?.message || err));
1662
1749
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.274",
3
+ "version": "0.2.276",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [