metheus-governance-mcp-cli 0.2.281 → 0.2.282

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
@@ -3111,20 +3111,25 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
3111
3111
  entry.sourceMessageBotUsername,
3112
3112
  ]),
3113
3113
  });
3114
- normalized[requestKey] = {
3115
- request_key: requestKey,
3116
- project_id: String(entry.project_id || entry.projectID || "").trim(),
3117
- provider: String(entry.provider || "").trim(),
3118
- chat_id: firstNonEmptyString([
3119
- entry.chat_id,
3120
- entry.chatID,
3121
- normalizedSourceMessageEnvelope.chat_id,
3122
- ]),
3123
- source_message_id: intFromRawAllowZero(
3124
- entry.source_message_id
3125
- || entry.sourceMessageID
3126
- || normalizedSourceMessageEnvelope.message_id,
3127
- 0,
3114
+ normalized[requestKey] = {
3115
+ request_key: requestKey,
3116
+ project_id: String(entry.project_id || entry.projectID || "").trim(),
3117
+ provider: String(entry.provider || "").trim(),
3118
+ chat_id: firstNonEmptyString([
3119
+ entry.chat_id,
3120
+ entry.chatID,
3121
+ normalizedSourceMessageEnvelope.chat_id,
3122
+ ]),
3123
+ canonical_human_message_key: firstNonEmptyString([
3124
+ entry.canonical_human_message_key,
3125
+ entry.canonicalHumanMessageKey,
3126
+ normalizedSourceMessageEnvelope.canonical_human_message_key,
3127
+ ]),
3128
+ source_message_id: intFromRawAllowZero(
3129
+ entry.source_message_id
3130
+ || entry.sourceMessageID
3131
+ || normalizedSourceMessageEnvelope.message_id,
3132
+ 0,
3128
3133
  ) || undefined,
3129
3134
  source_message_thread_id: intFromRawAllowZero(
3130
3135
  entry.source_message_thread_id
@@ -3813,18 +3818,35 @@ function shouldBypassRunnerStartupLoopForContractFollowup({
3813
3818
  return true;
3814
3819
  }
3815
3820
 
3816
- function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
3817
- const chatID = String(selectors.chatID || "").trim();
3818
- const messageID = intFromRawAllowZero(selectors.messageID, 0);
3819
- if (!chatID || messageID <= 0) {
3820
- return [];
3821
- }
3822
- return findRunnerRequestsForScope(state, normalizedRoute, { chatID })
3823
- .filter((entry) => (
3824
- intFromRawAllowZero(entry.source_message_id, 0) === messageID
3825
- || intFromRawAllowZero(entry.last_source_message_id, 0) === messageID
3826
- ));
3827
- }
3821
+ function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
3822
+ const chatID = String(selectors.chatID || "").trim();
3823
+ const messageID = intFromRawAllowZero(selectors.messageID, 0);
3824
+ const canonicalHumanMessageKey = String(selectors.canonicalHumanMessageKey || "").trim();
3825
+ if (!chatID || (messageID <= 0 && !canonicalHumanMessageKey)) {
3826
+ return [];
3827
+ }
3828
+ return findRunnerRequestsForScope(state, normalizedRoute, { chatID })
3829
+ .filter((entryRaw) => {
3830
+ const entry = safeObject(entryRaw);
3831
+ if (canonicalHumanMessageKey) {
3832
+ const requestCanonicalHumanMessageKey = firstNonEmptyString([
3833
+ entry.canonical_human_message_key,
3834
+ safeObject(entry.source_message_envelope).canonical_human_message_key,
3835
+ buildRunnerCanonicalHumanInboundKey(safeObject(entry.source_message_envelope)),
3836
+ ]);
3837
+ if (requestCanonicalHumanMessageKey === canonicalHumanMessageKey) {
3838
+ return true;
3839
+ }
3840
+ }
3841
+ return (
3842
+ messageID > 0
3843
+ && (
3844
+ intFromRawAllowZero(entry.source_message_id, 0) === messageID
3845
+ || intFromRawAllowZero(entry.last_source_message_id, 0) === messageID
3846
+ )
3847
+ );
3848
+ });
3849
+ }
3828
3850
 
3829
3851
  function sortRunnerRequestEntriesNewestFirst(entries = []) {
3830
3852
  return ensureArray(entries).slice().sort((leftRaw, rightRaw) => {
@@ -3839,24 +3861,26 @@ function sortRunnerRequestEntriesNewestFirst(entries = []) {
3839
3861
  });
3840
3862
  }
3841
3863
 
3842
- async function findServerRunnerRequestForMessageID({
3843
- normalizedRoute,
3844
- runtime,
3845
- chatID,
3846
- messageID,
3847
- }) {
3864
+ async function findServerRunnerRequestForMessageID({
3865
+ normalizedRoute,
3866
+ runtime,
3867
+ chatID,
3868
+ messageID,
3869
+ canonicalHumanMessageKey = "",
3870
+ }) {
3848
3871
  const projectID = String(normalizedRoute?.projectID || "").trim();
3849
3872
  const provider = String(normalizedRoute?.provider || "").trim();
3850
- const normalizedChatID = String(chatID || "").trim();
3851
- const normalizedMessageID = intFromRawAllowZero(messageID, 0);
3852
- if (
3853
- !projectID
3854
- || !provider
3855
- || !normalizedChatID
3856
- || normalizedMessageID <= 0
3857
- || !runtime?.baseURL
3858
- || !runtime?.token
3859
- ) {
3873
+ const normalizedChatID = String(chatID || "").trim();
3874
+ const normalizedMessageID = intFromRawAllowZero(messageID, 0);
3875
+ const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
3876
+ if (
3877
+ !projectID
3878
+ || !provider
3879
+ || !normalizedChatID
3880
+ || (normalizedMessageID <= 0 && !normalizedCanonicalHumanMessageKey)
3881
+ || !runtime?.baseURL
3882
+ || !runtime?.token
3883
+ ) {
3860
3884
  return null;
3861
3885
  }
3862
3886
  try {
@@ -3869,18 +3893,29 @@ async function findServerRunnerRequestForMessageID({
3869
3893
  limit: 500,
3870
3894
  offset: 0,
3871
3895
  });
3872
- const matched = sortRunnerRequestEntriesNewestFirst(serverRequests.filter((entryRaw) => {
3873
- const entry = safeObject(entryRaw);
3874
- return (
3875
- String(entry.project_id || "").trim() === projectID
3876
- && String(entry.provider || "").trim() === provider
3877
- && String(entry.chat_id || "").trim() === normalizedChatID
3878
- && (
3879
- intFromRawAllowZero(entry.source_message_id, 0) === normalizedMessageID
3880
- || intFromRawAllowZero(entry.last_source_message_id, 0) === normalizedMessageID
3881
- )
3882
- );
3883
- }));
3896
+ const matched = sortRunnerRequestEntriesNewestFirst(serverRequests.filter((entryRaw) => {
3897
+ const entry = safeObject(entryRaw);
3898
+ const requestCanonicalHumanMessageKey = firstNonEmptyString([
3899
+ entry.canonical_human_message_key,
3900
+ safeObject(entry.source_message_envelope).canonical_human_message_key,
3901
+ buildRunnerCanonicalHumanInboundKey(safeObject(entry.source_message_envelope)),
3902
+ ]);
3903
+ return (
3904
+ String(entry.project_id || "").trim() === projectID
3905
+ && String(entry.provider || "").trim() === provider
3906
+ && String(entry.chat_id || "").trim() === normalizedChatID
3907
+ && (
3908
+ (normalizedCanonicalHumanMessageKey && requestCanonicalHumanMessageKey === normalizedCanonicalHumanMessageKey)
3909
+ || (
3910
+ normalizedMessageID > 0
3911
+ && (
3912
+ intFromRawAllowZero(entry.source_message_id, 0) === normalizedMessageID
3913
+ || intFromRawAllowZero(entry.last_source_message_id, 0) === normalizedMessageID
3914
+ )
3915
+ )
3916
+ )
3917
+ );
3918
+ }));
3884
3919
  return safeObject(matched[0]);
3885
3920
  } catch {
3886
3921
  return null;
@@ -4321,25 +4356,27 @@ function pickRunnerSharedConversationSourceRequest(entries = [], excludeRequestK
4321
4356
  return safeObject(matched[0]);
4322
4357
  }
4323
4358
 
4324
- async function findServerRunnerConversationSourceRequestForMessageID({
4325
- normalizedRoute,
4326
- runtime,
4327
- chatID,
4328
- messageID,
4329
- excludeRequestKey = "",
4330
- }) {
4359
+ async function findServerRunnerConversationSourceRequestForMessageID({
4360
+ normalizedRoute,
4361
+ runtime,
4362
+ chatID,
4363
+ messageID,
4364
+ canonicalHumanMessageKey = "",
4365
+ excludeRequestKey = "",
4366
+ }) {
4331
4367
  const projectID = String(normalizedRoute?.projectID || "").trim();
4332
4368
  const provider = String(normalizedRoute?.provider || "").trim();
4333
- const normalizedChatID = String(chatID || "").trim();
4334
- const normalizedMessageID = intFromRawAllowZero(messageID, 0);
4335
- if (
4336
- !projectID
4337
- || !provider
4338
- || !normalizedChatID
4339
- || normalizedMessageID <= 0
4340
- || !runtime?.baseURL
4341
- || !runtime?.token
4342
- ) {
4369
+ const normalizedChatID = String(chatID || "").trim();
4370
+ const normalizedMessageID = intFromRawAllowZero(messageID, 0);
4371
+ const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
4372
+ if (
4373
+ !projectID
4374
+ || !provider
4375
+ || !normalizedChatID
4376
+ || (normalizedMessageID <= 0 && !normalizedCanonicalHumanMessageKey)
4377
+ || !runtime?.baseURL
4378
+ || !runtime?.token
4379
+ ) {
4343
4380
  return null;
4344
4381
  }
4345
4382
  try {
@@ -4352,18 +4389,29 @@ async function findServerRunnerConversationSourceRequestForMessageID({
4352
4389
  limit: 500,
4353
4390
  offset: 0,
4354
4391
  });
4355
- const matched = serverRequests.filter((entryRaw) => {
4356
- const entry = safeObject(entryRaw);
4357
- return (
4358
- String(entry.project_id || "").trim() === projectID
4359
- && String(entry.provider || "").trim() === provider
4360
- && String(entry.chat_id || "").trim() === normalizedChatID
4361
- && (
4362
- intFromRawAllowZero(entry.source_message_id, 0) === normalizedMessageID
4363
- || intFromRawAllowZero(entry.last_source_message_id, 0) === normalizedMessageID
4364
- )
4365
- );
4366
- });
4392
+ const matched = serverRequests.filter((entryRaw) => {
4393
+ const entry = safeObject(entryRaw);
4394
+ const requestCanonicalHumanMessageKey = firstNonEmptyString([
4395
+ entry.canonical_human_message_key,
4396
+ safeObject(entry.source_message_envelope).canonical_human_message_key,
4397
+ buildRunnerCanonicalHumanInboundKey(safeObject(entry.source_message_envelope)),
4398
+ ]);
4399
+ return (
4400
+ String(entry.project_id || "").trim() === projectID
4401
+ && String(entry.provider || "").trim() === provider
4402
+ && String(entry.chat_id || "").trim() === normalizedChatID
4403
+ && (
4404
+ (normalizedCanonicalHumanMessageKey && requestCanonicalHumanMessageKey === normalizedCanonicalHumanMessageKey)
4405
+ || (
4406
+ normalizedMessageID > 0
4407
+ && (
4408
+ intFromRawAllowZero(entry.source_message_id, 0) === normalizedMessageID
4409
+ || intFromRawAllowZero(entry.last_source_message_id, 0) === normalizedMessageID
4410
+ )
4411
+ )
4412
+ )
4413
+ );
4414
+ });
4367
4415
  return pickRunnerSharedConversationSourceRequest(matched, excludeRequestKey);
4368
4416
  } catch {
4369
4417
  return null;
@@ -5026,13 +5074,14 @@ async function claimRunnerRequestForHumanComment({
5026
5074
  runtime,
5027
5075
  archiveThreadID,
5028
5076
  });
5029
- const replyChainContext = safeObject(replyChainResolution.replyChainContext);
5077
+ const replyChainContext = safeObject(replyChainResolution.replyChainContext);
5030
5078
  const referencedRequest = safeObject(replyChainContext.referencedRequest);
5031
5079
  const resolvedNormalizedIntent = resolveRunnerRequestClaimIntent({
5032
5080
  normalizedIntent,
5033
5081
  });
5034
- let stateForClaim = safeObject(replyChainResolution.state);
5035
- const normalizedSharedHumanIntent = safeObject(sharedHumanIntent);
5082
+ const canonicalHumanMessageKey = buildRunnerCanonicalHumanInboundKey(parsed);
5083
+ let stateForClaim = safeObject(replyChainResolution.state);
5084
+ const normalizedSharedHumanIntent = safeObject(sharedHumanIntent);
5036
5085
  const provisionalConversationID = String(
5037
5086
  parsed.conversationID
5038
5087
  || replyChainContext.conversationID
@@ -5067,29 +5116,31 @@ async function claimRunnerRequestForHumanComment({
5067
5116
  };
5068
5117
  }
5069
5118
  const currentMessageID = intFromRawAllowZero(parsed.messageID, 0);
5070
- let sharedConversationSource = currentMessageID > 0
5071
- ? pickRunnerSharedConversationSourceRequest(
5072
- findRunnerRequestsForMessageID(stateForClaim, normalizedRoute, {
5073
- chatID: String(parsed.chatID || parsed.chatId || "").trim(),
5074
- messageID: currentMessageID,
5075
- }),
5076
- provisionalRequestKey,
5077
- )
5078
- : {};
5119
+ let sharedConversationSource = currentMessageID > 0
5120
+ ? pickRunnerSharedConversationSourceRequest(
5121
+ findRunnerRequestsForMessageID(stateForClaim, normalizedRoute, {
5122
+ chatID: String(parsed.chatID || parsed.chatId || "").trim(),
5123
+ messageID: currentMessageID,
5124
+ canonicalHumanMessageKey,
5125
+ }),
5126
+ provisionalRequestKey,
5127
+ )
5128
+ : {};
5079
5129
  if (
5080
5130
  !Object.keys(sharedConversationSource).length
5081
5131
  && currentMessageID > 0
5082
5132
  && runtime?.baseURL
5083
5133
  && runtime?.token
5084
5134
  ) {
5085
- sharedConversationSource = safeObject(await findServerRunnerConversationSourceRequestForMessageID({
5086
- normalizedRoute,
5087
- runtime,
5088
- chatID: String(parsed.chatID || parsed.chatId || "").trim(),
5089
- messageID: currentMessageID,
5090
- excludeRequestKey: provisionalRequestKey,
5091
- }));
5092
- }
5135
+ sharedConversationSource = safeObject(await findServerRunnerConversationSourceRequestForMessageID({
5136
+ normalizedRoute,
5137
+ runtime,
5138
+ chatID: String(parsed.chatID || parsed.chatId || "").trim(),
5139
+ messageID: currentMessageID,
5140
+ canonicalHumanMessageKey,
5141
+ excludeRequestKey: provisionalRequestKey,
5142
+ }));
5143
+ }
5093
5144
  const authorityContext = resolveRunnerHumanCommentAuthorityContext({
5094
5145
  normalizedRoute,
5095
5146
  selectedRecord,
@@ -5183,9 +5234,10 @@ async function claimRunnerRequestForHumanComment({
5183
5234
  provider: String(normalizedRoute?.provider || "").trim(),
5184
5235
  chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
5185
5236
  source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
5186
- source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || undefined,
5187
- source_message_body: String(parsed.body || "").trim(),
5188
- source_message_origin: String(sourceMessageEnvelope.source_origin || "").trim().toLowerCase(),
5237
+ source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || undefined,
5238
+ source_message_body: String(parsed.body || "").trim(),
5239
+ canonical_human_message_key: canonicalHumanMessageKey,
5240
+ source_message_origin: String(sourceMessageEnvelope.source_origin || "").trim().toLowerCase(),
5189
5241
  source_message_route_key: String(sourceMessageEnvelope.source_route_key || "").trim(),
5190
5242
  source_message_bot_username: normalizeTelegramMentionUsername(sourceMessageEnvelope.source_bot_username),
5191
5243
  source_message_envelope: sourceMessageEnvelope,
@@ -1,4 +1,5 @@
1
1
  import {
2
+ buildCanonicalHumanInboundKey,
2
3
  buildTelegramMessageEnvelopeFromParsedArchive,
3
4
  findRecentTelegramMessageEnvelope,
4
5
  isTelegramLocalInboundEnvelopeForRoute,
@@ -27,6 +28,20 @@ function normalizeMentionSelector(value) {
27
28
  return String(value || "").trim().replace(/^@+/, "").toLowerCase();
28
29
  }
29
30
 
31
+ function firstNonEmptyString(values) {
32
+ for (const value of ensureArray(values)) {
33
+ const normalized = String(value ?? "").trim();
34
+ if (normalized) {
35
+ return normalized;
36
+ }
37
+ }
38
+ return "";
39
+ }
40
+
41
+ function normalizeCanonicalHumanMessageKey(rawValue) {
42
+ return String(rawValue || "").trim();
43
+ }
44
+
30
45
  function uniqueOrdered(values) {
31
46
  const ordered = [];
32
47
  const seen = new Set();
@@ -82,6 +97,15 @@ function doesTelegramEnvelopeMatchMessage(rawEnvelope, {
82
97
  && intFromRawAllowZero(envelope.message_id, 0) === normalizedMessageID;
83
98
  }
84
99
 
100
+ function doesTelegramEnvelopeMatchCanonicalHumanMessage(rawEnvelope, canonicalHumanMessageKey = "") {
101
+ const normalizedCanonicalHumanMessageKey = normalizeCanonicalHumanMessageKey(canonicalHumanMessageKey);
102
+ if (!normalizedCanonicalHumanMessageKey) {
103
+ return false;
104
+ }
105
+ const envelope = normalizeTelegramMessageEnvelope(rawEnvelope);
106
+ return String(envelope.canonical_human_message_key || "").trim() === normalizedCanonicalHumanMessageKey;
107
+ }
108
+
85
109
  function normalizeRunnerRecentLocalInboundReceipt(rawReceipt) {
86
110
  const receipt = safeObject(rawReceipt);
87
111
  const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
@@ -89,7 +113,7 @@ function normalizeRunnerRecentLocalInboundReceipt(rawReceipt) {
89
113
  if (!chatID || !(messageID > 0)) {
90
114
  return {};
91
115
  }
92
- return {
116
+ const normalized = {
93
117
  chat_id: chatID,
94
118
  message_id: messageID,
95
119
  ...(intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0) > 0
@@ -107,10 +131,16 @@ function normalizeRunnerRecentLocalInboundReceipt(rawReceipt) {
107
131
  ...(normalizeMentionSelector(receipt.sender_username || receipt.senderUsername || "")
108
132
  ? { sender_username: normalizeMentionSelector(receipt.sender_username || receipt.senderUsername || "") }
109
133
  : {}),
134
+ ...(String(receipt.sender_id || receipt.senderID || "").trim()
135
+ ? { sender_id: String(receipt.sender_id || receipt.senderID || "").trim() }
136
+ : {}),
110
137
  sender_is_bot: receipt.sender_is_bot === true || receipt.senderIsBot === true,
111
138
  ...(String(receipt.body || "").trim()
112
139
  ? { body: String(receipt.body || "").trim() }
113
140
  : {}),
141
+ ...(String(receipt.occurred_at || receipt.occurredAt || "").trim()
142
+ ? { occurred_at: String(receipt.occurred_at || receipt.occurredAt || "").trim() }
143
+ : {}),
114
144
  ...(String(receipt.receipt_origin || receipt.receiptOrigin || "").trim()
115
145
  ? { receipt_origin: String(receipt.receipt_origin || receipt.receiptOrigin || "").trim().toLowerCase() }
116
146
  : {}),
@@ -121,16 +151,59 @@ function normalizeRunnerRecentLocalInboundReceipt(rawReceipt) {
121
151
  ? { receipt_bot_username: normalizeMentionSelector(receipt.receipt_bot_username || receipt.receiptBotUsername || "") }
122
152
  : {}),
123
153
  };
154
+ const canonicalHumanMessageKey = buildCanonicalHumanInboundKey({
155
+ chat_id: normalized.chat_id,
156
+ message_id: normalized.message_id,
157
+ message_thread_id: normalized.message_thread_id,
158
+ reply_to_message_id: normalized.reply_to_message_id,
159
+ kind: normalized.kind,
160
+ sender_id: normalized.sender_id,
161
+ sender_username: normalized.sender_username,
162
+ sender_is_bot: normalized.sender_is_bot === true,
163
+ body: normalized.body,
164
+ occurred_at: normalized.occurred_at,
165
+ canonical_human_message_key: firstNonEmptyString([
166
+ receipt.canonical_human_message_key,
167
+ receipt.canonicalHumanMessageKey,
168
+ ]),
169
+ });
170
+ if (canonicalHumanMessageKey) {
171
+ normalized.canonical_human_message_key = canonicalHumanMessageKey;
172
+ }
173
+ return normalized;
124
174
  }
125
175
 
126
- function findRecentTelegramInboundReceipt(rawMap, { chatID = "", messageID = 0 } = {}) {
176
+ function findRecentTelegramInboundReceipt(rawMap, {
177
+ chatID = "",
178
+ messageID = 0,
179
+ canonicalHumanMessageKey = "",
180
+ } = {}) {
127
181
  const normalizedChatID = String(chatID || "").trim();
128
182
  const normalizedMessageID = intFromRawAllowZero(messageID, 0);
129
- if (!normalizedChatID || !(normalizedMessageID > 0)) {
183
+ const normalizedCanonicalHumanMessageKey = normalizeCanonicalHumanMessageKey(canonicalHumanMessageKey);
184
+ if (!normalizedChatID || (!(normalizedMessageID > 0) && !normalizedCanonicalHumanMessageKey)) {
185
+ return {};
186
+ }
187
+ if (normalizedMessageID > 0) {
188
+ const key = `${normalizedChatID}:${normalizedMessageID}`;
189
+ const exactReceipt = normalizeRunnerRecentLocalInboundReceipt(safeObject(safeObject(rawMap)[key]));
190
+ if (Object.keys(exactReceipt).length > 0) {
191
+ return exactReceipt;
192
+ }
193
+ }
194
+ if (!normalizedCanonicalHumanMessageKey) {
130
195
  return {};
131
196
  }
132
- const key = `${normalizedChatID}:${normalizedMessageID}`;
133
- return normalizeRunnerRecentLocalInboundReceipt(safeObject(safeObject(rawMap)[key]));
197
+ for (const value of Object.values(safeObject(rawMap))) {
198
+ const normalizedReceipt = normalizeRunnerRecentLocalInboundReceipt(value);
199
+ if (
200
+ String(normalizedReceipt.chat_id || "").trim() === normalizedChatID
201
+ && String(normalizedReceipt.canonical_human_message_key || "").trim() === normalizedCanonicalHumanMessageKey
202
+ ) {
203
+ return normalizedReceipt;
204
+ }
205
+ }
206
+ return {};
134
207
  }
135
208
 
136
209
  function buildTelegramMessageEnvelopeFromRecentReceipt(rawReceipt) {
@@ -154,6 +227,35 @@ function buildTelegramMessageEnvelopeFromRecentReceipt(rawReceipt) {
154
227
  });
155
228
  }
156
229
 
230
+ function findRecentTelegramMessageEnvelopeWithCanonicalFallback(rawMap, {
231
+ chatID = "",
232
+ messageID = 0,
233
+ canonicalHumanMessageKey = "",
234
+ } = {}) {
235
+ const exactEnvelope = findRecentTelegramMessageEnvelope(rawMap, {
236
+ chatID,
237
+ messageID,
238
+ });
239
+ if (Object.keys(safeObject(exactEnvelope)).length > 0) {
240
+ return exactEnvelope;
241
+ }
242
+ const normalizedChatID = String(chatID || "").trim();
243
+ const normalizedCanonicalHumanMessageKey = normalizeCanonicalHumanMessageKey(canonicalHumanMessageKey);
244
+ if (!normalizedChatID || !normalizedCanonicalHumanMessageKey) {
245
+ return {};
246
+ }
247
+ for (const rawEnvelope of Object.values(safeObject(rawMap))) {
248
+ const normalizedEnvelope = normalizeTelegramMessageEnvelope(rawEnvelope);
249
+ if (
250
+ String(normalizedEnvelope.chat_id || "").trim() === normalizedChatID
251
+ && String(normalizedEnvelope.canonical_human_message_key || "").trim() === normalizedCanonicalHumanMessageKey
252
+ ) {
253
+ return normalizedEnvelope;
254
+ }
255
+ }
256
+ return {};
257
+ }
258
+
157
259
  function resolveRunnerTelegramSourceEnvelopeCandidates({
158
260
  routeState,
159
261
  persistedRequest,
@@ -173,19 +275,26 @@ function resolveRunnerTelegramSourceEnvelopeCandidates({
173
275
  archivedSourceEnvelope.message_id || archiveEnvelope.message_id,
174
276
  0,
175
277
  );
278
+ const archiveCanonicalHumanMessageKey = firstNonEmptyString([
279
+ archivedSourceEnvelope.canonical_human_message_key,
280
+ archiveEnvelope.canonical_human_message_key,
281
+ buildCanonicalHumanInboundKey(selectedRecord?.parsedArchive),
282
+ ]);
176
283
  const routeLocalReceipt = findRecentTelegramInboundReceipt(
177
284
  safeObject(routeState).recent_local_inbound_receipts,
178
285
  {
179
286
  chatID: archiveChatID,
180
287
  messageID: archiveMessageID,
288
+ canonicalHumanMessageKey: archiveCanonicalHumanMessageKey,
181
289
  },
182
290
  );
183
291
  const routeLocalReceiptEnvelope = buildTelegramMessageEnvelopeFromRecentReceipt(routeLocalReceipt);
184
- const routeLocalEnvelope = findRecentTelegramMessageEnvelope(
292
+ const routeLocalEnvelope = findRecentTelegramMessageEnvelopeWithCanonicalFallback(
185
293
  safeObject(routeState).recent_local_inbound_envelopes,
186
294
  {
187
295
  chatID: archiveChatID,
188
296
  messageID: archiveMessageID,
297
+ canonicalHumanMessageKey: archiveCanonicalHumanMessageKey,
189
298
  },
190
299
  );
191
300
  const persistedSourceEnvelope = normalizeTelegramMessageEnvelope(
@@ -197,6 +306,7 @@ function resolveRunnerTelegramSourceEnvelopeCandidates({
197
306
  archiveEnvelope,
198
307
  archiveChatID,
199
308
  archiveMessageID,
309
+ archiveCanonicalHumanMessageKey,
200
310
  routeLocalReceipt,
201
311
  routeLocalReceiptEnvelope,
202
312
  routeLocalEnvelope,
@@ -222,6 +332,7 @@ function resolveRunnerLocalSourceEnvelopeForRoute({
222
332
  archivedSourceEnvelope,
223
333
  archiveChatID,
224
334
  archiveMessageID,
335
+ archiveCanonicalHumanMessageKey,
225
336
  routeLocalReceiptEnvelope,
226
337
  routeLocalEnvelope,
227
338
  persistedSourceEnvelope,
@@ -251,10 +362,16 @@ function resolveRunnerLocalSourceEnvelopeForRoute({
251
362
  routeKey,
252
363
  botUsername: currentBotSelector,
253
364
  })
254
- && doesTelegramEnvelopeMatchMessage(persistedSourceEnvelope, {
255
- chatID: archiveChatID,
256
- messageID: archiveMessageID,
257
- })
365
+ && (
366
+ doesTelegramEnvelopeMatchMessage(persistedSourceEnvelope, {
367
+ chatID: archiveChatID,
368
+ messageID: archiveMessageID,
369
+ })
370
+ || doesTelegramEnvelopeMatchCanonicalHumanMessage(
371
+ persistedSourceEnvelope,
372
+ archiveCanonicalHumanMessageKey,
373
+ )
374
+ )
258
375
  ) {
259
376
  return {
260
377
  envelope: persistedSourceEnvelope,
@@ -267,10 +384,16 @@ function resolveRunnerLocalSourceEnvelopeForRoute({
267
384
  routeKey,
268
385
  botUsername: currentBotSelector,
269
386
  })
270
- && doesTelegramEnvelopeMatchMessage(archivedSourceEnvelope, {
271
- chatID: archiveChatID,
272
- messageID: archiveMessageID,
273
- })
387
+ && (
388
+ doesTelegramEnvelopeMatchMessage(archivedSourceEnvelope, {
389
+ chatID: archiveChatID,
390
+ messageID: archiveMessageID,
391
+ })
392
+ || doesTelegramEnvelopeMatchCanonicalHumanMessage(
393
+ archivedSourceEnvelope,
394
+ archiveCanonicalHumanMessageKey,
395
+ )
396
+ )
274
397
  ) {
275
398
  return {
276
399
  envelope: archivedSourceEnvelope,
@@ -289,14 +412,21 @@ function hasForeignRouteLocalEnvelopeForMessage({
289
412
  envelopes,
290
413
  archiveChatID,
291
414
  archiveMessageID,
415
+ archiveCanonicalHumanMessageKey,
292
416
  routeKey,
293
417
  currentBotSelector,
294
418
  }) {
295
419
  return ensureArray(envelopes).some((rawEnvelope) => (
296
- doesTelegramEnvelopeMatchMessage(rawEnvelope, {
297
- chatID: archiveChatID,
298
- messageID: archiveMessageID,
299
- })
420
+ (
421
+ doesTelegramEnvelopeMatchMessage(rawEnvelope, {
422
+ chatID: archiveChatID,
423
+ messageID: archiveMessageID,
424
+ })
425
+ || doesTelegramEnvelopeMatchCanonicalHumanMessage(
426
+ rawEnvelope,
427
+ archiveCanonicalHumanMessageKey,
428
+ )
429
+ )
300
430
  && String(safeObject(normalizeTelegramMessageEnvelope(rawEnvelope)).source_origin || "").trim().toLowerCase() === "local_telegram_inbound"
301
431
  && !isTelegramLocalInboundEnvelopeForRoute(rawEnvelope, {
302
432
  routeKey,
@@ -352,6 +482,7 @@ function resolveRunnerReceiptBackedHumanInboundVisibility({
352
482
  archivedSourceEnvelope,
353
483
  archiveChatID,
354
484
  archiveMessageID,
485
+ archiveCanonicalHumanMessageKey,
355
486
  routeLocalReceipt,
356
487
  persistedSourceEnvelope,
357
488
  } = safeObject(localMatch.candidates);
@@ -367,6 +498,7 @@ function resolveRunnerReceiptBackedHumanInboundVisibility({
367
498
  envelopes: [persistedSourceEnvelope, archivedSourceEnvelope],
368
499
  archiveChatID,
369
500
  archiveMessageID,
501
+ archiveCanonicalHumanMessageKey,
370
502
  routeKey,
371
503
  currentBotSelector,
372
504
  });
@@ -413,6 +545,7 @@ function buildRunnerDiagnosticTraceSummary({
413
545
  archivedSourceEnvelope,
414
546
  archiveChatID,
415
547
  archiveMessageID,
548
+ archiveCanonicalHumanMessageKey,
416
549
  persistedSourceEnvelope,
417
550
  routeLocalEnvelope,
418
551
  routeLocalReceipt,
@@ -436,6 +569,7 @@ function buildRunnerDiagnosticTraceSummary({
436
569
  envelopes: [persistedSourceEnvelope, archivedSourceEnvelope],
437
570
  archiveChatID,
438
571
  archiveMessageID,
572
+ archiveCanonicalHumanMessageKey,
439
573
  routeKey,
440
574
  currentBotSelector,
441
575
  });
@@ -528,6 +528,27 @@ function normalizeRunnerRecentLocalInboundReceipt(rawReceipt, fallbackKey = "")
528
528
  if (chatTitle) {
529
529
  normalized.chat_title = chatTitle;
530
530
  }
531
+ const canonicalHumanMessageKey = String(
532
+ safeObject(normalizeTelegramMessageEnvelope({
533
+ chat_id: normalized.chat_id,
534
+ message_id: normalized.message_id,
535
+ message_thread_id: normalized.message_thread_id,
536
+ reply_to_message_id: normalized.reply_to_message_id,
537
+ kind: normalized.kind,
538
+ sender_id: normalized.sender_id,
539
+ sender_username: normalized.sender_username,
540
+ sender_is_bot: normalized.sender_is_bot === true,
541
+ body: normalized.body,
542
+ occurred_at: normalized.occurred_at,
543
+ canonical_human_message_key: firstNonEmptyString([
544
+ receipt.canonical_human_message_key,
545
+ receipt.canonicalHumanMessageKey,
546
+ ]),
547
+ })).canonical_human_message_key || "",
548
+ ).trim();
549
+ if (canonicalHumanMessageKey) {
550
+ normalized.canonical_human_message_key = canonicalHumanMessageKey;
551
+ }
531
552
  return [receiptKey, normalized];
532
553
  }
533
554
 
@@ -2303,20 +2303,31 @@ export async function runSelftestRunnerScenarios(push, deps) {
2303
2303
  },
2304
2304
  });
2305
2305
  const sharedHumanState = loadBotRunnerState();
2306
- const sharedHumanRequestCount = Object.values(safeObject(sharedHumanState.requests))
2306
+ const sharedHumanRequests = Object.values(safeObject(sharedHumanState.requests))
2307
2307
  .filter((entryRaw) => {
2308
2308
  const entry = safeObject(entryRaw);
2309
2309
  return String(entry.chat_id || "") === "-100123"
2310
2310
  && String(entry.source_message_body || "") === sharedHumanBody;
2311
- })
2312
- .length;
2311
+ });
2312
+ const sharedHumanRequestCount = sharedHumanRequests.length;
2313
+ const sharedHumanCanonicalKey = buildArchivedInboundMessageKey({
2314
+ chatID: "-100123",
2315
+ messageID: 1189,
2316
+ messageThreadID: 0,
2317
+ kind: "telegram_message",
2318
+ senderID: "7001",
2319
+ senderIsBot: false,
2320
+ occurredAt: "2026-04-01T06:00:00.000Z",
2321
+ body: sharedHumanBody,
2322
+ }).replace(/^human:/, "");
2313
2323
  push(
2314
2324
  "runner_human_opening_request_identity_is_canonical_across_routes",
2315
2325
  sharedHumanClaimPrimary.ok === true
2316
2326
  && (sharedHumanClaimPeer.ok === true || String(sharedHumanClaimPeer.reason || "") === "request_already_claimed")
2317
2327
  && String(sharedHumanClaimPrimary.requestKey || "") === String(sharedHumanClaimPeer.requestKey || "")
2318
- && sharedHumanRequestCount === 1,
2319
- `primary=${String(sharedHumanClaimPrimary.requestKey || "(none)")} peer=${String(sharedHumanClaimPeer.requestKey || "(none)")} peer_reason=${String(sharedHumanClaimPeer.reason || "(none)")} count=${sharedHumanRequestCount}`,
2328
+ && sharedHumanRequestCount === 1
2329
+ && String(safeObject(sharedHumanRequests[0]).canonical_human_message_key || "") === sharedHumanCanonicalKey,
2330
+ `primary=${String(sharedHumanClaimPrimary.requestKey || "(none)")} peer=${String(sharedHumanClaimPeer.requestKey || "(none)")} peer_reason=${String(sharedHumanClaimPeer.reason || "(none)")} count=${sharedHumanRequestCount} canonical=${String(safeObject(sharedHumanRequests[0]).canonical_human_message_key || "(none)")}`,
2320
2331
  );
2321
2332
 
2322
2333
  const rootTaskRecord = {
@@ -16534,6 +16545,97 @@ export async function runSelftestRunnerScenarios(push, deps) {
16534
16545
  }
16535
16546
  }
16536
16547
 
16548
+ try {
16549
+ const canonicalVisibility = resolveRunnerHumanInboundVisibility({
16550
+ routeState: {
16551
+ recent_local_inbound_envelopes: {},
16552
+ recent_local_inbound_receipts: {
16553
+ "-100123:5201": {
16554
+ chat_id: "-100123",
16555
+ message_id: 5201,
16556
+ sender_id: "7001",
16557
+ body: "@ryoai_bot canonical hello",
16558
+ occurred_at: "2026-04-02T00:00:00.000Z",
16559
+ receipt_origin: "local_telegram_inbound",
16560
+ receipt_route_key: "telegram-monitor-canonical-owner::project::telegram::monitor::dest::actor",
16561
+ receipt_bot_username: "ryoai_bot",
16562
+ received_at: "2026-04-02T00:00:01.000Z",
16563
+ },
16564
+ },
16565
+ },
16566
+ persistedRequest: null,
16567
+ selectedRecord: {
16568
+ id: "comment-canonical-visibility-1",
16569
+ parsedArchive: {
16570
+ kind: "telegram_message",
16571
+ chatID: "-100123",
16572
+ chatType: "supergroup",
16573
+ body: "@ryoai_bot canonical hello",
16574
+ messageID: 6301,
16575
+ senderID: "7001",
16576
+ senderIsBot: false,
16577
+ occurredAt: "2026-04-02T00:00:00.000Z",
16578
+ mentionUsernames: ["ryoai_bot"],
16579
+ },
16580
+ },
16581
+ routeKey: "telegram-monitor-canonical-owner::project::telegram::monitor::dest::actor",
16582
+ currentBotSelector: "ryoai_bot",
16583
+ triggerDecision: {
16584
+ shouldRespond: true,
16585
+ triggerEligible: true,
16586
+ candidateHintOnly: true,
16587
+ trigger: "mention",
16588
+ reason: "canonical receipt-backed mention trigger",
16589
+ candidateBotUsernames: ["ryoai_bot"],
16590
+ },
16591
+ });
16592
+ const canonicalSourceEnvelope = resolveRunnerDeliverySourceMessageEnvelope({
16593
+ routeState: {
16594
+ recent_local_inbound_envelopes: {},
16595
+ recent_local_inbound_receipts: {
16596
+ "-100123:5201": {
16597
+ chat_id: "-100123",
16598
+ message_id: 5201,
16599
+ sender_id: "7001",
16600
+ body: "@ryoai_bot canonical hello",
16601
+ occurred_at: "2026-04-02T00:00:00.000Z",
16602
+ receipt_origin: "local_telegram_inbound",
16603
+ receipt_route_key: "telegram-monitor-canonical-owner::project::telegram::monitor::dest::actor",
16604
+ receipt_bot_username: "ryoai_bot",
16605
+ received_at: "2026-04-02T00:00:01.000Z",
16606
+ },
16607
+ },
16608
+ },
16609
+ persistedRequest: null,
16610
+ selectedRecord: {
16611
+ id: "comment-canonical-visibility-1",
16612
+ parsedArchive: {
16613
+ kind: "telegram_message",
16614
+ chatID: "-100123",
16615
+ chatType: "supergroup",
16616
+ body: "@ryoai_bot canonical hello",
16617
+ messageID: 6301,
16618
+ senderID: "7001",
16619
+ senderIsBot: false,
16620
+ occurredAt: "2026-04-02T00:00:00.000Z",
16621
+ mentionUsernames: ["ryoai_bot"],
16622
+ },
16623
+ },
16624
+ routeKey: "telegram-monitor-canonical-owner::project::telegram::monitor::dest::actor",
16625
+ currentBotSelector: "ryoai_bot",
16626
+ });
16627
+ push(
16628
+ "runner_visibility_receipt_backed_human_opening_matches_canonical_key_across_route_local_copies",
16629
+ canonicalVisibility.applies === true
16630
+ && canonicalVisibility.executable === true
16631
+ && String(canonicalVisibility.visibilitySource || "") === "route_local_inbound_receipt"
16632
+ && Number(safeObject(canonicalSourceEnvelope).message_id || 0) === 5201,
16633
+ `mode=${String(canonicalVisibility.visibilityMode || "(none)")} status=${String(canonicalVisibility.visibilityStatus || "(none)")} source=${String(canonicalVisibility.visibilitySource || "(none)")} reply_message_id=${String(safeObject(canonicalSourceEnvelope).message_id || "(none)")}`,
16634
+ );
16635
+ } catch (err) {
16636
+ push("runner_visibility_receipt_backed_human_opening_matches_canonical_key_across_route_local_copies", false, String(err?.message || err));
16637
+ }
16638
+
16537
16639
  for (const [testName, triggerKind, messageID] of [
16538
16640
  [
16539
16641
  "runner_visibility_receipt_first_mention_without_exact_receipt_is_blocked",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.281",
3
+ "version": "0.2.282",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [