metheus-governance-mcp-cli 0.2.210 → 0.2.212

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
@@ -2764,6 +2764,62 @@ function sessionAllowsConversationResponder(sessionRaw, responderSelectorRaw = "
2764
2764
  return allowedResponders.includes(responderSelector);
2765
2765
  }
2766
2766
 
2767
+ function shouldBypassRunnerStartupLoopForContractFollowup({
2768
+ selectedRecord,
2769
+ bot,
2770
+ conversationSessionFacts = null,
2771
+ }) {
2772
+ const parsed = safeObject(selectedRecord?.parsedArchive);
2773
+ if (String(parsed.kind || "").trim().toLowerCase() !== "bot_reply") {
2774
+ return false;
2775
+ }
2776
+ const currentBotSelector = normalizeTelegramMentionUsername(bot?.username || bot?.name);
2777
+ if (!currentBotSelector) {
2778
+ return false;
2779
+ }
2780
+ const conversationID = String(parsed.conversationID || "").trim();
2781
+ if (!conversationID) {
2782
+ return false;
2783
+ }
2784
+ const executionContractType = String(parsed.executionContractType || "").trim().toLowerCase();
2785
+ if (executionContractType !== "delegation") {
2786
+ return false;
2787
+ }
2788
+ const senderBotSelector = normalizeTelegramMentionUsername(
2789
+ parsed.botUsername
2790
+ || parsed.botName
2791
+ || parsed.username
2792
+ || parsed.sender,
2793
+ );
2794
+ if (!senderBotSelector || senderBotSelector === currentBotSelector) {
2795
+ return false;
2796
+ }
2797
+ const assignmentTargets = ensureArray(parsed.executionContractAssignments)
2798
+ .map((item) => normalizeTelegramMentionUsername(safeObject(item).targetBot || safeObject(item).target_bot))
2799
+ .filter(Boolean);
2800
+ const nextResponders = ensureArray(parsed.executionContractNextResponders)
2801
+ .map((item) => normalizeTelegramMentionUsername(item))
2802
+ .filter(Boolean);
2803
+ const conversationTargetBot = normalizeTelegramMentionUsername(parsed.conversationTargetBotUsername);
2804
+ const currentBotIsExpectedResponder = assignmentTargets.includes(currentBotSelector)
2805
+ || nextResponders.includes(currentBotSelector)
2806
+ || conversationTargetBot === currentBotSelector;
2807
+ if (!currentBotIsExpectedResponder) {
2808
+ return false;
2809
+ }
2810
+ const sessionFacts = safeObject(conversationSessionFacts);
2811
+ const sessionExpectedResponders = ensureArray(sessionFacts.next_expected_responders)
2812
+ .map((item) => normalizeTelegramMentionUsername(item))
2813
+ .filter(Boolean);
2814
+ if (sessionExpectedResponders.length > 0 && !sessionExpectedResponders.includes(currentBotSelector)) {
2815
+ return false;
2816
+ }
2817
+ if (sessionFacts.any_closed === true && sessionFacts.any_open !== true) {
2818
+ return false;
2819
+ }
2820
+ return true;
2821
+ }
2822
+
2767
2823
  function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
2768
2824
  const chatID = String(selectors.chatID || "").trim();
2769
2825
  const messageID = intFromRawAllowZero(selectors.messageID, 0);
@@ -3591,13 +3647,29 @@ function runnerRequestRequiresActionableContract(requestRaw) {
3591
3647
  const request = safeObject(requestRaw);
3592
3648
  const replyExpectation = String(request.conversation_reply_expectation || "").trim().toLowerCase();
3593
3649
  const executionContractType = String(request.execution_contract_type || "").trim().toLowerCase();
3650
+ const intentMode = String(request.conversation_intent_mode || "").trim().toLowerCase();
3651
+ const intentType = String(
3652
+ request.conversation_intent_type
3653
+ || request.intent_type
3654
+ || request.normalized_intent
3655
+ || "",
3656
+ ).trim().toLowerCase();
3657
+ const hasExecutionIntent = [
3658
+ "general_execution",
3659
+ "ctxpack_mutation",
3660
+ "workitem_mutation",
3661
+ ].includes(intentType);
3594
3662
  if (request.execution_contract_actionable === true) {
3595
3663
  return true;
3596
3664
  }
3597
- if (replyExpectation === "actionable") {
3665
+ if (["delegation", "direct_result", "summary_request", "final_summary"].includes(executionContractType)) {
3598
3666
  return true;
3599
3667
  }
3600
- if (["delegation", "direct_result"].includes(executionContractType)) {
3668
+ if (
3669
+ intentMode === "single_bot"
3670
+ && replyExpectation === "actionable"
3671
+ && hasExecutionIntent
3672
+ ) {
3601
3673
  return true;
3602
3674
  }
3603
3675
  return false;
@@ -7691,6 +7763,13 @@ async function maybeHandleRunnerStartupLoopCandidate({
7691
7763
  }
7692
7764
  const conversationID = String(parsed.conversationID || "").trim();
7693
7765
  const conversationSessionFacts = collectBotRunnerConversationSessionFacts(normalizedRoute, conversationID);
7766
+ if (shouldBypassRunnerStartupLoopForContractFollowup({
7767
+ selectedRecord,
7768
+ bot,
7769
+ conversationSessionFacts,
7770
+ })) {
7771
+ return null;
7772
+ }
7694
7773
  const adjudication = await resolveRunnerStartupLoopAdjudication({
7695
7774
  selectedRecord,
7696
7775
  pendingOrdered,
@@ -15472,6 +15551,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
15472
15551
  selectRunnerPendingWork,
15473
15552
  processRunnerSelectedRecord,
15474
15553
  resolveRunnerStartupLoopAdjudication,
15554
+ shouldBypassRunnerStartupLoopForContractFollowup,
15475
15555
  claimRunnerRequestForHumanComment,
15476
15556
  markRunnerRequestLifecycle,
15477
15557
  resolveRunnerContinuationRequestForBotReply,
@@ -1294,7 +1294,6 @@ export async function resolveHumanIntentContext({
1294
1294
  executionPlan,
1295
1295
  contractGuardrail: {
1296
1296
  require_complete_contract: true,
1297
- require_explicit_actionable_intent: true,
1298
1297
  reason: "Return a complete human intent contract for this managed-bot conversation.",
1299
1298
  },
1300
1299
  });
@@ -2584,6 +2583,11 @@ async function maybeExecuteDynamicRolePlan({
2584
2583
  safeObject(directHumanResponseContract).intentType
2585
2584
  || safeObject(safeObject(humanIntentContext).humanIntent).intentType,
2586
2585
  );
2586
+ const humanIntentMode = normalizeIntentMode(
2587
+ safeObject(directHumanResponseContract).intentMode
2588
+ || safeObject(safeObject(humanIntentContext).humanIntent).intentMode,
2589
+ );
2590
+ const actionableConversationRequiresContract = safeObject(directHumanResponseContract).requiresActionableContract === true;
2587
2591
  const plannerContextComments = ensureArray(contextWindow).length
2588
2592
  ? ensureArray(contextWindow)
2589
2593
  : buildRunnerContextWindow(
@@ -2595,6 +2599,13 @@ async function maybeExecuteDynamicRolePlan({
2595
2599
  if (!assignmentTasks.length && isInformationalHumanIntentType(humanIntentType)) {
2596
2600
  return null;
2597
2601
  }
2602
+ if (
2603
+ !assignmentTasks.length
2604
+ && actionableConversationRequiresContract
2605
+ && ["delegated_single_lead", "multi_bot_collab", "multi_bot_direct"].includes(humanIntentMode)
2606
+ ) {
2607
+ return null;
2608
+ }
2598
2609
  const executionIntentRequested = [
2599
2610
  "general_execution",
2600
2611
  "ctxpack_mutation",
@@ -5193,6 +5204,14 @@ export async function processRunnerSelectedRecord({
5193
5204
  }
5194
5205
  }
5195
5206
  const normalizedExecutionContractType = String(executionContract?.type || "").trim().toLowerCase();
5207
+ const effectiveResolvedIntentType = normalizeHumanIntentType(
5208
+ directHumanResponseContract.intentType || resolvedIntentType,
5209
+ );
5210
+ const effectiveResolvedIntentMode = normalizeIntentMode(
5211
+ directHumanResponseContract.intentMode
5212
+ || conversationContext?.intentMode
5213
+ || safeObject(safeObject(humanIntentContext).humanIntent).intentMode,
5214
+ );
5196
5215
  const shouldEnforceArtifactValidation = Boolean(
5197
5216
  (artifactValidationOverride && Object.keys(artifactValidationOverride).length > 0)
5198
5217
  || ensureArray(aiResult?.artifacts).length > 0
@@ -5202,7 +5221,8 @@ export async function processRunnerSelectedRecord({
5202
5221
  "general_execution",
5203
5222
  "ctxpack_mutation",
5204
5223
  "workitem_mutation",
5205
- ].includes(normalizeHumanIntentType(resolvedIntentType))
5224
+ ].includes(effectiveResolvedIntentType)
5225
+ && !["delegated_single_lead", "multi_bot_collab", "multi_bot_direct"].includes(effectiveResolvedIntentMode)
5206
5226
  );
5207
5227
  const artifactValidation = shouldEnforceArtifactValidation
5208
5228
  ? artifactValidationOverride && Object.keys(artifactValidationOverride).length > 0
@@ -96,12 +96,29 @@ function buildRunnerTaskName(humanIntentType, humanIntentContract = null) {
96
96
  const contract = safeObject(humanIntentContract);
97
97
  const replyExpectation = String(contract.replyExpectation || contract.reply_expectation || "").trim().toLowerCase();
98
98
  const executionContractType = String(contract.executionContractType || contract.execution_contract_type || "").trim().toLowerCase();
99
+ const intentMode = String(contract.intentMode || contract.intent_mode || "").trim().toLowerCase();
100
+ const executionIntentRequested = [
101
+ "general_execution",
102
+ "ctxpack_mutation",
103
+ "workitem_mutation",
104
+ ].includes(normalizedIntentType);
105
+ const collaborativeIntentMode = [
106
+ "delegated_single_lead",
107
+ "multi_bot_collab",
108
+ "multi_bot_direct",
109
+ ].includes(intentMode);
99
110
  if (normalizedIntentType === "status_query") return "answer_project_status_query";
100
111
  if (normalizedIntentType === "workspace_query") return "answer_project_workspace_query";
101
112
  if (normalizedIntentType === "bot_role_query") return "answer_project_bot_role_query";
102
113
  if (normalizedIntentType === "artifact_location_query") return "answer_project_artifact_location_query";
103
114
  if (normalizedIntentType === "small_talk") return "reply_to_project_chat_greeting";
104
- if (executionContractType || replyExpectation === "actionable") {
115
+ if (executionContractType) {
116
+ return "reply_to_project_actionable_message";
117
+ }
118
+ if (collaborativeIntentMode && replyExpectation === "actionable") {
119
+ return "reply_to_project_chat_message";
120
+ }
121
+ if (executionIntentRequested) {
105
122
  return "reply_to_project_actionable_message";
106
123
  }
107
124
  return "reply_to_project_chat_message";
@@ -118,16 +118,17 @@ export async function runSelftestRunnerScenarios(push, deps) {
118
118
  const safeObject = requireDependency(deps, "safeObject");
119
119
  const normalizeRunnerTriggerPolicy = requireDependency(deps, "normalizeRunnerTriggerPolicy");
120
120
  const evaluateTelegramRunnerTrigger = requireDependency(deps, "evaluateTelegramRunnerTrigger");
121
- const resolveRunnerResponderAdjudication = requireDependency(deps, "resolveRunnerResponderAdjudication");
122
- const selectPendingArchiveComments = requireDependency(deps, "selectPendingArchiveComments");
123
- const selectRunnerPendingWork = requireDependency(deps, "selectRunnerPendingWork");
124
- const processRunnerSelectedRecord = requireDependency(deps, "processRunnerSelectedRecord");
125
- const resolveRunnerStartupLoopAdjudication = requireDependency(deps, "resolveRunnerStartupLoopAdjudication");
126
- const runRunnerAIExecution = requireDependency(deps, "runRunnerAIExecution");
127
- const formatBotReplyArchiveComment = requireDependency(deps, "formatBotReplyArchiveComment");
128
- const findArchivedBotReplyRecord = requireDependency(deps, "findArchivedBotReplyRecord");
129
- const parseArchivedChatComment = requireDependency(deps, "parseArchivedChatComment");
130
- const validateWorkspaceArtifacts = requireDependency(deps, "validateWorkspaceArtifacts");
121
+ const resolveRunnerResponderAdjudication = requireDependency(deps, "resolveRunnerResponderAdjudication");
122
+ const selectPendingArchiveComments = requireDependency(deps, "selectPendingArchiveComments");
123
+ const selectRunnerPendingWork = requireDependency(deps, "selectRunnerPendingWork");
124
+ const processRunnerSelectedRecord = requireDependency(deps, "processRunnerSelectedRecord");
125
+ const resolveRunnerStartupLoopAdjudication = requireDependency(deps, "resolveRunnerStartupLoopAdjudication");
126
+ const shouldBypassRunnerStartupLoopForContractFollowup = requireDependency(deps, "shouldBypassRunnerStartupLoopForContractFollowup");
127
+ const runRunnerAIExecution = requireDependency(deps, "runRunnerAIExecution");
128
+ const formatBotReplyArchiveComment = requireDependency(deps, "formatBotReplyArchiveComment");
129
+ const findArchivedBotReplyRecord = requireDependency(deps, "findArchivedBotReplyRecord");
130
+ const parseArchivedChatComment = requireDependency(deps, "parseArchivedChatComment");
131
+ const validateWorkspaceArtifacts = requireDependency(deps, "validateWorkspaceArtifacts");
131
132
  const buildLocalBotPrompt = requireDependency(deps, "buildLocalBotPrompt");
132
133
 
133
134
  try {
@@ -367,13 +368,53 @@ export async function runSelftestRunnerScenarios(push, deps) {
367
368
  && adjudication?.startup_facts?.repeated_same_delegation_without_new_human === true,
368
369
  `decision=${String(adjudication?.decision || "")} action=${String(adjudication?.action || "")} imported=${String(adjudication?.startup_facts?.imported_on_current_poll)} repeated_delegation=${String(adjudication?.startup_facts?.repeated_same_delegation_without_new_human)}`,
369
370
  );
370
- } catch (err) {
371
- push(
372
- "runner_loop_adjudication_detects_repeated_same_delegation_without_new_human_input",
373
- false,
374
- String(err?.message || err),
375
- );
376
- }
371
+ } catch (err) {
372
+ push(
373
+ "runner_loop_adjudication_detects_repeated_same_delegation_without_new_human_input",
374
+ false,
375
+ String(err?.message || err),
376
+ );
377
+ }
378
+
379
+ try {
380
+ const shouldBypass = shouldBypassRunnerStartupLoopForContractFollowup({
381
+ selectedRecord: {
382
+ id: "bot-reply-4",
383
+ parsedArchive: {
384
+ kind: "bot_reply",
385
+ messageID: 52,
386
+ senderIsBot: true,
387
+ botUsername: "@bot-a",
388
+ conversationID: "conv-4",
389
+ executionContractType: "delegation",
390
+ executionContractAssignments: [
391
+ { targetBot: "@bot-b", task: "say hello" },
392
+ ],
393
+ executionContractNextResponders: ["@bot-b"],
394
+ conversationTargetBotUsername: "@bot-b",
395
+ },
396
+ },
397
+ bot: {
398
+ username: "@bot-b",
399
+ },
400
+ conversationSessionFacts: {
401
+ any_open: true,
402
+ any_closed: false,
403
+ next_expected_responders: ["bot-b"],
404
+ },
405
+ });
406
+ push(
407
+ "runner_startup_loop_bypasses_expected_delegation_followup_for_target_bot",
408
+ shouldBypass === true,
409
+ `should_bypass=${String(shouldBypass)}`,
410
+ );
411
+ } catch (err) {
412
+ push(
413
+ "runner_startup_loop_bypasses_expected_delegation_followup_for_target_bot",
414
+ false,
415
+ String(err?.message || err),
416
+ );
417
+ }
377
418
 
378
419
  try {
379
420
  const pendingSelection = selectPendingArchiveComments(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.210",
3
+ "version": "0.2.212",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [