metheus-governance-mcp-cli 0.2.212 → 0.2.213

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
@@ -2358,6 +2358,16 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
2358
2358
  next_expected_responders: ensureArray(entry.next_expected_responders || entry.nextExpectedResponders)
2359
2359
  .map((value) => normalizeTelegramMentionUsername(value))
2360
2360
  .filter(Boolean),
2361
+ ai_reply_generated: boolFromRaw(
2362
+ entry.ai_reply_generated ?? entry.aiReplyGenerated,
2363
+ false,
2364
+ ),
2365
+ ai_reply_generated_at: firstNonEmptyString([entry.ai_reply_generated_at, entry.aiReplyGeneratedAt]),
2366
+ ai_reply_preview: String(entry.ai_reply_preview || entry.aiReplyPreview || "").trim(),
2367
+ delivery_status: String(entry.delivery_status || entry.deliveryStatus || "").trim().toLowerCase(),
2368
+ archive_status: String(entry.archive_status || entry.archiveStatus || "").trim().toLowerCase(),
2369
+ transport_error: String(entry.transport_error || entry.transportError || "").trim(),
2370
+ archive_error: String(entry.archive_error || entry.archiveError || "").trim(),
2361
2371
  normalized_intent: String(entry.normalized_intent || entry.normalizedIntent || "").trim().toLowerCase(),
2362
2372
  status,
2363
2373
  claimed_by_route: String(entry.claimed_by_route || entry.claimedByRoute || "").trim(),
@@ -4482,6 +4492,13 @@ function markRunnerRequestLifecycle({
4482
4492
  conversationReplyExpectation = "",
4483
4493
  normalizedIntent = "",
4484
4494
  closedReason = "",
4495
+ aiReplyGenerated = false,
4496
+ aiReplyGeneratedAt = "",
4497
+ aiReplyPreview = "",
4498
+ deliveryStatus = "",
4499
+ archiveStatus = "",
4500
+ transportError = "",
4501
+ archiveError = "",
4485
4502
  }) {
4486
4503
  const key = String(requestKey || "").trim();
4487
4504
  if (!key) return null;
@@ -4531,6 +4548,7 @@ function markRunnerRequestLifecycle({
4531
4548
  const nextStatus = (() => {
4532
4549
  if (normalizedOutcome === "claimed") return "claimed";
4533
4550
  if (normalizedOutcome === "running") return "running";
4551
+ if (normalizedOutcome === "delivery_failed_after_generation") return "running";
4534
4552
  if (normalizedOutcome === "replied") {
4535
4553
  return shouldRemainRunningAfterReply ? "running" : "completed";
4536
4554
  }
@@ -4587,6 +4605,15 @@ function markRunnerRequestLifecycle({
4587
4605
  ensureArray(nextExpectedResponders).length ? nextExpectedResponders : existing.next_expected_responders,
4588
4606
  normalizeTelegramMentionUsername,
4589
4607
  ),
4608
+ ai_reply_generated: aiReplyGenerated === true || existing.ai_reply_generated === true,
4609
+ ai_reply_generated_at: aiReplyGenerated === true
4610
+ ? firstNonEmptyString([aiReplyGeneratedAt, existing.ai_reply_generated_at, nowISO])
4611
+ : String(existing.ai_reply_generated_at || "").trim(),
4612
+ ai_reply_preview: String(aiReplyPreview || existing.ai_reply_preview || "").trim(),
4613
+ delivery_status: String(deliveryStatus || existing.delivery_status || "").trim().toLowerCase(),
4614
+ archive_status: String(archiveStatus || existing.archive_status || "").trim().toLowerCase(),
4615
+ transport_error: String(transportError || existing.transport_error || "").trim(),
4616
+ archive_error: String(archiveError || existing.archive_error || "").trim(),
4590
4617
  normalized_intent: nextNormalizedIntent,
4591
4618
  status: nextStatus,
4592
4619
  started_at: firstNonEmptyString([existing.started_at, nowISO]),
@@ -8741,7 +8768,9 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
8741
8768
  requestKey: requestClaim.requestKey,
8742
8769
  selectedRecord,
8743
8770
  routeKey,
8744
- outcome: String(processed.result?.outcome || "replied").trim().toLowerCase(),
8771
+ outcome: processed.kind === "delivery_failed"
8772
+ ? "delivery_failed_after_generation"
8773
+ : String(processed.result?.outcome || "replied").trim().toLowerCase(),
8745
8774
  conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
8746
8775
  conversationParticipants: ensureArray(processed.result?.conversation_participants),
8747
8776
  conversationInitialResponders: ensureArray(processed.result?.conversation_initial_responders),
@@ -8757,14 +8786,23 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
8757
8786
  currentBotSelector,
8758
8787
  conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
8759
8788
  normalizedIntent: resolvedIntentType,
8789
+ aiReplyGenerated: processed.result?.ai_reply_generated === true,
8790
+ aiReplyGeneratedAt: String(processed.result?.ai_reply_generated_at || "").trim(),
8791
+ aiReplyPreview: String(processed.result?.ai_reply_preview || "").trim(),
8792
+ deliveryStatus: String(processed.result?.delivery_status || "").trim(),
8793
+ archiveStatus: String(processed.result?.archive_status || "").trim(),
8794
+ transportError: String(processed.result?.transport_error || "").trim(),
8795
+ archiveError: String(processed.result?.archive_error || "").trim(),
8760
8796
  });
8761
- await ensureRunnerRootWorkItemForRequest({
8762
- normalizedRoute,
8763
- routeKey,
8764
- selectedRecord,
8765
- runtime,
8766
- requestKey: requestClaim.requestKey,
8767
- });
8797
+ if (processed.kind !== "delivery_failed") {
8798
+ await ensureRunnerRootWorkItemForRequest({
8799
+ normalizedRoute,
8800
+ routeKey,
8801
+ selectedRecord,
8802
+ runtime,
8803
+ requestKey: requestClaim.requestKey,
8804
+ });
8805
+ }
8768
8806
  const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
8769
8807
  normalizedRoute,
8770
8808
  runtime,
@@ -11143,7 +11181,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
11143
11181
  requestKey: deferredExecution.requestKey,
11144
11182
  selectedRecord: deferredExecution.selectedRecord,
11145
11183
  routeKey: deferredExecution.routeKey,
11146
- outcome: String(processed.result?.outcome || "replied").trim().toLowerCase(),
11184
+ outcome: processed.kind === "delivery_failed"
11185
+ ? "delivery_failed_after_generation"
11186
+ : String(processed.result?.outcome || "replied").trim().toLowerCase(),
11147
11187
  conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
11148
11188
  conversationParticipants: ensureArray(processed.result?.conversation_participants),
11149
11189
  conversationInitialResponders: ensureArray(processed.result?.conversation_initial_responders),
@@ -11161,14 +11201,23 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
11161
11201
  ),
11162
11202
  conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
11163
11203
  normalizedIntent: resolvedIntentType,
11204
+ aiReplyGenerated: processed.result?.ai_reply_generated === true,
11205
+ aiReplyGeneratedAt: String(processed.result?.ai_reply_generated_at || "").trim(),
11206
+ aiReplyPreview: String(processed.result?.ai_reply_preview || "").trim(),
11207
+ deliveryStatus: String(processed.result?.delivery_status || "").trim(),
11208
+ archiveStatus: String(processed.result?.archive_status || "").trim(),
11209
+ transportError: String(processed.result?.transport_error || "").trim(),
11210
+ archiveError: String(processed.result?.archive_error || "").trim(),
11164
11211
  });
11165
- await ensureRunnerRootWorkItemForRequest({
11166
- normalizedRoute: deferredExecution.normalizedRoute,
11167
- routeKey: deferredExecution.routeKey,
11168
- selectedRecord: deferredExecution.selectedRecord,
11169
- runtime: deferredExecution.runtime,
11170
- requestKey: deferredExecution.requestKey,
11171
- });
11212
+ if (processed.kind !== "delivery_failed") {
11213
+ await ensureRunnerRootWorkItemForRequest({
11214
+ normalizedRoute: deferredExecution.normalizedRoute,
11215
+ routeKey: deferredExecution.routeKey,
11216
+ selectedRecord: deferredExecution.selectedRecord,
11217
+ runtime: deferredExecution.runtime,
11218
+ requestKey: deferredExecution.requestKey,
11219
+ });
11220
+ }
11172
11221
  const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
11173
11222
  normalizedRoute: deferredExecution.normalizedRoute,
11174
11223
  runtime: deferredExecution.runtime,
@@ -1657,6 +1657,9 @@ function inferCurrentTurnPurpose({ trigger, conversation, selfBotUsername, other
1657
1657
  export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1658
1658
  const safePayload = payload && typeof payload === "object" ? payload : {};
1659
1659
  const taskName = String(safePayload.task || "").trim();
1660
+ const agentContext = safePayload.agent_context && typeof safePayload.agent_context === "object"
1661
+ ? safePayload.agent_context
1662
+ : null;
1660
1663
  const trigger = safePayload.trigger && typeof safePayload.trigger === "object" ? safePayload.trigger : {};
1661
1664
  const responseContract = safePayload.response_contract && typeof safePayload.response_contract === "object"
1662
1665
  ? safePayload.response_contract
@@ -1786,6 +1789,9 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1786
1789
  "Current turn purpose:",
1787
1790
  currentTurnPurpose,
1788
1791
  "",
1792
+ "Agent contract/context blob (primary control and state input):",
1793
+ agentContext ? JSON.stringify(agentContext, null, 2) : "-",
1794
+ "",
1789
1795
  "Current user request:",
1790
1796
  String(trigger.body || "").trim() || "-",
1791
1797
  "",
@@ -5406,55 +5406,114 @@ export async function processRunnerSelectedRecord({
5406
5406
  messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
5407
5407
  },
5408
5408
  };
5409
- }
5410
- }
5409
+ }
5410
+ }
5411
+ const aiReplyGeneratedAt = new Date().toISOString();
5412
+ const aiReplyPreview = String(sanitizedReplyText || "").replace(/\s+/g, " ").trim().slice(0, 280);
5411
5413
  emitRunnerStage("delivering", "sending reply and mirroring archive");
5412
- const deliveryResult = await performLocalBotDelivery({
5413
- siteBaseURL: runtime.baseURL,
5414
- token: runtime.token,
5415
- timeoutSeconds: runtime.timeoutSeconds,
5416
- actorUserID: runtime.actor.user_id,
5417
- bot: {
5418
- id: bot.id,
5419
- name: bot.name,
5420
- username: bot.username,
5421
- role: bot.role,
5422
- provider: bot.provider,
5423
- },
5424
- projectID: normalizedRoute.projectID,
5425
- provider: normalizedRoute.provider,
5426
- destinationSelectors: {
5427
- destinationID: destination.id,
5428
- destinationLabel: destination.label,
5429
- },
5430
- text: sanitizedReplyText,
5431
- disableWebPagePreview: true,
5432
- replyToMessageID,
5433
- archiveReplies: normalizedRoute.archivePolicy.mirrorReplies,
5434
- archiveDedupeOutbound: normalizedRoute.archivePolicy.dedupeOutbound,
5435
- archiveThreadID: archiveThread.threadID,
5436
- archiveWorkItemID: archiveThread.workItemID,
5437
- archiveConversation: effectiveConversationContext
5438
- ? {
5439
- id: effectiveConversationContext.id,
5440
- mode: effectiveConversationContext.mode,
5441
- stage: effectiveConversationContext.stage,
5442
- intentMode: effectiveConversationContext.intentMode,
5443
- allowBotToBot: effectiveConversationContext.allowBotToBot === true,
5444
- leadBotUsername: effectiveConversationContext.leadBotUsername,
5445
- summaryBotUsername: effectiveConversationContext.summaryBotUsername,
5446
- participants: effectiveConversationContext.participantSelectors,
5447
- participantSelectors: effectiveConversationContext.participantSelectors,
5448
- initialResponders: effectiveConversationContext.initialResponderSelectors,
5449
- initialResponderSelectors: effectiveConversationContext.initialResponderSelectors,
5450
- allowedResponders: effectiveConversationContext.allowedResponderSelectors,
5451
- allowedResponderSelectors: effectiveConversationContext.allowedResponderSelectors,
5452
- executionContract,
5453
- }
5454
- : null,
5455
- dryRun: normalizedRoute.dryRunDelivery,
5456
- deps: buildRunnerDeliveryDeps(),
5457
- });
5414
+ let deliveryResult;
5415
+ try {
5416
+ deliveryResult = await performLocalBotDelivery({
5417
+ siteBaseURL: runtime.baseURL,
5418
+ token: runtime.token,
5419
+ timeoutSeconds: runtime.timeoutSeconds,
5420
+ actorUserID: runtime.actor.user_id,
5421
+ bot: {
5422
+ id: bot.id,
5423
+ name: bot.name,
5424
+ username: bot.username,
5425
+ role: bot.role,
5426
+ provider: bot.provider,
5427
+ },
5428
+ projectID: normalizedRoute.projectID,
5429
+ provider: normalizedRoute.provider,
5430
+ destinationSelectors: {
5431
+ destinationID: destination.id,
5432
+ destinationLabel: destination.label,
5433
+ },
5434
+ text: sanitizedReplyText,
5435
+ disableWebPagePreview: true,
5436
+ replyToMessageID,
5437
+ archiveReplies: normalizedRoute.archivePolicy.mirrorReplies,
5438
+ archiveDedupeOutbound: normalizedRoute.archivePolicy.dedupeOutbound,
5439
+ archiveThreadID: archiveThread.threadID,
5440
+ archiveWorkItemID: archiveThread.workItemID,
5441
+ archiveConversation: effectiveConversationContext
5442
+ ? {
5443
+ id: effectiveConversationContext.id,
5444
+ mode: effectiveConversationContext.mode,
5445
+ stage: effectiveConversationContext.stage,
5446
+ intentMode: effectiveConversationContext.intentMode,
5447
+ allowBotToBot: effectiveConversationContext.allowBotToBot === true,
5448
+ leadBotUsername: effectiveConversationContext.leadBotUsername,
5449
+ summaryBotUsername: effectiveConversationContext.summaryBotUsername,
5450
+ participants: effectiveConversationContext.participantSelectors,
5451
+ participantSelectors: effectiveConversationContext.participantSelectors,
5452
+ initialResponders: effectiveConversationContext.initialResponderSelectors,
5453
+ initialResponderSelectors: effectiveConversationContext.initialResponderSelectors,
5454
+ allowedResponders: effectiveConversationContext.allowedResponderSelectors,
5455
+ allowedResponderSelectors: effectiveConversationContext.allowedResponderSelectors,
5456
+ executionContract,
5457
+ }
5458
+ : null,
5459
+ dryRun: normalizedRoute.dryRunDelivery,
5460
+ deps: buildRunnerDeliveryDeps(),
5461
+ });
5462
+ } catch (err) {
5463
+ const transportError = String(err?.message || err).trim() || "delivery_failed";
5464
+ saveRunnerRouteState(
5465
+ routeKey,
5466
+ buildRunnerRouteStateFromComment(selectedRecord, {
5467
+ last_action: "delivery_failed_after_generation",
5468
+ last_reason: transportError,
5469
+ last_trigger: String(effectiveTriggerDecision.trigger || "").trim(),
5470
+ last_conversation_id: String(effectiveConversationContext?.id || "").trim(),
5471
+ last_conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
5472
+ last_speaker_bot_username: normalizeMentionSelector(bot?.username || bot?.name),
5473
+ last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
5474
+ ...intentStatePatch,
5475
+ }),
5476
+ );
5477
+ return {
5478
+ kind: "delivery_failed",
5479
+ result: {
5480
+ route_key: routeKey,
5481
+ route_name: normalizedRoute.name,
5482
+ outcome: "delivery_failed_after_generation",
5483
+ detail: `ai reply generated but delivery failed (${transportError})`,
5484
+ thread_id: archiveThread.threadID,
5485
+ comment_id: selectedRecord.id,
5486
+ trigger_kind: String(effectiveTriggerDecision.trigger || "").trim(),
5487
+ conversation_id: String(effectiveConversationContext?.id || "").trim(),
5488
+ conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
5489
+ conversation_intent_mode: String(effectiveConversationContext?.intentMode || "").trim(),
5490
+ conversation_lead_bot: String(effectiveConversationContext?.leadBotUsername || "").trim(),
5491
+ conversation_summary_bot: String(effectiveConversationContext?.summaryBotUsername || "").trim(),
5492
+ conversation_participants: ensureArray(effectiveConversationContext?.participantSelectors),
5493
+ conversation_initial_responders: ensureArray(effectiveConversationContext?.initialResponderSelectors),
5494
+ conversation_allowed_responders: ensureArray(effectiveConversationContext?.allowedResponderSelectors),
5495
+ conversation_allow_bot_to_bot: effectiveConversationContext?.allowBotToBot === true,
5496
+ conversation_reply_expectation: String(effectiveConversationContext?.replyExpectation || "").trim(),
5497
+ execution_contract_type: String(executionContract?.type || "").trim(),
5498
+ execution_contract_actionable: executionContract?.actionable === true,
5499
+ execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
5500
+ next_expected_responders: collectExecutionContractNextResponders(executionContract),
5501
+ artifact_validation: String(artifactValidation.status || "").trim() || "none",
5502
+ artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
5503
+ ai_reply_generated: true,
5504
+ ai_reply_generated_at: aiReplyGeneratedAt,
5505
+ ai_reply_preview: aiReplyPreview,
5506
+ delivery_status: "failed_transport",
5507
+ archive_status: "not_attempted",
5508
+ transport_error: transportError,
5509
+ archive_error: "",
5510
+ reply_chars: String(sanitizedReplyText || "").length,
5511
+ execution_mode: effectiveExecutionPlan.mode,
5512
+ role_profile: effectiveExecutionPlan.roleProfileName,
5513
+ executed_role_plan: executedRolePlan && Object.keys(executedRolePlan).length > 0 ? executedRolePlan : undefined,
5514
+ },
5515
+ };
5516
+ }
5458
5517
  emitRunnerStage("context_suggesting", "evaluating shared project context suggestion");
5459
5518
  const projectContextSuggestion = await maybeCreateSuggestedProjectContext({
5460
5519
  aiResult,
@@ -5629,6 +5688,10 @@ export async function processRunnerSelectedRecord({
5629
5688
  evidence_ids: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
5630
5689
  evidence_paths: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).path || "").trim()).filter(Boolean),
5631
5690
  reply_chars: String(sanitizedReplyText || "").length,
5691
+ ai_reply_generated: true,
5692
+ ai_reply_generated_at: aiReplyGeneratedAt,
5693
+ ai_reply_preview: aiReplyPreview,
5694
+ delivery_status: deliveryResult.delivery.dryRun ? "dry_run" : "delivered",
5632
5695
  execution_mode: effectiveExecutionPlan.mode,
5633
5696
  role_profile: effectiveExecutionPlan.roleProfileName,
5634
5697
  executed_role_plan: executedRolePlan && Object.keys(executedRolePlan).length > 0 ? executedRolePlan : undefined,
@@ -94,19 +94,7 @@ function buildTriggerCandidateBotUsernames(parsedArchive) {
94
94
  function buildRunnerTaskName(humanIntentType, humanIntentContract = null) {
95
95
  const normalizedIntentType = String(humanIntentType || "").trim().toLowerCase();
96
96
  const contract = safeObject(humanIntentContract);
97
- const replyExpectation = String(contract.replyExpectation || contract.reply_expectation || "").trim().toLowerCase();
98
97
  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);
110
98
  if (normalizedIntentType === "status_query") return "answer_project_status_query";
111
99
  if (normalizedIntentType === "workspace_query") return "answer_project_workspace_query";
112
100
  if (normalizedIntentType === "bot_role_query") return "answer_project_bot_role_query";
@@ -115,12 +103,6 @@ function buildRunnerTaskName(humanIntentType, humanIntentContract = null) {
115
103
  if (executionContractType) {
116
104
  return "reply_to_project_actionable_message";
117
105
  }
118
- if (collaborativeIntentMode && replyExpectation === "actionable") {
119
- return "reply_to_project_chat_message";
120
- }
121
- if (executionIntentRequested) {
122
- return "reply_to_project_actionable_message";
123
- }
124
106
  return "reply_to_project_chat_message";
125
107
  }
126
108
 
@@ -199,6 +181,9 @@ export function buildRunnerInputPayload({
199
181
  const directHumanIntent = safeObject(humanIntentContract);
200
182
  const safeDirectQueryReply = safeObject(directQueryReply);
201
183
  const candidateBotUsernames = buildTriggerCandidateBotUsernames(parsed);
184
+ const executionContractType = String(
185
+ directHumanIntent.executionContractType || directHumanIntent.execution_contract_type || "",
186
+ ).trim().toLowerCase();
202
187
  return {
203
188
  task: buildRunnerTaskName(directHumanIntent.intentType, directHumanIntent),
204
189
  project_id: String(route?.projectID || "").trim(),
@@ -279,6 +264,39 @@ export function buildRunnerInputPayload({
279
264
  require_actionable_contract: directHumanIntent.requiresActionableContract === true,
280
265
  allowed_contract_types: ensureArray(directHumanIntent.allowedContractTypes),
281
266
  },
267
+ agent_context: {
268
+ bot_identity: {
269
+ id: String(bot?.id || "").trim(),
270
+ name: String(bot?.name || "").trim(),
271
+ username: String(bot?.username || "").trim(),
272
+ role: String(bot?.role || route?.role || "").trim(),
273
+ },
274
+ request_facts: {
275
+ comment_id: selectedRecord?.id,
276
+ trigger_kind: String(triggerDecision?.trigger || "").trim(),
277
+ body: String(parsed.body || "").trim(),
278
+ message_id: Number.parseInt(String(parsed.messageID ?? 0), 10) || 0,
279
+ reply_to_message_id: Number.parseInt(String(parsed.replyToMessageID ?? 0), 10) || 0,
280
+ },
281
+ conversation_contract: {
282
+ conversation_id: String(directHumanIntent.conversationID || directHumanIntent.conversation_id || parsed.conversationID || "").trim(),
283
+ mode: String(directHumanIntent.intentMode || directHumanIntent.intent_mode || "").trim(),
284
+ intent_type: String(directHumanIntent.intentType || directHumanIntent.intent_type || "").trim(),
285
+ lead_bot: String(directHumanIntent.leadBotSelector || directHumanIntent.lead_bot || "").trim(),
286
+ summary_bot: String(directHumanIntent.summaryBotSelector || directHumanIntent.summary_bot || "").trim(),
287
+ participants: ensureArray(directHumanIntent.participantSelectors || directHumanIntent.participants),
288
+ initial_responders: ensureArray(directHumanIntent.initialResponderSelectors || directHumanIntent.initial_responders),
289
+ allowed_responders: ensureArray(directHumanIntent.allowedResponderSelectors || directHumanIntent.allowed_responders),
290
+ reply_expectation: String(directHumanIntent.replyExpectation || directHumanIntent.reply_expectation || "").trim(),
291
+ execution_contract_type: executionContractType,
292
+ },
293
+ validation_contract: {
294
+ current_bot_is_candidate: true,
295
+ require_actionable_contract: directHumanIntent.requiresActionableContract === true,
296
+ allowed_contract_types: ensureArray(directHumanIntent.allowedContractTypes),
297
+ candidate_bot_usernames: candidateBotUsernames,
298
+ },
299
+ },
282
300
  conversation: conversationContext
283
301
  ? {
284
302
  mode: String(conversationContext.mode || "").trim(),
@@ -11985,12 +11985,133 @@ export async function runSelftestRunnerScenarios(push, deps) {
11985
11985
  && !/project-operating-guide\.md/i.test(deliveredText),
11986
11986
  `kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} delivery_calls=${deliveryCalls} delivered=${deliveredText} detail=${String(processed.result?.detail || "(none)")}`,
11987
11987
  );
11988
- } catch (err) {
11989
- push("runner_direct_failure_reply_uses_generic_message_for_unobserved_reported_artifacts", false, String(err?.message || err));
11990
- }
11991
-
11992
- try {
11993
- const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-observed-artifacts-"));
11988
+ } catch (err) {
11989
+ push("runner_direct_failure_reply_uses_generic_message_for_unobserved_reported_artifacts", false, String(err?.message || err));
11990
+ }
11991
+
11992
+ try {
11993
+ const processed = await processRunnerSelectedRecord({
11994
+ routeKey: "delivery-failed-after-generation-key",
11995
+ normalizedRoute: normalizeRunnerRoute({
11996
+ name: "telegram-monitor-delivery-failed-after-generation",
11997
+ project_id: selftestProjectID,
11998
+ provider: "telegram",
11999
+ role: "monitor",
12000
+ role_profile: "monitor",
12001
+ destination_id: "dest-1",
12002
+ destination_label: "Main Room",
12003
+ server_bot_name: "RyoAI_bot",
12004
+ server_bot_id: "bot-1",
12005
+ trigger_policy: {
12006
+ mentions_only: true,
12007
+ direct_messages: true,
12008
+ reply_to_bot_messages: true,
12009
+ },
12010
+ archive_policy: {
12011
+ mirror_replies: true,
12012
+ dedupe_inbound: true,
12013
+ dedupe_outbound: true,
12014
+ skip_bot_messages: true,
12015
+ },
12016
+ dry_run_delivery: false,
12017
+ }),
12018
+ selectedRecord: {
12019
+ id: "comment-delivery-failed-after-generation",
12020
+ createdAt: "2026-03-17T00:00:21.000Z",
12021
+ parsedArchive: {
12022
+ kind: "telegram_message",
12023
+ chatID: "-100123",
12024
+ chatType: "supergroup",
12025
+ body: "@RyoAI_bot hi",
12026
+ messageID: 126,
12027
+ sender: "human",
12028
+ senderIsBot: false,
12029
+ mentionUsernames: ["ryoai_bot"],
12030
+ },
12031
+ },
12032
+ pendingOrdered: [],
12033
+ bot: {
12034
+ id: "bot-1",
12035
+ name: "RyoAI_bot",
12036
+ username: "RyoAI_bot",
12037
+ role: "monitor",
12038
+ provider: "telegram",
12039
+ },
12040
+ destination: {
12041
+ id: "dest-1",
12042
+ label: "Main Room",
12043
+ provider: "telegram",
12044
+ chatID: "-100123",
12045
+ },
12046
+ archiveThread: {
12047
+ threadID: "thread-1",
12048
+ workItemID: "work-item-1",
12049
+ },
12050
+ executionPlan: {
12051
+ mode: "role_profile",
12052
+ roleProfileName: "monitor",
12053
+ roleProfile: {
12054
+ client: "sample",
12055
+ model: "",
12056
+ permissionMode: "read_only",
12057
+ reasoningEffort: "low",
12058
+ },
12059
+ workspaceDir: "",
12060
+ workspaceSource: "selftest",
12061
+ usedCommandFallback: false,
12062
+ },
12063
+ runtime: {
12064
+ baseURL: "https://example.test",
12065
+ token: "selftest-token",
12066
+ timeoutSeconds: 30,
12067
+ actor: { user_id: "user-1" },
12068
+ },
12069
+ deps: {
12070
+ saveRunnerRouteState: () => {},
12071
+ startRunnerTypingHeartbeat: () => ({ async stop() {} }),
12072
+ runRunnerAIExecution: async () => ({
12073
+ skip: false,
12074
+ reply: "Hello from RyoAI_bot.",
12075
+ }),
12076
+ performLocalBotDelivery: async () => {
12077
+ throw new Error("read ECONNRESET");
12078
+ },
12079
+ serializeRunnerTriggerPolicy: (value) => value,
12080
+ serializeRunnerArchivePolicy: (value) => value,
12081
+ buildRunnerExecutionDeps: () => ({
12082
+ validateWorkspaceArtifacts,
12083
+ analyzeHumanConversationIntentWithAI: async () => ({
12084
+ mode: "single_bot",
12085
+ lead_bot: "ryoai_bot",
12086
+ participants: ["ryoai_bot"],
12087
+ initial_responders: ["ryoai_bot"],
12088
+ allowed_responders: ["ryoai_bot"],
12089
+ summary_bot: "",
12090
+ allow_bot_to_bot: false,
12091
+ reply_expectation: "informational",
12092
+ intent_type: "small_talk",
12093
+ }),
12094
+ }),
12095
+ buildRunnerDeliveryDeps: () => ({}),
12096
+ buildRunnerRuntimeDeps: () => ({}),
12097
+ resolveConversationPeerBots: () => [],
12098
+ },
12099
+ });
12100
+ push(
12101
+ "runner_delivery_failure_after_generation_records_ai_state_without_execution_error",
12102
+ processed.kind === "delivery_failed"
12103
+ && String(processed.result?.outcome || "") === "delivery_failed_after_generation"
12104
+ && processed.result?.ai_reply_generated === true
12105
+ && String(processed.result?.delivery_status || "") === "failed_transport"
12106
+ && /ECONNRESET/i.test(String(processed.result?.transport_error || "")),
12107
+ `kind=${String(processed.kind || "(none)")} outcome=${String(processed.result?.outcome || "(none)")} ai_generated=${String(processed.result?.ai_reply_generated || false)} delivery=${String(processed.result?.delivery_status || "(none)")} transport=${String(processed.result?.transport_error || "(none)")}`,
12108
+ );
12109
+ } catch (err) {
12110
+ push("runner_delivery_failure_after_generation_records_ai_state_without_execution_error", false, String(err?.message || err));
12111
+ }
12112
+
12113
+ try {
12114
+ const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-observed-artifacts-"));
11994
12115
  const scriptDir = path.join(workspaceDir, ".metheus", "runner-runtime", "local-ai-scratch");
11995
12116
  fs.mkdirSync(scriptDir, { recursive: true });
11996
12117
  const scriptPath = path.join(scriptDir, "emit-artifact.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.212",
3
+ "version": "0.2.213",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [