metheus-governance-mcp-cli 0.2.195 → 0.2.197
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/README.md +5 -0
- package/cli.mjs +363 -13
- package/lib/local-ai-adapters.mjs +23 -7
- package/lib/runner-orchestration.mjs +80 -50
- package/lib/runner-trigger.mjs +8 -3
- package/lib/selftest-runner-scenarios.mjs +513 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -664,6 +664,11 @@ Runner command contract:
|
|
|
664
664
|
Notes:
|
|
665
665
|
- `runner once` processes the most recent pending archived inbound message
|
|
666
666
|
- `runner start` keeps polling and stores per-route cursor state in `~/.metheus/bot-runner-state.json`
|
|
667
|
+
- request execution is request-ledger first: one human root message becomes one `request_key`, and request status is the real execution gate
|
|
668
|
+
- `conversation_session` is advisory metadata only; participants, last speaker, and next expected responders stay there, but session `open` alone must not revive old work
|
|
669
|
+
- bot replies and public bot-to-bot delegation replies do not create a fresh request on their own; they continue only when an active request already exists for that conversation
|
|
670
|
+
- consumed archived comments are recorded in the runner request comment ledger so the same bot reply does not get re-queued later as new work
|
|
671
|
+
- startup cleanup closes stale open sessions without an active request and excludes their archived bot replies before pending selection runs
|
|
667
672
|
- first start primes the cursor to the latest inbound message and does not reply to old backlog
|
|
668
673
|
- when inline filters match a configured route in `~/.metheus/bot-runner.json`, the runner reuses that route's canonical name/destination and state cursor instead of creating a new anonymous route key
|
|
669
674
|
- stale anonymous route keys in `~/.metheus/bot-runner-state.json` are auto-migrated to the matching configured route when possible; `doctor` warns if ambiguous legacy keys still remain
|
package/cli.mjs
CHANGED
|
@@ -2363,6 +2363,16 @@ function buildRunnerRequestKey({
|
|
|
2363
2363
|
].join("::");
|
|
2364
2364
|
}
|
|
2365
2365
|
|
|
2366
|
+
function buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID) {
|
|
2367
|
+
const provider = String(normalizedRoute?.provider || "").trim() || "unknown";
|
|
2368
|
+
const normalizedChatID = String(chatID || "").trim() || "-";
|
|
2369
|
+
const normalizedAnchorMessageID = intFromRawAllowZero(anchorMessageID, 0);
|
|
2370
|
+
if (normalizedAnchorMessageID <= 0) {
|
|
2371
|
+
return "";
|
|
2372
|
+
}
|
|
2373
|
+
return `reply_chain:${provider}:${normalizedChatID}:${normalizedAnchorMessageID}`;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2366
2376
|
function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
2367
2377
|
const requests = normalizeBotRunnerRequests(state?.requests);
|
|
2368
2378
|
const projectID = String(normalizedRoute?.projectID || "").trim();
|
|
@@ -2386,6 +2396,61 @@ function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
|
2386
2396
|
});
|
|
2387
2397
|
}
|
|
2388
2398
|
|
|
2399
|
+
function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
|
|
2400
|
+
const chatID = String(selectors.chatID || "").trim();
|
|
2401
|
+
const messageID = intFromRawAllowZero(selectors.messageID, 0);
|
|
2402
|
+
if (!chatID || messageID <= 0) {
|
|
2403
|
+
return [];
|
|
2404
|
+
}
|
|
2405
|
+
return findRunnerRequestsForScope(state, normalizedRoute, { chatID })
|
|
2406
|
+
.filter((entry) => (
|
|
2407
|
+
intFromRawAllowZero(entry.source_message_id, 0) === messageID
|
|
2408
|
+
|| intFromRawAllowZero(entry.last_source_message_id, 0) === messageID
|
|
2409
|
+
));
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
function resolveRunnerReplyChainConversationContext(state, normalizedRoute, selectedRecord) {
|
|
2413
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2414
|
+
const explicitConversationID = String(parsed.conversationID || "").trim();
|
|
2415
|
+
if (explicitConversationID) {
|
|
2416
|
+
return {
|
|
2417
|
+
conversationID: explicitConversationID,
|
|
2418
|
+
replyToMessageID: intFromRawAllowZero(parsed.replyToMessageID, 0),
|
|
2419
|
+
anchorMessageID: 0,
|
|
2420
|
+
reason: "archive_conversation",
|
|
2421
|
+
referencedRequest: null,
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
2425
|
+
const replyToMessageID = intFromRawAllowZero(parsed.replyToMessageID, 0);
|
|
2426
|
+
if (!chatID || replyToMessageID <= 0) {
|
|
2427
|
+
return {
|
|
2428
|
+
conversationID: "",
|
|
2429
|
+
replyToMessageID,
|
|
2430
|
+
anchorMessageID: 0,
|
|
2431
|
+
reason: "",
|
|
2432
|
+
referencedRequest: null,
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
const referencedRequest = safeObject(findRunnerRequestsForMessageID(state, normalizedRoute, {
|
|
2436
|
+
chatID,
|
|
2437
|
+
messageID: replyToMessageID,
|
|
2438
|
+
})[0]);
|
|
2439
|
+
const referencedConversationID = String(referencedRequest.conversation_id || "").trim();
|
|
2440
|
+
const anchorMessageID = intFromRawAllowZero(referencedRequest.source_message_id, 0) || replyToMessageID;
|
|
2441
|
+
return {
|
|
2442
|
+
conversationID: referencedConversationID || buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID),
|
|
2443
|
+
replyToMessageID,
|
|
2444
|
+
anchorMessageID,
|
|
2445
|
+
reason: referencedConversationID
|
|
2446
|
+
? "reply_request_conversation"
|
|
2447
|
+
: Object.keys(referencedRequest).length > 0
|
|
2448
|
+
? "reply_request_synthetic"
|
|
2449
|
+
: "reply_message_synthetic",
|
|
2450
|
+
referencedRequest: Object.keys(referencedRequest).length > 0 ? referencedRequest : null,
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2389
2454
|
function upsertRunnerRequest(state, requestKey, patch = {}) {
|
|
2390
2455
|
const currentState = safeObject(state);
|
|
2391
2456
|
const requests = normalizeBotRunnerRequests(currentState.requests);
|
|
@@ -2447,7 +2512,24 @@ function claimRunnerRequestForHumanComment({
|
|
|
2447
2512
|
normalizedIntent,
|
|
2448
2513
|
});
|
|
2449
2514
|
const currentState = loadBotRunnerState();
|
|
2450
|
-
const
|
|
2515
|
+
const replyChainContext = resolveRunnerReplyChainConversationContext(currentState, normalizedRoute, selectedRecord);
|
|
2516
|
+
const conversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
2517
|
+
let stateForClaim = currentState;
|
|
2518
|
+
if (
|
|
2519
|
+
replyChainContext.referencedRequest
|
|
2520
|
+
&& conversationID
|
|
2521
|
+
&& !String(replyChainContext.referencedRequest.conversation_id || "").trim()
|
|
2522
|
+
&& String(replyChainContext.referencedRequest.request_key || "").trim()
|
|
2523
|
+
) {
|
|
2524
|
+
const backfilled = upsertRunnerRequest(stateForClaim, replyChainContext.referencedRequest.request_key, {
|
|
2525
|
+
conversation_id: conversationID,
|
|
2526
|
+
});
|
|
2527
|
+
stateForClaim = {
|
|
2528
|
+
...stateForClaim,
|
|
2529
|
+
requests: backfilled.requests,
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
const requests = normalizeBotRunnerRequests(stateForClaim.requests);
|
|
2451
2533
|
const existing = safeObject(requests[requestKey]);
|
|
2452
2534
|
if (isFinalRunnerRequestStatus(existing.status)) {
|
|
2453
2535
|
return {
|
|
@@ -2468,14 +2550,14 @@ function claimRunnerRequestForHumanComment({
|
|
|
2468
2550
|
};
|
|
2469
2551
|
}
|
|
2470
2552
|
const nowISO = new Date().toISOString();
|
|
2471
|
-
const { requests: nextRequests, request } = upsertRunnerRequest(
|
|
2553
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
2472
2554
|
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2473
2555
|
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2474
2556
|
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
2475
2557
|
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2476
2558
|
root_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2477
2559
|
root_comment_kind: commentKind,
|
|
2478
|
-
conversation_id:
|
|
2560
|
+
conversation_id: conversationID,
|
|
2479
2561
|
selected_bot_usernames: uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername),
|
|
2480
2562
|
normalized_intent: String(normalizedIntent || "").trim().toLowerCase(),
|
|
2481
2563
|
status: "claimed",
|
|
@@ -2485,20 +2567,20 @@ function claimRunnerRequestForHumanComment({
|
|
|
2485
2567
|
last_comment_kind: commentKind,
|
|
2486
2568
|
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2487
2569
|
});
|
|
2488
|
-
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(
|
|
2570
|
+
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(stateForClaim, selectedRecord?.id, {
|
|
2489
2571
|
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2490
2572
|
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2491
2573
|
request_key: requestKey,
|
|
2492
2574
|
route_key: String(routeKey || "").trim(),
|
|
2493
|
-
conversation_id:
|
|
2575
|
+
conversation_id: conversationID,
|
|
2494
2576
|
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2495
2577
|
comment_kind: commentKind,
|
|
2496
2578
|
request_status: "claimed",
|
|
2497
2579
|
});
|
|
2498
2580
|
saveBotRunnerState({
|
|
2499
|
-
routes:
|
|
2500
|
-
sharedInboxes:
|
|
2501
|
-
excludedComments:
|
|
2581
|
+
routes: stateForClaim.routes,
|
|
2582
|
+
sharedInboxes: stateForClaim.sharedInboxes || stateForClaim.shared_inboxes,
|
|
2583
|
+
excludedComments: stateForClaim.excludedComments || stateForClaim.excluded_comments,
|
|
2502
2584
|
requests: nextRequests,
|
|
2503
2585
|
consumedComments: nextConsumedComments,
|
|
2504
2586
|
});
|
|
@@ -2579,6 +2661,11 @@ function markRunnerRequestLifecycle({
|
|
|
2579
2661
|
outcome,
|
|
2580
2662
|
conversationIDRaw = "",
|
|
2581
2663
|
allowedResponders = [],
|
|
2664
|
+
executionContractType = "",
|
|
2665
|
+
executionContractTargets = [],
|
|
2666
|
+
nextExpectedResponders = [],
|
|
2667
|
+
currentBotSelector = "",
|
|
2668
|
+
conversationIntentMode = "",
|
|
2582
2669
|
normalizedIntent = "",
|
|
2583
2670
|
closedReason = "",
|
|
2584
2671
|
}) {
|
|
@@ -2589,20 +2676,34 @@ function markRunnerRequestLifecycle({
|
|
|
2589
2676
|
if (!Object.keys(existing).length) return null;
|
|
2590
2677
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2591
2678
|
const conversationID = String(conversationIDRaw || existing.conversation_id || parsed.conversationID || "").trim();
|
|
2592
|
-
const
|
|
2593
|
-
|
|
2594
|
-
|
|
2679
|
+
const normalizedCurrentBotSelector = normalizeTelegramMentionUsername(
|
|
2680
|
+
currentBotSelector
|
|
2681
|
+
|| safeObject(currentState.routes)[String(routeKey || "").trim()]?.last_speaker_bot_username
|
|
2682
|
+
|| "",
|
|
2683
|
+
);
|
|
2684
|
+
const continuationSelectors = uniqueOrderedStrings(
|
|
2685
|
+
[
|
|
2686
|
+
...ensureArray(executionContractTargets).length
|
|
2687
|
+
? ensureArray(executionContractTargets)
|
|
2688
|
+
: ensureArray(existing.execution_contract_targets),
|
|
2689
|
+
...ensureArray(nextExpectedResponders).length
|
|
2690
|
+
? ensureArray(nextExpectedResponders)
|
|
2691
|
+
: ensureArray(existing.next_expected_responders),
|
|
2692
|
+
],
|
|
2693
|
+
normalizeTelegramMentionUsername,
|
|
2694
|
+
).filter((selector) => selector && selector !== normalizedCurrentBotSelector);
|
|
2695
|
+
const shouldRemainRunningAfterReply = continuationSelectors.length > 0;
|
|
2595
2696
|
const normalizedOutcome = String(outcome || "").trim().toLowerCase();
|
|
2596
2697
|
const nextStatus = (() => {
|
|
2597
2698
|
if (normalizedOutcome === "claimed") return "claimed";
|
|
2598
2699
|
if (normalizedOutcome === "running") return "running";
|
|
2599
2700
|
if (normalizedOutcome === "replied") {
|
|
2600
|
-
return
|
|
2701
|
+
return shouldRemainRunningAfterReply ? "running" : "completed";
|
|
2601
2702
|
}
|
|
2602
2703
|
if (normalizedOutcome === "loop_closed") return "loop_closed";
|
|
2603
2704
|
if (normalizedOutcome === "expired") return "expired";
|
|
2604
2705
|
if (normalizedOutcome === "error" || normalizedOutcome === "skipped" || normalizedOutcome === "closed") {
|
|
2605
|
-
return
|
|
2706
|
+
return "closed";
|
|
2606
2707
|
}
|
|
2607
2708
|
return normalizeRunnerRequestStatus(existing.status);
|
|
2608
2709
|
})();
|
|
@@ -2613,6 +2714,24 @@ function markRunnerRequestLifecycle({
|
|
|
2613
2714
|
ensureArray(allowedResponders).length ? allowedResponders : existing.conversation_allowed_responders,
|
|
2614
2715
|
normalizeTelegramMentionUsername,
|
|
2615
2716
|
),
|
|
2717
|
+
conversation_intent_mode: String(
|
|
2718
|
+
conversationIntentMode
|
|
2719
|
+
|| existing.conversation_intent_mode
|
|
2720
|
+
|| "",
|
|
2721
|
+
).trim().toLowerCase(),
|
|
2722
|
+
execution_contract_type: String(
|
|
2723
|
+
executionContractType
|
|
2724
|
+
|| existing.execution_contract_type
|
|
2725
|
+
|| "",
|
|
2726
|
+
).trim().toLowerCase(),
|
|
2727
|
+
execution_contract_targets: uniqueOrderedStrings(
|
|
2728
|
+
ensureArray(executionContractTargets).length ? executionContractTargets : existing.execution_contract_targets,
|
|
2729
|
+
normalizeTelegramMentionUsername,
|
|
2730
|
+
),
|
|
2731
|
+
next_expected_responders: uniqueOrderedStrings(
|
|
2732
|
+
ensureArray(nextExpectedResponders).length ? nextExpectedResponders : existing.next_expected_responders,
|
|
2733
|
+
normalizeTelegramMentionUsername,
|
|
2734
|
+
),
|
|
2616
2735
|
normalized_intent: String(normalizedIntent || existing.normalized_intent || "").trim().toLowerCase(),
|
|
2617
2736
|
status: nextStatus,
|
|
2618
2737
|
started_at: firstNonEmptyString([existing.started_at, nowISO]),
|
|
@@ -5107,6 +5226,170 @@ function buildRunnerSmallTalkReply({ route, executionPlan } = {}) {
|
|
|
5107
5226
|
return templatePool[stableTextModulo(`${displayName}:${roleProfileName}`, templatePool.length)];
|
|
5108
5227
|
}
|
|
5109
5228
|
|
|
5229
|
+
function summarizeRunnerRequestForStatusLookup(entryRaw) {
|
|
5230
|
+
const entry = safeObject(entryRaw);
|
|
5231
|
+
return {
|
|
5232
|
+
status: String(entry.status || "").trim(),
|
|
5233
|
+
normalized_intent: String(entry.normalized_intent || "").trim(),
|
|
5234
|
+
conversation_id: String(entry.conversation_id || "").trim(),
|
|
5235
|
+
closed_reason: String(entry.closed_reason || "").trim(),
|
|
5236
|
+
claimed_at: firstNonEmptyString([entry.claimed_at, entry.started_at]),
|
|
5237
|
+
started_at: firstNonEmptyString([entry.started_at, entry.claimed_at]),
|
|
5238
|
+
updated_at: String(entry.updated_at || "").trim(),
|
|
5239
|
+
source_message_id: intFromRawAllowZero(entry.source_message_id, 0) || undefined,
|
|
5240
|
+
last_source_message_id: intFromRawAllowZero(entry.last_source_message_id, 0) || undefined,
|
|
5241
|
+
selected_bot_usernames: ensureArray(entry.selected_bot_usernames)
|
|
5242
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
5243
|
+
.filter(Boolean),
|
|
5244
|
+
};
|
|
5245
|
+
}
|
|
5246
|
+
|
|
5247
|
+
function isInformationalRunnerRequestIntent(intentType) {
|
|
5248
|
+
return new Set([
|
|
5249
|
+
"status_query",
|
|
5250
|
+
"bot_role_query",
|
|
5251
|
+
"workspace_query",
|
|
5252
|
+
"explanation_query",
|
|
5253
|
+
"small_talk",
|
|
5254
|
+
]).has(String(intentType || "").trim().toLowerCase());
|
|
5255
|
+
}
|
|
5256
|
+
|
|
5257
|
+
function requestEligibleForStatusLookup(entryRaw, routeKey, selfBotUsername, currentMessageID) {
|
|
5258
|
+
const entry = safeObject(entryRaw);
|
|
5259
|
+
const sourceMessageID = intFromRawAllowZero(entry.source_message_id, 0);
|
|
5260
|
+
const lastSourceMessageID = intFromRawAllowZero(entry.last_source_message_id, 0);
|
|
5261
|
+
if (
|
|
5262
|
+
(currentMessageID > 0 && sourceMessageID === currentMessageID)
|
|
5263
|
+
|| (currentMessageID > 0 && lastSourceMessageID === currentMessageID)
|
|
5264
|
+
) {
|
|
5265
|
+
return false;
|
|
5266
|
+
}
|
|
5267
|
+
if (String(entry.claimed_by_route || "").trim() === routeKey) {
|
|
5268
|
+
return true;
|
|
5269
|
+
}
|
|
5270
|
+
if (!selfBotUsername) {
|
|
5271
|
+
return true;
|
|
5272
|
+
}
|
|
5273
|
+
return ensureArray(entry.selected_bot_usernames)
|
|
5274
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
5275
|
+
.filter(Boolean)
|
|
5276
|
+
.includes(selfBotUsername);
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
function pickPreferredStatusLookupRequest(entries = []) {
|
|
5280
|
+
const candidates = ensureArray(entries).map((entry) => safeObject(entry)).filter((entry) => Object.keys(entry).length > 0);
|
|
5281
|
+
if (!candidates.length) {
|
|
5282
|
+
return null;
|
|
5283
|
+
}
|
|
5284
|
+
const activeNonInformational = candidates.filter((entry) => (
|
|
5285
|
+
isActiveRunnerRequestStatus(entry.status)
|
|
5286
|
+
&& !isInformationalRunnerRequestIntent(entry.normalized_intent)
|
|
5287
|
+
));
|
|
5288
|
+
if (activeNonInformational.length) {
|
|
5289
|
+
return activeNonInformational[0];
|
|
5290
|
+
}
|
|
5291
|
+
const nonInformational = candidates.filter((entry) => !isInformationalRunnerRequestIntent(entry.normalized_intent));
|
|
5292
|
+
if (nonInformational.length) {
|
|
5293
|
+
return nonInformational[0];
|
|
5294
|
+
}
|
|
5295
|
+
const activeAny = candidates.filter((entry) => isActiveRunnerRequestStatus(entry.status));
|
|
5296
|
+
if (activeAny.length) {
|
|
5297
|
+
return activeAny[0];
|
|
5298
|
+
}
|
|
5299
|
+
return candidates[0];
|
|
5300
|
+
}
|
|
5301
|
+
|
|
5302
|
+
function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
5303
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
5304
|
+
const currentMessageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
5305
|
+
const currentChatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
5306
|
+
const routeKey = runnerRouteKey(route);
|
|
5307
|
+
const selfBotUsername = normalizeTelegramMentionUsername(firstNonEmptyString([
|
|
5308
|
+
route?.server_bot_name,
|
|
5309
|
+
route?.serverBotName,
|
|
5310
|
+
route?.botName,
|
|
5311
|
+
route?.name,
|
|
5312
|
+
]));
|
|
5313
|
+
const activeExecution = resolveRunnerActiveExecutionState(routeState);
|
|
5314
|
+
const activeSourceMessageID = intFromRawAllowZero(activeExecution.sourceMessageID, 0);
|
|
5315
|
+
const selfBusyFiltered = Boolean(
|
|
5316
|
+
activeExecution.active
|
|
5317
|
+
&& currentMessageID > 0
|
|
5318
|
+
&& activeSourceMessageID > 0
|
|
5319
|
+
&& currentMessageID === activeSourceMessageID
|
|
5320
|
+
);
|
|
5321
|
+
let runnerState = { requests: {} };
|
|
5322
|
+
try {
|
|
5323
|
+
runnerState = loadBotRunnerState();
|
|
5324
|
+
} catch {}
|
|
5325
|
+
const replyChainContext = resolveRunnerReplyChainConversationContext(runnerState, route, selectedRecord);
|
|
5326
|
+
const currentConversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
5327
|
+
const requestMatchesCurrentRoute = (entry) => requestEligibleForStatusLookup(
|
|
5328
|
+
entry,
|
|
5329
|
+
routeKey,
|
|
5330
|
+
selfBotUsername,
|
|
5331
|
+
currentMessageID,
|
|
5332
|
+
);
|
|
5333
|
+
let relatedActiveRequest = null;
|
|
5334
|
+
let relatedRequest = null;
|
|
5335
|
+
const selectors = currentConversationID
|
|
5336
|
+
? { conversationID: currentConversationID, chatID: currentChatID }
|
|
5337
|
+
: { chatID: currentChatID };
|
|
5338
|
+
let scopedRequests = findRunnerRequestsForScope(runnerState, route, selectors);
|
|
5339
|
+
if (!scopedRequests.length && currentConversationID) {
|
|
5340
|
+
scopedRequests = findRunnerRequestsForScope(runnerState, route, { chatID: currentChatID });
|
|
5341
|
+
}
|
|
5342
|
+
const eligibleScopedRequests = scopedRequests.filter(requestMatchesCurrentRoute);
|
|
5343
|
+
relatedActiveRequest = eligibleScopedRequests
|
|
5344
|
+
.filter((entry) => isActiveRunnerRequestStatus(entry.status))[0] || null;
|
|
5345
|
+
relatedRequest = pickPreferredStatusLookupRequest(
|
|
5346
|
+
eligibleScopedRequests.length
|
|
5347
|
+
? eligibleScopedRequests
|
|
5348
|
+
: (replyChainContext.referencedRequest ? [replyChainContext.referencedRequest] : []).filter(requestMatchesCurrentRoute),
|
|
5349
|
+
);
|
|
5350
|
+
const lastAction = String(safeObject(routeState).last_action || "").trim();
|
|
5351
|
+
const lastReason = String(safeObject(routeState).last_reason || "").trim();
|
|
5352
|
+
const lastIntentType = String(safeObject(routeState).last_intent_type || "").trim();
|
|
5353
|
+
const routeConversationID = String(safeObject(routeState).last_conversation_id || "").trim();
|
|
5354
|
+
const routeWorkItemIDs = ensureArray(safeObject(routeState).last_work_item_ids).map((item) => String(item || "").trim()).filter(Boolean);
|
|
5355
|
+
const routeWorkItemTitles = ensureArray(safeObject(routeState).last_work_item_titles).map((item) => String(item || "").trim()).filter(Boolean);
|
|
5356
|
+
return {
|
|
5357
|
+
kind: "runner_status",
|
|
5358
|
+
status: (!selfBusyFiltered && activeExecution.active) || relatedActiveRequest
|
|
5359
|
+
? "running"
|
|
5360
|
+
: String(safeObject(relatedRequest).status || "").trim() || "idle",
|
|
5361
|
+
resolved_conversation_id: currentConversationID,
|
|
5362
|
+
reply_chain_resolution: {
|
|
5363
|
+
reason: String(replyChainContext.reason || "").trim(),
|
|
5364
|
+
reply_to_message_id: intFromRawAllowZero(replyChainContext.replyToMessageID, 0) || undefined,
|
|
5365
|
+
anchor_message_id: intFromRawAllowZero(replyChainContext.anchorMessageID, 0) || undefined,
|
|
5366
|
+
},
|
|
5367
|
+
self_busy_filtered: selfBusyFiltered,
|
|
5368
|
+
active_execution: activeExecution.active && !selfBusyFiltered
|
|
5369
|
+
? {
|
|
5370
|
+
started_at: String(activeExecution.startedAt || "").trim(),
|
|
5371
|
+
age_seconds: intFromRawAllowZero(activeExecution.ageSeconds, 0),
|
|
5372
|
+
stale: activeExecution.stale === true,
|
|
5373
|
+
stuck: activeExecution.stuck === true,
|
|
5374
|
+
warning: String(activeExecution.warning || "").trim(),
|
|
5375
|
+
}
|
|
5376
|
+
: null,
|
|
5377
|
+
related_active_request: relatedActiveRequest ? summarizeRunnerRequestForStatusLookup(relatedActiveRequest) : null,
|
|
5378
|
+
related_request: relatedRequest ? summarizeRunnerRequestForStatusLookup(relatedRequest) : null,
|
|
5379
|
+
route_work_items: currentConversationID && routeConversationID === currentConversationID && (routeWorkItemIDs.length > 0 || routeWorkItemTitles.length > 0)
|
|
5380
|
+
? {
|
|
5381
|
+
ids: routeWorkItemIDs,
|
|
5382
|
+
titles: routeWorkItemTitles,
|
|
5383
|
+
}
|
|
5384
|
+
: null,
|
|
5385
|
+
last_route_result: {
|
|
5386
|
+
action: lastAction,
|
|
5387
|
+
reason: lastReason,
|
|
5388
|
+
intent_type: lastIntentType,
|
|
5389
|
+
},
|
|
5390
|
+
};
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5110
5393
|
async function resolveInformationalQueryReply({
|
|
5111
5394
|
intentType,
|
|
5112
5395
|
route,
|
|
@@ -5159,6 +5442,17 @@ async function resolveInformationalQueryReply({
|
|
|
5159
5442
|
};
|
|
5160
5443
|
}
|
|
5161
5444
|
if (normalizedIntentType === "status_query") {
|
|
5445
|
+
return {
|
|
5446
|
+
handled: true,
|
|
5447
|
+
source: "runner.status",
|
|
5448
|
+
response_mode: "lookup_only",
|
|
5449
|
+
reply: "",
|
|
5450
|
+
lookup: buildRunnerStatusQueryLookup({
|
|
5451
|
+
route,
|
|
5452
|
+
routeState,
|
|
5453
|
+
selectedRecord,
|
|
5454
|
+
}),
|
|
5455
|
+
};
|
|
5162
5456
|
const activeExecution = resolveRunnerActiveExecutionState(routeState);
|
|
5163
5457
|
if (activeExecution.active) {
|
|
5164
5458
|
const startedAt = String(activeExecution.startedAt || "").trim();
|
|
@@ -5839,6 +6133,24 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5839
6133
|
skippedRecords.push(duplicateArchivedSkip);
|
|
5840
6134
|
continue;
|
|
5841
6135
|
}
|
|
6136
|
+
const triggerDecision = evaluateTelegramRunnerTrigger(selectedRecord, normalizedRoute, bot);
|
|
6137
|
+
if (triggerDecision.shouldRespond !== true) {
|
|
6138
|
+
saveRunnerRouteState(
|
|
6139
|
+
routeKey,
|
|
6140
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
6141
|
+
last_action: "trigger_skipped",
|
|
6142
|
+
last_reason: String(triggerDecision.reason || "trigger policy skipped message").trim() || "trigger policy skipped message",
|
|
6143
|
+
last_trigger: String(triggerDecision.trigger || "").trim() || "trigger_policy",
|
|
6144
|
+
}),
|
|
6145
|
+
);
|
|
6146
|
+
skippedRecords.push({
|
|
6147
|
+
id: selectedRecord.id,
|
|
6148
|
+
reason: String(triggerDecision.reason || "trigger policy skipped message").trim() || "trigger policy skipped message",
|
|
6149
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
6150
|
+
suppressDiagnostic: true,
|
|
6151
|
+
});
|
|
6152
|
+
continue;
|
|
6153
|
+
}
|
|
5842
6154
|
const startupLoopSkipped = await maybeHandleRunnerStartupLoopCandidate({
|
|
5843
6155
|
routeKey,
|
|
5844
6156
|
normalizedRoute,
|
|
@@ -5947,6 +6259,8 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5947
6259
|
runtime,
|
|
5948
6260
|
managedConversationBots,
|
|
5949
6261
|
requestKey: String(requestClaim.requestKey || "").trim(),
|
|
6262
|
+
triggerDecision,
|
|
6263
|
+
responderAdjudication: adjudication,
|
|
5950
6264
|
},
|
|
5951
6265
|
};
|
|
5952
6266
|
}
|
|
@@ -5992,6 +6306,24 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5992
6306
|
skippedRecords.push(duplicateArchivedSkip);
|
|
5993
6307
|
continue;
|
|
5994
6308
|
}
|
|
6309
|
+
const triggerDecision = evaluateTelegramRunnerTrigger(selectedRecord, normalizedRoute, bot);
|
|
6310
|
+
if (triggerDecision.shouldRespond !== true) {
|
|
6311
|
+
saveRunnerRouteState(
|
|
6312
|
+
routeKey,
|
|
6313
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
6314
|
+
last_action: "trigger_skipped",
|
|
6315
|
+
last_reason: String(triggerDecision.reason || "trigger policy skipped message").trim() || "trigger policy skipped message",
|
|
6316
|
+
last_trigger: String(triggerDecision.trigger || "").trim() || "trigger_policy",
|
|
6317
|
+
}),
|
|
6318
|
+
);
|
|
6319
|
+
skippedRecords.push({
|
|
6320
|
+
id: selectedRecord.id,
|
|
6321
|
+
reason: String(triggerDecision.reason || "trigger policy skipped message").trim() || "trigger policy skipped message",
|
|
6322
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
6323
|
+
suppressDiagnostic: true,
|
|
6324
|
+
});
|
|
6325
|
+
continue;
|
|
6326
|
+
}
|
|
5995
6327
|
const startupLoopSkipped = await maybeHandleRunnerStartupLoopCandidate({
|
|
5996
6328
|
routeKey,
|
|
5997
6329
|
normalizedRoute,
|
|
@@ -6099,6 +6431,8 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6099
6431
|
archiveThread,
|
|
6100
6432
|
executionPlan,
|
|
6101
6433
|
runtime,
|
|
6434
|
+
triggerDecision,
|
|
6435
|
+
responderAdjudication: inlineAdjudication,
|
|
6102
6436
|
deps: {
|
|
6103
6437
|
saveRunnerRouteState,
|
|
6104
6438
|
startRunnerTypingHeartbeat,
|
|
@@ -6140,6 +6474,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6140
6474
|
outcome: String(processed.result?.outcome || "replied").trim().toLowerCase(),
|
|
6141
6475
|
conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
|
|
6142
6476
|
allowedResponders: ensureArray(processed.result?.conversation_allowed_responders),
|
|
6477
|
+
executionContractType: String(processed.result?.execution_contract_type || "").trim(),
|
|
6478
|
+
executionContractTargets: ensureArray(processed.result?.execution_contract_targets),
|
|
6479
|
+
nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
|
|
6480
|
+
currentBotSelector,
|
|
6481
|
+
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
6143
6482
|
normalizedIntent: String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim(),
|
|
6144
6483
|
});
|
|
6145
6484
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
@@ -8403,6 +8742,8 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8403
8742
|
archiveThread: deferredExecution.archiveThread,
|
|
8404
8743
|
executionPlan: deferredExecution.executionPlan,
|
|
8405
8744
|
runtime: deferredExecution.runtime,
|
|
8745
|
+
triggerDecision: deferredExecution.triggerDecision,
|
|
8746
|
+
responderAdjudication: deferredExecution.responderAdjudication,
|
|
8406
8747
|
deps: {
|
|
8407
8748
|
saveRunnerRouteState,
|
|
8408
8749
|
startRunnerTypingHeartbeat,
|
|
@@ -8473,6 +8814,13 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8473
8814
|
outcome: String(processed.result?.outcome || "replied").trim().toLowerCase(),
|
|
8474
8815
|
conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
|
|
8475
8816
|
allowedResponders: ensureArray(processed.result?.conversation_allowed_responders),
|
|
8817
|
+
executionContractType: String(processed.result?.execution_contract_type || "").trim(),
|
|
8818
|
+
executionContractTargets: ensureArray(processed.result?.execution_contract_targets),
|
|
8819
|
+
nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
|
|
8820
|
+
currentBotSelector: normalizeTelegramMentionUsername(
|
|
8821
|
+
deferredExecution.bot?.username || deferredExecution.bot?.name,
|
|
8822
|
+
),
|
|
8823
|
+
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
8476
8824
|
normalizedIntent: String(safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "").trim(),
|
|
8477
8825
|
});
|
|
8478
8826
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
@@ -12782,6 +13130,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
12782
13130
|
runnerRouteLogicalSignature,
|
|
12783
13131
|
loadBotRunnerState,
|
|
12784
13132
|
saveBotRunnerState,
|
|
13133
|
+
buildRunnerStatusQueryLookup,
|
|
12785
13134
|
tryJsonParse,
|
|
12786
13135
|
safeObject,
|
|
12787
13136
|
normalizeRunnerTriggerPolicy,
|
|
@@ -12791,6 +13140,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
12791
13140
|
processRunnerSelectedRecord,
|
|
12792
13141
|
resolveRunnerStartupLoopAdjudication,
|
|
12793
13142
|
claimRunnerRequestForHumanComment,
|
|
13143
|
+
markRunnerRequestLifecycle,
|
|
12794
13144
|
resolveRunnerContinuationRequestForBotReply,
|
|
12795
13145
|
cleanupBotRunnerRequestState,
|
|
12796
13146
|
runRunnerAIExecution,
|
|
@@ -1703,6 +1703,10 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1703
1703
|
const queryLookup = safePayload.query_lookup && typeof safePayload.query_lookup === "object"
|
|
1704
1704
|
? safePayload.query_lookup
|
|
1705
1705
|
: null;
|
|
1706
|
+
const queryLookupFacts = queryLookup && Object.prototype.hasOwnProperty.call(queryLookup, "facts")
|
|
1707
|
+
? queryLookup.facts
|
|
1708
|
+
: null;
|
|
1709
|
+
const queryLookupResponseMode = String(queryLookup?.response_mode || "").trim().toLowerCase();
|
|
1706
1710
|
const currentTurnPurpose = inferCurrentTurnPurpose({
|
|
1707
1711
|
trigger,
|
|
1708
1712
|
conversation,
|
|
@@ -1777,13 +1781,25 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1777
1781
|
"",
|
|
1778
1782
|
];
|
|
1779
1783
|
if (queryLookup?.handled === true) {
|
|
1780
|
-
lines.push(
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
`-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1784
|
+
lines.push("Tool-assisted factual lookup for this turn:");
|
|
1785
|
+
lines.push(`- Source: ${String(queryLookup.source || "").trim() || "-"}`);
|
|
1786
|
+
if (queryLookupResponseMode) {
|
|
1787
|
+
lines.push(`- Response mode: ${queryLookupResponseMode}`);
|
|
1788
|
+
}
|
|
1789
|
+
if (String(queryLookup.proposed_reply || "").trim()) {
|
|
1790
|
+
lines.push(`- Proposed factual reply: ${String(queryLookup.proposed_reply || "").trim()}`);
|
|
1791
|
+
}
|
|
1792
|
+
if (queryLookupFacts != null) {
|
|
1793
|
+
lines.push("Structured lookup facts:");
|
|
1794
|
+
lines.push(JSON.stringify(queryLookupFacts, null, 2));
|
|
1795
|
+
}
|
|
1796
|
+
if (queryLookupResponseMode === "lookup_only") {
|
|
1797
|
+
lines.push("Use these facts to answer naturally in the user's language.");
|
|
1798
|
+
lines.push("Do not expose raw internal ids, request keys, or exact ISO timestamps unless the user explicitly asks for debug details.");
|
|
1799
|
+
} else {
|
|
1800
|
+
lines.push("If you decide this bot should answer, prefer this factual reply and refine it only as needed.");
|
|
1801
|
+
}
|
|
1802
|
+
lines.push("");
|
|
1787
1803
|
}
|
|
1788
1804
|
if (isInternalExecutionStep) {
|
|
1789
1805
|
const stepIndex = intFromRawAllowZero(executionStep.index, 0);
|