metheus-governance-mcp-cli 0.2.198 → 0.2.199

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
@@ -2386,6 +2386,66 @@ function buildRunnerRequestKey({
2386
2386
  ].join("::");
2387
2387
  }
2388
2388
 
2389
+ function looksLikeRunnerClaimQuestion(rawText) {
2390
+ return /[??]/.test(String(rawText || ""));
2391
+ }
2392
+
2393
+ function inferRunnerRequestClaimIntent(selectedRecord) {
2394
+ const parsed = safeObject(selectedRecord?.parsedArchive);
2395
+ const commentKind = String(parsed.kind || "").trim().toLowerCase();
2396
+ if (!isInboundArchiveKind(commentKind)) {
2397
+ return "";
2398
+ }
2399
+ const rawText = String(parsed.body || "").trim();
2400
+ const normalizedText = rawText.toLowerCase();
2401
+ const replyToMessageID = intFromRawAllowZero(parsed.replyToMessageID, 0);
2402
+ if (!rawText) {
2403
+ return "";
2404
+ }
2405
+ if (/^(hi|hello|hey|thanks|thank you|good morning|good afternoon|good evening)\b/.test(normalizedText)) {
2406
+ return "small_talk";
2407
+ }
2408
+ if (/\b(bot role|your role|what do you do|who are you|which bot|who should respond)\b/.test(normalizedText)) {
2409
+ return "bot_role_query";
2410
+ }
2411
+ if (/\b(workspace|working directory|workdir|project folder|local folder)\b/.test(normalizedText)) {
2412
+ return "workspace_query";
2413
+ }
2414
+ if (
2415
+ (/\b(where|path|locate|find)\b/.test(normalizedText) || /\.[a-z0-9]{1,8}\b/.test(normalizedText))
2416
+ && /\b(file|folder|path|doc|guide|readme|workspace)\b/.test(normalizedText)
2417
+ ) {
2418
+ return "artifact_location_query";
2419
+ }
2420
+ if (/\b(why|explain|what is|what does|how does|describe)\b/.test(normalizedText) && looksLikeRunnerClaimQuestion(rawText)) {
2421
+ return "explanation_query";
2422
+ }
2423
+ if (
2424
+ /\b(status|progress|done|finished|complete|completed|working on|current work|did you|handled|handle it|what are you working|check status)\b/
2425
+ .test(normalizedText)
2426
+ ) {
2427
+ return "status_query";
2428
+ }
2429
+ if (replyToMessageID > 0 && looksLikeRunnerClaimQuestion(rawText)) {
2430
+ return "status_query";
2431
+ }
2432
+ if (looksLikeRunnerClaimQuestion(rawText) && normalizedText.split(/\s+/).filter(Boolean).length <= 8) {
2433
+ return "status_query";
2434
+ }
2435
+ return "general_execution";
2436
+ }
2437
+
2438
+ function resolveRunnerRequestClaimIntent({
2439
+ normalizedIntent = "",
2440
+ selectedRecord,
2441
+ }) {
2442
+ const explicitIntent = String(normalizedIntent || "").trim().toLowerCase();
2443
+ if (explicitIntent) {
2444
+ return explicitIntent;
2445
+ }
2446
+ return inferRunnerRequestClaimIntent(selectedRecord);
2447
+ }
2448
+
2389
2449
  function buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID) {
2390
2450
  const provider = String(normalizedRoute?.provider || "").trim() || "unknown";
2391
2451
  const normalizedChatID = String(chatID || "").trim() || "-";
@@ -2658,12 +2718,6 @@ async function claimRunnerRequestForHumanComment({
2658
2718
  reason: "non_human_comment_cannot_create_request",
2659
2719
  };
2660
2720
  }
2661
- const requestKey = buildRunnerRequestKey({
2662
- normalizedRoute,
2663
- selectedRecord,
2664
- selectedBotUsernames,
2665
- normalizedIntent,
2666
- });
2667
2721
  const currentState = loadBotRunnerState();
2668
2722
  const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
2669
2723
  state: currentState,
@@ -2674,6 +2728,16 @@ async function claimRunnerRequestForHumanComment({
2674
2728
  const replyChainContext = safeObject(replyChainResolution.replyChainContext);
2675
2729
  const referencedRequest = safeObject(replyChainContext.referencedRequest);
2676
2730
  const conversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
2731
+ const resolvedNormalizedIntent = resolveRunnerRequestClaimIntent({
2732
+ normalizedIntent,
2733
+ selectedRecord,
2734
+ });
2735
+ const requestKey = buildRunnerRequestKey({
2736
+ normalizedRoute,
2737
+ selectedRecord,
2738
+ selectedBotUsernames,
2739
+ normalizedIntent: resolvedNormalizedIntent,
2740
+ });
2677
2741
  let stateForClaim = safeObject(replyChainResolution.state);
2678
2742
  if (
2679
2743
  Object.keys(referencedRequest).length > 0
@@ -2719,7 +2783,7 @@ async function claimRunnerRequestForHumanComment({
2719
2783
  root_comment_kind: commentKind,
2720
2784
  conversation_id: conversationID,
2721
2785
  selected_bot_usernames: uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername),
2722
- normalized_intent: String(normalizedIntent || "").trim().toLowerCase(),
2786
+ normalized_intent: resolvedNormalizedIntent,
2723
2787
  status: "claimed",
2724
2788
  claimed_by_route: String(routeKey || "").trim(),
2725
2789
  claimed_at: firstNonEmptyString([existing.claimed_at, nowISO]) || nowISO,
@@ -2767,6 +2831,22 @@ function isActionableRunnerRequestIntent(rawIntent) {
2767
2831
  return Boolean(normalizedIntent) && !isInformationalRunnerRequestIntent(normalizedIntent);
2768
2832
  }
2769
2833
 
2834
+ function loadRunnerRequestByKey(requestKey) {
2835
+ const key = String(requestKey || "").trim();
2836
+ if (!key) {
2837
+ return {};
2838
+ }
2839
+ return safeObject(normalizeBotRunnerRequests(loadBotRunnerState().requests)[key]);
2840
+ }
2841
+
2842
+ function actionableRunnerRequestMissingRootWorkItem(requestRaw) {
2843
+ const request = safeObject(requestRaw);
2844
+ return (
2845
+ isActionableRunnerRequestIntent(request.normalized_intent)
2846
+ && !String(request.root_work_item_id || "").trim()
2847
+ );
2848
+ }
2849
+
2770
2850
  function truncateRunnerWorkItemTitleText(rawText, maxLength = 96) {
2771
2851
  const text = String(rawText || "").replace(/\s+/g, " ").trim();
2772
2852
  if (!text) {
@@ -3850,6 +3930,12 @@ async function syncRunnerRequestLedgerForProjectToServer({ normalizedRoute, runt
3850
3930
  const commentStates = buildProjectRunnerRequestCommentStatesForSync(state, normalizedRoute);
3851
3931
 
3852
3932
  for (const request of requests) {
3933
+ if (
3934
+ isActionableRunnerRequestIntent(request.normalized_intent)
3935
+ && !String(request.root_work_item_id || "").trim()
3936
+ ) {
3937
+ continue;
3938
+ }
3853
3939
  await upsertProjectRunnerRequest({
3854
3940
  siteBaseURL: runtime.baseURL,
3855
3941
  projectID,
@@ -7070,7 +7156,19 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7070
7156
  runtime,
7071
7157
  requestKey: requestClaim.requestKey,
7072
7158
  });
7073
- if (!rootWorkItemClaim.ok) {
7159
+ const claimedRequest = safeObject(
7160
+ loadRunnerRequestByKey(requestClaim.requestKey)
7161
+ || rootWorkItemClaim.request
7162
+ || inheritedRootReference.request
7163
+ || requestClaim.request,
7164
+ );
7165
+ const missingRequiredRootWorkItem = actionableRunnerRequestMissingRootWorkItem(claimedRequest);
7166
+ if (!rootWorkItemClaim.ok || missingRequiredRootWorkItem) {
7167
+ const rootWorkItemFailure = String(
7168
+ rootWorkItemClaim.error
7169
+ || rootWorkItemClaim.reason
7170
+ || (missingRequiredRootWorkItem ? "root_work_item_missing" : "root_work_item_create_failed"),
7171
+ ).trim() || "root_work_item_create_failed";
7074
7172
  if (String(requestClaim.requestKey || "").trim()) {
7075
7173
  markRunnerRequestLifecycle({
7076
7174
  normalizedRoute,
@@ -7078,7 +7176,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7078
7176
  selectedRecord,
7079
7177
  routeKey,
7080
7178
  outcome: "closed",
7081
- closedReason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim(),
7179
+ closedReason: rootWorkItemFailure,
7082
7180
  });
7083
7181
  await syncRunnerRequestLedgerForProjectToServer({
7084
7182
  normalizedRoute,
@@ -7089,22 +7187,21 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7089
7187
  routeKey,
7090
7188
  buildRunnerRouteStateFromComment(selectedRecord, {
7091
7189
  last_action: "request_skipped",
7092
- last_reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
7190
+ last_reason: rootWorkItemFailure,
7093
7191
  last_trigger: "work_item_root",
7094
7192
  last_request_key: String(requestClaim.requestKey || "").trim(),
7095
7193
  }),
7096
7194
  );
7097
7195
  skippedRecords.push({
7098
7196
  id: selectedRecord.id,
7099
- reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
7197
+ reason: rootWorkItemFailure,
7100
7198
  messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
7101
7199
  diagnosticType: "skip",
7102
7200
  action: "skip_missing_root_work_item",
7103
- closedReason: String(rootWorkItemClaim.reason || "").trim(),
7201
+ closedReason: rootWorkItemFailure,
7104
7202
  });
7105
7203
  continue;
7106
7204
  }
7107
- const claimedRequest = safeObject(rootWorkItemClaim.request || inheritedRootReference.request || requestClaim.request);
7108
7205
  saveRunnerRouteState(routeKey, {
7109
7206
  ...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
7110
7207
  id: String(claimedRequest.root_work_item_id || "").trim(),
@@ -7299,7 +7396,19 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7299
7396
  runtime,
7300
7397
  requestKey: requestClaim.requestKey,
7301
7398
  });
7302
- if (!rootWorkItemClaim.ok) {
7399
+ const claimedRequest = safeObject(
7400
+ loadRunnerRequestByKey(requestClaim.requestKey)
7401
+ || rootWorkItemClaim.request
7402
+ || inheritedRootReference.request
7403
+ || requestClaim.request,
7404
+ );
7405
+ const missingRequiredRootWorkItem = actionableRunnerRequestMissingRootWorkItem(claimedRequest);
7406
+ if (!rootWorkItemClaim.ok || missingRequiredRootWorkItem) {
7407
+ const rootWorkItemFailure = String(
7408
+ rootWorkItemClaim.error
7409
+ || rootWorkItemClaim.reason
7410
+ || (missingRequiredRootWorkItem ? "root_work_item_missing" : "root_work_item_create_failed"),
7411
+ ).trim() || "root_work_item_create_failed";
7303
7412
  if (String(requestClaim.requestKey || "").trim()) {
7304
7413
  markRunnerRequestLifecycle({
7305
7414
  normalizedRoute,
@@ -7307,7 +7416,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7307
7416
  selectedRecord,
7308
7417
  routeKey,
7309
7418
  outcome: "closed",
7310
- closedReason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim(),
7419
+ closedReason: rootWorkItemFailure,
7311
7420
  });
7312
7421
  await syncRunnerRequestLedgerForProjectToServer({
7313
7422
  normalizedRoute,
@@ -7318,22 +7427,21 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7318
7427
  routeKey,
7319
7428
  buildRunnerRouteStateFromComment(selectedRecord, {
7320
7429
  last_action: "request_skipped",
7321
- last_reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
7430
+ last_reason: rootWorkItemFailure,
7322
7431
  last_trigger: "work_item_root",
7323
7432
  last_request_key: String(requestClaim.requestKey || "").trim(),
7324
7433
  }),
7325
7434
  );
7326
7435
  skippedRecords.push({
7327
7436
  id: selectedRecord.id,
7328
- reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
7437
+ reason: rootWorkItemFailure,
7329
7438
  messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
7330
7439
  diagnosticType: "skip",
7331
7440
  action: "skip_missing_root_work_item",
7332
- closedReason: String(rootWorkItemClaim.reason || "").trim(),
7441
+ closedReason: rootWorkItemFailure,
7333
7442
  });
7334
7443
  continue;
7335
7444
  }
7336
- const claimedRequest = safeObject(rootWorkItemClaim.request || inheritedRootReference.request || requestClaim.request);
7337
7445
  saveRunnerRouteState(routeKey, {
7338
7446
  ...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
7339
7447
  id: String(claimedRequest.root_work_item_id || "").trim(),
@@ -9891,6 +9999,12 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
9891
9999
  last_error: errorText,
9892
10000
  });
9893
10001
  if (String(deferredExecution.requestKey || "").trim()) {
10002
+ const currentRequest = safeObject(loadRunnerRequestByKey(deferredExecution.requestKey));
10003
+ const resolvedIntentType = String(
10004
+ safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type
10005
+ || currentRequest.normalized_intent
10006
+ || "",
10007
+ ).trim();
9894
10008
  markRunnerRequestLifecycle({
9895
10009
  normalizedRoute: deferredExecution.normalizedRoute,
9896
10010
  requestKey: deferredExecution.requestKey,
@@ -9898,6 +10012,14 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
9898
10012
  routeKey: deferredExecution.routeKey,
9899
10013
  outcome: "error",
9900
10014
  closedReason: errorText || "execution_error",
10015
+ normalizedIntent: resolvedIntentType,
10016
+ });
10017
+ await ensureRunnerRootWorkItemForRequest({
10018
+ normalizedRoute: deferredExecution.normalizedRoute,
10019
+ routeKey: deferredExecution.routeKey,
10020
+ selectedRecord: deferredExecution.selectedRecord,
10021
+ runtime: deferredExecution.runtime,
10022
+ requestKey: deferredExecution.requestKey,
9901
10023
  });
9902
10024
  const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
9903
10025
  normalizedRoute: deferredExecution.normalizedRoute,
@@ -379,6 +379,7 @@ export async function upsertProjectRunnerRequest(
379
379
  const postJSONWithAuthHeaders = requireDependency(deps, "postJSONWithAuthHeaders");
380
380
  const parseJSONText = requireDependency(deps, "parseJSONText");
381
381
  const raw = safeObject(request);
382
+ const rootWorkItemID = String(raw.root_work_item_id || "").trim();
382
383
  const payload = {
383
384
  request_key: String(raw.request_key || "").trim(),
384
385
  project_id: String(raw.project_id || "").trim(),
@@ -398,12 +399,12 @@ export async function upsertProjectRunnerRequest(
398
399
  completed_at: String(raw.completed_at || "").trim() || undefined,
399
400
  closed_at: String(raw.closed_at || "").trim() || undefined,
400
401
  closed_reason: String(raw.closed_reason || "").trim(),
401
- root_work_item_id: String(raw.root_work_item_id || "").trim(),
402
- root_work_item_title: String(raw.root_work_item_title || "").trim(),
403
- root_work_item_status: String(raw.root_work_item_status || "").trim(),
404
- root_thread_id: String(raw.root_thread_id || "").trim(),
405
- root_work_item_created_at: String(raw.root_work_item_created_at || "").trim() || undefined,
406
- root_work_item_last_error: String(raw.root_work_item_last_error || "").trim(),
402
+ root_work_item_id: rootWorkItemID,
403
+ root_work_item_title: rootWorkItemID ? String(raw.root_work_item_title || "").trim() : "",
404
+ root_work_item_status: rootWorkItemID ? String(raw.root_work_item_status || "").trim() : "",
405
+ root_thread_id: rootWorkItemID ? String(raw.root_thread_id || "").trim() : "",
406
+ root_work_item_created_at: rootWorkItemID ? String(raw.root_work_item_created_at || "").trim() || undefined : undefined,
407
+ root_work_item_last_error: rootWorkItemID ? String(raw.root_work_item_last_error || "").trim() : "",
407
408
  last_comment_id: String(raw.last_comment_id || "").trim(),
408
409
  last_comment_kind: String(raw.last_comment_kind || "").trim(),
409
410
  last_source_message_id: Number.isFinite(Number(raw.last_source_message_id)) ? Number(raw.last_source_message_id) : undefined,
@@ -1426,6 +1426,33 @@ export async function runSelftestRunnerScenarios(push, deps) {
1426
1426
  `request=${String(claimed.requestKey || "(none)")} status=${String(claimedRequest.status || "(none)")} consumed=${String(claimedConsumed.request_key || "(none)")}`,
1427
1427
  );
1428
1428
 
1429
+ const inferredExecutionRecord = {
1430
+ id: "comment-request-human-2",
1431
+ createdAt: "2026-03-22T00:01:00.000Z",
1432
+ updatedAt: "2026-03-22T00:01:00.000Z",
1433
+ parsedArchive: {
1434
+ kind: "telegram_message",
1435
+ chatID: "-100123",
1436
+ chatType: "supergroup",
1437
+ body: "@RyoAI_bot draft the project charter and outline the deliverables",
1438
+ messageID: 502,
1439
+ senderIsBot: false,
1440
+ },
1441
+ };
1442
+ const inferredExecutionClaim = await claimRunnerRequestForHumanComment({
1443
+ normalizedRoute: requestRoute,
1444
+ routeKey: requestRouteKey,
1445
+ selectedRecord: inferredExecutionRecord,
1446
+ selectedBotUsernames: ["ryoai_bot"],
1447
+ });
1448
+ const inferredExecutionRequest = safeObject(safeObject(loadBotRunnerState().requests)[inferredExecutionClaim.requestKey]);
1449
+ push(
1450
+ "runner_request_claim_defaults_actionable_messages_to_general_execution",
1451
+ inferredExecutionClaim.ok === true
1452
+ && String(inferredExecutionRequest.normalized_intent || "") === "general_execution",
1453
+ `intent=${String(inferredExecutionRequest.normalized_intent || "(none)")} request=${String(inferredExecutionClaim.requestKey || "(none)")}`,
1454
+ );
1455
+
1429
1456
  const rootTaskRecord = {
1430
1457
  id: "comment-request-root-task-1",
1431
1458
  createdAt: "2026-03-22T00:05:00.000Z",
@@ -1468,6 +1495,34 @@ export async function runSelftestRunnerScenarios(push, deps) {
1468
1495
  selectedBotUsernames: ["ryoai_bot"],
1469
1496
  normalizedIntent: "status_query",
1470
1497
  });
1498
+ const inferredReplyTaskRecord = {
1499
+ id: "comment-request-root-task-2b",
1500
+ createdAt: "2026-03-22T00:06:30.000Z",
1501
+ updatedAt: "2026-03-22T00:06:30.000Z",
1502
+ parsedArchive: {
1503
+ kind: "telegram_message",
1504
+ chatID: "-100123",
1505
+ chatType: "supergroup",
1506
+ body: "@RyoAI_bot did you handle it?",
1507
+ messageID: 6021,
1508
+ replyToMessageID: 601,
1509
+ senderIsBot: false,
1510
+ mentionUsernames: ["ryoai_bot"],
1511
+ },
1512
+ };
1513
+ const inferredReplyTaskClaim = await claimRunnerRequestForHumanComment({
1514
+ normalizedRoute: requestRoute,
1515
+ routeKey: requestRouteKey,
1516
+ selectedRecord: inferredReplyTaskRecord,
1517
+ selectedBotUsernames: ["ryoai_bot"],
1518
+ });
1519
+ const inferredReplyTaskRequest = safeObject(safeObject(loadBotRunnerState().requests)[inferredReplyTaskClaim.requestKey]);
1520
+ push(
1521
+ "runner_request_claim_infers_status_query_for_short_reply_questions",
1522
+ inferredReplyTaskClaim.ok === true
1523
+ && String(inferredReplyTaskRequest.normalized_intent || "") === "status_query",
1524
+ `intent=${String(inferredReplyTaskRequest.normalized_intent || "(none)")} request=${String(inferredReplyTaskClaim.requestKey || "(none)")}`,
1525
+ );
1471
1526
  const syntheticConversationID = "reply_chain:telegram:-100123:601";
1472
1527
  const replyChainState = loadBotRunnerState();
1473
1528
  const rootTaskRequest = safeObject(safeObject(replyChainState.requests)[rootTaskClaim.requestKey]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.198",
3
+ "version": "0.2.199",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [