metheus-governance-mcp-cli 0.2.250 → 0.2.252

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
@@ -141,6 +141,7 @@ import {
141
141
  buildProcessableArchiveLogicalKey,
142
142
  findEarlierProcessableArchiveDuplicate,
143
143
  findRecentTelegramMessageEnvelope,
144
+ isTelegramLocalInboundEnvelopeForRoute,
144
145
  isInboundArchiveKind,
145
146
  normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
146
147
  normalizeArchiveCommentRecord,
@@ -2665,14 +2666,17 @@ function buildRunnerSourceMessageEnvelope({
2665
2666
  parsedArchive = null,
2666
2667
  }) {
2667
2668
  const localEnvelope = findRunnerRouteLocalInboundEnvelope(routeState, parsedArchive);
2668
- if (String(localEnvelope.source_origin || "").trim().toLowerCase() === "local_telegram_inbound") {
2669
- return localEnvelope;
2670
- }
2671
2669
  const fallbackBotSelector = normalizeTelegramMentionUsername(
2672
2670
  normalizedRoute?.botName
2673
2671
  || normalizedRoute?.serverBotName
2674
2672
  || "",
2675
2673
  );
2674
+ if (isTelegramLocalInboundEnvelopeForRoute(localEnvelope, {
2675
+ routeKey,
2676
+ botUsername: fallbackBotSelector,
2677
+ })) {
2678
+ return localEnvelope;
2679
+ }
2676
2680
  return buildRunnerTelegramMessageEnvelopeFromParsedArchive(parsedArchive, {
2677
2681
  source_origin: "archive_reconstructed",
2678
2682
  source_route_key: String(routeKey || "").trim(),
@@ -7865,30 +7869,23 @@ function extractTelegramEntityText(text, entity) {
7865
7869
  return body.slice(offset, offset + length);
7866
7870
  }
7867
7871
 
7868
- function extractTelegramMentionUsernames(text, entities) {
7869
- const set = new Set();
7870
- for (const entityRaw of ensureArray(entities)) {
7871
- const entity = safeObject(entityRaw);
7872
+ function extractTelegramMentionUsernames(text, entities) {
7873
+ const set = new Set();
7874
+ for (const entityRaw of ensureArray(entities)) {
7875
+ const entity = safeObject(entityRaw);
7872
7876
  const type = String(entity.type || "").trim().toLowerCase();
7873
7877
  if (type === "mention") {
7874
7878
  const username = normalizeTelegramMentionUsername(extractTelegramEntityText(text, entity));
7875
7879
  if (username) set.add(username);
7876
7880
  continue;
7877
7881
  }
7878
- if (type === "text_mention") {
7879
- const username = normalizeTelegramMentionUsername(entity.user?.username);
7880
- if (username) set.add(username);
7881
- }
7882
- }
7883
- const regex = /(^|[^A-Za-z0-9_])@([A-Za-z0-9_]{3,})\b/g;
7884
- let match = regex.exec(String(text || ""));
7885
- while (match) {
7886
- const username = normalizeTelegramMentionUsername(match[2]);
7887
- if (username) set.add(username);
7888
- match = regex.exec(String(text || ""));
7889
- }
7890
- return Array.from(set);
7891
- }
7882
+ if (type === "text_mention") {
7883
+ const username = normalizeTelegramMentionUsername(entity.user?.username);
7884
+ if (username) set.add(username);
7885
+ }
7886
+ }
7887
+ return Array.from(set);
7888
+ }
7892
7889
 
7893
7890
  function normalizeLocalTelegramUpdate(rawUpdate) {
7894
7891
  const update = safeObject(rawUpdate);
@@ -17539,11 +17536,12 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
17539
17536
  mergeServerRunnerRequestLedgerIntoLocalState,
17540
17537
  buildRunnerStatusQueryLookup,
17541
17538
  tryJsonParse,
17542
- safeObject,
17543
- normalizeRunnerTriggerPolicy,
17544
- evaluateTelegramRunnerTrigger,
17545
- resolveRunnerResponderAdjudication,
17546
- selectPendingArchiveComments,
17539
+ safeObject,
17540
+ normalizeRunnerTriggerPolicy,
17541
+ evaluateTelegramRunnerTrigger,
17542
+ resolveHumanIntentContext,
17543
+ resolveRunnerResponderAdjudication,
17544
+ selectPendingArchiveComments,
17547
17545
  selectRunnerPendingWork,
17548
17546
  processRunnerSelectedRecord,
17549
17547
  resolveRunnerStartupLoopAdjudication,
@@ -2669,7 +2669,7 @@ function suggestedAIModelsForClient(clientName) {
2669
2669
  { value: "Opus 4.6", label: "Opus 4.6", description: "display label; runs as opus" },
2670
2670
  ];
2671
2671
  }
2672
- if (normalizedClient === "gemini") {
2672
+ if (normalizedClient === "gemini") {
2673
2673
  return [
2674
2674
  { value: "gemini-3.1-pro", label: "gemini-3.1-pro", description: "display label; runs as auto-gemini-3" },
2675
2675
  ];
@@ -18,6 +18,7 @@ const GEMINI_HOME_SYNC_FILES = [
18
18
  "settings.json",
19
19
  ];
20
20
  const GEMINI_STDIN_BRIDGE_PROMPT = "Use the full task provided on standard input as the authoritative prompt. Follow it exactly and output only the final answer.";
21
+ const GEMINI_CLI_TIMEOUT_MS = 90 * 1000;
21
22
  const LOCAL_AI_MODEL_MAPPINGS = {
22
23
  gpt: [
23
24
  {
@@ -769,13 +770,36 @@ function runGeminiRawText({ promptText, workspaceDir, model, permissionMode, rea
769
770
  env: runtime.env,
770
771
  input: String(promptText || ""),
771
772
  maxBuffer: 8 * 1024 * 1024,
773
+ timeout: GEMINI_CLI_TIMEOUT_MS,
772
774
  },
773
775
  );
774
776
  if (result.error) {
777
+ const errorCode = String(result.error?.code || "").trim().toUpperCase();
778
+ if (errorCode === "ETIMEDOUT") {
779
+ const stderrText = String(result.stderr || "").trim();
780
+ const stdoutText = String(result.stdout || "").trim();
781
+ const details = [stderrText, stdoutText]
782
+ .filter(Boolean)
783
+ .map((value) => value.replace(/\s+/g, " ").trim())
784
+ .filter(Boolean)
785
+ .slice(0, 2)
786
+ .join(" | ");
787
+ throw new Error(
788
+ details
789
+ ? `Gemini CLI timed out after ${Math.round(GEMINI_CLI_TIMEOUT_MS / 1000)}s while waiting for a model response (${details})`
790
+ : `Gemini CLI timed out after ${Math.round(GEMINI_CLI_TIMEOUT_MS / 1000)}s while waiting for a model response`,
791
+ );
792
+ }
775
793
  throw new Error(String(result.error?.message || result.error));
776
794
  }
777
795
  if (result.status !== 0) {
778
- throw new Error(String(result.stderr || result.stdout || `gemini exited with status ${result.status}`));
796
+ const stderrText = String(result.stderr || "");
797
+ const stdoutText = String(result.stdout || "");
798
+ const combinedText = `${stderrText}\n${stdoutText}`.trim();
799
+ if (/MODEL_CAPACITY_EXHAUSTED|No capacity available for model/i.test(combinedText)) {
800
+ throw new Error(`Gemini model capacity is currently unavailable for ${String(model || "").trim() || "the configured model"}`);
801
+ }
802
+ throw new Error(combinedText || `gemini exited with status ${result.status}`);
779
803
  }
780
804
  return String(result.stdout || "");
781
805
  } finally {
@@ -1377,7 +1401,8 @@ function buildGeminiArgs({ model, permissionMode }) {
1377
1401
  "--allowed-mcp-server-names",
1378
1402
  "none",
1379
1403
  ];
1380
- if (model) {
1404
+ const normalizedModel = normalizeModelAliasText(model);
1405
+ if (model && normalizedModel !== "auto-gemini-3") {
1381
1406
  args.push("--model", model);
1382
1407
  }
1383
1408
  return args;
@@ -1433,6 +1458,9 @@ function buildGeminiThinkingConfig(model, reasoningEffort) {
1433
1458
 
1434
1459
  export function resolveGeminiReasoningConfig(rawModelValue = "", reasoningEffort = "medium") {
1435
1460
  const executionModel = resolveLocalAIExecutionModel("gemini", rawModelValue);
1461
+ if (!executionModel || executionModel === "auto-gemini-3") {
1462
+ return null;
1463
+ }
1436
1464
  const thinkingConfig = buildGeminiThinkingConfig(executionModel, reasoningEffort);
1437
1465
  if (!thinkingConfig) {
1438
1466
  return null;
@@ -284,6 +284,28 @@ function normalizeMentionSelector(rawValue) {
284
284
  return String(rawValue || "").trim().replace(/^@+/, "").toLowerCase();
285
285
  }
286
286
 
287
+ export function isTelegramLocalInboundEnvelopeForRoute(rawEnvelope, {
288
+ routeKey = "",
289
+ botUsername = "",
290
+ } = {}) {
291
+ const envelope = normalizeTelegramMessageEnvelope(rawEnvelope);
292
+ const sourceOrigin = String(envelope.source_origin || "").trim().toLowerCase();
293
+ if (sourceOrigin !== "local_telegram_inbound") {
294
+ return false;
295
+ }
296
+ const expectedRouteKey = String(routeKey || "").trim();
297
+ const actualRouteKey = String(envelope.source_route_key || "").trim();
298
+ if (expectedRouteKey && expectedRouteKey !== actualRouteKey) {
299
+ return false;
300
+ }
301
+ const expectedBotUsername = normalizeMentionSelector(botUsername);
302
+ const actualBotUsername = normalizeMentionSelector(envelope.source_bot_username || "");
303
+ if (expectedBotUsername && expectedBotUsername !== actualBotUsername) {
304
+ return false;
305
+ }
306
+ return true;
307
+ }
308
+
287
309
  function uniqueNormalizedSelectors(values) {
288
310
  return Array.from(new Set(
289
311
  ensureArray(values)
@@ -8,6 +8,7 @@ import {
8
8
  compareArchiveCommentRecords,
9
9
  dedupeProcessableArchiveComments,
10
10
  findRecentTelegramMessageEnvelope,
11
+ isTelegramLocalInboundEnvelopeForRoute,
11
12
  isInboundArchiveKind,
12
13
  normalizeTelegramMessageEnvelope,
13
14
  selectPendingArchiveComments,
@@ -402,18 +403,23 @@ function resolveRunnerDeliverySourceMessageEnvelope({
402
403
  messageID: archiveMessageID,
403
404
  },
404
405
  );
405
- if (String(routeLocalEnvelope.source_origin || "").trim().toLowerCase() === "local_telegram_inbound") {
406
+ if (isTelegramLocalInboundEnvelopeForRoute(routeLocalEnvelope, {
407
+ routeKey,
408
+ botUsername: currentBotSelector,
409
+ })) {
406
410
  return routeLocalEnvelope;
407
411
  }
408
412
  const persistedSourceEnvelope = normalizeTelegramMessageEnvelope(
409
413
  safeObject(persistedRequest).source_message_envelope
410
414
  || safeObject(persistedRequest).sourceMessageEnvelope,
411
415
  );
412
- const persistedOrigin = String(persistedSourceEnvelope.source_origin || "").trim().toLowerCase();
413
416
  const persistedChatID = String(persistedSourceEnvelope.chat_id || "").trim();
414
417
  const persistedMessageID = intFromRawAllowZero(persistedSourceEnvelope.message_id, 0);
415
418
  if (
416
- persistedOrigin === "local_telegram_inbound"
419
+ isTelegramLocalInboundEnvelopeForRoute(persistedSourceEnvelope, {
420
+ routeKey,
421
+ botUsername: currentBotSelector,
422
+ })
417
423
  && archiveChatID
418
424
  && archiveChatID === persistedChatID
419
425
  && archiveMessageID > 0
@@ -3382,21 +3388,22 @@ async function maybeExecuteDynamicRolePlan({
3382
3388
  };
3383
3389
  }
3384
3390
 
3385
- function buildConversationPeerMap(bot, normalizedRoute, deps) {
3386
- const peers = typeof deps?.resolveConversationPeerBots === "function"
3387
- ? ensureArray(deps.resolveConversationPeerBots(normalizedRoute))
3388
- : [];
3389
- const output = new Map();
3390
- const register = (peerRaw) => {
3391
- const peer = safeObject(peerRaw);
3392
- const displayName = String(peer.name || peer.username || peer.id || "").trim();
3393
- const selectors = uniqueOrdered([
3394
- normalizeMentionSelector(peer.username),
3395
- normalizeMentionSelector(peer.name),
3396
- ]);
3397
- for (const selector of selectors) {
3398
- output.set(selector, {
3399
- selector,
3391
+ function buildConversationPeerMap(bot, normalizedRoute, deps) {
3392
+ const peers = typeof deps?.resolveConversationPeerBots === "function"
3393
+ ? ensureArray(deps.resolveConversationPeerBots(normalizedRoute))
3394
+ : [];
3395
+ const output = new Map();
3396
+ const register = (peerRaw) => {
3397
+ const peer = safeObject(peerRaw);
3398
+ const displayName = String(peer.name || peer.username || peer.id || "").trim();
3399
+ const explicitUsername = normalizeMentionSelector(peer.username);
3400
+ const selectors = uniqueOrdered([
3401
+ explicitUsername,
3402
+ explicitUsername ? "" : normalizeMentionSelector(peer.name),
3403
+ ]);
3404
+ for (const selector of selectors) {
3405
+ output.set(selector, {
3406
+ selector,
3400
3407
  displayName: displayName || selector,
3401
3408
  id: String(peer.id || "").trim(),
3402
3409
  });
@@ -4935,7 +4942,10 @@ export async function processRunnerSelectedRecord({
4935
4942
  && !precomputedConversationResponderSelection;
4936
4943
  if (!currentBotSelected && !shouldDeferBotReplyConversationAuthorization) {
4937
4944
  const adjudicationDecision = String(responderAdjudication.decision || "").trim() || "no_responder";
4938
- const adjudicationReason = String(responderAdjudication.reason_code || "").trim() || "not_selected_by_adjudicator";
4945
+ const rawAdjudicationReason = String(responderAdjudication.reason_code || "").trim() || "not_selected_by_adjudicator";
4946
+ const adjudicationReason = rawAdjudicationReason === "precomputed_human_intent_contract" && selectedResponderSelectors.length > 0
4947
+ ? "precomputed_human_intent_contract_selected_other_responder"
4948
+ : rawAdjudicationReason;
4939
4949
  saveRunnerRouteState(
4940
4950
  routeKey,
4941
4951
  buildRunnerRouteStateFromComment(selectedRecord, {
@@ -35,19 +35,11 @@ function normalizeTelegramMentionUsername(rawValue) {
35
35
  }
36
36
 
37
37
  function listTelegramMentionUsernames(parsedArchive) {
38
- const usernames = new Set(
38
+ return uniqueOrdered(
39
39
  ensureArray(parsedArchive?.mentionUsernames)
40
40
  .map((value) => normalizeTelegramMentionUsername(value))
41
41
  .filter(Boolean),
42
42
  );
43
- const text = String(parsedArchive?.body || "").trim().toLowerCase();
44
- for (const match of text.matchAll(/@([a-z0-9_]{3,})/gi)) {
45
- const normalized = normalizeTelegramMentionUsername(match?.[1]);
46
- if (normalized) {
47
- usernames.add(normalized);
48
- }
49
- }
50
- return Array.from(usernames);
51
43
  }
52
44
 
53
45
  function inferTelegramArchiveChatType(parsedArchive) {
@@ -60,10 +52,13 @@ function inferTelegramArchiveChatType(parsedArchive) {
60
52
 
61
53
  function buildTelegramBotUsernameCandidates(bot, route) {
62
54
  const set = new Set();
55
+ const explicitUsername = normalizeTelegramMentionUsername(bot?.username);
56
+ const fallbackDisplayCandidates = explicitUsername
57
+ ? []
58
+ : [bot?.name, route?.botName];
63
59
  for (const candidate of [
64
- bot?.username,
65
- bot?.name,
66
- route?.botName,
60
+ explicitUsername,
61
+ ...fallbackDisplayCandidates,
67
62
  ]) {
68
63
  const normalized = normalizeTelegramMentionUsername(candidate);
69
64
  if (normalized) {
@@ -74,13 +69,11 @@ function buildTelegramBotUsernameCandidates(bot, route) {
74
69
  }
75
70
 
76
71
  function doesTelegramArchiveMentionBot(parsedArchive, bot, route) {
77
- const text = String(parsedArchive?.body || "").trim().toLowerCase();
78
72
  const mentions = new Set(listTelegramMentionUsernames(parsedArchive));
79
73
  const candidates = buildTelegramBotUsernameCandidates(bot, route);
80
74
  for (const username of candidates) {
81
75
  if (!username) continue;
82
76
  if (mentions.has(username)) return true;
83
- if (text.includes(`@${username}`)) return true;
84
77
  }
85
78
  return false;
86
79
  }
@@ -117,7 +117,8 @@ export async function runSelftestRunnerScenarios(push, deps) {
117
117
  const tryJsonParse = requireDependency(deps, "tryJsonParse");
118
118
  const safeObject = requireDependency(deps, "safeObject");
119
119
  const normalizeRunnerTriggerPolicy = requireDependency(deps, "normalizeRunnerTriggerPolicy");
120
- const evaluateTelegramRunnerTrigger = requireDependency(deps, "evaluateTelegramRunnerTrigger");
120
+ const evaluateTelegramRunnerTrigger = requireDependency(deps, "evaluateTelegramRunnerTrigger");
121
+ const resolveHumanIntentContext = requireDependency(deps, "resolveHumanIntentContext");
121
122
  const resolveRunnerResponderAdjudication = requireDependency(deps, "resolveRunnerResponderAdjudication");
122
123
  const selectPendingArchiveComments = requireDependency(deps, "selectPendingArchiveComments");
123
124
  const selectRunnerPendingWork = requireDependency(deps, "selectRunnerPendingWork");
@@ -2464,13 +2465,80 @@ export async function runSelftestRunnerScenarios(push, deps) {
2464
2465
  mentionOnlyRoute,
2465
2466
  { name: "ServerProtocolMonitorBot", role: "monitor" },
2466
2467
  );
2467
- push(
2468
- "telegram_trigger_mentions_only_accepts_bot_mention",
2469
- mentionOnlyMatched.shouldRespond === true && mentionOnlyMatched.trigger === "mention",
2470
- `shouldRespond=${mentionOnlyMatched.shouldRespond} trigger=${mentionOnlyMatched.trigger}`,
2471
- );
2472
-
2473
- const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
2468
+ push(
2469
+ "telegram_trigger_mentions_only_accepts_bot_mention",
2470
+ mentionOnlyMatched.shouldRespond === true && mentionOnlyMatched.trigger === "mention",
2471
+ `shouldRespond=${mentionOnlyMatched.shouldRespond} trigger=${mentionOnlyMatched.trigger}`,
2472
+ );
2473
+
2474
+ const mentionOnlyAliasCandidate = evaluateTelegramRunnerTrigger(
2475
+ {
2476
+ id: "comment-2a",
2477
+ parsedArchive: {
2478
+ kind: "telegram_message",
2479
+ chatID: "-100123",
2480
+ chatType: "supergroup",
2481
+ body: "hello @RyoAI_bot2",
2482
+ mentionUsernames: [],
2483
+ replyToSenderIsBot: false,
2484
+ },
2485
+ },
2486
+ mentionOnlyRoute,
2487
+ { username: "ryoai2_bot", name: "RyoAI_bot2", role: "monitor" },
2488
+ );
2489
+ push(
2490
+ "telegram_trigger_does_not_treat_raw_alias_text_as_authoritative_mention",
2491
+ mentionOnlyAliasCandidate.shouldRespond === true
2492
+ && mentionOnlyAliasCandidate.trigger === "mentions_only_unaddressed_candidate",
2493
+ `shouldRespond=${mentionOnlyAliasCandidate.shouldRespond} trigger=${mentionOnlyAliasCandidate.trigger}`,
2494
+ );
2495
+
2496
+ try {
2497
+ const aliasHumanIntentContext = await resolveHumanIntentContext({
2498
+ selectedRecord: {
2499
+ id: "comment-alias-managed-mention",
2500
+ parsedArchive: {
2501
+ kind: "telegram_message",
2502
+ body: "@RyoAI_bot2 hi",
2503
+ senderIsBot: false,
2504
+ },
2505
+ },
2506
+ normalizedRoute: {
2507
+ name: "telegram-monitor-ryoai2-bot-2",
2508
+ },
2509
+ bot: {
2510
+ username: "ryoai2_bot",
2511
+ name: "RyoAI_bot2",
2512
+ },
2513
+ executionPlan: {},
2514
+ deps: {
2515
+ resolveConversationPeerBots: () => [],
2516
+ },
2517
+ persistedRequest: {
2518
+ conversation_intent_mode: "single_bot",
2519
+ conversation_reply_expectation: "informational",
2520
+ conversation_initial_responders: ["ryoai2_bot"],
2521
+ conversation_allowed_responders: ["ryoai2_bot"],
2522
+ conversation_participants: ["ryoai2_bot"],
2523
+ conversation_lead_bot: "ryoai2_bot",
2524
+ conversation_summary_bot: "ryoai2_bot",
2525
+ },
2526
+ });
2527
+ push(
2528
+ "runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
2529
+ ensureArray(aliasHumanIntentContext?.managedMentions).length === 0
2530
+ && aliasHumanIntentContext?.reusedPersistedContract === true,
2531
+ `managedMentions=${JSON.stringify(ensureArray(aliasHumanIntentContext?.managedMentions))} reused=${String(aliasHumanIntentContext?.reusedPersistedContract)}`,
2532
+ );
2533
+ } catch (err) {
2534
+ push(
2535
+ "runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
2536
+ false,
2537
+ String(err?.message || err),
2538
+ );
2539
+ }
2540
+
2541
+ const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
2474
2542
  {
2475
2543
  id: "comment-2b",
2476
2544
  parsedArchive: {
@@ -12986,6 +13054,141 @@ export async function runSelftestRunnerScenarios(push, deps) {
12986
13054
  push("runner_delivery_prefers_route_local_inbound_provenance_envelope", false, String(err?.message || err));
12987
13055
  }
12988
13056
 
13057
+ try {
13058
+ let capturedSourceMessageEnvelope = {};
13059
+ const processed = await processRunnerSelectedRecord({
13060
+ routeKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
13061
+ normalizedRoute: {
13062
+ name: "telegram-monitor-ryoai2-bot-2",
13063
+ provider: "telegram",
13064
+ role: "monitor",
13065
+ roleProfile: "monitor",
13066
+ botName: "RyoAI2_bot",
13067
+ archivePolicy: { mirrorReplies: true, dedupeOutbound: true },
13068
+ triggerPolicy: {
13069
+ directMessages: true,
13070
+ mentionsOnly: false,
13071
+ replyToBotMessages: true,
13072
+ ignoreEditedMessages: true,
13073
+ },
13074
+ },
13075
+ routeState: {
13076
+ recent_local_inbound_envelopes: {},
13077
+ },
13078
+ selectedRecord: {
13079
+ id: "comment-foreign-provenance",
13080
+ threadID: "thread-1",
13081
+ parsedArchive: {
13082
+ kind: "telegram_message",
13083
+ chatID: "-100123",
13084
+ chatType: "supergroup",
13085
+ body: "@RyoAI2_bot hi",
13086
+ messageID: 228,
13087
+ sender: "human",
13088
+ senderIsBot: false,
13089
+ mentionUsernames: ["ryoai2_bot"],
13090
+ },
13091
+ },
13092
+ persistedHumanIntentRequest: {
13093
+ source_message_envelope: {
13094
+ chat_id: "-100123",
13095
+ message_id: 228,
13096
+ source_origin: "local_telegram_inbound",
13097
+ source_route_key: "telegram-monitor-ryoai-bot-2::project::telegram::monitor::dest::actor",
13098
+ source_bot_username: "ryoai_bot",
13099
+ },
13100
+ },
13101
+ pendingOrdered: [],
13102
+ bot: {
13103
+ id: "bot-2",
13104
+ name: "RyoAI2_bot",
13105
+ username: "RyoAI2_bot",
13106
+ role: "monitor",
13107
+ provider: "telegram",
13108
+ },
13109
+ destination: {
13110
+ id: "dest-1",
13111
+ label: "Main Room",
13112
+ provider: "telegram",
13113
+ chatID: "-100123",
13114
+ },
13115
+ archiveThread: {
13116
+ threadID: "thread-1",
13117
+ workItemID: "work-item-1",
13118
+ },
13119
+ executionPlan: {
13120
+ mode: "role_profile",
13121
+ roleProfileName: "monitor",
13122
+ roleProfile: {
13123
+ client: "sample",
13124
+ model: "",
13125
+ permissionMode: "read_only",
13126
+ reasoningEffort: "low",
13127
+ },
13128
+ workspaceDir: "",
13129
+ workspaceSource: "selftest",
13130
+ usedCommandFallback: false,
13131
+ },
13132
+ runtime: {
13133
+ baseURL: "https://example.test",
13134
+ token: "selftest-token",
13135
+ timeoutSeconds: 30,
13136
+ actor: { user_id: "user-1" },
13137
+ },
13138
+ deps: {
13139
+ saveRunnerRouteState: () => {},
13140
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
13141
+ runRunnerAIExecution: async () => ({
13142
+ skip: false,
13143
+ reply: "Hello from RyoAI2_bot.",
13144
+ }),
13145
+ performLocalBotDelivery: async ({ sourceMessageEnvelope }) => {
13146
+ capturedSourceMessageEnvelope = safeObject(sourceMessageEnvelope);
13147
+ return {
13148
+ delivery: {
13149
+ dryRun: false,
13150
+ body: {
13151
+ result: {
13152
+ message_id: 9002,
13153
+ },
13154
+ },
13155
+ },
13156
+ archive: {},
13157
+ };
13158
+ },
13159
+ serializeRunnerTriggerPolicy: (value) => value,
13160
+ serializeRunnerArchivePolicy: (value) => value,
13161
+ buildRunnerExecutionDeps: () => ({
13162
+ validateWorkspaceArtifacts,
13163
+ analyzeHumanConversationIntentWithAI: async () => ({
13164
+ mode: "single_bot",
13165
+ lead_bot: "ryoai2_bot",
13166
+ participants: ["ryoai2_bot"],
13167
+ initial_responders: ["ryoai2_bot"],
13168
+ allowed_responders: ["ryoai2_bot"],
13169
+ summary_bot: "",
13170
+ allow_bot_to_bot: false,
13171
+ reply_expectation: "informational",
13172
+ intent_type: "small_talk",
13173
+ }),
13174
+ }),
13175
+ buildRunnerDeliveryDeps: () => ({}),
13176
+ buildRunnerRuntimeDeps: () => ({}),
13177
+ resolveConversationPeerBots: () => [],
13178
+ },
13179
+ });
13180
+ push(
13181
+ "runner_delivery_rejects_foreign_route_local_provenance",
13182
+ processed.kind === "replied"
13183
+ && String(capturedSourceMessageEnvelope.source_origin || "") === "archive_reconstructed"
13184
+ && String(capturedSourceMessageEnvelope.source_route_key || "").includes("telegram-monitor-ryoai2-bot-2")
13185
+ && String(capturedSourceMessageEnvelope.source_bot_username || "") === "ryoai2_bot",
13186
+ `kind=${String(processed.kind || "(none)")} origin=${String(capturedSourceMessageEnvelope.source_origin || "(none)")} route=${String(capturedSourceMessageEnvelope.source_route_key || "(none)")} bot=${String(capturedSourceMessageEnvelope.source_bot_username || "(none)")}`,
13187
+ );
13188
+ } catch (err) {
13189
+ push("runner_delivery_rejects_foreign_route_local_provenance", false, String(err?.message || err));
13190
+ }
13191
+
12989
13192
  try {
12990
13193
  const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-observed-artifacts-"));
12991
13194
  const scriptDir = path.join(workspaceDir, ".metheus", "runner-runtime", "local-ai-scratch");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.250",
3
+ "version": "0.2.252",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [