metheus-governance-mcp-cli 0.2.276 → 0.2.277

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
@@ -5770,7 +5770,21 @@ async function finalizePreparedRunnerSelectedRecordExecutionContext({
5770
5770
  };
5771
5771
  }
5772
5772
  if (mode === "continuation_finalize") {
5773
- const requestClaim = normalizePreparedRunnerRequestClaim(requestPreparation.requestClaim);
5773
+ const requestClaim = normalizePreparedRunnerRequestClaim(resolveRunnerContinuationRequestForBotReplyImpl({
5774
+ normalizedRoute,
5775
+ routeKey,
5776
+ selectedRecord,
5777
+ persist: true,
5778
+ deps: buildRunnerRecoveryDeps(),
5779
+ }));
5780
+ if (!requestClaim.ok) {
5781
+ return finalizeRunnerRequestClaimFailureRecorderState({
5782
+ normalizedRoute,
5783
+ runtime,
5784
+ requestClaim,
5785
+ syncRunnerRequestLedgerForProjectToServer,
5786
+ });
5787
+ }
5774
5788
  const finalizedRequest = await finalizePreparedRunnerRequestForExecution({
5775
5789
  normalizedRoute,
5776
5790
  routeKey,
@@ -16,6 +16,10 @@ function ensureArray(value) {
16
16
  return Array.isArray(value) ? value : [];
17
17
  }
18
18
 
19
+ function normalizeMentionSelector(rawValue) {
20
+ return String(rawValue || "").trim().replace(/^@+/, "").toLowerCase();
21
+ }
22
+
19
23
  function intFromRawAllowZero(raw, fallback = 0) {
20
24
  if (raw === null || raw === undefined || raw === "") {
21
25
  return fallback;
@@ -48,6 +52,45 @@ function buildRunnerContinuationContractTriggerDecision(triggerDecision) {
48
52
  };
49
53
  }
50
54
 
55
+ function isRunnerBotReplyContinuationAuthorizedForCurrentBot({
56
+ selectedRecord,
57
+ persistedRequest = null,
58
+ currentBotSelector = "",
59
+ }) {
60
+ const normalizedCurrentBotSelector = normalizeMentionSelector(currentBotSelector);
61
+ if (!normalizedCurrentBotSelector) {
62
+ return false;
63
+ }
64
+ const parsed = safeObject(selectedRecord?.parsedArchive);
65
+ const normalizedSenderSelectors = ensureArray([
66
+ parsed.botUsername,
67
+ parsed.senderUsername,
68
+ parsed.sender,
69
+ ])
70
+ .map((value) => normalizeMentionSelector(value))
71
+ .filter(Boolean);
72
+ if (normalizedSenderSelectors.includes(normalizedCurrentBotSelector)) {
73
+ return false;
74
+ }
75
+ const explicitReplyTargets = ensureArray([
76
+ ...(Array.isArray(parsed.mentionUsernames) ? parsed.mentionUsernames : []),
77
+ parsed.replyToBotUsername,
78
+ parsed.targetBotUsername,
79
+ parsed.assignBotUsername,
80
+ parsed.nextResponderBotUsername,
81
+ ])
82
+ .map((value) => normalizeMentionSelector(value))
83
+ .filter(Boolean);
84
+ if (explicitReplyTargets.includes(normalizedCurrentBotSelector)) {
85
+ return true;
86
+ }
87
+ const request = safeObject(persistedRequest);
88
+ const nextExpectedResponders = ensureArray(request.next_expected_responders)
89
+ .map((value) => normalizeMentionSelector(value))
90
+ .filter(Boolean);
91
+ return nextExpectedResponders.includes(normalizedCurrentBotSelector);
92
+ }
93
+
51
94
  export async function resolveRunnerPrecomputedResponderAdjudication({
52
95
  selectedRecord,
53
96
  pendingOrdered,
@@ -189,6 +232,11 @@ export async function resolveRunnerPrecomputedBotReplyAuthorityContext({
189
232
  persistedRequest: resolveRunnerContinuationLinkedRequest(normalizedRequestClaim),
190
233
  triggerDecision,
191
234
  });
235
+ const continuationAuthorizedForCurrentBot = isRunnerBotReplyContinuationAuthorizedForCurrentBot({
236
+ selectedRecord,
237
+ persistedRequest: resolveRunnerContinuationLinkedRequest(normalizedRequestClaim),
238
+ currentBotSelector,
239
+ });
192
240
  const currentBotSelected = ensureArray(continuationAdjudication?.selected_bot_usernames)
193
241
  .map((value) => String(value || "").trim().replace(/^@+/, "").toLowerCase())
194
242
  .includes(currentBotSelector);
@@ -204,6 +252,18 @@ export async function resolveRunnerPrecomputedBotReplyAuthorityContext({
204
252
  sharedHumanIntentContext: null,
205
253
  };
206
254
  }
255
+ if (!continuationAuthorizedForCurrentBot) {
256
+ return {
257
+ shouldSkip: true,
258
+ requestless: false,
259
+ skipAction: "adjudication_skipped",
260
+ skipReason: "bot_reply_not_explicitly_handed_off",
261
+ skipTrigger: "request_contract",
262
+ skipRecordPatch: {},
263
+ adjudication: continuationAdjudication,
264
+ sharedHumanIntentContext: null,
265
+ };
266
+ }
207
267
  return {
208
268
  shouldSkip: false,
209
269
  requestless: false,
@@ -431,6 +431,7 @@ export function peekRunnerContinuationRequestForBotReply({
431
431
  export function recoverRunnerContinuationRequestForBotReply({
432
432
  normalizedRoute,
433
433
  continuationPreview,
434
+ persist = true,
434
435
  deps = {},
435
436
  }) {
436
437
  const upsertRunnerRequest = requireDependency(deps, "upsertRunnerRequest");
@@ -506,6 +507,18 @@ export function recoverRunnerContinuationRequestForBotReply({
506
507
  || "",
507
508
  ).trim().toLowerCase(),
508
509
  };
510
+ if (persist !== true) {
511
+ return {
512
+ ok: true,
513
+ linkedRequestKey: fallbackRequestKey,
514
+ linkedRequest: seedRequest,
515
+ currentState,
516
+ conversationID: String(preview.conversationID || "").trim(),
517
+ commentKind: String(preview.commentKind || "").trim().toLowerCase(),
518
+ linkResolutionKind: "recovered_request_link",
519
+ linkRecovered: true,
520
+ };
521
+ }
509
522
  const seededRequest = upsertRunnerRequest(currentState, fallbackRequestKey, seedRequest);
510
523
  currentState.requests = seededRequest.requests;
511
524
  saveBotRunnerState({
@@ -531,6 +544,7 @@ export function resolveRunnerContinuationRequestForBotReply({
531
544
  normalizedRoute,
532
545
  routeKey,
533
546
  selectedRecord,
547
+ persist = true,
534
548
  deps = {},
535
549
  }) {
536
550
  const upsertRunnerConsumedComment = requireDependency(deps, "upsertRunnerConsumedComment");
@@ -547,12 +561,16 @@ export function resolveRunnerContinuationRequestForBotReply({
547
561
  continuation = recoverRunnerContinuationRequestForBotReply({
548
562
  normalizedRoute,
549
563
  continuationPreview: continuation,
564
+ persist,
550
565
  deps,
551
566
  });
552
567
  }
553
568
  if (!continuation.ok) {
554
569
  return continuation;
555
570
  }
571
+ if (persist !== true) {
572
+ return continuation;
573
+ }
556
574
  const {
557
575
  currentState,
558
576
  linkedRequest,
@@ -609,6 +627,7 @@ export function prepareRunnerBotReplyContinuationContext({
609
627
  normalizedRoute,
610
628
  routeKey,
611
629
  selectedRecord,
630
+ persist: false,
612
631
  deps,
613
632
  })
614
633
  : continuationPreview;
@@ -16340,11 +16340,82 @@ export async function runSelftestRunnerScenarios(push, deps) {
16340
16340
  && !Object.prototype.hasOwnProperty.call(continuation, "request"),
16341
16341
  `linked_key=${String(continuation.linkedRequestKey || "(none)")} has_request=${String(Object.prototype.hasOwnProperty.call(continuation, "request"))}`,
16342
16342
  );
16343
- } finally {
16344
- process.env.HOME = previousHome;
16345
- process.env.USERPROFILE = previousUserProfile;
16346
- try {
16347
- fs.rmSync(provenanceTempRoot, { recursive: true, force: true });
16343
+ saveBotRunnerState({
16344
+ routes: {
16345
+ [provenanceRouteKey]: {
16346
+ conversation_sessions: {
16347
+ "conversation-provenance-seed": {
16348
+ status: "open",
16349
+ started_at: "2026-03-27T00:00:00.000Z",
16350
+ participants: ["ryoai_bot"],
16351
+ initial_responders: ["ryoai_bot"],
16352
+ allowed_responders: ["ryoai_bot"],
16353
+ intent_mode: "single_bot",
16354
+ allow_bot_to_bot: false,
16355
+ },
16356
+ },
16357
+ recent_local_inbound_receipts: {
16358
+ "-100123:779": {
16359
+ chat_id: "-100123",
16360
+ message_id: 779,
16361
+ receipt_origin: "local_telegram_inbound",
16362
+ receipt_route_key: provenanceRouteKey,
16363
+ receipt_bot_username: "ryoai_bot",
16364
+ source_origin: "local_telegram_inbound",
16365
+ source_route_key: provenanceRouteKey,
16366
+ source_bot_username: "ryoai_bot",
16367
+ body: "hello provenance",
16368
+ message_thread_id: 43,
16369
+ },
16370
+ },
16371
+ last_followup_source_message_envelope: {
16372
+ message_id: 779,
16373
+ message_thread_id: 43,
16374
+ body: "hello provenance",
16375
+ source_origin: "local_telegram_inbound",
16376
+ source_route_key: provenanceRouteKey,
16377
+ source_bot_username: "ryoai_bot",
16378
+ },
16379
+ },
16380
+ },
16381
+ sharedInboxes: {},
16382
+ excludedComments: {},
16383
+ requests: {},
16384
+ consumedComments: {},
16385
+ });
16386
+ const stateBeforePreviewOnlyContinuation = loadBotRunnerState();
16387
+ const previewOnlyRecoveryContext = prepareRunnerSelectedRecordRecoveryContext({
16388
+ normalizedRoute: provenanceRoute,
16389
+ routeKey: provenanceRouteKey,
16390
+ selectedRecord: {
16391
+ id: "bot-reply-preview-only-seed",
16392
+ parsedArchive: {
16393
+ kind: "bot_reply",
16394
+ chatID: "-100123",
16395
+ messageID: 881,
16396
+ senderIsBot: true,
16397
+ conversationID: "conversation-provenance-seed",
16398
+ botUsername: "@ryoai_bot",
16399
+ },
16400
+ },
16401
+ });
16402
+ const stateAfterPreviewOnlyContinuation = loadBotRunnerState();
16403
+ const previewRequestsBefore = Object.keys(normalizeBotRunnerRequests(stateBeforePreviewOnlyContinuation.requests)).length;
16404
+ const previewRequestsAfter = Object.keys(normalizeBotRunnerRequests(stateAfterPreviewOnlyContinuation.requests)).length;
16405
+ const previewConsumedBefore = Object.keys(safeObject(stateBeforePreviewOnlyContinuation.consumedComments || stateBeforePreviewOnlyContinuation.consumed_comments)).length;
16406
+ const previewConsumedAfter = Object.keys(safeObject(stateAfterPreviewOnlyContinuation.consumedComments || stateAfterPreviewOnlyContinuation.consumed_comments)).length;
16407
+ push(
16408
+ "runner_continuation_preview_does_not_write_request_or_consumed_state",
16409
+ safeObject(previewOnlyRecoveryContext.botReplyContinuationContext).continuationLinkRecoverable === true
16410
+ && previewRequestsBefore === previewRequestsAfter
16411
+ && previewConsumedBefore === previewConsumedAfter,
16412
+ `recoverable=${String(safeObject(previewOnlyRecoveryContext.botReplyContinuationContext).continuationLinkRecoverable)} requests=${String(previewRequestsBefore)}->${String(previewRequestsAfter)} consumed=${String(previewConsumedBefore)}->${String(previewConsumedAfter)}`,
16413
+ );
16414
+ } finally {
16415
+ process.env.HOME = previousHome;
16416
+ process.env.USERPROFILE = previousUserProfile;
16417
+ try {
16418
+ fs.rmSync(provenanceTempRoot, { recursive: true, force: true });
16348
16419
  } catch {}
16349
16420
  }
16350
16421
  } catch (err) {
@@ -17447,6 +17518,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
17447
17518
  linkedRequest: {
17448
17519
  request_key: "req-1",
17449
17520
  selected_bot_usernames: ["alpha_bot"],
17521
+ next_expected_responders: ["alpha_bot"],
17450
17522
  },
17451
17523
  },
17452
17524
  },
@@ -17493,6 +17565,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
17493
17565
  linkedRequest: {
17494
17566
  request_key: "req-1",
17495
17567
  selected_bot_usernames: ["alpha_bot"],
17568
+ next_expected_responders: ["alpha_bot"],
17496
17569
  },
17497
17570
  },
17498
17571
  },
@@ -17529,6 +17602,69 @@ export async function runSelftestRunnerScenarios(push, deps) {
17529
17602
  push("runner_selected_record_execution_context_promotes_continuation_only_after_recovery", false, String(err?.message || err));
17530
17603
  }
17531
17604
 
17605
+ try {
17606
+ const preparation = await resolveRunnerPrecomputedSelectedRecordExecutionContext({
17607
+ selectedRecord: {
17608
+ id: "comment-continuation-no-explicit-handoff",
17609
+ parsedArchive: {
17610
+ kind: "bot_reply",
17611
+ chatID: "-100123",
17612
+ chatType: "supergroup",
17613
+ messageID: 3504,
17614
+ body: "foreign bot follow up",
17615
+ sender: "other bot",
17616
+ senderIsBot: true,
17617
+ botUsername: "@other_bot",
17618
+ mentionUsernames: [],
17619
+ },
17620
+ },
17621
+ recoveryContext: {
17622
+ selectedRecordKind: "bot_reply",
17623
+ botReplyContinuationContext: {
17624
+ continuationLinkRecoverable: true,
17625
+ continuationLinkResolution: {
17626
+ ok: true,
17627
+ linkedRequestKey: "req-no-explicit-handoff",
17628
+ linkedRequest: {
17629
+ request_key: "req-no-explicit-handoff",
17630
+ selected_bot_usernames: ["alpha_bot"],
17631
+ next_expected_responders: [],
17632
+ },
17633
+ },
17634
+ },
17635
+ },
17636
+ normalizedRoute: {
17637
+ provider: "telegram",
17638
+ triggerPolicy: {
17639
+ replyToBotMessages: true,
17640
+ },
17641
+ },
17642
+ routeKey: "telegram-monitor-alpha::project::telegram::monitor::dest::actor",
17643
+ routeState: {},
17644
+ fallbackRouteState: {},
17645
+ bot: {
17646
+ username: "alpha_bot",
17647
+ name: "alpha_bot",
17648
+ },
17649
+ executionPlan: {},
17650
+ pendingOrdered: [],
17651
+ currentBotSelector: "alpha_bot",
17652
+ routingExecutionDeps: {
17653
+ managedConversationBots: [],
17654
+ },
17655
+ });
17656
+ push(
17657
+ "runner_selected_record_execution_context_requires_explicit_handoff_for_bot_reply_continuation",
17658
+ preparation.kind === "skip"
17659
+ && String(preparation?.skipAction || "") === "adjudication_skipped"
17660
+ && String(preparation?.skipReason || "") === "bot_reply_not_explicitly_handed_off"
17661
+ && String(preparation?.skipTrigger || "") === "request_contract",
17662
+ `kind=${String(preparation?.kind || "(none)")} action=${String(preparation?.skipAction || "(none)")} reason=${String(preparation?.skipReason || "(none)")} trigger=${String(preparation?.skipTrigger || "(none)")}`,
17663
+ );
17664
+ } catch (err) {
17665
+ push("runner_selected_record_execution_context_requires_explicit_handoff_for_bot_reply_continuation", false, String(err?.message || err));
17666
+ }
17667
+
17532
17668
  try {
17533
17669
  const preparation = await resolveRunnerPrecomputedSelectedRecordExecutionContext({
17534
17670
  selectedRecord: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.276",
3
+ "version": "0.2.277",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [