metheus-governance-mcp-cli 0.2.147 → 0.2.148

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
@@ -12,6 +12,7 @@ import https from "node:https";
12
12
  import {
13
13
  DEFAULT_LOCAL_AI_CLIENT,
14
14
  analyzeHumanConversationIntentWithAI,
15
+ auditRoleExecutionPlanWithAI,
15
16
  auditDirectHumanReplyWithAI,
16
17
  normalizeExecutionArtifacts,
17
18
  planRoleExecutionWithAI,
@@ -3001,6 +3002,7 @@ function buildRunnerDeliveryDeps() {
3001
3002
  function buildRunnerExecutionDeps() {
3002
3003
  return {
3003
3004
  analyzeHumanConversationIntentWithAI,
3005
+ auditRoleExecutionPlanWithAI,
3004
3006
  auditDirectHumanReplyWithAI,
3005
3007
  planRoleExecutionWithAI,
3006
3008
  normalizeRunnerRoleProfileName,
@@ -1513,6 +1513,7 @@ function buildRoleExecutionPlanPrompt({
1513
1513
  currentAssignmentTasks = [],
1514
1514
  contextComments = [],
1515
1515
  conversationMode = "",
1516
+ planningGuardrail = null,
1516
1517
  }) {
1517
1518
  const bots = ensureArray(managedBots).map((item) => {
1518
1519
  const bot = safeObject(item);
@@ -1524,6 +1525,7 @@ function buildRoleExecutionPlanPrompt({
1524
1525
  const normalizedContext = ensureArray(contextComments)
1525
1526
  .slice(0, 10)
1526
1527
  .map((item) => formatContextCommentLine(item));
1528
+ const guardrail = safeObject(planningGuardrail);
1527
1529
  return [
1528
1530
  "You are an execution planner for managed local Telegram bots.",
1529
1531
  "Read the human request and recent room context, then decide whether actual project execution is required now.",
@@ -1543,6 +1545,15 @@ function buildRoleExecutionPlanPrompt({
1543
1545
  "Return strict JSON only with this shape:",
1544
1546
  "{\"requires_execution\":true|false,\"summary_role\":\"monitor|worker|review|approval\",\"reason\":\"short explanation\",\"steps\":[{\"role\":\"monitor|worker|review|approval\",\"goal\":\"short goal\",\"artifacts_required\":true|false}]}",
1545
1547
  "",
1548
+ guardrail.require_worker_step === true
1549
+ ? "Guardrail: the final plan must include a worker step that produces or updates project artifacts now."
1550
+ : "",
1551
+ guardrail.reject_monitor_only_execution === true
1552
+ ? "Guardrail: do not return a monitor-only plan for this request."
1553
+ : "",
1554
+ String(guardrail.reason || "").trim()
1555
+ ? `Guardrail Reason: ${String(guardrail.reason || "").trim()}`
1556
+ : "",
1546
1557
  `current_bot_username=${JSON.stringify(String(currentBotUsername || "").trim().replace(/^@+/, ""))}`,
1547
1558
  `current_bot_route_role=${JSON.stringify(String(currentBotRole || "").trim().toLowerCase())}`,
1548
1559
  `conversation_mode=${JSON.stringify(String(conversationMode || "").trim())}`,
@@ -1555,6 +1566,52 @@ function buildRoleExecutionPlanPrompt({
1555
1566
  ].join("\n");
1556
1567
  }
1557
1568
 
1569
+ function buildRoleExecutionPlanAuditPrompt({
1570
+ messageText,
1571
+ currentBotUsername,
1572
+ currentBotRole,
1573
+ managedBots,
1574
+ workspaceDir,
1575
+ currentAssignmentTasks = [],
1576
+ contextComments = [],
1577
+ conversationMode = "",
1578
+ executionPlan = null,
1579
+ }) {
1580
+ const bots = ensureArray(managedBots).map((item) => {
1581
+ const bot = safeObject(item);
1582
+ return {
1583
+ username: String(bot.username || "").trim().replace(/^@+/, ""),
1584
+ display_name: String(bot.display_name || bot.displayName || bot.username || "").trim(),
1585
+ };
1586
+ }).filter((item) => item.username);
1587
+ const normalizedContext = ensureArray(contextComments)
1588
+ .slice(0, 10)
1589
+ .map((item) => formatContextCommentLine(item));
1590
+ return [
1591
+ "You are an execution plan adequacy auditor for managed local Telegram bots.",
1592
+ "Judge by meaning, not by keywords or @mentions alone.",
1593
+ "Decide whether the human request requires actual project execution now.",
1594
+ "Then decide whether the proposed execution plan is sufficient to satisfy that request.",
1595
+ "A plan that only inspects, analyzes, checks status, or says it will plan later does NOT satisfy an execution request that should produce concrete project work now.",
1596
+ "If satisfying the request requires creating/updating/deleting project files now, then requires_worker_step must be true.",
1597
+ "If the request is informational only, requires_execution should be false and requires_worker_step should be false.",
1598
+ "",
1599
+ "Return strict JSON only with this shape:",
1600
+ "{\"requires_execution\":true|false,\"requires_worker_step\":true|false,\"plan_satisfies_request\":true|false,\"reason\":\"short explanation\"}",
1601
+ "",
1602
+ `current_bot_username=${JSON.stringify(String(currentBotUsername || "").trim().replace(/^@+/, ""))}`,
1603
+ `current_bot_route_role=${JSON.stringify(String(currentBotRole || "").trim().toLowerCase())}`,
1604
+ `conversation_mode=${JSON.stringify(String(conversationMode || "").trim())}`,
1605
+ `workspace_dir=${JSON.stringify(String(workspaceDir || "").trim())}`,
1606
+ `managed_bots=${JSON.stringify(bots)}`,
1607
+ `current_assignment_tasks=${JSON.stringify(ensureArray(currentAssignmentTasks).map((item) => String(item || "").trim()).filter(Boolean))}`,
1608
+ `human_message=${JSON.stringify(String(messageText || "").trim())}`,
1609
+ `candidate_execution_plan=${JSON.stringify(safeObject(executionPlan))}`,
1610
+ "recent_context=",
1611
+ normalizedContext.length ? normalizedContext.join("\n") : "- none",
1612
+ ].join("\n");
1613
+ }
1614
+
1558
1615
  function buildDirectReplyGuardrailPrompt({
1559
1616
  humanMessageText,
1560
1617
  botReplyText,
@@ -1695,6 +1752,7 @@ export function planRoleExecutionWithAI({
1695
1752
  currentAssignmentTasks = [],
1696
1753
  contextComments = [],
1697
1754
  conversationMode = "",
1755
+ planningGuardrail = null,
1698
1756
  client = "",
1699
1757
  model = "",
1700
1758
  env = process.env,
@@ -1725,6 +1783,7 @@ export function planRoleExecutionWithAI({
1725
1783
  currentAssignmentTasks,
1726
1784
  contextComments,
1727
1785
  conversationMode,
1786
+ planningGuardrail,
1728
1787
  }),
1729
1788
  workspaceDir,
1730
1789
  model: plannerModel,
@@ -1739,6 +1798,66 @@ export function planRoleExecutionWithAI({
1739
1798
  return normalizeRoleExecutionPlan(parsed);
1740
1799
  }
1741
1800
 
1801
+ export function auditRoleExecutionPlanWithAI({
1802
+ messageText,
1803
+ currentBotUsername,
1804
+ currentBotRole = "",
1805
+ managedBots,
1806
+ workspaceDir,
1807
+ currentAssignmentTasks = [],
1808
+ contextComments = [],
1809
+ conversationMode = "",
1810
+ executionPlan = null,
1811
+ client = "",
1812
+ model = "",
1813
+ env = process.env,
1814
+ }) {
1815
+ const bots = ensureArray(managedBots);
1816
+ if (!bots.length) {
1817
+ return null;
1818
+ }
1819
+ const plannerClient = normalizeLocalAIClientName(
1820
+ String(client || env?.METHEUS_ROLE_PLANNER_CLIENT || env?.METHEUS_INTENT_PARSER_CLIENT || "").trim(),
1821
+ "gpt",
1822
+ );
1823
+ const plannerModel = String(
1824
+ model
1825
+ || env?.METHEUS_ROLE_PLANNER_MODEL
1826
+ || env?.METHEUS_INTENT_PARSER_MODEL
1827
+ || suggestLocalAIModelDisplayName(plannerClient, "")
1828
+ || "",
1829
+ ).trim();
1830
+ const rawText = runLocalAIPromptRawText({
1831
+ client: plannerClient,
1832
+ promptText: buildRoleExecutionPlanAuditPrompt({
1833
+ messageText,
1834
+ currentBotUsername,
1835
+ currentBotRole,
1836
+ managedBots: bots,
1837
+ workspaceDir,
1838
+ currentAssignmentTasks,
1839
+ contextComments,
1840
+ conversationMode,
1841
+ executionPlan,
1842
+ }),
1843
+ workspaceDir,
1844
+ model: plannerModel,
1845
+ permissionMode: "read_only",
1846
+ reasoningEffort: String(env?.METHEUS_ROLE_PLANNER_REASONING_EFFORT || "medium").trim() || "medium",
1847
+ env,
1848
+ });
1849
+ const parsed = tryJsonParse(rawText) || tryParseEmbeddedJsonObject(rawText);
1850
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1851
+ throw new Error("role execution plan auditor did not return a JSON object");
1852
+ }
1853
+ return {
1854
+ requires_execution: parsed.requires_execution === true || parsed.requiresExecution === true,
1855
+ requires_worker_step: parsed.requires_worker_step === true || parsed.requiresWorkerStep === true,
1856
+ plan_satisfies_request: parsed.plan_satisfies_request === true || parsed.planSatisfiesRequest === true,
1857
+ reason: String(parsed.reason || "").trim(),
1858
+ };
1859
+ }
1860
+
1742
1861
  export function runLocalAIClient({
1743
1862
  client,
1744
1863
  inputPayload,
@@ -447,6 +447,32 @@ function requiresArtifactsForExecutionStep(step) {
447
447
  return safeObject(step).artifactsRequired === true || role === "worker";
448
448
  }
449
449
 
450
+ function executionPlanIncludesWorkerStep(plan) {
451
+ return ensureArray(safeObject(plan).steps).some((item) => requiresArtifactsForExecutionStep(item));
452
+ }
453
+
454
+ function describeExecutionPlanAdequacyFailure(plan, audit) {
455
+ const normalizedPlan = safeObject(plan);
456
+ const normalizedAudit = safeObject(audit);
457
+ const steps = ensureArray(normalizedPlan.steps).filter((item) => String(safeObject(item).role || "").trim());
458
+ if (normalizedAudit.requires_execution === true && normalizedPlan.requiresExecution !== true) {
459
+ return String(normalizedAudit.reason || "").trim()
460
+ || "execution planner did not return an executable plan for a request that requires project work";
461
+ }
462
+ if (normalizedPlan.requiresExecution === true && !steps.length) {
463
+ return "execution planner required execution but did not return any executable steps";
464
+ }
465
+ if (normalizedAudit.requires_worker_step === true && !executionPlanIncludesWorkerStep(normalizedPlan)) {
466
+ return String(normalizedAudit.reason || "").trim()
467
+ || "execution planner returned a monitor-only plan for a request that requires worker execution";
468
+ }
469
+ if (normalizedAudit.plan_satisfies_request === false) {
470
+ return String(normalizedAudit.reason || "").trim()
471
+ || "execution plan auditor rejected the candidate execution plan";
472
+ }
473
+ return "";
474
+ }
475
+
450
476
  function summarizeExecutedRolePlan(plan, stepResults) {
451
477
  return {
452
478
  requires_execution: safeObject(plan).requiresExecution === true,
@@ -560,6 +586,9 @@ async function maybeExecuteDynamicRolePlan({
560
586
  const planner = typeof executionDeps.planRoleExecutionWithAI === "function"
561
587
  ? executionDeps.planRoleExecutionWithAI
562
588
  : null;
589
+ const plannerAuditor = typeof executionDeps.auditRoleExecutionPlanWithAI === "function"
590
+ ? executionDeps.auditRoleExecutionPlanWithAI
591
+ : null;
563
592
  const resolveRunnerExecutionPlanForRole = typeof executionDeps.resolveRunnerExecutionPlanForRole === "function"
564
593
  ? executionDeps.resolveRunnerExecutionPlanForRole
565
594
  : null;
@@ -579,23 +608,87 @@ async function maybeExecuteDynamicRolePlan({
579
608
  ? humanIntentContext.peerMap
580
609
  : buildConversationPeerMap(bot, normalizedRoute, executionDeps);
581
610
  const managedBots = buildConversationParticipantViews(Array.from(peerMap.keys()), peerMap);
611
+ const invokePlanner = async (planningGuardrail = null) => planner({
612
+ messageText: String(safeObject(selectedRecord?.parsedArchive).body || "").trim(),
613
+ currentBotUsername: String(bot?.username || bot?.name || "").trim(),
614
+ currentBotRole: String(executionPlan.roleProfileName || normalizedRoute.role || "").trim(),
615
+ managedBots,
616
+ workspaceDir: String(executionPlan.workspaceDir || process.cwd()).trim() || process.cwd(),
617
+ currentAssignmentTasks: assignmentTasks,
618
+ contextComments: buildRunnerContextWindow(pendingOrdered, selectedRecord, normalizedRoute.contextComments),
619
+ conversationMode: String(conversationContext?.mode || "").trim(),
620
+ planningGuardrail,
621
+ client: String(executionPlan.roleProfile?.client || "").trim(),
622
+ model: String(executionPlan.roleProfile?.model || "").trim(),
623
+ });
624
+ const auditPlannedExecution = async (candidatePlan) => {
625
+ if (!plannerAuditor) {
626
+ return null;
627
+ }
628
+ try {
629
+ return await plannerAuditor({
630
+ messageText: String(safeObject(selectedRecord?.parsedArchive).body || "").trim(),
631
+ currentBotUsername: String(bot?.username || bot?.name || "").trim(),
632
+ currentBotRole: String(executionPlan.roleProfileName || normalizedRoute.role || "").trim(),
633
+ managedBots,
634
+ workspaceDir: String(executionPlan.workspaceDir || process.cwd()).trim() || process.cwd(),
635
+ currentAssignmentTasks: assignmentTasks,
636
+ contextComments: buildRunnerContextWindow(pendingOrdered, selectedRecord, normalizedRoute.contextComments),
637
+ conversationMode: String(conversationContext?.mode || "").trim(),
638
+ executionPlan: candidatePlan,
639
+ client: String(executionPlan.roleProfile?.client || "").trim(),
640
+ model: String(executionPlan.roleProfile?.model || "").trim(),
641
+ });
642
+ } catch {
643
+ return null;
644
+ }
645
+ };
582
646
  let executedPlan;
647
+ let executedPlanAudit = null;
583
648
  try {
584
- executedPlan = await planner({
585
- messageText: String(safeObject(selectedRecord?.parsedArchive).body || "").trim(),
586
- currentBotUsername: String(bot?.username || bot?.name || "").trim(),
587
- currentBotRole: String(executionPlan.roleProfileName || normalizedRoute.role || "").trim(),
588
- managedBots,
589
- workspaceDir: String(executionPlan.workspaceDir || process.cwd()).trim() || process.cwd(),
590
- currentAssignmentTasks: assignmentTasks,
591
- contextComments: buildRunnerContextWindow(pendingOrdered, selectedRecord, normalizedRoute.contextComments),
592
- conversationMode: String(conversationContext?.mode || "").trim(),
593
- client: String(executionPlan.roleProfile?.client || "").trim(),
594
- model: String(executionPlan.roleProfile?.model || "").trim(),
595
- });
649
+ executedPlan = await invokePlanner();
596
650
  } catch {
597
651
  return null;
598
652
  }
653
+ executedPlanAudit = await auditPlannedExecution(executedPlan);
654
+ let adequacyFailure = describeExecutionPlanAdequacyFailure(executedPlan, executedPlanAudit);
655
+ if (adequacyFailure) {
656
+ try {
657
+ executedPlan = await invokePlanner({
658
+ require_worker_step: safeObject(executedPlanAudit).requires_worker_step === true,
659
+ reject_monitor_only_execution: safeObject(executedPlanAudit).requires_execution === true,
660
+ reason: adequacyFailure,
661
+ });
662
+ executedPlanAudit = await auditPlannedExecution(executedPlan);
663
+ adequacyFailure = describeExecutionPlanAdequacyFailure(executedPlan, executedPlanAudit);
664
+ } catch {
665
+ executedPlan = null;
666
+ }
667
+ }
668
+ if (adequacyFailure) {
669
+ saveRunnerRouteState(
670
+ routeKey,
671
+ buildRunnerRouteStateFromComment(selectedRecord, {
672
+ last_action: "execution_failed",
673
+ last_reason: adequacyFailure,
674
+ last_conversation_id: String(conversationContext?.id || "").trim(),
675
+ last_conversation_stage: String(conversationContext?.stage || "").trim(),
676
+ last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
677
+ }),
678
+ );
679
+ return {
680
+ kind: "error",
681
+ result: {
682
+ route_key: routeKey,
683
+ route_name: normalizedRoute.name,
684
+ outcome: "execution_failed",
685
+ detail: adequacyFailure,
686
+ thread_id: archiveThread.threadID,
687
+ comment_id: selectedRecord.id,
688
+ trigger_kind: String(triggerDecision.trigger || "").trim(),
689
+ },
690
+ };
691
+ }
599
692
  if (!safeObject(executedPlan).requiresExecution) {
600
693
  return null;
601
694
  }
@@ -3080,6 +3080,378 @@ export async function runSelftestRunnerScenarios(push, deps) {
3080
3080
  push("single_bot_worker_step_requires_artifacts_in_multi_role_plan", false, String(err?.message || err));
3081
3081
  }
3082
3082
 
3083
+ try {
3084
+ let deliveryCalls = 0;
3085
+ let plannerCalls = 0;
3086
+ let auditCalls = 0;
3087
+ let aiCalls = 0;
3088
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-multi-step-replan-"));
3089
+ const processed = await processRunnerSelectedRecord({
3090
+ routeKey: "single-bot-multi-step-replan-key",
3091
+ normalizedRoute: normalizeRunnerRoute({
3092
+ name: "telegram-monitor-single-bot-multi-step-replan",
3093
+ project_id: selftestProjectID,
3094
+ provider: "telegram",
3095
+ role: "monitor",
3096
+ role_profile: "monitor",
3097
+ destination_id: "dest-1",
3098
+ destination_label: "Main Room",
3099
+ server_bot_name: "RyoAI_bot",
3100
+ server_bot_id: "bot-lead-1",
3101
+ trigger_policy: {
3102
+ mentions_only: true,
3103
+ direct_messages: true,
3104
+ reply_to_bot_messages: true,
3105
+ },
3106
+ archive_policy: {
3107
+ mirror_replies: true,
3108
+ dedupe_inbound: true,
3109
+ dedupe_outbound: true,
3110
+ skip_bot_messages: true,
3111
+ },
3112
+ dry_run_delivery: true,
3113
+ }),
3114
+ selectedRecord: {
3115
+ id: "comment-single-bot-multi-step-replan",
3116
+ createdAt: "2026-03-17T01:00:02.000Z",
3117
+ parsedArchive: {
3118
+ kind: "telegram_message",
3119
+ chatID: "-100123",
3120
+ chatType: "supergroup",
3121
+ senderIsBot: false,
3122
+ body: "@RyoAI_bot redo the project work and create the README file now.",
3123
+ mentionUsernames: ["RyoAI_bot"],
3124
+ messageID: 1303,
3125
+ },
3126
+ },
3127
+ pendingOrdered: [],
3128
+ bot: {
3129
+ id: "bot-lead-1",
3130
+ name: "RyoAI_bot",
3131
+ username: "RyoAI_bot",
3132
+ role: "monitor",
3133
+ provider: "telegram",
3134
+ },
3135
+ destination: {
3136
+ id: "dest-1",
3137
+ label: "Main Room",
3138
+ provider: "telegram",
3139
+ chatID: "-100123",
3140
+ },
3141
+ archiveThread: {
3142
+ threadID: "thread-1",
3143
+ workItemID: "work-item-1",
3144
+ },
3145
+ executionPlan: {
3146
+ mode: "role_profile",
3147
+ roleProfileName: "monitor",
3148
+ roleProfile: {
3149
+ client: "sample",
3150
+ model: "",
3151
+ permissionMode: "read_only",
3152
+ reasoningEffort: "low",
3153
+ },
3154
+ workspaceDir,
3155
+ workspaceSource: "selftest",
3156
+ usedCommandFallback: false,
3157
+ },
3158
+ runtime: {
3159
+ baseURL: "https://example.test",
3160
+ token: "selftest-token",
3161
+ timeoutSeconds: 30,
3162
+ actor: { user_id: "user-1" },
3163
+ },
3164
+ deps: {
3165
+ saveRunnerRouteState: () => {},
3166
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
3167
+ runRunnerAIExecution: async ({ inputPayload }) => {
3168
+ aiCalls += 1;
3169
+ const stepRole = String(inputPayload?.execution_step?.role || "").trim();
3170
+ if (stepRole === "monitor") {
3171
+ return {
3172
+ skip: false,
3173
+ reply: "Inspected the project requirements and identified the missing README work.",
3174
+ };
3175
+ }
3176
+ if (stepRole === "worker") {
3177
+ const readmePath = path.join(workspaceDir, "README.md");
3178
+ fs.writeFileSync(readmePath, "# README\n", "utf8");
3179
+ return {
3180
+ skip: false,
3181
+ reply: "Created the README document.",
3182
+ artifacts: [{ path: "README.md", kind: "document", operation: "create" }],
3183
+ };
3184
+ }
3185
+ throw new Error(`unexpected step role ${stepRole}`);
3186
+ },
3187
+ performLocalBotDelivery: async () => {
3188
+ deliveryCalls += 1;
3189
+ return {
3190
+ delivery: { dryRun: true, body: {} },
3191
+ archive: {},
3192
+ };
3193
+ },
3194
+ serializeRunnerTriggerPolicy: (value) => value,
3195
+ serializeRunnerArchivePolicy: (value) => value,
3196
+ buildRunnerExecutionDeps: () => ({
3197
+ validateWorkspaceArtifacts,
3198
+ analyzeHumanConversationIntentWithAI: async () => ({
3199
+ mode: "single_bot",
3200
+ lead_bot: "ryoai_bot",
3201
+ participants: ["ryoai_bot"],
3202
+ initial_responders: ["ryoai_bot"],
3203
+ allowed_responders: ["ryoai_bot"],
3204
+ summary_bot: "",
3205
+ allow_bot_to_bot: false,
3206
+ reply_expectation: "actionable",
3207
+ }),
3208
+ planRoleExecutionWithAI: async ({ planningGuardrail } = {}) => {
3209
+ plannerCalls += 1;
3210
+ if (safeObject(planningGuardrail).require_worker_step === true) {
3211
+ return {
3212
+ requiresExecution: true,
3213
+ summaryRole: "worker",
3214
+ steps: [
3215
+ { role: "monitor", goal: "Inspect the request" },
3216
+ { role: "worker", goal: "Create the README", artifactsRequired: true },
3217
+ ],
3218
+ };
3219
+ }
3220
+ return {
3221
+ requiresExecution: true,
3222
+ summaryRole: "monitor",
3223
+ steps: [
3224
+ { role: "monitor", goal: "Inspect the request" },
3225
+ ],
3226
+ };
3227
+ },
3228
+ auditRoleExecutionPlanWithAI: async ({ executionPlan }) => {
3229
+ auditCalls += 1;
3230
+ const steps = ensureArray(safeObject(executionPlan).steps).map((item) => String(safeObject(item).role || "").trim().toLowerCase());
3231
+ const hasWorker = steps.includes("worker");
3232
+ return {
3233
+ requires_execution: true,
3234
+ requires_worker_step: true,
3235
+ plan_satisfies_request: hasWorker,
3236
+ reason: hasWorker
3237
+ ? "worker step present"
3238
+ : "execution request must include a worker step that creates project artifacts",
3239
+ };
3240
+ },
3241
+ resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
3242
+ mode: "role_profile",
3243
+ roleProfileName: String(roleName || "").trim(),
3244
+ roleProfile: {
3245
+ client: "sample",
3246
+ model: "",
3247
+ permissionMode: String(roleName || "").trim() === "worker" ? "workspace_write" : "read_only",
3248
+ reasoningEffort: String(roleName || "").trim() === "worker" ? "medium" : "low",
3249
+ },
3250
+ workspaceDir,
3251
+ workspaceSource: "selftest",
3252
+ usedCommandFallback: false,
3253
+ }),
3254
+ loadBotRunnerConfig: () => ({
3255
+ roleProfiles: {},
3256
+ botBindings: {},
3257
+ routes: [],
3258
+ projectMappings: {},
3259
+ }),
3260
+ }),
3261
+ buildRunnerDeliveryDeps: () => ({}),
3262
+ buildRunnerRuntimeDeps: () => ({}),
3263
+ resolveConversationPeerBots: () => [
3264
+ { id: "bot-lead-1", name: "RyoAI_bot" },
3265
+ ],
3266
+ },
3267
+ });
3268
+ push(
3269
+ "single_bot_execution_request_replans_monitor_only_plan_into_worker_plan",
3270
+ processed.kind === "replied"
3271
+ && plannerCalls === 2
3272
+ && auditCalls >= 2
3273
+ && aiCalls === 2
3274
+ && deliveryCalls === 1
3275
+ && ensureArray(processed.result?.artifact_paths).includes("README.md"),
3276
+ `kind=${String(processed.kind || "(none)")} planner_calls=${plannerCalls} audit_calls=${auditCalls} ai_calls=${aiCalls} delivery_calls=${deliveryCalls} artifacts=${ensureArray(processed.result?.artifact_paths).join(",")}`,
3277
+ );
3278
+ } catch (err) {
3279
+ push("single_bot_execution_request_replans_monitor_only_plan_into_worker_plan", false, String(err?.message || err));
3280
+ }
3281
+
3282
+ try {
3283
+ let deliveryCalls = 0;
3284
+ let plannerCalls = 0;
3285
+ let auditCalls = 0;
3286
+ let aiCalls = 0;
3287
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-multi-step-replan-fail-"));
3288
+ const processed = await processRunnerSelectedRecord({
3289
+ routeKey: "single-bot-multi-step-replan-fail-key",
3290
+ normalizedRoute: normalizeRunnerRoute({
3291
+ name: "telegram-monitor-single-bot-multi-step-replan-fail",
3292
+ project_id: selftestProjectID,
3293
+ provider: "telegram",
3294
+ role: "monitor",
3295
+ role_profile: "monitor",
3296
+ destination_id: "dest-1",
3297
+ destination_label: "Main Room",
3298
+ server_bot_name: "RyoAI_bot",
3299
+ server_bot_id: "bot-lead-1",
3300
+ trigger_policy: {
3301
+ mentions_only: true,
3302
+ direct_messages: true,
3303
+ reply_to_bot_messages: true,
3304
+ },
3305
+ archive_policy: {
3306
+ mirror_replies: true,
3307
+ dedupe_inbound: true,
3308
+ dedupe_outbound: true,
3309
+ skip_bot_messages: true,
3310
+ },
3311
+ dry_run_delivery: true,
3312
+ }),
3313
+ selectedRecord: {
3314
+ id: "comment-single-bot-multi-step-replan-fail",
3315
+ createdAt: "2026-03-17T01:00:03.000Z",
3316
+ parsedArchive: {
3317
+ kind: "telegram_message",
3318
+ chatID: "-100123",
3319
+ chatType: "supergroup",
3320
+ senderIsBot: false,
3321
+ body: "@RyoAI_bot redo the project work and create the README file now.",
3322
+ mentionUsernames: ["RyoAI_bot"],
3323
+ messageID: 1304,
3324
+ },
3325
+ },
3326
+ pendingOrdered: [],
3327
+ bot: {
3328
+ id: "bot-lead-1",
3329
+ name: "RyoAI_bot",
3330
+ username: "RyoAI_bot",
3331
+ role: "monitor",
3332
+ provider: "telegram",
3333
+ },
3334
+ destination: {
3335
+ id: "dest-1",
3336
+ label: "Main Room",
3337
+ provider: "telegram",
3338
+ chatID: "-100123",
3339
+ },
3340
+ archiveThread: {
3341
+ threadID: "thread-1",
3342
+ workItemID: "work-item-1",
3343
+ },
3344
+ executionPlan: {
3345
+ mode: "role_profile",
3346
+ roleProfileName: "monitor",
3347
+ roleProfile: {
3348
+ client: "sample",
3349
+ model: "",
3350
+ permissionMode: "read_only",
3351
+ reasoningEffort: "low",
3352
+ },
3353
+ workspaceDir,
3354
+ workspaceSource: "selftest",
3355
+ usedCommandFallback: false,
3356
+ },
3357
+ runtime: {
3358
+ baseURL: "https://example.test",
3359
+ token: "selftest-token",
3360
+ timeoutSeconds: 30,
3361
+ actor: { user_id: "user-1" },
3362
+ },
3363
+ deps: {
3364
+ saveRunnerRouteState: () => {},
3365
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
3366
+ runRunnerAIExecution: async () => {
3367
+ aiCalls += 1;
3368
+ return {
3369
+ skip: false,
3370
+ reply: "This path should not execute because the monitor-only plan must fail first.",
3371
+ };
3372
+ },
3373
+ performLocalBotDelivery: async () => {
3374
+ deliveryCalls += 1;
3375
+ return {
3376
+ delivery: { dryRun: true, body: {} },
3377
+ archive: {},
3378
+ };
3379
+ },
3380
+ serializeRunnerTriggerPolicy: (value) => value,
3381
+ serializeRunnerArchivePolicy: (value) => value,
3382
+ buildRunnerExecutionDeps: () => ({
3383
+ validateWorkspaceArtifacts,
3384
+ analyzeHumanConversationIntentWithAI: async () => ({
3385
+ mode: "single_bot",
3386
+ lead_bot: "ryoai_bot",
3387
+ participants: ["ryoai_bot"],
3388
+ initial_responders: ["ryoai_bot"],
3389
+ allowed_responders: ["ryoai_bot"],
3390
+ summary_bot: "",
3391
+ allow_bot_to_bot: false,
3392
+ reply_expectation: "actionable",
3393
+ }),
3394
+ planRoleExecutionWithAI: async () => {
3395
+ plannerCalls += 1;
3396
+ return {
3397
+ requiresExecution: true,
3398
+ summaryRole: "monitor",
3399
+ steps: [
3400
+ { role: "monitor", goal: "Inspect the request" },
3401
+ ],
3402
+ };
3403
+ },
3404
+ auditRoleExecutionPlanWithAI: async () => {
3405
+ auditCalls += 1;
3406
+ return {
3407
+ requires_execution: true,
3408
+ requires_worker_step: true,
3409
+ plan_satisfies_request: false,
3410
+ reason: "execution request must include a worker step that creates project artifacts",
3411
+ };
3412
+ },
3413
+ resolveRunnerExecutionPlanForRole: (_route, _bot, roleName) => ({
3414
+ mode: "role_profile",
3415
+ roleProfileName: String(roleName || "").trim(),
3416
+ roleProfile: {
3417
+ client: "sample",
3418
+ model: "",
3419
+ permissionMode: "read_only",
3420
+ reasoningEffort: "low",
3421
+ },
3422
+ workspaceDir,
3423
+ workspaceSource: "selftest",
3424
+ usedCommandFallback: false,
3425
+ }),
3426
+ loadBotRunnerConfig: () => ({
3427
+ roleProfiles: {},
3428
+ botBindings: {},
3429
+ routes: [],
3430
+ projectMappings: {},
3431
+ }),
3432
+ }),
3433
+ buildRunnerDeliveryDeps: () => ({}),
3434
+ buildRunnerRuntimeDeps: () => ({}),
3435
+ resolveConversationPeerBots: () => [
3436
+ { id: "bot-lead-1", name: "RyoAI_bot" },
3437
+ ],
3438
+ },
3439
+ });
3440
+ push(
3441
+ "single_bot_execution_request_fails_when_replanned_plan_still_lacks_worker_step",
3442
+ processed.kind === "error"
3443
+ && String(processed.result?.outcome || "") === "execution_failed"
3444
+ && plannerCalls === 2
3445
+ && auditCalls >= 2
3446
+ && aiCalls === 0
3447
+ && deliveryCalls === 0
3448
+ && /worker step/i.test(String(processed.result?.detail || "")),
3449
+ `kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} planner_calls=${plannerCalls} audit_calls=${auditCalls} ai_calls=${aiCalls} delivery_calls=${deliveryCalls} detail=${String(processed.result?.detail || "(none)")}`,
3450
+ );
3451
+ } catch (err) {
3452
+ push("single_bot_execution_request_fails_when_replanned_plan_still_lacks_worker_step", false, String(err?.message || err));
3453
+ }
3454
+
3083
3455
  try {
3084
3456
  let aiCalls = 0;
3085
3457
  const processed = await processRunnerSelectedRecord({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.147",
3
+ "version": "0.2.148",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [