metheus-governance-mcp-cli 0.2.230 → 0.2.231

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
@@ -2430,12 +2430,32 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
2430
2430
  next_expected_responders: ensureArray(entry.next_expected_responders || entry.nextExpectedResponders)
2431
2431
  .map((value) => normalizeTelegramMentionUsername(value))
2432
2432
  .filter(Boolean),
2433
+ normalized_execution_contract_type: String(entry.normalized_execution_contract_type || entry.normalizedExecutionContractType || "").trim().toLowerCase(),
2434
+ normalized_execution_contract_targets: ensureArray(entry.normalized_execution_contract_targets || entry.normalizedExecutionContractTargets)
2435
+ .map((value) => normalizeTelegramMentionUsername(value))
2436
+ .filter(Boolean),
2437
+ normalized_execution_next_responders: ensureArray(entry.normalized_execution_next_responders || entry.normalizedExecutionNextResponders)
2438
+ .map((value) => normalizeTelegramMentionUsername(value))
2439
+ .filter(Boolean),
2433
2440
  ai_reply_generated: boolFromRaw(
2434
2441
  entry.ai_reply_generated ?? entry.aiReplyGenerated,
2435
2442
  false,
2436
2443
  ),
2437
2444
  ai_reply_generated_at: firstNonEmptyString([entry.ai_reply_generated_at, entry.aiReplyGeneratedAt]),
2438
2445
  ai_reply_preview: String(entry.ai_reply_preview || entry.aiReplyPreview || "").trim(),
2446
+ root_execution_contract_type: String(entry.root_execution_contract_type || entry.rootExecutionContractType || "").trim().toLowerCase(),
2447
+ root_execution_contract_targets: ensureArray(entry.root_execution_contract_targets || entry.rootExecutionContractTargets)
2448
+ .map((value) => normalizeTelegramMentionUsername(value))
2449
+ .filter(Boolean),
2450
+ root_next_expected_responders: ensureArray(entry.root_next_expected_responders || entry.rootNextExpectedResponders)
2451
+ .map((value) => normalizeTelegramMentionUsername(value))
2452
+ .filter(Boolean),
2453
+ root_ai_reply_preview: String(entry.root_ai_reply_preview || entry.rootAiReplyPreview || "").trim(),
2454
+ root_response_contract_validation_status: String(entry.root_response_contract_validation_status || entry.rootResponseContractValidationStatus || "").trim().toLowerCase(),
2455
+ root_response_contract_validation_reason: String(entry.root_response_contract_validation_reason || entry.rootResponseContractValidationReason || "").trim(),
2456
+ root_response_contract_validation_targets: ensureArray(entry.root_response_contract_validation_targets || entry.rootResponseContractValidationTargets)
2457
+ .map((value) => normalizeTelegramMentionUsername(value))
2458
+ .filter(Boolean),
2439
2459
  response_contract_validation_status: String(entry.response_contract_validation_status || entry.responseContractValidationStatus || "").trim().toLowerCase(),
2440
2460
  response_contract_validation_reason: String(entry.response_contract_validation_reason || entry.responseContractValidationReason || "").trim(),
2441
2461
  response_contract_validation_targets: ensureArray(entry.response_contract_validation_targets || entry.responseContractValidationTargets)
@@ -4597,6 +4617,9 @@ function markRunnerRequestLifecycle({
4597
4617
  aiReplyGenerated = false,
4598
4618
  aiReplyGeneratedAt = "",
4599
4619
  aiReplyPreview = "",
4620
+ normalizedExecutionContractType = "",
4621
+ normalizedExecutionContractTargets = [],
4622
+ normalizedExecutionNextResponders = [],
4600
4623
  responseContractValidationStatus = "",
4601
4624
  responseContractValidationReason = "",
4602
4625
  responseContractValidationTargets = [],
@@ -4668,6 +4691,8 @@ function markRunnerRequestLifecycle({
4668
4691
  return normalizeRunnerRequestStatus(existing.status);
4669
4692
  })();
4670
4693
  const nowISO = new Date().toISOString();
4694
+ const commentKind = String(parsed.kind || "").trim().toLowerCase();
4695
+ const isRootHumanComment = ["telegram_message", "telegram_edited_message"].includes(commentKind);
4671
4696
  const patch = {
4672
4697
  conversation_id: conversationID,
4673
4698
  conversation_participants: uniqueOrderedStrings(
@@ -4713,11 +4738,70 @@ function markRunnerRequestLifecycle({
4713
4738
  ensureArray(nextExpectedResponders).length ? nextExpectedResponders : existing.next_expected_responders,
4714
4739
  normalizeTelegramMentionUsername,
4715
4740
  ),
4741
+ normalized_execution_contract_type: String(
4742
+ normalizedExecutionContractType || existing.normalized_execution_contract_type || "",
4743
+ ).trim().toLowerCase(),
4744
+ normalized_execution_contract_targets: uniqueOrderedStrings(
4745
+ ensureArray(normalizedExecutionContractTargets).length
4746
+ ? normalizedExecutionContractTargets
4747
+ : existing.normalized_execution_contract_targets,
4748
+ normalizeTelegramMentionUsername,
4749
+ ),
4750
+ normalized_execution_next_responders: uniqueOrderedStrings(
4751
+ ensureArray(normalizedExecutionNextResponders).length
4752
+ ? normalizedExecutionNextResponders
4753
+ : existing.normalized_execution_next_responders,
4754
+ normalizeTelegramMentionUsername,
4755
+ ),
4716
4756
  ai_reply_generated: aiReplyGenerated === true || existing.ai_reply_generated === true,
4717
4757
  ai_reply_generated_at: aiReplyGenerated === true
4718
4758
  ? firstNonEmptyString([aiReplyGeneratedAt, existing.ai_reply_generated_at, nowISO])
4719
4759
  : String(existing.ai_reply_generated_at || "").trim(),
4720
4760
  ai_reply_preview: String(aiReplyPreview || existing.ai_reply_preview || "").trim(),
4761
+ root_execution_contract_type: String(
4762
+ isRootHumanComment
4763
+ ? nextExecutionContractType
4764
+ : existing.root_execution_contract_type || existing.execution_contract_type || "",
4765
+ ).trim().toLowerCase(),
4766
+ root_execution_contract_targets: uniqueOrderedStrings(
4767
+ isRootHumanComment && ensureArray(executionContractTargets).length
4768
+ ? executionContractTargets
4769
+ : ensureArray(existing.root_execution_contract_targets).length
4770
+ ? existing.root_execution_contract_targets
4771
+ : existing.execution_contract_targets,
4772
+ normalizeTelegramMentionUsername,
4773
+ ),
4774
+ root_next_expected_responders: uniqueOrderedStrings(
4775
+ isRootHumanComment && ensureArray(nextExpectedResponders).length
4776
+ ? nextExpectedResponders
4777
+ : ensureArray(existing.root_next_expected_responders).length
4778
+ ? existing.root_next_expected_responders
4779
+ : existing.next_expected_responders,
4780
+ normalizeTelegramMentionUsername,
4781
+ ),
4782
+ root_ai_reply_preview: String(
4783
+ isRootHumanComment
4784
+ ? aiReplyPreview || existing.root_ai_reply_preview || existing.ai_reply_preview || ""
4785
+ : existing.root_ai_reply_preview || existing.ai_reply_preview || "",
4786
+ ).trim(),
4787
+ root_response_contract_validation_status: String(
4788
+ isRootHumanComment
4789
+ ? responseContractValidationStatus || existing.root_response_contract_validation_status || existing.response_contract_validation_status || ""
4790
+ : existing.root_response_contract_validation_status || existing.response_contract_validation_status || "",
4791
+ ).trim().toLowerCase(),
4792
+ root_response_contract_validation_reason: String(
4793
+ isRootHumanComment
4794
+ ? responseContractValidationReason || existing.root_response_contract_validation_reason || existing.response_contract_validation_reason || ""
4795
+ : existing.root_response_contract_validation_reason || existing.response_contract_validation_reason || "",
4796
+ ).trim(),
4797
+ root_response_contract_validation_targets: uniqueOrderedStrings(
4798
+ isRootHumanComment && ensureArray(responseContractValidationTargets).length
4799
+ ? responseContractValidationTargets
4800
+ : ensureArray(existing.root_response_contract_validation_targets).length
4801
+ ? existing.root_response_contract_validation_targets
4802
+ : existing.response_contract_validation_targets,
4803
+ normalizeTelegramMentionUsername,
4804
+ ),
4721
4805
  response_contract_validation_status: String(
4722
4806
  responseContractValidationStatus || existing.response_contract_validation_status || "",
4723
4807
  ).trim().toLowerCase(),
@@ -8967,6 +9051,9 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
8967
9051
  executionContractActionable: processed.result?.execution_contract_actionable === true,
8968
9052
  executionContractTargets: ensureArray(processed.result?.execution_contract_targets),
8969
9053
  nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
9054
+ normalizedExecutionContractType: String(processed.result?.normalized_execution_contract_type || "").trim(),
9055
+ normalizedExecutionContractTargets: ensureArray(processed.result?.normalized_execution_contract_targets),
9056
+ normalizedExecutionNextResponders: ensureArray(processed.result?.normalized_execution_next_responders),
8970
9057
  currentBotSelector,
8971
9058
  conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
8972
9059
  normalizedIntent: resolvedIntentType,
@@ -11388,6 +11475,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
11388
11475
  executionContractActionable: processed.result?.execution_contract_actionable === true,
11389
11476
  executionContractTargets: ensureArray(processed.result?.execution_contract_targets),
11390
11477
  nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
11478
+ normalizedExecutionContractType: String(processed.result?.normalized_execution_contract_type || "").trim(),
11479
+ normalizedExecutionContractTargets: ensureArray(processed.result?.normalized_execution_contract_targets),
11480
+ normalizedExecutionNextResponders: ensureArray(processed.result?.normalized_execution_next_responders),
11391
11481
  currentBotSelector: normalizeTelegramMentionUsername(
11392
11482
  deferredExecution.bot?.username || deferredExecution.bot?.name,
11393
11483
  ),
@@ -2502,8 +2502,8 @@ function buildWorkerFallbackExecutionPlan(plan, audit, options = {}) {
2502
2502
  .map((item) => safeObject(item))
2503
2503
  .filter((item) => String(item.role || "").trim());
2504
2504
  const hasWorker = executionPlanIncludesWorkerStep(normalizedPlan);
2505
- const requiresCtxpackUpdate = normalizedAudit.requires_ctxpack_update_step === true
2506
- || humanIntentType === "ctxpack_mutation";
2505
+ const requiresCtxpackUpdate = safeObject(options).forceCtxpackUpdate === true
2506
+ || normalizedAudit.requires_ctxpack_update_step === true;
2507
2507
  const requiresWorkItems = normalizedAudit.requires_work_items_step === true;
2508
2508
  if (hasWorker) {
2509
2509
  const patchedSteps = existingSteps.map((item) => {
@@ -2581,16 +2581,17 @@ function describeExecutionPlanAdequacyFailure(plan, audit) {
2581
2581
  function shouldForceWorkerFallbackExecutionPlan(plan, audit) {
2582
2582
  const normalizedPlan = safeObject(plan);
2583
2583
  const normalizedAudit = safeObject(audit);
2584
- if (executionPlanIncludesWorkerStep(normalizedPlan)) {
2585
- return false;
2584
+ if (normalizedAudit.requires_ctxpack_update_step === true && !executionPlanIncludesCtxpackUpdateStep(normalizedPlan)) {
2585
+ return true;
2586
2586
  }
2587
- if (normalizedAudit.requires_worker_step === true) {
2587
+ if (normalizedAudit.requires_work_items_step === true && !executionPlanIncludesWorkItemsStep(normalizedPlan)) {
2588
2588
  return true;
2589
2589
  }
2590
- if (normalizedAudit.requires_ctxpack_update_step === true || normalizedAudit.requires_work_items_step === true) {
2590
+ if (normalizedAudit.requires_worker_step === true && !executionPlanIncludesWorkerStep(normalizedPlan)) {
2591
2591
  return true;
2592
2592
  }
2593
2593
  return normalizedAudit.requires_execution === true
2594
+ && !executionPlanIncludesWorkerStep(normalizedPlan)
2594
2595
  && normalizedAudit.plan_satisfies_request === false;
2595
2596
  }
2596
2597
 
@@ -2840,6 +2841,9 @@ async function maybeExecuteDynamicRolePlan({
2840
2841
  };
2841
2842
  let executedPlan;
2842
2843
  let executedPlanAudit = null;
2844
+ const sourceMessageText = String(safeObject(selectedRecord?.parsedArchive).body || "").trim();
2845
+ const explicitCtxpackRequest = looksLikeCtxpackGuidanceMutationRequest(sourceMessageText)
2846
+ || /\bctxpack\b/i.test(sourceMessageText);
2843
2847
  try {
2844
2848
  executedPlan = await invokePlanner();
2845
2849
  } catch {
@@ -2890,8 +2894,9 @@ async function maybeExecuteDynamicRolePlan({
2890
2894
  let forcedWorkerFallback = false;
2891
2895
  if (adequacyFailure && shouldForceWorkerFallbackExecutionPlan(executedPlan, executedPlanAudit)) {
2892
2896
  executedPlan = buildWorkerFallbackExecutionPlan(executedPlan, executedPlanAudit, {
2893
- messageText: String(safeObject(selectedRecord?.parsedArchive).body || "").trim(),
2897
+ messageText: sourceMessageText,
2894
2898
  humanIntentType,
2899
+ forceCtxpackUpdate: explicitCtxpackRequest || safeObject(executedPlanAudit).requires_ctxpack_update_step === true,
2895
2900
  });
2896
2901
  executedPlanAudit = await auditPlannedExecution(executedPlan);
2897
2902
  adequacyFailure = describeExecutionPlanAdequacyFailure(executedPlan, executedPlanAudit);
@@ -2905,10 +2910,9 @@ async function maybeExecuteDynamicRolePlan({
2905
2910
  ) {
2906
2911
  adequacyFailure = "";
2907
2912
  }
2908
- const sourceMessageText = String(safeObject(selectedRecord?.parsedArchive).body || "").trim();
2909
2913
  if (
2910
2914
  (
2911
- humanIntentType === "ctxpack_mutation"
2915
+ explicitCtxpackRequest
2912
2916
  || safeObject(executedPlanAudit).requires_ctxpack_update_step === true
2913
2917
  )
2914
2918
  && (
@@ -2923,7 +2927,11 @@ async function maybeExecuteDynamicRolePlan({
2923
2927
  requiresExecution: true,
2924
2928
  },
2925
2929
  executedPlanAudit,
2926
- { messageText: sourceMessageText, humanIntentType },
2930
+ {
2931
+ messageText: sourceMessageText,
2932
+ humanIntentType,
2933
+ forceCtxpackUpdate: true,
2934
+ },
2927
2935
  );
2928
2936
  executedPlanAudit = await auditPlannedExecution(executedPlan);
2929
2937
  adequacyFailure = describeExecutionPlanAdequacyFailure(executedPlan, executedPlanAudit);
@@ -5421,6 +5429,12 @@ export async function processRunnerSelectedRecord({
5421
5429
  triggerDecision: effectiveTriggerDecision,
5422
5430
  conversationContext: effectiveConversationContext,
5423
5431
  });
5432
+ const normalizedExecutionTargets = ensureArray(executionContract?.assignments)
5433
+ .map((item) => normalizeMentionSelector(item?.targetBot))
5434
+ .filter(Boolean);
5435
+ const normalizedExecutionNextResponders = collectExecutionContractNextResponders(executionContract)
5436
+ .map((item) => normalizeMentionSelector(item))
5437
+ .filter(Boolean);
5424
5438
  const visibleDelegationTargets = uniqueOrdered(
5425
5439
  effectiveConversationContext?.mode === "public_multi_bot"
5426
5440
  && String(effectiveConversationContext?.stage || "").trim() === "human_opening"
@@ -5428,9 +5442,7 @@ export async function processRunnerSelectedRecord({
5428
5442
  && currentBotSelector
5429
5443
  && currentBotSelector === normalizeMentionSelector(effectiveConversationContext?.leadBotUsername)
5430
5444
  && normalizedExecutionContractType === "delegation"
5431
- ? ensureArray(executionContract?.assignments)
5432
- .map((item) => normalizeMentionSelector(item?.targetBot))
5433
- .filter(Boolean)
5445
+ ? normalizedExecutionTargets
5434
5446
  : [],
5435
5447
  );
5436
5448
  if (responseContractValidation.ok && visibleDelegationTargets.length > 0) {
@@ -5457,14 +5469,22 @@ export async function processRunnerSelectedRecord({
5457
5469
  && currentBotSelector !== normalizeMentionSelector(effectiveConversationContext.leadBotUsername)
5458
5470
  && normalizedExecutionContractType === "delegation"
5459
5471
  ) {
5460
- const unauthorizedDelegationTargets = uniqueOrdered([
5461
- ...ensureArray(executionContract?.assignments)
5462
- .map((item) => normalizeMentionSelector(item?.targetBot))
5463
- .filter(Boolean),
5464
- ...collectExecutionContractNextResponders(executionContract)
5472
+ const currentContractScopeTargets = uniqueOrdered([
5473
+ ...collectExecutionContractNextResponders(safeObject(effectiveConversationContext.executionContract))
5465
5474
  .map((item) => normalizeMentionSelector(item))
5466
5475
  .filter(Boolean),
5467
- ]).filter((item) => item && item !== currentBotSelector);
5476
+ normalizeMentionSelector(
5477
+ safeObject(effectiveConversationContext.executionContract).summaryBot
5478
+ || safeObject(effectiveConversationContext.executionContract).summary_bot
5479
+ || effectiveConversationContext.summaryBotUsername,
5480
+ ),
5481
+ normalizeMentionSelector(effectiveConversationContext.leadBotUsername),
5482
+ normalizeMentionSelector(effectiveConversationContext.summaryBotUsername),
5483
+ ].filter(Boolean));
5484
+ const unauthorizedDelegationTargets = uniqueOrdered([
5485
+ ...normalizedExecutionTargets,
5486
+ ...normalizedExecutionNextResponders,
5487
+ ]).filter((item) => item && item !== currentBotSelector && !currentContractScopeTargets.includes(item));
5468
5488
  if (unauthorizedDelegationTargets.length > 0) {
5469
5489
  responseContractValidation = {
5470
5490
  ok: false,
@@ -5474,55 +5494,6 @@ export async function processRunnerSelectedRecord({
5474
5494
  };
5475
5495
  }
5476
5496
  }
5477
- if (!responseContractValidation.ok && String(responseContractValidation.status || "").trim() === "non_lead_public_assignment") {
5478
- const reason = String(responseContractValidation.reason || "").trim()
5479
- || "response contract validation failed";
5480
- saveRunnerRouteState(
5481
- routeKey,
5482
- buildRunnerRouteStateFromComment(selectedRecord, {
5483
- last_action: "conversation_skipped",
5484
- last_reason: reason,
5485
- last_conversation_id: String(effectiveConversationContext?.id || "").trim(),
5486
- last_conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
5487
- last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5488
- last_contract_validation_reason: reason,
5489
- last_contract_validation_targets: ensureArray(responseContractValidation.targets),
5490
- ...intentStatePatch,
5491
- }),
5492
- );
5493
- return {
5494
- kind: "skipped",
5495
- skippedRecord: {
5496
- id: selectedRecord.id,
5497
- reason,
5498
- messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
5499
- },
5500
- result: {
5501
- route_key: routeKey,
5502
- route_name: normalizedRoute.name,
5503
- outcome: "contract_validation_failed",
5504
- detail: reason,
5505
- thread_id: archiveThread.threadID,
5506
- comment_id: selectedRecord.id,
5507
- trigger_kind: String(effectiveTriggerDecision.trigger || "").trim(),
5508
- conversation_id: String(effectiveConversationContext?.id || "").trim(),
5509
- conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
5510
- conversation_intent_mode: String(effectiveConversationContext?.intentMode || "").trim(),
5511
- conversation_lead_bot: String(effectiveConversationContext?.leadBotUsername || "").trim(),
5512
- conversation_summary_bot: String(effectiveConversationContext?.summaryBotUsername || "").trim(),
5513
- execution_contract_type: String(executionContract?.type || "").trim(),
5514
- execution_contract_actionable: executionContract?.actionable === true,
5515
- execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
5516
- next_expected_responders: collectExecutionContractNextResponders(executionContract),
5517
- response_contract_validation_status: String(responseContractValidation.status || "").trim(),
5518
- response_contract_validation_reason: reason,
5519
- response_contract_validation_targets: ensureArray(responseContractValidation.targets),
5520
- assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5521
- assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5522
- assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
5523
- },
5524
- };
5525
- }
5526
5497
  if (effectiveConversationContext?.mode === "public_multi_bot") {
5527
5498
  const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
5528
5499
  const currentSession = safeObject(effectiveConversationContext.session);
@@ -5679,6 +5650,9 @@ export async function processRunnerSelectedRecord({
5679
5650
  execution_contract_actionable: executionContract?.actionable === true,
5680
5651
  execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
5681
5652
  next_expected_responders: collectExecutionContractNextResponders(executionContract),
5653
+ normalized_execution_contract_type: normalizedExecutionContractType,
5654
+ normalized_execution_contract_targets: normalizedExecutionTargets,
5655
+ normalized_execution_next_responders: normalizedExecutionNextResponders,
5682
5656
  artifact_validation: String(artifactValidation.status || "").trim() || "none",
5683
5657
  artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
5684
5658
  ai_reply_generated: true,
@@ -5755,15 +5729,15 @@ export async function processRunnerSelectedRecord({
5755
5729
  participants: ensureArray(effectiveConversationContext.participantSelectors),
5756
5730
  initial_responders: ensureArray(effectiveConversationContext.initialResponderSelectors),
5757
5731
  allowed_responders: ensureArray(effectiveConversationContext.allowedResponderSelectors),
5758
- last_execution_contract_type: String(executionContract?.type || "").trim(),
5759
- last_execution_contract_actionable: executionContract?.actionable === true,
5760
- last_execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
5761
- next_expected_responders: collectExecutionContractNextResponders(executionContract),
5762
- last_speaker_bot_username: currentBotSelector,
5763
- speaker_counts: speakerCounts,
5764
- last_sender_bot_username: String(effectiveConversationContext?.senderBotUsername || "").trim(),
5765
- last_reply_fingerprint_by_bot: {
5766
- ...safeObject(currentSession.last_reply_fingerprint_by_bot),
5732
+ last_execution_contract_type: String(executionContract?.type || "").trim(),
5733
+ last_execution_contract_actionable: executionContract?.actionable === true,
5734
+ last_execution_contract_targets: normalizedExecutionTargets,
5735
+ next_expected_responders: normalizedExecutionNextResponders,
5736
+ last_speaker_bot_username: currentBotSelector,
5737
+ speaker_counts: speakerCounts,
5738
+ last_sender_bot_username: String(effectiveConversationContext?.senderBotUsername || "").trim(),
5739
+ last_reply_fingerprint_by_bot: {
5740
+ ...safeObject(currentSession.last_reply_fingerprint_by_bot),
5767
5741
  [currentBotSelector]: normalizeConversationReplyFingerprint(sanitizedReplyText),
5768
5742
  },
5769
5743
  },
@@ -5781,14 +5755,17 @@ export async function processRunnerSelectedRecord({
5781
5755
  safeObject(deliveryResult.delivery.body).result?.message_id ?? safeObject(deliveryResult.delivery.body).message_id,
5782
5756
  0,
5783
5757
  ),
5784
- last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5785
- last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5786
- last_contract_validation_targets: ensureArray(responseContractValidation.targets),
5787
- last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5788
- last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5789
- last_trigger: String(effectiveTriggerDecision.trigger || "").trim(),
5790
- last_reason: effectiveConversationContext?.mode === "public_multi_bot"
5791
- ? [
5758
+ last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5759
+ last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5760
+ last_contract_validation_targets: ensureArray(responseContractValidation.targets),
5761
+ last_normalized_execution_contract_type: normalizedExecutionContractType,
5762
+ last_normalized_execution_contract_targets: normalizedExecutionTargets,
5763
+ last_normalized_execution_next_responders: normalizedExecutionNextResponders,
5764
+ last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5765
+ last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5766
+ last_trigger: String(effectiveTriggerDecision.trigger || "").trim(),
5767
+ last_reason: effectiveConversationContext?.mode === "public_multi_bot"
5768
+ ? [
5792
5769
  `conversation=${String(effectiveConversationContext.id || "").trim() || "-"}`,
5793
5770
  `stage=${String(effectiveConversationContext.stage || "").trim() || "-"}`,
5794
5771
  formatConversationContractSummary(effectiveConversationContext),
@@ -5855,13 +5832,16 @@ export async function processRunnerSelectedRecord({
5855
5832
  conversation_initial_responders: ensureArray(effectiveConversationContext?.initialResponderSelectors),
5856
5833
  conversation_allowed_responders: ensureArray(effectiveConversationContext?.allowedResponderSelectors),
5857
5834
  conversation_allow_bot_to_bot: effectiveConversationContext?.allowBotToBot === true,
5858
- conversation_reply_expectation: String(effectiveConversationContext?.replyExpectation || "").trim(),
5859
- execution_contract_type: String(executionContract?.type || "").trim(),
5860
- execution_contract_actionable: executionContract?.actionable === true,
5861
- execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
5862
- next_expected_responders: collectExecutionContractNextResponders(executionContract),
5863
- artifact_validation: String(artifactValidation.status || "").trim() || "none",
5864
- artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
5835
+ conversation_reply_expectation: String(effectiveConversationContext?.replyExpectation || "").trim(),
5836
+ execution_contract_type: String(executionContract?.type || "").trim(),
5837
+ execution_contract_actionable: executionContract?.actionable === true,
5838
+ execution_contract_targets: normalizedExecutionTargets,
5839
+ next_expected_responders: normalizedExecutionNextResponders,
5840
+ normalized_execution_contract_type: normalizedExecutionContractType,
5841
+ normalized_execution_contract_targets: normalizedExecutionTargets,
5842
+ normalized_execution_next_responders: normalizedExecutionNextResponders,
5843
+ artifact_validation: String(artifactValidation.status || "").trim() || "none",
5844
+ artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
5865
5845
  ctxpack_version_id: String(safeObject(aiResult?.ctxpackUpdate).version_id || "").trim(),
5866
5846
  ctxpack_changed_paths: ensureArray(safeObject(aiResult?.ctxpackUpdate).changed_paths).map((item) => String(item || "").trim()).filter(Boolean),
5867
5847
  ctxpack_pushed_file_count: intFromRawAllowZero(safeObject(aiResult?.ctxpackUpdate).pushed_file_count, 0),
@@ -9644,16 +9644,168 @@ export async function runSelftestRunnerScenarios(push, deps) {
9644
9644
  && String(processed.result?.execution_contract_type || "") === "summary_request",
9645
9645
  `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9646
9646
  );
9647
- } catch (err) {
9648
- push("delegated_single_lead_non_lead_summary_request_allowed", false, String(err?.message || err));
9649
- }
9647
+ } catch (err) {
9648
+ push("delegated_single_lead_non_lead_summary_request_allowed", false, String(err?.message || err));
9649
+ }
9650
9650
 
9651
- try {
9652
- let aiCalls = 0;
9653
- const processed = await processRunnerSelectedRecord({
9654
- routeKey: "delegated-single-lead-non-lead-new-assignment-blocked-key",
9655
- normalizedRoute: normalizeRunnerRoute({
9656
- name: "telegram-monitor-delegated-single-lead-non-lead-new-assignment-blocked",
9651
+ try {
9652
+ let aiCalls = 0;
9653
+ const processed = await processRunnerSelectedRecord({
9654
+ routeKey: "delegated-single-lead-non-lead-handoff-back-to-lead-key",
9655
+ normalizedRoute: normalizeRunnerRoute({
9656
+ name: "telegram-monitor-delegated-single-lead-non-lead-handoff-back-to-lead",
9657
+ project_id: selftestProjectID,
9658
+ provider: "telegram",
9659
+ role: "monitor",
9660
+ role_profile: "monitor",
9661
+ destination_id: "dest-1",
9662
+ destination_label: "Main Room",
9663
+ server_bot_name: "RyoAI2_bot",
9664
+ server_bot_id: "bot-peer-1",
9665
+ trigger_policy: {
9666
+ mentions_only: true,
9667
+ direct_messages: true,
9668
+ reply_to_bot_messages: true,
9669
+ },
9670
+ }),
9671
+ routeState: {
9672
+ conversation_sessions: {
9673
+ "delegated-single-lead-non-lead-handoff-back-to-lead": {
9674
+ id: "delegated-single-lead-non-lead-handoff-back-to-lead",
9675
+ mode: "public_multi_bot",
9676
+ stage: "bot_reply",
9677
+ status: "open",
9678
+ intent_mode: "delegated_single_lead",
9679
+ lead_bot_username: "ryoai_bot",
9680
+ participants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9681
+ initial_responders: ["ryoai_bot"],
9682
+ allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9683
+ next_expected_responders: ["ryoai2_bot"],
9684
+ last_execution_contract_type: "delegation",
9685
+ last_execution_contract_targets: ["ryoai2_bot"],
9686
+ last_execution_contract_actionable: true,
9687
+ },
9688
+ },
9689
+ },
9690
+ selectedRecord: {
9691
+ id: "delegated-single-lead-non-lead-handoff-back-to-lead-comment",
9692
+ body: "@RyoAI2_bot 현재 관점으로 의견 주세요.",
9693
+ threadID: "thread-1",
9694
+ createdAt: "2026-03-24T00:00:00.000Z",
9695
+ updatedAt: "2026-03-24T00:00:00.000Z",
9696
+ parsedArchive: {
9697
+ kind: "bot_reply",
9698
+ body: "@RyoAI2_bot 현재 관점으로 의견 주세요.",
9699
+ senderIsBot: true,
9700
+ botUsername: "ryoai_bot",
9701
+ botName: "RyoAI_bot",
9702
+ sender: "RyoAI_bot",
9703
+ senderName: "RyoAI_bot",
9704
+ senderBotUsername: "ryoai_bot",
9705
+ messageID: 990212,
9706
+ chatID: "-1001",
9707
+ mentionUsernames: ["ryoai2_bot"],
9708
+ conversationID: "delegated-single-lead-non-lead-handoff-back-to-lead",
9709
+ executionContract: {
9710
+ type: "delegation",
9711
+ actionable: true,
9712
+ assignments: [{ targetBot: "ryoai2_bot", task: "현재 관점으로 의견을 말하고 리드에게 정리 요청" }],
9713
+ nextResponders: ["ryoai2_bot"],
9714
+ },
9715
+ },
9716
+ },
9717
+ pendingOrdered: [],
9718
+ bot: { id: "bot-peer-1", username: "ryoai2_bot", name: "RyoAI2_bot" },
9719
+ destination: { id: "dest-1", provider: "telegram", chatID: "-1001", label: "Main Room" },
9720
+ archiveThread: {
9721
+ threadID: "thread-1",
9722
+ source: "telegram",
9723
+ projectID: selftestProjectID,
9724
+ },
9725
+ executionPlan: {
9726
+ mode: "role_profile",
9727
+ roleProfileName: "monitor",
9728
+ roleProfile: {
9729
+ client: "sample",
9730
+ model: "",
9731
+ permissionMode: "read_only",
9732
+ reasoningEffort: "low",
9733
+ },
9734
+ workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-non-lead-handoff-back-to-lead"),
9735
+ workspaceSource: "selftest",
9736
+ usedCommandFallback: false,
9737
+ },
9738
+ runtime: {
9739
+ baseURL: "https://example.test",
9740
+ token: "selftest-token",
9741
+ timeoutSeconds: 30,
9742
+ actor: { user_id: "user-1" },
9743
+ },
9744
+ triggerDecision: {
9745
+ trigger: "bot_reply_mention",
9746
+ requiresDirectReply: true,
9747
+ candidateResponder: true,
9748
+ },
9749
+ responderAdjudication: {
9750
+ decision: "reply",
9751
+ selected_bot_usernames: ["ryoai2_bot"],
9752
+ },
9753
+ deps: {
9754
+ saveRunnerRouteState: () => {},
9755
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
9756
+ runRunnerAIExecution: async () => {
9757
+ aiCalls += 1;
9758
+ return {
9759
+ reply: "@RyoAI_bot 현재 공개된 맥락 기준으로 우선순위와 리스크를 먼저 정리했습니다. 다음 정리를 부탁드립니다.",
9760
+ artifacts: [],
9761
+ ctxpackFiles: [],
9762
+ workItems: [],
9763
+ contract: {
9764
+ type: "delegation",
9765
+ actionable: true,
9766
+ assignments: [{ target_bot: "ryoai_bot", task: "현재 contributor 의견을 정리해서 다음 단계 제안" }],
9767
+ next_responders: ["ryoai_bot"],
9768
+ },
9769
+ };
9770
+ },
9771
+ managedConversationBots: [
9772
+ { id: "bot-lead", name: "RyoAI_bot" },
9773
+ { id: "bot-peer-1", name: "RyoAI2_bot" },
9774
+ { id: "bot-peer-2", name: "RyoAI3_bot" },
9775
+ ],
9776
+ performLocalBotDelivery: async () => ({
9777
+ delivery: { dryRun: true, body: {} },
9778
+ archive: {},
9779
+ }),
9780
+ serializeRunnerTriggerPolicy: (value) => value,
9781
+ serializeRunnerArchivePolicy: (value) => value,
9782
+ buildRunnerExecutionDeps: () => ({}),
9783
+ buildRunnerDeliveryDeps: () => ({}),
9784
+ buildRunnerRuntimeDeps: () => ({}),
9785
+ resolveConversationPeerBots: () => [
9786
+ { id: "bot-lead", name: "RyoAI_bot" },
9787
+ { id: "bot-peer-1", name: "RyoAI2_bot" },
9788
+ { id: "bot-peer-2", name: "RyoAI3_bot" },
9789
+ ],
9790
+ },
9791
+ });
9792
+ push(
9793
+ "delegated_single_lead_non_lead_handoff_back_to_lead_not_blocked",
9794
+ processed.kind === "replied"
9795
+ && aiCalls === 1
9796
+ && String(processed.result?.response_contract_validation_status || "") !== "non_lead_public_assignment",
9797
+ `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} validation=${String(processed.result?.response_contract_validation_status || "(none)")} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9798
+ );
9799
+ } catch (err) {
9800
+ push("delegated_single_lead_non_lead_handoff_back_to_lead_not_blocked", false, String(err?.message || err));
9801
+ }
9802
+
9803
+ try {
9804
+ let aiCalls = 0;
9805
+ const processed = await processRunnerSelectedRecord({
9806
+ routeKey: "delegated-single-lead-non-lead-new-assignment-recorded-key",
9807
+ normalizedRoute: normalizeRunnerRoute({
9808
+ name: "telegram-monitor-delegated-single-lead-non-lead-new-assignment-recorded",
9657
9809
  project_id: selftestProjectID,
9658
9810
  provider: "telegram",
9659
9811
  role: "monitor",
@@ -9777,16 +9929,16 @@ export async function runSelftestRunnerScenarios(push, deps) {
9777
9929
  ],
9778
9930
  },
9779
9931
  });
9780
- push(
9781
- "delegated_single_lead_non_lead_new_assignment_blocked",
9782
- processed.kind === "skipped"
9783
- && aiCalls === 1
9784
- && String(processed.result?.response_contract_validation_status || "") === "non_lead_public_assignment",
9785
- `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} validation=${String(processed.result?.response_contract_validation_status || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9786
- );
9787
- } catch (err) {
9788
- push("delegated_single_lead_non_lead_new_assignment_blocked", false, String(err?.message || err));
9789
- }
9932
+ push(
9933
+ "delegated_single_lead_non_lead_new_assignment_recorded",
9934
+ processed.kind === "replied"
9935
+ && aiCalls === 1
9936
+ && String(processed.result?.response_contract_validation_status || "") === "non_lead_public_assignment",
9937
+ `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} validation=${String(processed.result?.response_contract_validation_status || "(none)")} reason=${String(processed.result?.response_contract_validation_reason || "(none)")}`,
9938
+ );
9939
+ } catch (err) {
9940
+ push("delegated_single_lead_non_lead_new_assignment_recorded", false, String(err?.message || err));
9941
+ }
9790
9942
 
9791
9943
  try {
9792
9944
  const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-artifacts-ok-"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.230",
3
+ "version": "0.2.231",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [