metheus-governance-mcp-cli 0.2.285 → 0.2.286

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
@@ -3562,7 +3562,9 @@ function buildRunnerRequestKey({
3562
3562
  const intent = canonicalHumanKey
3563
3563
  ? "-"
3564
3564
  : String(normalizedIntent || "").trim().toLowerCase() || "-";
3565
- const resolvedConversationID = String(conversationID || parsed.conversationID || "").trim() || "-";
3565
+ const resolvedConversationID = canonicalHumanKey
3566
+ ? "-"
3567
+ : String(conversationID || parsed.conversationID || "").trim() || "-";
3566
3568
  return [
3567
3569
  String(normalizedRoute?.projectID || "").trim() || "-",
3568
3570
  String(normalizedRoute?.provider || "").trim() || "-",
@@ -3994,10 +3996,10 @@ function runnerRequestPreferredNextExpectedResponders(entryRaw) {
3994
3996
  );
3995
3997
  }
3996
3998
 
3997
- function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
3998
- const entry = safeObject(entryRaw);
3999
- const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
4000
- return uniqueOrderedStrings(
3999
+ function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
4000
+ const entry = safeObject(entryRaw);
4001
+ const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
4002
+ return uniqueOrderedStrings(
4001
4003
  runnerRequestPreferredNextExpectedResponders(entry).length
4002
4004
  ? runnerRequestPreferredNextExpectedResponders(entry)
4003
4005
  : runnerRequestPreferredExecutionContractTargets(entry).length
@@ -4025,6 +4027,35 @@ function extractRunnerExplicitSelectedBotUsernamesFromParsed(parsedArchiveRaw) {
4025
4027
  normalizeTelegramMentionUsername,
4026
4028
  );
4027
4029
  }
4030
+
4031
+ function runnerRequestAllowsSharedHumanClaimTakeover({
4032
+ entryRaw,
4033
+ currentBotUsername = "",
4034
+ canonicalHumanMessageKey = "",
4035
+ }) {
4036
+ const entry = safeObject(entryRaw);
4037
+ const normalizedCurrentBotUsername = normalizeTelegramMentionUsername(currentBotUsername);
4038
+ const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
4039
+ if (!normalizedCurrentBotUsername || !normalizedCanonicalHumanMessageKey) {
4040
+ return false;
4041
+ }
4042
+ if (String(entry.canonical_human_message_key || "").trim() !== normalizedCanonicalHumanMessageKey) {
4043
+ return false;
4044
+ }
4045
+ const pendingResponders = runnerRequestPreferredNextExpectedResponders(entry);
4046
+ if (pendingResponders.length > 0) {
4047
+ return pendingResponders.includes(normalizedCurrentBotUsername);
4048
+ }
4049
+ const directHumanResponders = uniqueOrderedStrings(
4050
+ [
4051
+ ...ensureArray(entry.selected_bot_usernames),
4052
+ ...ensureArray(entry.conversation_initial_responders),
4053
+ ...ensureArray(entry.conversation_allowed_responders),
4054
+ ],
4055
+ normalizeTelegramMentionUsername,
4056
+ );
4057
+ return directHumanResponders.includes(normalizedCurrentBotUsername);
4058
+ }
4028
4059
 
4029
4060
  function runnerRequestPreferredAIReplyPreview(entryRaw) {
4030
4061
  const entry = safeObject(entryRaw);
@@ -5067,8 +5098,8 @@ async function claimRunnerRequestForHumanComment({
5067
5098
  reason: "non_human_comment_cannot_create_request",
5068
5099
  };
5069
5100
  }
5070
- const currentState = loadBotRunnerState();
5071
- const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
5101
+ const currentState = loadBotRunnerState();
5102
+ const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
5072
5103
  const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
5073
5104
  state: currentState,
5074
5105
  normalizedRoute,
@@ -5168,6 +5199,16 @@ async function claimRunnerRequestForHumanComment({
5168
5199
  const authoritativeDecisionBundle = decisionBundleValidation.ok === true
5169
5200
  ? safeObject(decisionBundleValidation.bundle)
5170
5201
  : {};
5202
+ const normalizedAuthoritativeSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(
5203
+ authoritativeSourceMessageEnvelope,
5204
+ );
5205
+ const currentClaimBotUsername = normalizeTelegramMentionUsername(
5206
+ normalizedAuthoritativeSourceMessageEnvelope.source_bot_username
5207
+ || normalizedAuthoritativeSourceMessageEnvelope.sender_username
5208
+ || currentRouteState.last_source_bot_username
5209
+ || currentRouteState.source_message_bot_username
5210
+ || currentRouteState.last_speaker_bot_username,
5211
+ );
5171
5212
  const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
5172
5213
  selectedRecord,
5173
5214
  replyChainContext,
@@ -5200,24 +5241,35 @@ async function claimRunnerRequestForHumanComment({
5200
5241
  requestKey,
5201
5242
  };
5202
5243
  }
5203
- if (
5204
- isActiveRunnerRequestStatus(existing.status)
5205
- && String(existing.claimed_by_route || "").trim()
5206
- && String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
5207
- ) {
5208
- return {
5209
- ok: false,
5210
- reason: "request_already_claimed",
5211
- requestKey,
5212
- };
5213
- }
5214
- const nowISO = new Date().toISOString();
5215
- const normalizedAuthoritativeSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(
5216
- authoritativeSourceMessageEnvelope,
5244
+ if (
5245
+ isActiveRunnerRequestStatus(existing.status)
5246
+ && String(existing.claimed_by_route || "").trim()
5247
+ && String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
5248
+ && !runnerRequestAllowsSharedHumanClaimTakeover({
5249
+ entryRaw: existing,
5250
+ currentBotUsername: currentClaimBotUsername,
5251
+ canonicalHumanMessageKey,
5252
+ })
5253
+ ) {
5254
+ return {
5255
+ ok: false,
5256
+ reason: "request_already_claimed",
5257
+ requestKey,
5258
+ };
5259
+ }
5260
+ const nowISO = new Date().toISOString();
5261
+ const existingSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
5262
+ const preserveExistingCanonicalSourceEnvelope = Boolean(
5263
+ canonicalHumanMessageKey
5264
+ && String(existing.canonical_human_message_key || "").trim() === canonicalHumanMessageKey
5265
+ && intFromRawAllowZero(existing.source_message_id, 0) === currentMessageID
5266
+ && Object.keys(existingSourceMessageEnvelope).length > 0
5217
5267
  );
5218
- const sourceMessageEnvelope = Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
5219
- ? normalizedAuthoritativeSourceMessageEnvelope
5220
- : normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
5268
+ const sourceMessageEnvelope = preserveExistingCanonicalSourceEnvelope
5269
+ ? existingSourceMessageEnvelope
5270
+ : Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
5271
+ ? normalizedAuthoritativeSourceMessageEnvelope
5272
+ : existingSourceMessageEnvelope;
5221
5273
  const decisionConversationParticipants = uniqueOrderedStrings(
5222
5274
  ensureArray(authoritativeDecisionBundle.participants),
5223
5275
  normalizeTelegramMentionUsername,
@@ -46,6 +46,58 @@ function uniqueOrderedStrings(values, normalizer = (value) => String(value || ""
46
46
  return output;
47
47
  }
48
48
 
49
+ function buildRunnerRequestRecoveryAuthoritativeDecisionBundle(requestRaw) {
50
+ return safeObject(safeObject(requestRaw).authoritative_decision_bundle);
51
+ }
52
+
53
+ function buildRunnerRequestRecoveryPreferredExecutionContractTargets(requestRaw) {
54
+ const request = safeObject(requestRaw);
55
+ const decisionBundle = buildRunnerRequestRecoveryAuthoritativeDecisionBundle(request);
56
+ return uniqueOrderedStrings(
57
+ ensureArray(decisionBundle.execution_contract_targets).length
58
+ ? decisionBundle.execution_contract_targets
59
+ : ensureArray(request.execution_contract_targets).length
60
+ ? request.execution_contract_targets
61
+ : ensureArray(request.root_execution_contract_targets),
62
+ normalizeTelegramMentionUsername,
63
+ );
64
+ }
65
+
66
+ function buildRunnerRequestRecoveryPreferredNextExpectedResponders(requestRaw) {
67
+ const request = safeObject(requestRaw);
68
+ const decisionBundle = buildRunnerRequestRecoveryAuthoritativeDecisionBundle(request);
69
+ return uniqueOrderedStrings(
70
+ ensureArray(decisionBundle.next_expected_responders).length
71
+ ? decisionBundle.next_expected_responders
72
+ : ensureArray(request.next_expected_responders).length
73
+ ? request.next_expected_responders
74
+ : ensureArray(request.root_next_expected_responders),
75
+ normalizeTelegramMentionUsername,
76
+ );
77
+ }
78
+
79
+ function runnerRequestShouldRemainRunningDuringRecovery(requestRaw) {
80
+ const request = safeObject(requestRaw);
81
+ const decisionBundle = buildRunnerRequestRecoveryAuthoritativeDecisionBundle(request);
82
+ const preferredExecutionContractType = String(
83
+ decisionBundle.execution_contract_type
84
+ || request.execution_contract_type
85
+ || request.root_execution_contract_type
86
+ || "",
87
+ ).trim().toLowerCase();
88
+ const preferredExecutionContractTargets = buildRunnerRequestRecoveryPreferredExecutionContractTargets(request);
89
+ const preferredNextExpectedResponders = buildRunnerRequestRecoveryPreferredNextExpectedResponders(request);
90
+ if (decisionBundle.should_close_after_reply === true) {
91
+ return false;
92
+ }
93
+ if (decisionBundle.should_close_after_reply === false) {
94
+ return true;
95
+ }
96
+ return preferredExecutionContractType === "delegation"
97
+ || preferredExecutionContractTargets.length > 0
98
+ || preferredNextExpectedResponders.length > 0;
99
+ }
100
+
49
101
  function normalizeRunnerRequestStatus(rawStatus) {
50
102
  const status = String(rawStatus || "").trim().toLowerCase();
51
103
  return [
@@ -144,25 +196,39 @@ export function buildRunnerRequestRecoveryPatchFromRouteState(currentStateRaw, r
144
196
  const requestStatus = normalizeRunnerRequestStatus(request.status);
145
197
  if (!isFinalRunnerRequestStatus(requestStatus)) {
146
198
  const routeAction = String(routeState.last_action || "").trim().toLowerCase();
199
+ const shouldRemainRunning = runnerRequestShouldRemainRunningDuringRecovery(request);
147
200
  if (routeAction === "replied") {
148
- patch.status = "completed";
149
- patch.completed_at = firstNonEmptyString([
150
- request.completed_at,
151
- request.updated_at,
152
- new Date().toISOString(),
153
- ]);
201
+ if (shouldRemainRunning) {
202
+ patch.status = "running";
203
+ patch.completed_at = "";
204
+ patch.closed_at = "";
205
+ patch.closed_reason = "";
206
+ } else {
207
+ patch.status = "completed";
208
+ patch.completed_at = firstNonEmptyString([
209
+ request.completed_at,
210
+ request.updated_at,
211
+ new Date().toISOString(),
212
+ ]);
213
+ }
154
214
  } else if (routeAction === "error" || routeAction === "skipped" || routeAction === "closed") {
155
- patch.status = "closed";
156
- patch.closed_at = firstNonEmptyString([
157
- request.closed_at,
158
- request.updated_at,
159
- new Date().toISOString(),
160
- ]);
161
- patch.closed_reason = firstNonEmptyString([
162
- request.closed_reason,
163
- routeState.last_reason,
164
- routeAction,
165
- ]);
215
+ if (shouldRemainRunning) {
216
+ patch.status = "running";
217
+ patch.closed_at = "";
218
+ patch.closed_reason = "";
219
+ } else {
220
+ patch.status = "closed";
221
+ patch.closed_at = firstNonEmptyString([
222
+ request.closed_at,
223
+ request.updated_at,
224
+ new Date().toISOString(),
225
+ ]);
226
+ patch.closed_reason = firstNonEmptyString([
227
+ request.closed_reason,
228
+ routeState.last_reason,
229
+ routeAction,
230
+ ]);
231
+ }
166
232
  }
167
233
  }
168
234
  if (Object.keys(patch).length) {
@@ -2334,8 +2334,238 @@ export async function runSelftestRunnerScenarios(push, deps) {
2334
2334
  `primary=${String(sharedHumanClaimPrimary.requestKey || "(none)")} peer=${String(sharedHumanClaimPeer.requestKey || "(none)")} peer_reason=${String(sharedHumanClaimPeer.reason || "(none)")} count=${sharedHumanRequestCount} canonical=${String(safeObject(sharedHumanRequests[0]).canonical_human_message_key || "(none)")}`,
2335
2335
  );
2336
2336
 
2337
+ const canonicalConversationVariantBody = "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 같은 사람 메시지";
2338
+ const canonicalConversationVariantPrimary = {
2339
+ id: "comment-request-shared-human-conversation-1",
2340
+ createdAt: "2026-04-01T06:10:00.000Z",
2341
+ updatedAt: "2026-04-01T06:10:00.000Z",
2342
+ parsedArchive: {
2343
+ kind: "telegram_message",
2344
+ chatID: "-100124",
2345
+ chatType: "supergroup",
2346
+ body: canonicalConversationVariantBody,
2347
+ messageID: 1290,
2348
+ senderID: "7002",
2349
+ senderIsBot: false,
2350
+ occurredAt: "2026-04-01T06:10:00.000Z",
2351
+ mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2352
+ },
2353
+ };
2354
+ const canonicalConversationVariantPeer = {
2355
+ id: "comment-request-shared-human-conversation-2",
2356
+ createdAt: "2026-04-01T06:10:00.000Z",
2357
+ updatedAt: "2026-04-01T06:10:00.000Z",
2358
+ parsedArchive: {
2359
+ kind: "telegram_message",
2360
+ chatID: "-100124",
2361
+ chatType: "supergroup",
2362
+ body: canonicalConversationVariantBody,
2363
+ messageID: 1290,
2364
+ senderID: "7002",
2365
+ senderIsBot: false,
2366
+ occurredAt: "2026-04-01T06:10:00.000Z",
2367
+ mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2368
+ conversationID: "comment-request-shared-human-conversation-1",
2369
+ },
2370
+ };
2371
+ const canonicalConversationVariantPrimaryClaim = await claimRunnerRequestForHumanComment({
2372
+ normalizedRoute: sharedHumanRouteRyoai1,
2373
+ routeKey: sharedHumanRouteRyoai1Key,
2374
+ selectedRecord: canonicalConversationVariantPrimary,
2375
+ selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2376
+ normalizedIntent: "discussion_request",
2377
+ authoritativeSourceMessageEnvelope: {
2378
+ chat_id: "-100124",
2379
+ message_id: 1290,
2380
+ sender_id: "7002",
2381
+ sender_is_bot: false,
2382
+ occurred_at: "2026-04-01T06:10:00.000Z",
2383
+ body: canonicalConversationVariantBody,
2384
+ source_origin: "local_telegram_inbound",
2385
+ source_route_key: sharedHumanRouteRyoai1Key,
2386
+ source_bot_username: "ryoai_bot",
2387
+ },
2388
+ });
2389
+ const canonicalConversationVariantPeerClaim = await claimRunnerRequestForHumanComment({
2390
+ normalizedRoute: sharedHumanRouteRyoai3,
2391
+ routeKey: sharedHumanRouteRyoai3Key,
2392
+ selectedRecord: canonicalConversationVariantPeer,
2393
+ selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2394
+ normalizedIntent: "discussion_request",
2395
+ authoritativeSourceMessageEnvelope: {
2396
+ chat_id: "-100124",
2397
+ message_id: 1290,
2398
+ sender_id: "7002",
2399
+ sender_is_bot: false,
2400
+ occurred_at: "2026-04-01T06:10:00.000Z",
2401
+ body: canonicalConversationVariantBody,
2402
+ source_origin: "local_telegram_inbound",
2403
+ source_route_key: sharedHumanRouteRyoai3Key,
2404
+ source_bot_username: "ryoai3_bot",
2405
+ },
2406
+ });
2407
+ const canonicalConversationVariantState = loadBotRunnerState();
2408
+ const canonicalConversationVariantRequests = Object.values(safeObject(canonicalConversationVariantState.requests))
2409
+ .filter((entryRaw) => {
2410
+ const entry = safeObject(entryRaw);
2411
+ return String(entry.chat_id || "") === "-100124"
2412
+ && intFromRawAllowZero(entry.source_message_id, 0) === 1290;
2413
+ });
2414
+ push(
2415
+ "runner_human_opening_request_key_ignores_conversation_id_for_canonical_inbound",
2416
+ canonicalConversationVariantPrimaryClaim.ok === true
2417
+ && (canonicalConversationVariantPeerClaim.ok === true || String(canonicalConversationVariantPeerClaim.reason || "") === "request_already_claimed")
2418
+ && String(canonicalConversationVariantPrimaryClaim.requestKey || "") === String(canonicalConversationVariantPeerClaim.requestKey || "")
2419
+ && canonicalConversationVariantRequests.length === 1,
2420
+ `primary=${String(canonicalConversationVariantPrimaryClaim.requestKey || "(none)")} peer=${String(canonicalConversationVariantPeerClaim.requestKey || "(none)")} peer_reason=${String(canonicalConversationVariantPeerClaim.reason || "(none)")} count=${canonicalConversationVariantRequests.length}`,
2421
+ );
2422
+
2423
+ const sharedDirectTakeoverBody = "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 같이 답해";
2424
+ const sharedDirectTakeoverRecordLead = {
2425
+ id: "comment-request-shared-direct-1",
2426
+ createdAt: "2026-04-01T06:20:00.000Z",
2427
+ updatedAt: "2026-04-01T06:20:00.000Z",
2428
+ parsedArchive: {
2429
+ kind: "telegram_message",
2430
+ chatID: "-100125",
2431
+ chatType: "supergroup",
2432
+ body: sharedDirectTakeoverBody,
2433
+ messageID: 1291,
2434
+ senderID: "7003",
2435
+ senderIsBot: false,
2436
+ occurredAt: "2026-04-01T06:20:00.000Z",
2437
+ mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2438
+ },
2439
+ };
2440
+ const sharedDirectTakeoverLeadClaim = await claimRunnerRequestForHumanComment({
2441
+ normalizedRoute: sharedHumanRouteRyoai1,
2442
+ routeKey: sharedHumanRouteRyoai1Key,
2443
+ selectedRecord: sharedDirectTakeoverRecordLead,
2444
+ selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2445
+ normalizedIntent: "discussion_request",
2446
+ authoritativeSourceMessageEnvelope: {
2447
+ chat_id: "-100125",
2448
+ message_id: 1291,
2449
+ sender_id: "7003",
2450
+ sender_is_bot: false,
2451
+ occurred_at: "2026-04-01T06:20:00.000Z",
2452
+ body: sharedDirectTakeoverBody,
2453
+ source_origin: "local_telegram_inbound",
2454
+ source_route_key: sharedHumanRouteRyoai1Key,
2455
+ source_bot_username: "ryoai_bot",
2456
+ },
2457
+ });
2458
+ const sharedDirectTakeoverCanonicalKey = buildArchivedInboundMessageKey({
2459
+ chatID: "-100125",
2460
+ messageID: 1291,
2461
+ messageThreadID: 0,
2462
+ kind: "telegram_message",
2463
+ senderID: "7003",
2464
+ senderIsBot: false,
2465
+ occurredAt: "2026-04-01T06:20:00.000Z",
2466
+ body: sharedDirectTakeoverBody,
2467
+ }).replace(/^human:/, "");
2468
+ const sharedDirectTakeoverState = loadBotRunnerState();
2469
+ saveBotRunnerState({
2470
+ ...sharedDirectTakeoverState,
2471
+ requests: {
2472
+ ...safeObject(sharedDirectTakeoverState.requests),
2473
+ [sharedDirectTakeoverLeadClaim.requestKey]: {
2474
+ ...safeObject(safeObject(sharedDirectTakeoverState.requests)[sharedDirectTakeoverLeadClaim.requestKey]),
2475
+ canonical_human_message_key: sharedDirectTakeoverCanonicalKey,
2476
+ source_message_id: 1291,
2477
+ source_message_origin: "local_telegram_inbound",
2478
+ source_message_route_key: sharedHumanRouteRyoai1Key,
2479
+ source_message_bot_username: "ryoai_bot",
2480
+ source_message_envelope: {
2481
+ chat_id: "-100125",
2482
+ message_id: 1291,
2483
+ body: sharedDirectTakeoverBody,
2484
+ source_origin: "local_telegram_inbound",
2485
+ source_route_key: sharedHumanRouteRyoai1Key,
2486
+ source_bot_username: "ryoai_bot",
2487
+ },
2488
+ selected_bot_usernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2489
+ conversation_initial_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2490
+ conversation_allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2491
+ conversation_intent_mode: "multi_bot_direct",
2492
+ execution_contract_type: "direct_result",
2493
+ execution_contract_targets: ["ryoai_bot"],
2494
+ next_expected_responders: ["ryoai2_bot", "ryoai3_bot"],
2495
+ status: "running",
2496
+ claimed_by_route: sharedHumanRouteRyoai1Key,
2497
+ authoritative_decision_bundle: {
2498
+ schema_version: "runner_conversation_decision.v1",
2499
+ decision_type: "reply_outcome",
2500
+ conversation_intent_mode: "multi_bot_direct",
2501
+ allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2502
+ initial_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2503
+ selected_bot_usernames: ["ryoai_bot"],
2504
+ allow_bot_to_bot: false,
2505
+ execution_contract_type: "direct_result",
2506
+ execution_contract_targets: ["ryoai_bot"],
2507
+ next_expected_responders: ["ryoai2_bot", "ryoai3_bot"],
2508
+ should_close_after_reply: false,
2509
+ turn_kind: "direct_reply",
2510
+ actionable_for_current_route: true,
2511
+ visible_handoff_required: false,
2512
+ visible_handoff_targets: [],
2513
+ reasoning_summary: "shared direct reply remains open for the remaining directly mentioned bots",
2514
+ },
2515
+ },
2516
+ },
2517
+ });
2518
+ const sharedDirectTakeoverRecordPeer = {
2519
+ id: "comment-request-shared-direct-2",
2520
+ createdAt: "2026-04-01T06:20:00.000Z",
2521
+ updatedAt: "2026-04-01T06:20:00.000Z",
2522
+ parsedArchive: {
2523
+ kind: "telegram_message",
2524
+ chatID: "-100125",
2525
+ chatType: "supergroup",
2526
+ body: sharedDirectTakeoverBody,
2527
+ messageID: 1291,
2528
+ senderID: "7003",
2529
+ senderIsBot: false,
2530
+ occurredAt: "2026-04-01T06:20:00.000Z",
2531
+ mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2532
+ conversationID: "comment-request-shared-direct-1",
2533
+ },
2534
+ };
2535
+ const sharedDirectTakeoverPeerClaim = await claimRunnerRequestForHumanComment({
2536
+ normalizedRoute: sharedHumanRouteRyoai3,
2537
+ routeKey: sharedHumanRouteRyoai3Key,
2538
+ selectedRecord: sharedDirectTakeoverRecordPeer,
2539
+ selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
2540
+ normalizedIntent: "discussion_request",
2541
+ authoritativeSourceMessageEnvelope: {
2542
+ chat_id: "-100125",
2543
+ message_id: 1291,
2544
+ sender_id: "7003",
2545
+ sender_is_bot: false,
2546
+ occurred_at: "2026-04-01T06:20:00.000Z",
2547
+ body: sharedDirectTakeoverBody,
2548
+ source_origin: "local_telegram_inbound",
2549
+ source_route_key: sharedHumanRouteRyoai3Key,
2550
+ source_bot_username: "ryoai3_bot",
2551
+ },
2552
+ });
2553
+ const sharedDirectTakeoverRequest = safeObject(
2554
+ safeObject(loadBotRunnerState().requests)[sharedDirectTakeoverLeadClaim.requestKey],
2555
+ );
2556
+ push(
2557
+ "runner_shared_human_direct_reply_allows_pending_route_takeover_without_rekeying",
2558
+ sharedDirectTakeoverLeadClaim.ok === true
2559
+ && sharedDirectTakeoverPeerClaim.ok === true
2560
+ && String(sharedDirectTakeoverPeerClaim.requestKey || "") === String(sharedDirectTakeoverLeadClaim.requestKey || "")
2561
+ && String(sharedDirectTakeoverRequest.claimed_by_route || "") === sharedHumanRouteRyoai3Key
2562
+ && String(sharedDirectTakeoverRequest.source_message_route_key || "") === sharedHumanRouteRyoai1Key
2563
+ && String(safeObject(sharedDirectTakeoverRequest.source_message_envelope).source_route_key || "") === sharedHumanRouteRyoai1Key,
2564
+ `lead=${String(sharedDirectTakeoverLeadClaim.requestKey || "(none)")} peer=${String(sharedDirectTakeoverPeerClaim.requestKey || "(none)")} claimed_by=${String(sharedDirectTakeoverRequest.claimed_by_route || "(none)")} source_route=${String(sharedDirectTakeoverRequest.source_message_route_key || "(none)")}`,
2565
+ );
2566
+
2337
2567
  const rootTaskRecord = {
2338
- id: "comment-request-root-task-1",
2568
+ id: "comment-request-root-task-1",
2339
2569
  createdAt: "2026-03-22T00:05:00.000Z",
2340
2570
  updatedAt: "2026-03-22T00:05:00.000Z",
2341
2571
  parsedArchive: {
@@ -17529,10 +17759,46 @@ export async function runSelftestRunnerScenarios(push, deps) {
17529
17759
  && !Number(recoveredSourceEnvelope.message_id || 0),
17530
17760
  `origin=${String(recoveryPatch.source_message_origin || "(none)")} route=${String(recoveryPatch.source_message_route_key || "(none)")} bot=${String(recoveryPatch.source_message_bot_username || "(none)")} message=${String(recoveredSourceEnvelope.message_id || "(none)")} thread=${String(recoveryPatch.source_message_thread_id || "(none)")}`,
17531
17761
  );
17532
-
17533
- saveBotRunnerState({
17534
- routes: {
17535
- [provenanceRouteKey]: {
17762
+
17763
+ const runningRecoveryPatch = buildRunnerRequestRecoveryPatchFromRouteState(
17764
+ {
17765
+ routes: {
17766
+ [provenanceRouteKey]: {
17767
+ last_source_message_id: 780,
17768
+ last_action: "replied",
17769
+ },
17770
+ },
17771
+ },
17772
+ {
17773
+ request_key: "request-provenance-running-recovery",
17774
+ claimed_by_route: provenanceRouteKey,
17775
+ chat_id: "-100123",
17776
+ source_message_id: 780,
17777
+ status: "running",
17778
+ execution_contract_type: "direct_result",
17779
+ next_expected_responders: ["ryoai2_bot"],
17780
+ authoritative_decision_bundle: {
17781
+ schema_version: "runner_conversation_decision.v1",
17782
+ decision_type: "reply_outcome",
17783
+ conversation_intent_mode: "multi_bot_direct",
17784
+ execution_contract_type: "direct_result",
17785
+ next_expected_responders: ["ryoai2_bot"],
17786
+ should_close_after_reply: false,
17787
+ },
17788
+ },
17789
+ provenanceRouteKey,
17790
+ );
17791
+ push(
17792
+ "runner_request_recovery_keeps_running_requests_with_pending_responders",
17793
+ String(runningRecoveryPatch.status || "") === "running"
17794
+ && !String(runningRecoveryPatch.completed_at || "").trim()
17795
+ && !String(runningRecoveryPatch.closed_at || "").trim(),
17796
+ `status=${String(runningRecoveryPatch.status || "(none)")} completed_at=${String(runningRecoveryPatch.completed_at || "(none)")} closed_at=${String(runningRecoveryPatch.closed_at || "(none)")}`,
17797
+ );
17798
+
17799
+ saveBotRunnerState({
17800
+ routes: {
17801
+ [provenanceRouteKey]: {
17536
17802
  active_request_key: "request-provenance-continuation",
17537
17803
  last_source_message_id: 779,
17538
17804
  recent_local_inbound_envelopes: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.285",
3
+ "version": "0.2.286",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [