metheus-governance-mcp-cli 0.2.225 → 0.2.226

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
@@ -2397,6 +2397,11 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
2397
2397
  response_contract_validation_targets: ensureArray(entry.response_contract_validation_targets || entry.responseContractValidationTargets)
2398
2398
  .map((value) => normalizeTelegramMentionUsername(value))
2399
2399
  .filter(Boolean),
2400
+ assignment_validation_status: String(entry.assignment_validation_status || entry.assignmentValidationStatus || "").trim().toLowerCase(),
2401
+ assignment_validation_reason: String(entry.assignment_validation_reason || entry.assignmentValidationReason || "").trim(),
2402
+ assignment_validation_modes: ensureArray(entry.assignment_validation_modes || entry.assignmentValidationModes)
2403
+ .map((value) => String(value || "").trim().toLowerCase())
2404
+ .filter(Boolean),
2400
2405
  delivery_status: String(entry.delivery_status || entry.deliveryStatus || "").trim().toLowerCase(),
2401
2406
  archive_status: String(entry.archive_status || entry.archiveStatus || "").trim().toLowerCase(),
2402
2407
  transport_error: String(entry.transport_error || entry.transportError || "").trim(),
@@ -4516,6 +4521,9 @@ function markRunnerRequestLifecycle({
4516
4521
  responseContractValidationStatus = "",
4517
4522
  responseContractValidationReason = "",
4518
4523
  responseContractValidationTargets = [],
4524
+ assignmentValidationStatus = "",
4525
+ assignmentValidationReason = "",
4526
+ assignmentValidationModes = [],
4519
4527
  deliveryStatus = "",
4520
4528
  archiveStatus = "",
4521
4529
  transportError = "",
@@ -4643,6 +4651,18 @@ function markRunnerRequestLifecycle({
4643
4651
  : existing.response_contract_validation_targets,
4644
4652
  normalizeTelegramMentionUsername,
4645
4653
  ),
4654
+ assignment_validation_status: String(
4655
+ assignmentValidationStatus || existing.assignment_validation_status || "",
4656
+ ).trim().toLowerCase(),
4657
+ assignment_validation_reason: String(
4658
+ assignmentValidationReason || existing.assignment_validation_reason || "",
4659
+ ).trim(),
4660
+ assignment_validation_modes: uniqueOrderedStrings(
4661
+ ensureArray(assignmentValidationModes).length
4662
+ ? assignmentValidationModes
4663
+ : existing.assignment_validation_modes,
4664
+ (value) => String(value || "").trim().toLowerCase(),
4665
+ ),
4646
4666
  delivery_status: String(deliveryStatus || existing.delivery_status || "").trim().toLowerCase(),
4647
4667
  archive_status: String(archiveStatus || existing.archive_status || "").trim().toLowerCase(),
4648
4668
  transport_error: String(transportError || existing.transport_error || "").trim(),
@@ -8873,6 +8893,9 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
8873
8893
  responseContractValidationStatus: String(processed.result?.response_contract_validation_status || "").trim(),
8874
8894
  responseContractValidationReason: String(processed.result?.response_contract_validation_reason || "").trim(),
8875
8895
  responseContractValidationTargets: ensureArray(processed.result?.response_contract_validation_targets),
8896
+ assignmentValidationStatus: String(processed.result?.assignment_validation_status || "").trim(),
8897
+ assignmentValidationReason: String(processed.result?.assignment_validation_reason || "").trim(),
8898
+ assignmentValidationModes: ensureArray(processed.result?.assignment_validation_modes),
8876
8899
  deliveryStatus: String(processed.result?.delivery_status || "").trim(),
8877
8900
  archiveStatus: String(processed.result?.archive_status || "").trim(),
8878
8901
  transportError: String(processed.result?.transport_error || "").trim(),
@@ -11293,6 +11316,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
11293
11316
  responseContractValidationStatus: String(processed.result?.response_contract_validation_status || "").trim(),
11294
11317
  responseContractValidationReason: String(processed.result?.response_contract_validation_reason || "").trim(),
11295
11318
  responseContractValidationTargets: ensureArray(processed.result?.response_contract_validation_targets),
11319
+ assignmentValidationStatus: String(processed.result?.assignment_validation_status || "").trim(),
11320
+ assignmentValidationReason: String(processed.result?.assignment_validation_reason || "").trim(),
11321
+ assignmentValidationModes: ensureArray(processed.result?.assignment_validation_modes),
11296
11322
  deliveryStatus: String(processed.result?.delivery_status || "").trim(),
11297
11323
  archiveStatus: String(processed.result?.archive_status || "").trim(),
11298
11324
  transportError: String(processed.result?.transport_error || "").trim(),
@@ -1918,7 +1918,10 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1918
1918
  "Do not mention another managed bot unless the contract explicitly names that bot in assignments or next_responders.",
1919
1919
  "Without a matching contract, newly mentioned bots will not act.",
1920
1920
  "When delegating to another managed bot, use contract.type=\"delegation\" with actionable=true, assignments, and next_responders.",
1921
- "Delegation contract example: {\"type\":\"delegation\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"ryoai2_bot\",\"task\":\"briefly greet in one line\"}],\"next_responders\":[\"ryoai2_bot\"]}.",
1921
+ "Each assignment must declare whether it is a conversational contribution or a real execution task.",
1922
+ "Use assignment.mode=\"conversation_contribution\" for opinions, discussion, review, comparison, synthesis, greetings, or other room-visible contributions that do not require workspace artifacts.",
1923
+ "Use assignment.mode=\"execution_task\" only when the delegated bot must change workspace files, create artifacts, update ctxpack, or produce other concrete project outputs. If it is an execution task, also set artifacts_required=true.",
1924
+ "Delegation contract example: {\"type\":\"delegation\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"ryoai2_bot\",\"task\":\"briefly greet in one line\",\"mode\":\"conversation_contribution\",\"artifacts_required\":false}],\"next_responders\":[\"ryoai2_bot\"]}.",
1922
1925
  ensureArray(responseContract.required_delegation_targets).length > 0
1923
1926
  ? `This reply must delegate to these exact managed bots now: ${ensureArray(responseContract.required_delegation_targets).map((item) => `@${String(item || "").trim().replace(/^@+/, "")}`).join(", ")}.`
1924
1927
  : "",
@@ -1970,7 +1973,10 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1970
1973
  "Do not mention another managed bot unless the contract explicitly names that bot in assignments or next_responders.",
1971
1974
  "Without a matching contract, mentioned bots will not act.",
1972
1975
  "When delegating to another managed bot, use contract.type=\"delegation\" with actionable=true, assignments, and next_responders.",
1973
- "Delegation contract example: {\"type\":\"delegation\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"ryoai2_bot\",\"task\":\"briefly greet in one line\"}],\"next_responders\":[\"ryoai2_bot\"]}.",
1976
+ "Each assignment must declare whether it is a conversational contribution or a real execution task.",
1977
+ "Use assignment.mode=\"conversation_contribution\" for opinions, discussion, review, comparison, synthesis, greetings, or other room-visible contributions that do not require workspace artifacts.",
1978
+ "Use assignment.mode=\"execution_task\" only when the delegated bot must change workspace files, create artifacts, update ctxpack, or produce other concrete project outputs. If it is an execution task, also set artifacts_required=true.",
1979
+ "Delegation contract example: {\"type\":\"delegation\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"ryoai2_bot\",\"task\":\"briefly greet in one line\",\"mode\":\"conversation_contribution\",\"artifacts_required\":false}],\"next_responders\":[\"ryoai2_bot\"]}.",
1974
1980
  ensureArray(responseContract.required_delegation_targets).length > 0
1975
1981
  ? `This reply must delegate to these exact managed bots now: ${ensureArray(responseContract.required_delegation_targets).map((item) => `@${String(item || "").trim().replace(/^@+/, "")}`).join(", ")}.`
1976
1982
  : "",
@@ -2006,7 +2012,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
2006
2012
  isInternalExecutionStep
2007
2013
  ? "Return JSON only in one line: {\"reply\":\"what was completed in this step\",\"artifacts\":[{\"path\":\"relative/or/absolute/path\",\"kind\":\"plan|code|doc|spec|test\",\"operation\":\"create|update|delete\"}],\"ctxpack_files\":[{\"path\":\"relative/path.md\",\"content\":\"full document text\",\"doc_type\":\"guide|readme|agenda|rule|architecture|manifest\",\"operation\":\"create|update|delete\"}],\"work_items\":[{\"title\":\"short atomic task\",\"description\":\"useful implementation detail\"}],\"contract\":{\"type\":\"direct_result|summary_request|final_summary\",\"actionable\":true,\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}. Use ctxpack_files when ctxpack-backed guidance/instruction files must be authored. If execution_step.ctxpack_update_required is true, ctxpack_files must not be empty. Use artifacts: [] only if this step truly changes no project files, and use work_items: [] only if this step truly creates no governance tasks."
2008
2014
  : responseContract.is_current_bot_candidate === true
2009
- ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"} or {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\"}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]},\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}}."
2015
+ ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"} or {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\",\"mode\":\"conversation_contribution|execution_task\",\"artifacts_required\":true|false}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]},\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}}."
2010
2016
  : terse
2011
2017
  ? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"}."
2012
2018
  : "Return JSON only: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"}. Keep the reply concise and directly useful in a group chat.",
@@ -2061,7 +2067,12 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
2061
2067
  }
2062
2068
  if (assignmentsForThisBot.length > 0) {
2063
2069
  lines.push(
2064
- `Current Assignment For This Bot: ${assignmentsForThisBot.map((item) => String(item.task || "").trim()).join(" | ")}`,
2070
+ `Current Assignment For This Bot: ${assignmentsForThisBot.map((item) => {
2071
+ const task = String(item.task || "").trim();
2072
+ const mode = String(item.mode || "").trim();
2073
+ const artifactsRequired = item.artifactsRequired === true || item.artifacts_required === true;
2074
+ return `${task}${mode ? ` [mode=${mode}]` : ""}${artifactsRequired ? " [artifacts_required=yes]" : ""}`;
2075
+ }).join(" | ")}`,
2065
2076
  );
2066
2077
  }
2067
2078
  if (String(currentExecutionContract.summary_bot || currentExecutionContract.summaryBot || "").trim()) {
@@ -710,6 +710,112 @@ function normalizeReplyExpectation(value, fallback = "informational") {
710
710
  return fallback;
711
711
  }
712
712
 
713
+ function boolFromRunnerRaw(raw, fallback = false) {
714
+ if (raw === true || raw === false) {
715
+ return raw;
716
+ }
717
+ const normalized = String(raw ?? "").trim().toLowerCase();
718
+ if (!normalized) return fallback;
719
+ if (["1", "true", "yes", "y", "on"].includes(normalized)) {
720
+ return true;
721
+ }
722
+ if (["0", "false", "no", "n", "off"].includes(normalized)) {
723
+ return false;
724
+ }
725
+ return fallback;
726
+ }
727
+
728
+ function normalizeExecutionAssignmentMode(rawValue, fallback = "conversation_contribution") {
729
+ const normalized = String(rawValue || "").trim().toLowerCase();
730
+ if (!normalized) {
731
+ return fallback;
732
+ }
733
+ if ([
734
+ "execution_task",
735
+ "workspace_action",
736
+ "artifact_work",
737
+ "artifact_task",
738
+ "file_work",
739
+ "ctxpack_update",
740
+ "workitem_task",
741
+ ].includes(normalized)) {
742
+ return "execution_task";
743
+ }
744
+ if ([
745
+ "conversation_contribution",
746
+ "discussion_contribution",
747
+ "opinion",
748
+ "analysis",
749
+ "review",
750
+ "comparison",
751
+ "summary_contribution",
752
+ "greeting",
753
+ "reply",
754
+ ].includes(normalized)) {
755
+ return "conversation_contribution";
756
+ }
757
+ return fallback;
758
+ }
759
+
760
+ function normalizeExecutionAssignment(rawAssignment, {
761
+ allowedResponderSet = null,
762
+ currentBotSelector = "",
763
+ excludeCurrentTargetAssignments = false,
764
+ } = {}) {
765
+ const assignment = safeObject(rawAssignment);
766
+ const targetBot = normalizeMentionSelector(
767
+ assignment.targetBot
768
+ || assignment.target_bot
769
+ || assignment.bot
770
+ || assignment.username,
771
+ );
772
+ const task = String(
773
+ assignment.task
774
+ || assignment.instruction
775
+ || assignment.assignment
776
+ || assignment.work
777
+ || assignment.description
778
+ || "",
779
+ ).trim();
780
+ if (!targetBot || !task) {
781
+ return null;
782
+ }
783
+ if (allowedResponderSet?.size && !allowedResponderSet.has(targetBot)) {
784
+ return null;
785
+ }
786
+ if (
787
+ excludeCurrentTargetAssignments === true
788
+ && currentBotSelector
789
+ && targetBot === normalizeMentionSelector(currentBotSelector)
790
+ ) {
791
+ return null;
792
+ }
793
+ const artifactsRequired = boolFromRunnerRaw(
794
+ assignment.artifactsRequired ?? assignment.artifacts_required,
795
+ false,
796
+ );
797
+ const workspaceAction = boolFromRunnerRaw(
798
+ assignment.workspaceAction ?? assignment.workspace_action,
799
+ false,
800
+ );
801
+ const mode = normalizeExecutionAssignmentMode(
802
+ assignment.mode
803
+ || assignment.assignment_mode
804
+ || assignment.kind
805
+ || assignment.type,
806
+ (artifactsRequired || workspaceAction) ? "execution_task" : "conversation_contribution",
807
+ );
808
+ const requiresExecution = mode === "execution_task" || artifactsRequired || workspaceAction;
809
+ return {
810
+ targetBot,
811
+ task,
812
+ mode,
813
+ artifactsRequired,
814
+ workspaceAction,
815
+ requiresExecution,
816
+ };
817
+ }
818
+
713
819
  function normalizeHumanIntentType(value, fallback = "") {
714
820
  const normalized = String(value || "").trim().toLowerCase();
715
821
  if ([
@@ -1811,14 +1917,84 @@ function buildFallbackArchivedBotReplyConversationContext({
1811
1917
  };
1812
1918
  }
1813
1919
 
1814
- function extractCurrentAssignmentTasks(conversationContext, currentBotSelector) {
1920
+ function extractCurrentAssignmentsForBot(conversationContext, currentBotSelector) {
1815
1921
  const currentSelector = normalizeMentionSelector(currentBotSelector);
1816
1922
  if (!currentSelector) return [];
1817
1923
  return ensureArray(safeObject(conversationContext?.executionContract).assignments)
1818
1924
  .map((item) => safeObject(item))
1819
1925
  .filter((item) => normalizeMentionSelector(item.targetBot || item.target_bot) === currentSelector)
1820
- .map((item) => String(item.task || item.instruction || "").trim())
1821
- .filter(Boolean);
1926
+ .map((item) => ({
1927
+ targetBot: normalizeMentionSelector(item.targetBot || item.target_bot),
1928
+ task: String(item.task || item.instruction || "").trim(),
1929
+ mode: normalizeExecutionAssignmentMode(
1930
+ item.mode || item.assignment_mode || item.kind || item.type,
1931
+ item.requiresExecution === true
1932
+ || item.artifactsRequired === true
1933
+ || item.workspaceAction === true
1934
+ || item.artifacts_required === true
1935
+ || item.workspace_action === true
1936
+ ? "execution_task"
1937
+ : "conversation_contribution",
1938
+ ),
1939
+ artifactsRequired: item.artifactsRequired === true || item.artifacts_required === true,
1940
+ workspaceAction: item.workspaceAction === true || item.workspace_action === true,
1941
+ requiresExecution: item.requiresExecution === true
1942
+ || item.artifactsRequired === true
1943
+ || item.workspaceAction === true
1944
+ || item.artifacts_required === true
1945
+ || item.workspace_action === true
1946
+ || normalizeExecutionAssignmentMode(
1947
+ item.mode || item.assignment_mode || item.kind || item.type,
1948
+ "conversation_contribution",
1949
+ ) === "execution_task",
1950
+ }))
1951
+ .filter((item) => item.task);
1952
+ }
1953
+
1954
+ function summarizeCurrentAssignmentExecutionValidation(conversationContext, currentBotSelector) {
1955
+ const assignments = extractCurrentAssignmentsForBot(conversationContext, currentBotSelector);
1956
+ const assignmentModes = uniqueOrdered(
1957
+ assignments.map((item) => String(item.mode || "").trim()).filter(Boolean),
1958
+ );
1959
+ const executionAssignments = assignments.filter((item) => item.requiresExecution === true);
1960
+ const conversationAssignments = assignments.filter((item) => item.requiresExecution !== true);
1961
+ if (!assignments.length) {
1962
+ return {
1963
+ status: "no_assignment",
1964
+ reason: "no assignment for current bot in the active execution contract",
1965
+ assignmentModes: [],
1966
+ assignments: [],
1967
+ executionAssignments: [],
1968
+ conversationAssignments: [],
1969
+ executionTasks: [],
1970
+ allTasks: [],
1971
+ requiresPlanner: false,
1972
+ };
1973
+ }
1974
+ if (executionAssignments.length > 0) {
1975
+ return {
1976
+ status: "execution_assignment",
1977
+ reason: "active assignment explicitly requires workspace/artifact execution",
1978
+ assignmentModes,
1979
+ assignments,
1980
+ executionAssignments,
1981
+ conversationAssignments,
1982
+ executionTasks: executionAssignments.map((item) => String(item.task || "").trim()).filter(Boolean),
1983
+ allTasks: assignments.map((item) => String(item.task || "").trim()).filter(Boolean),
1984
+ requiresPlanner: true,
1985
+ };
1986
+ }
1987
+ return {
1988
+ status: "conversation_assignment",
1989
+ reason: "active assignment is a conversational contribution and does not require planner/worker execution",
1990
+ assignmentModes,
1991
+ assignments,
1992
+ executionAssignments: [],
1993
+ conversationAssignments,
1994
+ executionTasks: [],
1995
+ allTasks: assignments.map((item) => String(item.task || "").trim()).filter(Boolean),
1996
+ requiresPlanner: false,
1997
+ };
1822
1998
  }
1823
1999
 
1824
2000
  function requiresArtifactsForExecutionStep(step) {
@@ -2557,6 +2733,7 @@ async function maybeExecuteDynamicRolePlan({
2557
2733
  saveRunnerRouteState,
2558
2734
  runRunnerAIExecution,
2559
2735
  validateWorkspaceArtifacts,
2736
+ assignmentExecutionValidation = null,
2560
2737
  }) {
2561
2738
  const planner = typeof executionDeps.planRoleExecutionWithAI === "function"
2562
2739
  ? executionDeps.planRoleExecutionWithAI
@@ -2576,8 +2753,12 @@ async function maybeExecuteDynamicRolePlan({
2576
2753
  if (!planner || !resolveRunnerExecutionPlanForRole) {
2577
2754
  return null;
2578
2755
  }
2579
- const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
2580
- const assignmentTasks = extractCurrentAssignmentTasks(conversationContext, currentBotSelector);
2756
+ const assignmentValidation = safeObject(assignmentExecutionValidation);
2757
+ const assignmentValidationStatus = String(assignmentValidation.status || "").trim();
2758
+ const assignmentValidationReason = String(assignmentValidation.reason || "").trim();
2759
+ const assignmentTasks = ensureArray(assignmentValidation.executionTasks)
2760
+ .map((item) => String(item || "").trim())
2761
+ .filter(Boolean);
2581
2762
  const humanIntentType = normalizeHumanIntentType(
2582
2763
  safeObject(directHumanResponseContract).intentType
2583
2764
  || safeObject(safeObject(humanIntentContext).humanIntent).intentType,
@@ -2610,7 +2791,7 @@ async function maybeExecuteDynamicRolePlan({
2610
2791
  ) {
2611
2792
  return null;
2612
2793
  }
2613
- const shouldPlanExecution = assignmentTasks.length > 0 || (
2794
+ const shouldPlanExecution = assignmentValidation.requiresPlanner === true || (
2614
2795
  triggerDecision.requiresDirectReply === true
2615
2796
  && humanIntentMode === "single_bot"
2616
2797
  && actionableReplyExpectation
@@ -2756,6 +2937,8 @@ async function maybeExecuteDynamicRolePlan({
2756
2937
  last_conversation_id: String(conversationContext?.id || "").trim(),
2757
2938
  last_conversation_stage: String(conversationContext?.stage || "").trim(),
2758
2939
  last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
2940
+ last_assignment_validation_status: assignmentValidationStatus,
2941
+ last_assignment_validation_reason: assignmentValidationReason,
2759
2942
  ...safeObject(intentStatePatch),
2760
2943
  }),
2761
2944
  );
@@ -2769,6 +2952,9 @@ async function maybeExecuteDynamicRolePlan({
2769
2952
  thread_id: archiveThread.threadID,
2770
2953
  comment_id: selectedRecord.id,
2771
2954
  trigger_kind: String(triggerDecision.trigger || "").trim(),
2955
+ assignment_validation_status: assignmentValidationStatus,
2956
+ assignment_validation_reason: assignmentValidationReason,
2957
+ assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
2772
2958
  },
2773
2959
  };
2774
2960
  }
@@ -2786,6 +2972,8 @@ async function maybeExecuteDynamicRolePlan({
2786
2972
  last_conversation_id: String(conversationContext?.id || "").trim(),
2787
2973
  last_conversation_stage: String(conversationContext?.stage || "").trim(),
2788
2974
  last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
2975
+ last_assignment_validation_status: assignmentValidationStatus,
2976
+ last_assignment_validation_reason: assignmentValidationReason,
2789
2977
  ...safeObject(intentStatePatch),
2790
2978
  }),
2791
2979
  );
@@ -2799,6 +2987,9 @@ async function maybeExecuteDynamicRolePlan({
2799
2987
  thread_id: archiveThread.threadID,
2800
2988
  comment_id: selectedRecord.id,
2801
2989
  trigger_kind: String(triggerDecision.trigger || "").trim(),
2990
+ assignment_validation_status: assignmentValidationStatus,
2991
+ assignment_validation_reason: assignmentValidationReason,
2992
+ assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
2802
2993
  },
2803
2994
  };
2804
2995
  }
@@ -2833,6 +3024,8 @@ async function maybeExecuteDynamicRolePlan({
2833
3024
  last_conversation_id: String(conversationContext?.id || "").trim(),
2834
3025
  last_conversation_stage: String(conversationContext?.stage || "").trim(),
2835
3026
  last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
3027
+ last_assignment_validation_status: assignmentValidationStatus,
3028
+ last_assignment_validation_reason: assignmentValidationReason,
2836
3029
  ...safeObject(intentStatePatch),
2837
3030
  }),
2838
3031
  );
@@ -2846,6 +3039,9 @@ async function maybeExecuteDynamicRolePlan({
2846
3039
  thread_id: archiveThread.threadID,
2847
3040
  comment_id: selectedRecord.id,
2848
3041
  trigger_kind: String(triggerDecision.trigger || "").trim(),
3042
+ assignment_validation_status: assignmentValidationStatus,
3043
+ assignment_validation_reason: assignmentValidationReason,
3044
+ assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
2849
3045
  },
2850
3046
  };
2851
3047
  }
@@ -2862,6 +3058,8 @@ async function maybeExecuteDynamicRolePlan({
2862
3058
  last_conversation_id: String(conversationContext?.id || "").trim(),
2863
3059
  last_conversation_stage: String(conversationContext?.stage || "").trim(),
2864
3060
  last_workspace_dir: String(stepExecutionPlan.workspaceDir || executionPlan.workspaceDir || "").trim(),
3061
+ last_assignment_validation_status: assignmentValidationStatus,
3062
+ last_assignment_validation_reason: assignmentValidationReason,
2865
3063
  ...safeObject(intentStatePatch),
2866
3064
  }),
2867
3065
  );
@@ -2875,6 +3073,9 @@ async function maybeExecuteDynamicRolePlan({
2875
3073
  thread_id: archiveThread.threadID,
2876
3074
  comment_id: selectedRecord.id,
2877
3075
  trigger_kind: String(triggerDecision.trigger || "").trim(),
3076
+ assignment_validation_status: assignmentValidationStatus,
3077
+ assignment_validation_reason: assignmentValidationReason,
3078
+ assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
2878
3079
  },
2879
3080
  };
2880
3081
  }
@@ -2918,6 +3119,8 @@ async function maybeExecuteDynamicRolePlan({
2918
3119
  last_conversation_id: String(conversationContext?.id || "").trim(),
2919
3120
  last_conversation_stage: String(conversationContext?.stage || "").trim(),
2920
3121
  last_workspace_dir: String(stepExecutionPlan.workspaceDir || executionPlan.workspaceDir || "").trim(),
3122
+ last_assignment_validation_status: assignmentValidationStatus,
3123
+ last_assignment_validation_reason: assignmentValidationReason,
2921
3124
  ...safeObject(intentStatePatch),
2922
3125
  }),
2923
3126
  );
@@ -2931,6 +3134,9 @@ async function maybeExecuteDynamicRolePlan({
2931
3134
  thread_id: archiveThread.threadID,
2932
3135
  comment_id: selectedRecord.id,
2933
3136
  trigger_kind: String(triggerDecision.trigger || "").trim(),
3137
+ assignment_validation_status: assignmentValidationStatus,
3138
+ assignment_validation_reason: assignmentValidationReason,
3139
+ assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
2934
3140
  },
2935
3141
  };
2936
3142
  }
@@ -3057,6 +3263,8 @@ async function maybeExecuteDynamicRolePlan({
3057
3263
  last_artifact_errors: stepErrors,
3058
3264
  last_boundary_violations: stepBoundaryViolations,
3059
3265
  last_workspace_dir: String(stepExecutionPlan.workspaceDir || executionPlan.workspaceDir || "").trim(),
3266
+ last_assignment_validation_status: assignmentValidationStatus,
3267
+ last_assignment_validation_reason: assignmentValidationReason,
3060
3268
  ...safeObject(intentStatePatch),
3061
3269
  }),
3062
3270
  );
@@ -3073,6 +3281,9 @@ async function maybeExecuteDynamicRolePlan({
3073
3281
  artifact_validation: String(stepValidation.status || "").trim() || "execution_failed",
3074
3282
  artifact_paths: summarizeValidatedArtifactPaths(stepValidation),
3075
3283
  artifact_errors: stepErrors,
3284
+ assignment_validation_status: assignmentValidationStatus,
3285
+ assignment_validation_reason: assignmentValidationReason,
3286
+ assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
3076
3287
  },
3077
3288
  };
3078
3289
  }
@@ -3380,47 +3591,21 @@ function normalizeConversationExecutionContract(
3380
3591
  || contract.contractType
3381
3592
  || "",
3382
3593
  ).trim().toLowerCase();
3383
- const normalizedType = [
3384
- "direct_result",
3385
- "delegation",
3386
- "summary_request",
3387
- "final_summary",
3388
- ].includes(type)
3389
- ? type
3390
- : "";
3391
- const assignments = ensureArray(contract.assignments)
3392
- .map((item) => {
3393
- const assignment = safeObject(item);
3394
- const targetBot = normalizeMentionSelector(
3395
- assignment.targetBot
3396
- || assignment.target_bot
3397
- || assignment.bot
3398
- || assignment.username,
3399
- );
3400
- const task = String(
3401
- assignment.task
3402
- || assignment.instruction
3403
- || assignment.assignment
3404
- || assignment.work
3405
- || assignment.description
3406
- || "",
3407
- ).trim();
3408
- if (!targetBot || !task) {
3409
- return null;
3410
- }
3411
- if (allowedResponderSet.size && !allowedResponderSet.has(targetBot)) {
3412
- return null;
3413
- }
3414
- if (
3415
- excludeCurrentTargetAssignments === true
3416
- && currentBotSelector
3417
- && targetBot === normalizeMentionSelector(currentBotSelector)
3418
- ) {
3419
- return null;
3420
- }
3421
- return { targetBot, task };
3422
- })
3423
- .filter(Boolean);
3594
+ const normalizedType = [
3595
+ "direct_result",
3596
+ "delegation",
3597
+ "summary_request",
3598
+ "final_summary",
3599
+ ].includes(type)
3600
+ ? type
3601
+ : "";
3602
+ const assignments = ensureArray(contract.assignments)
3603
+ .map((item) => normalizeExecutionAssignment(item, {
3604
+ allowedResponderSet,
3605
+ currentBotSelector,
3606
+ excludeCurrentTargetAssignments,
3607
+ }))
3608
+ .filter(Boolean);
3424
3609
  const summaryBot = normalizeMentionSelector(
3425
3610
  contract.summaryBot
3426
3611
  || contract.summary_bot
@@ -3483,30 +3668,10 @@ function normalizeResponseExecutionContract(rawContract, responseContract, { cur
3483
3668
  return null;
3484
3669
  }
3485
3670
  const assignments = ensureArray(contract.assignments)
3486
- .map((item) => {
3487
- const assignment = safeObject(item);
3488
- const targetBot = normalizeMentionSelector(
3489
- assignment.targetBot
3490
- || assignment.target_bot
3491
- || assignment.bot
3492
- || assignment.username,
3493
- );
3494
- const task = String(
3495
- assignment.task
3496
- || assignment.instruction
3497
- || assignment.assignment
3498
- || assignment.work
3499
- || assignment.description
3500
- || "",
3501
- ).trim();
3502
- if (!targetBot || !task) {
3503
- return null;
3504
- }
3505
- if (currentBotSelector && targetBot === currentBotSelector) {
3506
- return null;
3507
- }
3508
- return { targetBot, task };
3509
- })
3671
+ .map((item) => normalizeExecutionAssignment(item, {
3672
+ currentBotSelector,
3673
+ excludeCurrentTargetAssignments: true,
3674
+ }))
3510
3675
  .filter(Boolean);
3511
3676
  const summaryBot = normalizeMentionSelector(
3512
3677
  contract.summaryBot
@@ -4826,6 +4991,10 @@ export async function processRunnerSelectedRecord({
4826
4991
  directQueryReply: directInformationalReply,
4827
4992
  responderAdjudication,
4828
4993
  });
4994
+ const assignmentExecutionValidation = summarizeCurrentAssignmentExecutionValidation(
4995
+ conversationContext,
4996
+ currentBotSelector,
4997
+ );
4829
4998
  const emitRunnerStage = (phase, detail) => {
4830
4999
  if (!reportRunnerStage) return;
4831
5000
  try {
@@ -4981,6 +5150,7 @@ export async function processRunnerSelectedRecord({
4981
5150
  saveRunnerRouteState,
4982
5151
  runRunnerAIExecution,
4983
5152
  validateWorkspaceArtifacts,
5153
+ assignmentExecutionValidation,
4984
5154
  });
4985
5155
  if (dynamicRoleExecution?.kind === "error") {
4986
5156
  dynamicExecutionError = dynamicRoleExecution.result;
@@ -5433,6 +5603,8 @@ export async function processRunnerSelectedRecord({
5433
5603
  last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5434
5604
  last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5435
5605
  last_contract_validation_targets: ensureArray(responseContractValidation.targets),
5606
+ last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5607
+ last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5436
5608
  last_speaker_bot_username: normalizeMentionSelector(bot?.username || bot?.name),
5437
5609
  last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
5438
5610
  ...intentStatePatch,
@@ -5474,6 +5646,9 @@ export async function processRunnerSelectedRecord({
5474
5646
  response_contract_validation_status: String(responseContractValidation.status || "").trim(),
5475
5647
  response_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5476
5648
  response_contract_validation_targets: ensureArray(responseContractValidation.targets),
5649
+ assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5650
+ assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5651
+ assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
5477
5652
  reply_chars: String(sanitizedReplyText || "").length,
5478
5653
  execution_mode: effectiveExecutionPlan.mode,
5479
5654
  role_profile: effectiveExecutionPlan.roleProfileName,
@@ -5564,6 +5739,8 @@ export async function processRunnerSelectedRecord({
5564
5739
  last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5565
5740
  last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5566
5741
  last_contract_validation_targets: ensureArray(responseContractValidation.targets),
5742
+ last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5743
+ last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5567
5744
  last_trigger: String(effectiveTriggerDecision.trigger || "").trim(),
5568
5745
  last_reason: effectiveConversationContext?.mode === "public_multi_bot"
5569
5746
  ? [
@@ -5664,6 +5841,9 @@ export async function processRunnerSelectedRecord({
5664
5841
  response_contract_validation_status: String(responseContractValidation.status || "").trim(),
5665
5842
  response_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5666
5843
  response_contract_validation_targets: ensureArray(responseContractValidation.targets),
5844
+ assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
5845
+ assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
5846
+ assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
5667
5847
  delivery_status: deliveryResult.delivery.dryRun ? "dry_run" : "delivered",
5668
5848
  execution_mode: effectiveExecutionPlan.mode,
5669
5849
  role_profile: effectiveExecutionPlan.roleProfileName,
@@ -5483,9 +5483,9 @@ export async function runSelftestRunnerScenarios(push, deps) {
5483
5483
  push("single_bot_execution_failure_uses_ai_failure_explainer_when_available", false, String(err?.message || err));
5484
5484
  }
5485
5485
 
5486
- try {
5487
- let aiCalls = 0;
5488
- const processed = await processRunnerSelectedRecord({
5486
+ try {
5487
+ let aiCalls = 0;
5488
+ const processed = await processRunnerSelectedRecord({
5489
5489
  routeKey: "single-bot-informational-human-request-key",
5490
5490
  normalizedRoute: normalizeRunnerRoute({
5491
5491
  name: "telegram-monitor-single-bot-informational",
@@ -9087,13 +9087,164 @@ export async function runSelftestRunnerScenarios(push, deps) {
9087
9087
  && String(processed.result?.execution_contract_type || "") === "summary_request",
9088
9088
  `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
9089
9089
  );
9090
- } catch (err) {
9091
- push("delegated_single_lead_lead_reply_can_wake_named_peer", false, String(err?.message || err));
9092
- }
9093
-
9094
- try {
9095
- let aiCalls = 0;
9096
- const deliveredConversation = [];
9090
+ } catch (err) {
9091
+ push("delegated_single_lead_lead_reply_can_wake_named_peer", false, String(err?.message || err));
9092
+ }
9093
+
9094
+ try {
9095
+ let aiCalls = 0;
9096
+ let plannerCalls = 0;
9097
+ const processed = await processRunnerSelectedRecord({
9098
+ routeKey: "delegated-single-lead-conversation-assignment-skips-planner-key",
9099
+ normalizedRoute: normalizeRunnerRoute({
9100
+ name: "telegram-monitor-delegated-single-lead-conversation-assignment-skips-planner",
9101
+ project_id: selftestProjectID,
9102
+ provider: "telegram",
9103
+ role: "monitor",
9104
+ role_profile: "monitor",
9105
+ destination_id: "dest-1",
9106
+ destination_label: "Main Room",
9107
+ server_bot_name: "RyoAI2_bot",
9108
+ server_bot_id: "bot-peer-1",
9109
+ trigger_policy: {
9110
+ mentions_only: true,
9111
+ direct_messages: true,
9112
+ reply_to_bot_messages: true,
9113
+ },
9114
+ archive_policy: {
9115
+ mirror_replies: true,
9116
+ dedupe_inbound: true,
9117
+ dedupe_outbound: true,
9118
+ skip_bot_messages: true,
9119
+ },
9120
+ dry_run_delivery: true,
9121
+ }),
9122
+ selectedRecord: {
9123
+ id: "comment-delegated-single-lead-conversation-assignment-skips-planner",
9124
+ createdAt: "2026-03-16T00:02:12.000Z",
9125
+ parsedArchive: {
9126
+ kind: "bot_reply",
9127
+ conversationID: "comment-delegated-single-lead-conversation-open",
9128
+ conversationMode: "public_multi_bot",
9129
+ conversationStage: "bot_reply",
9130
+ conversationIntentMode: "delegated_single_lead",
9131
+ conversationAllowBotToBot: true,
9132
+ conversationLeadBotUsername: "ryoai_bot",
9133
+ conversationParticipants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9134
+ conversationInitialResponders: ["ryoai_bot"],
9135
+ conversationAllowedResponders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
9136
+ conversationSummaryBotUsername: "ryoai_bot",
9137
+ botUsername: "RyoAI_bot",
9138
+ botName: "RyoAI_bot",
9139
+ sender: "RyoAI_bot",
9140
+ body: "@RyoAI2_bot 관점 하나만 짧게 말해줘.",
9141
+ mentionUsernames: ["ryoai2_bot"],
9142
+ executionContract: {
9143
+ type: "delegation",
9144
+ actionable: true,
9145
+ assignments: [
9146
+ { target_bot: "ryoai2_bot", task: "관점 하나만 짧게 말해줘.", mode: "conversation_contribution", artifacts_required: false },
9147
+ ],
9148
+ summary_bot: "ryoai_bot",
9149
+ next_responders: ["ryoai2_bot"],
9150
+ },
9151
+ },
9152
+ },
9153
+ pendingOrdered: [],
9154
+ bot: {
9155
+ id: "bot-peer-1",
9156
+ name: "RyoAI2_bot",
9157
+ username: "RyoAI2_bot",
9158
+ role: "monitor",
9159
+ provider: "telegram",
9160
+ },
9161
+ destination: {
9162
+ id: "dest-1",
9163
+ label: "Main Room",
9164
+ provider: "telegram",
9165
+ chatID: "-100123",
9166
+ },
9167
+ archiveThread: {
9168
+ threadID: "thread-1",
9169
+ workItemID: "work-item-1",
9170
+ },
9171
+ executionPlan: {
9172
+ mode: "role_profile",
9173
+ roleProfileName: "monitor",
9174
+ roleProfile: {
9175
+ client: "sample",
9176
+ model: "",
9177
+ permissionMode: "read_only",
9178
+ reasoningEffort: "low",
9179
+ },
9180
+ workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-conversation-assignment-skips-planner"),
9181
+ workspaceSource: "selftest",
9182
+ usedCommandFallback: false,
9183
+ },
9184
+ runtime: {
9185
+ baseURL: "https://example.test",
9186
+ token: "selftest-token",
9187
+ timeoutSeconds: 30,
9188
+ actor: { user_id: "user-1" },
9189
+ },
9190
+ deps: {
9191
+ saveRunnerRouteState: () => {},
9192
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
9193
+ runRunnerAIExecution: async () => {
9194
+ aiCalls += 1;
9195
+ return {
9196
+ skip: false,
9197
+ reply: "제 관점에서는 범위와 우선순위를 먼저 고정하는 게 맞습니다.",
9198
+ contract: {
9199
+ type: "summary_request",
9200
+ actionable: true,
9201
+ assignments: [],
9202
+ summary_bot: "ryoai_bot",
9203
+ next_responders: ["ryoai_bot"],
9204
+ },
9205
+ };
9206
+ },
9207
+ performLocalBotDelivery: async () => ({
9208
+ delivery: { dryRun: true, body: {} },
9209
+ archive: {},
9210
+ }),
9211
+ serializeRunnerTriggerPolicy: (value) => value,
9212
+ serializeRunnerArchivePolicy: (value) => value,
9213
+ buildRunnerExecutionDeps: () => ({
9214
+ planRoleExecutionWithAI: async () => {
9215
+ plannerCalls += 1;
9216
+ return {
9217
+ requiresExecution: true,
9218
+ summaryRole: "worker",
9219
+ steps: [{ role: "worker", goal: "unexpected", artifactsRequired: true }],
9220
+ };
9221
+ },
9222
+ }),
9223
+ buildRunnerDeliveryDeps: () => ({}),
9224
+ buildRunnerRuntimeDeps: () => ({}),
9225
+ resolveConversationPeerBots: () => [
9226
+ { id: "bot-lead-1", name: "RyoAI_bot" },
9227
+ { id: "bot-peer-1", name: "RyoAI2_bot" },
9228
+ { id: "bot-peer-2", name: "RyoAI3_bot" },
9229
+ ],
9230
+ },
9231
+ });
9232
+ push(
9233
+ "delegated_single_lead_conversation_assignment_skips_planner",
9234
+ processed.kind === "replied"
9235
+ && aiCalls === 1
9236
+ && plannerCalls === 0
9237
+ && String(processed.result?.assignment_validation_status || "") === "conversation_assignment"
9238
+ && ensureArray(processed.result?.assignment_validation_modes).includes("conversation_contribution"),
9239
+ `kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} planner_calls=${plannerCalls} assignment_status=${String(processed.result?.assignment_validation_status || "(none)")} modes=${JSON.stringify(processed.result?.assignment_validation_modes || [])}`,
9240
+ );
9241
+ } catch (err) {
9242
+ push("delegated_single_lead_conversation_assignment_skips_planner", false, String(err?.message || err));
9243
+ }
9244
+
9245
+ try {
9246
+ let aiCalls = 0;
9247
+ const deliveredConversation = [];
9097
9248
  const processed = await processRunnerSelectedRecord({
9098
9249
  routeKey: "delegated-single-lead-restored-bot-reply-key",
9099
9250
  normalizedRoute: normalizeRunnerRoute({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.225",
3
+ "version": "0.2.226",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [