metheus-governance-mcp-cli 0.2.249 → 0.2.252
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 +96 -41
- package/lib/bot-commands.mjs +1 -1
- package/lib/local-ai-adapters.mjs +30 -2
- package/lib/runner-delivery.mjs +17 -0
- package/lib/runner-helpers.mjs +112 -0
- package/lib/runner-orchestration.mjs +86 -24
- package/lib/runner-runtime.mjs +53 -1
- package/lib/runner-trigger.mjs +7 -14
- package/lib/selftest-runner-scenarios.mjs +368 -8
- package/lib/selftest-telegram-e2e.mjs +45 -11
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -140,6 +140,8 @@ import {
|
|
|
140
140
|
buildRunnerRouteStateFromComment,
|
|
141
141
|
buildProcessableArchiveLogicalKey,
|
|
142
142
|
findEarlierProcessableArchiveDuplicate,
|
|
143
|
+
findRecentTelegramMessageEnvelope,
|
|
144
|
+
isTelegramLocalInboundEnvelopeForRoute,
|
|
143
145
|
isInboundArchiveKind,
|
|
144
146
|
normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
|
|
145
147
|
normalizeArchiveCommentRecord,
|
|
@@ -1989,6 +1991,7 @@ function mergeRunnerStateRecords(preferred, fallback) {
|
|
|
1989
1991
|
last_followup_source_message_envelope: pickObjectField("last_followup_source_message_envelope"),
|
|
1990
1992
|
last_followup_last_reply_message_envelope: pickObjectField("last_followup_last_reply_message_envelope"),
|
|
1991
1993
|
last_followup_attempted_delivery_envelope: pickObjectField("last_followup_attempted_delivery_envelope"),
|
|
1994
|
+
recent_local_inbound_envelopes: pickObjectField("recent_local_inbound_envelopes"),
|
|
1992
1995
|
last_contract_validation_targets: pickArrayField("last_contract_validation_targets", normalizeTelegramMentionUsername),
|
|
1993
1996
|
last_normalized_execution_contract_targets: pickArrayField("last_normalized_execution_contract_targets", normalizeTelegramMentionUsername),
|
|
1994
1997
|
last_normalized_execution_next_responders: pickArrayField("last_normalized_execution_next_responders", normalizeTelegramMentionUsername),
|
|
@@ -2642,6 +2645,45 @@ function normalizeRunnerReplyChainContext(rawContext) {
|
|
|
2642
2645
|
return normalized;
|
|
2643
2646
|
}
|
|
2644
2647
|
|
|
2648
|
+
function findRunnerRouteLocalInboundEnvelope(routeStateRaw, parsedArchiveRaw) {
|
|
2649
|
+
const routeState = safeObject(routeStateRaw);
|
|
2650
|
+
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
2651
|
+
const chatID = String(parsedArchive.chatID || parsedArchive.chatId || "").trim();
|
|
2652
|
+
const messageID = intFromRawAllowZero(parsedArchive.messageID, 0);
|
|
2653
|
+
if (!chatID || !(messageID > 0)) {
|
|
2654
|
+
return {};
|
|
2655
|
+
}
|
|
2656
|
+
return findRecentTelegramMessageEnvelope(routeState.recent_local_inbound_envelopes, {
|
|
2657
|
+
chatID,
|
|
2658
|
+
messageID,
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
function buildRunnerSourceMessageEnvelope({
|
|
2663
|
+
routeState = {},
|
|
2664
|
+
routeKey = "",
|
|
2665
|
+
normalizedRoute = null,
|
|
2666
|
+
parsedArchive = null,
|
|
2667
|
+
}) {
|
|
2668
|
+
const localEnvelope = findRunnerRouteLocalInboundEnvelope(routeState, parsedArchive);
|
|
2669
|
+
const fallbackBotSelector = normalizeTelegramMentionUsername(
|
|
2670
|
+
normalizedRoute?.botName
|
|
2671
|
+
|| normalizedRoute?.serverBotName
|
|
2672
|
+
|| "",
|
|
2673
|
+
);
|
|
2674
|
+
if (isTelegramLocalInboundEnvelopeForRoute(localEnvelope, {
|
|
2675
|
+
routeKey,
|
|
2676
|
+
botUsername: fallbackBotSelector,
|
|
2677
|
+
})) {
|
|
2678
|
+
return localEnvelope;
|
|
2679
|
+
}
|
|
2680
|
+
return buildRunnerTelegramMessageEnvelopeFromParsedArchive(parsedArchive, {
|
|
2681
|
+
source_origin: "archive_reconstructed",
|
|
2682
|
+
source_route_key: String(routeKey || "").trim(),
|
|
2683
|
+
source_bot_username: fallbackBotSelector,
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2645
2687
|
function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
2646
2688
|
const normalized = {};
|
|
2647
2689
|
for (const [requestKeyRaw, entryRaw] of Object.entries(safeObject(rawRequests))) {
|
|
@@ -2676,6 +2718,7 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
2676
2718
|
body: entry.source_message_body || entry.sourceMessageBody,
|
|
2677
2719
|
sender: "human",
|
|
2678
2720
|
sender_is_bot: false,
|
|
2721
|
+
source_origin: "archive_reconstructed",
|
|
2679
2722
|
}
|
|
2680
2723
|
: {}),
|
|
2681
2724
|
),
|
|
@@ -4408,9 +4451,10 @@ async function claimRunnerRequestForHumanComment({
|
|
|
4408
4451
|
reason: "non_human_comment_cannot_create_request",
|
|
4409
4452
|
};
|
|
4410
4453
|
}
|
|
4411
|
-
const currentState = loadBotRunnerState();
|
|
4412
|
-
const
|
|
4413
|
-
|
|
4454
|
+
const currentState = loadBotRunnerState();
|
|
4455
|
+
const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
|
|
4456
|
+
const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
4457
|
+
state: currentState,
|
|
4414
4458
|
normalizedRoute,
|
|
4415
4459
|
selectedRecord,
|
|
4416
4460
|
runtime,
|
|
@@ -4530,14 +4574,19 @@ async function claimRunnerRequestForHumanComment({
|
|
|
4530
4574
|
};
|
|
4531
4575
|
}
|
|
4532
4576
|
const nowISO = new Date().toISOString();
|
|
4533
|
-
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
4577
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
4534
4578
|
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
4535
4579
|
provider: String(normalizedRoute?.provider || "").trim(),
|
|
4536
4580
|
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
4537
4581
|
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
4538
4582
|
source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || undefined,
|
|
4539
4583
|
source_message_body: String(parsed.body || "").trim(),
|
|
4540
|
-
source_message_envelope:
|
|
4584
|
+
source_message_envelope: buildRunnerSourceMessageEnvelope({
|
|
4585
|
+
routeState: currentRouteState,
|
|
4586
|
+
routeKey,
|
|
4587
|
+
normalizedRoute,
|
|
4588
|
+
parsedArchive: parsed,
|
|
4589
|
+
}),
|
|
4541
4590
|
root_comment_id: String(selectedRecord?.id || "").trim(),
|
|
4542
4591
|
root_comment_kind: commentKind,
|
|
4543
4592
|
conversation_id: resolvedConversationID,
|
|
@@ -7552,11 +7601,11 @@ function parseArchivedChatComment(rawBody) {
|
|
|
7552
7601
|
.filter(Boolean),
|
|
7553
7602
|
}
|
|
7554
7603
|
: null;
|
|
7555
|
-
return {
|
|
7556
|
-
kind,
|
|
7557
|
-
header,
|
|
7558
|
-
metadata,
|
|
7559
|
-
body,
|
|
7604
|
+
return {
|
|
7605
|
+
kind,
|
|
7606
|
+
header,
|
|
7607
|
+
metadata,
|
|
7608
|
+
body,
|
|
7560
7609
|
chatID: String(metadata.chat_id || "").trim(),
|
|
7561
7610
|
chatType: String(metadata.chat_type || "").trim().toLowerCase(),
|
|
7562
7611
|
messageID: intFromRawAllowZero(metadata.message_id, 0),
|
|
@@ -7569,8 +7618,11 @@ function parseArchivedChatComment(rawBody) {
|
|
|
7569
7618
|
.split(",")
|
|
7570
7619
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
7571
7620
|
.filter(Boolean),
|
|
7572
|
-
occurredAt: String(metadata.occurred_at || "").trim(),
|
|
7573
|
-
|
|
7621
|
+
occurredAt: String(metadata.occurred_at || "").trim(),
|
|
7622
|
+
sourceOrigin: String(metadata.source_origin || "").trim().toLowerCase(),
|
|
7623
|
+
sourceRouteKey: String(metadata.source_route_key || "").trim(),
|
|
7624
|
+
sourceBotUsername: normalizeTelegramMentionUsername(metadata.source_bot_username || ""),
|
|
7625
|
+
replyToMessageID: intFromRawAllowZero(metadata.reply_to_message_id, 0),
|
|
7574
7626
|
replyToSender: String(metadata.reply_to_sender || "").trim(),
|
|
7575
7627
|
replyToUsername: String(metadata.reply_to_telegram_username || "").trim(),
|
|
7576
7628
|
replyToSenderIsBot: boolFromRaw(metadata.reply_to_sender_is_bot, false),
|
|
@@ -7817,30 +7869,23 @@ function extractTelegramEntityText(text, entity) {
|
|
|
7817
7869
|
return body.slice(offset, offset + length);
|
|
7818
7870
|
}
|
|
7819
7871
|
|
|
7820
|
-
function extractTelegramMentionUsernames(text, entities) {
|
|
7821
|
-
const set = new Set();
|
|
7822
|
-
for (const entityRaw of ensureArray(entities)) {
|
|
7823
|
-
const entity = safeObject(entityRaw);
|
|
7872
|
+
function extractTelegramMentionUsernames(text, entities) {
|
|
7873
|
+
const set = new Set();
|
|
7874
|
+
for (const entityRaw of ensureArray(entities)) {
|
|
7875
|
+
const entity = safeObject(entityRaw);
|
|
7824
7876
|
const type = String(entity.type || "").trim().toLowerCase();
|
|
7825
7877
|
if (type === "mention") {
|
|
7826
7878
|
const username = normalizeTelegramMentionUsername(extractTelegramEntityText(text, entity));
|
|
7827
7879
|
if (username) set.add(username);
|
|
7828
7880
|
continue;
|
|
7829
7881
|
}
|
|
7830
|
-
if (type === "text_mention") {
|
|
7831
|
-
const username = normalizeTelegramMentionUsername(entity.user?.username);
|
|
7832
|
-
if (username) set.add(username);
|
|
7833
|
-
}
|
|
7834
|
-
}
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
while (match) {
|
|
7838
|
-
const username = normalizeTelegramMentionUsername(match[2]);
|
|
7839
|
-
if (username) set.add(username);
|
|
7840
|
-
match = regex.exec(String(text || ""));
|
|
7841
|
-
}
|
|
7842
|
-
return Array.from(set);
|
|
7843
|
-
}
|
|
7882
|
+
if (type === "text_mention") {
|
|
7883
|
+
const username = normalizeTelegramMentionUsername(entity.user?.username);
|
|
7884
|
+
if (username) set.add(username);
|
|
7885
|
+
}
|
|
7886
|
+
}
|
|
7887
|
+
return Array.from(set);
|
|
7888
|
+
}
|
|
7844
7889
|
|
|
7845
7890
|
function normalizeLocalTelegramUpdate(rawUpdate) {
|
|
7846
7891
|
const update = safeObject(rawUpdate);
|
|
@@ -7886,18 +7931,27 @@ function buildArchivedInboundMessageKey(chatID, messageID) {
|
|
|
7886
7931
|
return `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
|
|
7887
7932
|
}
|
|
7888
7933
|
|
|
7889
|
-
function formatTelegramInboundArchiveComment(normalized) {
|
|
7890
|
-
const headerLines = [
|
|
7934
|
+
function formatTelegramInboundArchiveComment(normalized) {
|
|
7935
|
+
const headerLines = [
|
|
7891
7936
|
`[Telegram ${normalized.eventName === "telegram.message.updated" ? "edited" : "message"}]`,
|
|
7892
7937
|
`chat_id: ${normalized.chatID || "<missing>"}`,
|
|
7893
7938
|
`chat_type: ${normalized.chatType || "unknown"}`,
|
|
7894
7939
|
`message_id: ${normalized.messageID || "<missing>"}`,
|
|
7895
7940
|
`occurred_at: ${normalized.occurredAt || new Date().toISOString()}`,
|
|
7896
7941
|
`sender_id: ${normalized.fromID || "<missing>"}`,
|
|
7897
|
-
`sender: ${normalized.fromName || normalized.fromUsername || normalized.fromID || "unknown"}`,
|
|
7898
|
-
`sender_is_bot: ${normalized.fromIsBot ? "true" : "false"}`,
|
|
7899
|
-
];
|
|
7900
|
-
if (normalized.
|
|
7942
|
+
`sender: ${normalized.fromName || normalized.fromUsername || normalized.fromID || "unknown"}`,
|
|
7943
|
+
`sender_is_bot: ${normalized.fromIsBot ? "true" : "false"}`,
|
|
7944
|
+
];
|
|
7945
|
+
if (String(normalized.sourceOrigin || "").trim()) {
|
|
7946
|
+
headerLines.push(`source_origin: ${String(normalized.sourceOrigin || "").trim()}`);
|
|
7947
|
+
}
|
|
7948
|
+
if (String(normalized.sourceRouteKey || "").trim()) {
|
|
7949
|
+
headerLines.push(`source_route_key: ${String(normalized.sourceRouteKey || "").trim()}`);
|
|
7950
|
+
}
|
|
7951
|
+
if (String(normalized.sourceBotUsername || "").trim()) {
|
|
7952
|
+
headerLines.push(`source_bot_username: @${String(normalized.sourceBotUsername || "").trim().replace(/^@+/, "")}`);
|
|
7953
|
+
}
|
|
7954
|
+
if (normalized.fromUsername) {
|
|
7901
7955
|
headerLines.push(`telegram_username: @${normalized.fromUsername.replace(/^@+/, "")}`);
|
|
7902
7956
|
}
|
|
7903
7957
|
if (normalized.mentionUsernames.length > 0) {
|
|
@@ -17482,11 +17536,12 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
17482
17536
|
mergeServerRunnerRequestLedgerIntoLocalState,
|
|
17483
17537
|
buildRunnerStatusQueryLookup,
|
|
17484
17538
|
tryJsonParse,
|
|
17485
|
-
safeObject,
|
|
17486
|
-
normalizeRunnerTriggerPolicy,
|
|
17487
|
-
evaluateTelegramRunnerTrigger,
|
|
17488
|
-
|
|
17489
|
-
|
|
17539
|
+
safeObject,
|
|
17540
|
+
normalizeRunnerTriggerPolicy,
|
|
17541
|
+
evaluateTelegramRunnerTrigger,
|
|
17542
|
+
resolveHumanIntentContext,
|
|
17543
|
+
resolveRunnerResponderAdjudication,
|
|
17544
|
+
selectPendingArchiveComments,
|
|
17490
17545
|
selectRunnerPendingWork,
|
|
17491
17546
|
processRunnerSelectedRecord,
|
|
17492
17547
|
resolveRunnerStartupLoopAdjudication,
|
package/lib/bot-commands.mjs
CHANGED
|
@@ -2669,7 +2669,7 @@ function suggestedAIModelsForClient(clientName) {
|
|
|
2669
2669
|
{ value: "Opus 4.6", label: "Opus 4.6", description: "display label; runs as opus" },
|
|
2670
2670
|
];
|
|
2671
2671
|
}
|
|
2672
|
-
|
|
2672
|
+
if (normalizedClient === "gemini") {
|
|
2673
2673
|
return [
|
|
2674
2674
|
{ value: "gemini-3.1-pro", label: "gemini-3.1-pro", description: "display label; runs as auto-gemini-3" },
|
|
2675
2675
|
];
|
|
@@ -18,6 +18,7 @@ const GEMINI_HOME_SYNC_FILES = [
|
|
|
18
18
|
"settings.json",
|
|
19
19
|
];
|
|
20
20
|
const GEMINI_STDIN_BRIDGE_PROMPT = "Use the full task provided on standard input as the authoritative prompt. Follow it exactly and output only the final answer.";
|
|
21
|
+
const GEMINI_CLI_TIMEOUT_MS = 90 * 1000;
|
|
21
22
|
const LOCAL_AI_MODEL_MAPPINGS = {
|
|
22
23
|
gpt: [
|
|
23
24
|
{
|
|
@@ -769,13 +770,36 @@ function runGeminiRawText({ promptText, workspaceDir, model, permissionMode, rea
|
|
|
769
770
|
env: runtime.env,
|
|
770
771
|
input: String(promptText || ""),
|
|
771
772
|
maxBuffer: 8 * 1024 * 1024,
|
|
773
|
+
timeout: GEMINI_CLI_TIMEOUT_MS,
|
|
772
774
|
},
|
|
773
775
|
);
|
|
774
776
|
if (result.error) {
|
|
777
|
+
const errorCode = String(result.error?.code || "").trim().toUpperCase();
|
|
778
|
+
if (errorCode === "ETIMEDOUT") {
|
|
779
|
+
const stderrText = String(result.stderr || "").trim();
|
|
780
|
+
const stdoutText = String(result.stdout || "").trim();
|
|
781
|
+
const details = [stderrText, stdoutText]
|
|
782
|
+
.filter(Boolean)
|
|
783
|
+
.map((value) => value.replace(/\s+/g, " ").trim())
|
|
784
|
+
.filter(Boolean)
|
|
785
|
+
.slice(0, 2)
|
|
786
|
+
.join(" | ");
|
|
787
|
+
throw new Error(
|
|
788
|
+
details
|
|
789
|
+
? `Gemini CLI timed out after ${Math.round(GEMINI_CLI_TIMEOUT_MS / 1000)}s while waiting for a model response (${details})`
|
|
790
|
+
: `Gemini CLI timed out after ${Math.round(GEMINI_CLI_TIMEOUT_MS / 1000)}s while waiting for a model response`,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
775
793
|
throw new Error(String(result.error?.message || result.error));
|
|
776
794
|
}
|
|
777
795
|
if (result.status !== 0) {
|
|
778
|
-
|
|
796
|
+
const stderrText = String(result.stderr || "");
|
|
797
|
+
const stdoutText = String(result.stdout || "");
|
|
798
|
+
const combinedText = `${stderrText}\n${stdoutText}`.trim();
|
|
799
|
+
if (/MODEL_CAPACITY_EXHAUSTED|No capacity available for model/i.test(combinedText)) {
|
|
800
|
+
throw new Error(`Gemini model capacity is currently unavailable for ${String(model || "").trim() || "the configured model"}`);
|
|
801
|
+
}
|
|
802
|
+
throw new Error(combinedText || `gemini exited with status ${result.status}`);
|
|
779
803
|
}
|
|
780
804
|
return String(result.stdout || "");
|
|
781
805
|
} finally {
|
|
@@ -1377,7 +1401,8 @@ function buildGeminiArgs({ model, permissionMode }) {
|
|
|
1377
1401
|
"--allowed-mcp-server-names",
|
|
1378
1402
|
"none",
|
|
1379
1403
|
];
|
|
1380
|
-
|
|
1404
|
+
const normalizedModel = normalizeModelAliasText(model);
|
|
1405
|
+
if (model && normalizedModel !== "auto-gemini-3") {
|
|
1381
1406
|
args.push("--model", model);
|
|
1382
1407
|
}
|
|
1383
1408
|
return args;
|
|
@@ -1433,6 +1458,9 @@ function buildGeminiThinkingConfig(model, reasoningEffort) {
|
|
|
1433
1458
|
|
|
1434
1459
|
export function resolveGeminiReasoningConfig(rawModelValue = "", reasoningEffort = "medium") {
|
|
1435
1460
|
const executionModel = resolveLocalAIExecutionModel("gemini", rawModelValue);
|
|
1461
|
+
if (!executionModel || executionModel === "auto-gemini-3") {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1436
1464
|
const thinkingConfig = buildGeminiThinkingConfig(executionModel, reasoningEffort);
|
|
1437
1465
|
if (!thinkingConfig) {
|
|
1438
1466
|
return null;
|
package/lib/runner-delivery.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { deliverLocalProviderMessage } from "./provider-local-transport.mjs";
|
|
2
2
|
import { normalizeBotProviderName } from "./provider-support.mjs";
|
|
3
|
+
import { normalizeTelegramMessageEnvelope } from "./runner-helpers.mjs";
|
|
3
4
|
|
|
4
5
|
function safeObject(value) {
|
|
5
6
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -71,6 +72,12 @@ function normalizeArchiveCommentRecord(rawComment, parseArchivedChatComment) {
|
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function telegramReplySourceEnvelopeIsLocal(rawEnvelope) {
|
|
76
|
+
const envelope = normalizeTelegramMessageEnvelope(rawEnvelope);
|
|
77
|
+
return String(envelope.source_origin || "").trim().toLowerCase() === "local_telegram_inbound"
|
|
78
|
+
&& intFromRawAllowZero(envelope.message_id, 0) > 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
function normalizeExecutionContractForArchive(rawContract) {
|
|
75
82
|
const contract = safeObject(rawContract);
|
|
76
83
|
if (!Object.keys(contract).length) {
|
|
@@ -274,6 +281,7 @@ export async function performLocalBotDelivery({
|
|
|
274
281
|
disableWebPagePreview,
|
|
275
282
|
messageThreadID,
|
|
276
283
|
replyToMessageID,
|
|
284
|
+
sourceMessageEnvelope = {},
|
|
277
285
|
archiveReplies = true,
|
|
278
286
|
archiveDedupeOutbound = true,
|
|
279
287
|
archiveThreadID = "",
|
|
@@ -295,6 +303,7 @@ export async function performLocalBotDelivery({
|
|
|
295
303
|
const parseArchivedChatComment = requireDependency(deps, "parseArchivedChatComment");
|
|
296
304
|
|
|
297
305
|
const normalizedProvider = normalizeBotProviderName(provider, "telegram");
|
|
306
|
+
const normalizedSourceMessageEnvelope = normalizeTelegramMessageEnvelope(sourceMessageEnvelope);
|
|
298
307
|
const destinations = await listProjectChatDestinations({
|
|
299
308
|
siteBaseURL,
|
|
300
309
|
projectID,
|
|
@@ -332,6 +341,14 @@ export async function performLocalBotDelivery({
|
|
|
332
341
|
replySupported: normalizedProvider === "telegram",
|
|
333
342
|
};
|
|
334
343
|
} else {
|
|
344
|
+
if (
|
|
345
|
+
normalizedProvider === "telegram"
|
|
346
|
+
&& intFromRawAllowZero(replyToMessageID, 0) > 0
|
|
347
|
+
&& !telegramReplySourceEnvelopeIsLocal(normalizedSourceMessageEnvelope)
|
|
348
|
+
) {
|
|
349
|
+
const origin = String(normalizedSourceMessageEnvelope.source_origin || "unknown").trim() || "unknown";
|
|
350
|
+
throw new Error(`telegram reply anchor is not local to this route (${origin})`);
|
|
351
|
+
}
|
|
335
352
|
delivery = await deliverLocalProviderMessage({
|
|
336
353
|
provider: normalizedProvider,
|
|
337
354
|
token: providerEnv.token,
|
package/lib/runner-helpers.mjs
CHANGED
|
@@ -68,6 +68,21 @@ export function normalizeTelegramMessageEnvelope(rawEnvelope) {
|
|
|
68
68
|
|| rawSenderIsBot === "1"
|
|
69
69
|
|| kind === "bot_reply";
|
|
70
70
|
const body = String(envelope.body || envelope.text || "").trim();
|
|
71
|
+
const sourceOrigin = String(
|
|
72
|
+
envelope.source_origin
|
|
73
|
+
|| envelope.sourceOrigin
|
|
74
|
+
|| "",
|
|
75
|
+
).trim().toLowerCase();
|
|
76
|
+
const sourceRouteKey = String(
|
|
77
|
+
envelope.source_route_key
|
|
78
|
+
|| envelope.sourceRouteKey
|
|
79
|
+
|| "",
|
|
80
|
+
).trim();
|
|
81
|
+
const sourceBotUsername = normalizeMentionSelector(
|
|
82
|
+
envelope.source_bot_username
|
|
83
|
+
|| envelope.sourceBotUsername
|
|
84
|
+
|| "",
|
|
85
|
+
);
|
|
71
86
|
if (
|
|
72
87
|
!chatID
|
|
73
88
|
&& !(messageID > 0)
|
|
@@ -77,6 +92,9 @@ export function normalizeTelegramMessageEnvelope(rawEnvelope) {
|
|
|
77
92
|
&& !sender
|
|
78
93
|
&& !senderUsername
|
|
79
94
|
&& !body
|
|
95
|
+
&& !sourceOrigin
|
|
96
|
+
&& !sourceRouteKey
|
|
97
|
+
&& !sourceBotUsername
|
|
80
98
|
) {
|
|
81
99
|
return {};
|
|
82
100
|
}
|
|
@@ -90,6 +108,9 @@ export function normalizeTelegramMessageEnvelope(rawEnvelope) {
|
|
|
90
108
|
...(senderUsername ? { sender_username: senderUsername } : {}),
|
|
91
109
|
sender_is_bot: senderIsBot === true,
|
|
92
110
|
...(body ? { body } : {}),
|
|
111
|
+
...(sourceOrigin ? { source_origin: sourceOrigin } : {}),
|
|
112
|
+
...(sourceRouteKey ? { source_route_key: sourceRouteKey } : {}),
|
|
113
|
+
...(sourceBotUsername ? { source_bot_username: sourceBotUsername } : {}),
|
|
93
114
|
};
|
|
94
115
|
}
|
|
95
116
|
|
|
@@ -119,6 +140,21 @@ export function buildTelegramMessageEnvelopeFromParsedArchive(parsedArchiveRaw,
|
|
|
119
140
|
]),
|
|
120
141
|
sender_is_bot: overrides.sender_is_bot ?? parsedArchive.senderIsBot,
|
|
121
142
|
body: firstNonEmptyString([overrides.body, parsedArchive.body]),
|
|
143
|
+
source_origin: firstNonEmptyString([
|
|
144
|
+
overrides.source_origin,
|
|
145
|
+
parsedArchive.sourceOrigin,
|
|
146
|
+
parsedArchive.metadata?.source_origin,
|
|
147
|
+
]),
|
|
148
|
+
source_route_key: firstNonEmptyString([
|
|
149
|
+
overrides.source_route_key,
|
|
150
|
+
parsedArchive.sourceRouteKey,
|
|
151
|
+
parsedArchive.metadata?.source_route_key,
|
|
152
|
+
]),
|
|
153
|
+
source_bot_username: firstNonEmptyString([
|
|
154
|
+
overrides.source_bot_username,
|
|
155
|
+
parsedArchive.sourceBotUsername,
|
|
156
|
+
parsedArchive.metadata?.source_bot_username,
|
|
157
|
+
]),
|
|
122
158
|
});
|
|
123
159
|
}
|
|
124
160
|
|
|
@@ -155,6 +191,60 @@ export function buildTelegramBotReplyEnvelope({
|
|
|
155
191
|
});
|
|
156
192
|
}
|
|
157
193
|
|
|
194
|
+
export function buildTelegramMessageEnvelopeKey(rawEnvelopeOrChatID, rawMessageID = undefined) {
|
|
195
|
+
const envelope = rawMessageID === undefined
|
|
196
|
+
? normalizeTelegramMessageEnvelope(rawEnvelopeOrChatID)
|
|
197
|
+
: normalizeTelegramMessageEnvelope({
|
|
198
|
+
chat_id: rawEnvelopeOrChatID,
|
|
199
|
+
message_id: rawMessageID,
|
|
200
|
+
});
|
|
201
|
+
const chatID = String(envelope.chat_id || "").trim();
|
|
202
|
+
const messageID = intFromRawAllowZero(envelope.message_id, 0);
|
|
203
|
+
if (!chatID || !(messageID > 0)) {
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
return `${chatID}:${messageID}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function findRecentTelegramMessageEnvelope(rawMap, { chatID = "", messageID = 0 } = {}) {
|
|
210
|
+
const envelopeKey = buildTelegramMessageEnvelopeKey(chatID, messageID);
|
|
211
|
+
if (!envelopeKey) {
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
return normalizeTelegramMessageEnvelope(safeObject(safeObject(rawMap)[envelopeKey]));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function mergeRecentTelegramMessageEnvelopes(rawMap, incomingEnvelopes, limit = 128) {
|
|
218
|
+
const merged = new Map();
|
|
219
|
+
for (const [key, value] of Object.entries(safeObject(rawMap))) {
|
|
220
|
+
const normalized = normalizeTelegramMessageEnvelope(value);
|
|
221
|
+
const normalizedKey = buildTelegramMessageEnvelopeKey(normalized) || String(key || "").trim();
|
|
222
|
+
if (!normalizedKey || !Object.keys(normalized).length) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
merged.set(normalizedKey, normalized);
|
|
226
|
+
}
|
|
227
|
+
for (const rawEnvelope of ensureArray(incomingEnvelopes)) {
|
|
228
|
+
const normalized = normalizeTelegramMessageEnvelope(rawEnvelope);
|
|
229
|
+
const normalizedKey = buildTelegramMessageEnvelopeKey(normalized);
|
|
230
|
+
if (!normalizedKey || !Object.keys(normalized).length) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (merged.has(normalizedKey)) {
|
|
234
|
+
merged.delete(normalizedKey);
|
|
235
|
+
}
|
|
236
|
+
merged.set(normalizedKey, normalized);
|
|
237
|
+
}
|
|
238
|
+
while (merged.size > Math.max(1, intFromRawAllowZero(limit, 128))) {
|
|
239
|
+
const oldestKey = merged.keys().next().value;
|
|
240
|
+
if (!oldestKey) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
merged.delete(oldestKey);
|
|
244
|
+
}
|
|
245
|
+
return Object.fromEntries(merged.entries());
|
|
246
|
+
}
|
|
247
|
+
|
|
158
248
|
function normalizePendingSelectionOptions(rawOptions) {
|
|
159
249
|
const options = safeObject(rawOptions);
|
|
160
250
|
const maxPendingAgeMs = intFromRawAllowZero(options.maxPendingAgeMs, 0);
|
|
@@ -194,6 +284,28 @@ function normalizeMentionSelector(rawValue) {
|
|
|
194
284
|
return String(rawValue || "").trim().replace(/^@+/, "").toLowerCase();
|
|
195
285
|
}
|
|
196
286
|
|
|
287
|
+
export function isTelegramLocalInboundEnvelopeForRoute(rawEnvelope, {
|
|
288
|
+
routeKey = "",
|
|
289
|
+
botUsername = "",
|
|
290
|
+
} = {}) {
|
|
291
|
+
const envelope = normalizeTelegramMessageEnvelope(rawEnvelope);
|
|
292
|
+
const sourceOrigin = String(envelope.source_origin || "").trim().toLowerCase();
|
|
293
|
+
if (sourceOrigin !== "local_telegram_inbound") {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const expectedRouteKey = String(routeKey || "").trim();
|
|
297
|
+
const actualRouteKey = String(envelope.source_route_key || "").trim();
|
|
298
|
+
if (expectedRouteKey && expectedRouteKey !== actualRouteKey) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const expectedBotUsername = normalizeMentionSelector(botUsername);
|
|
302
|
+
const actualBotUsername = normalizeMentionSelector(envelope.source_bot_username || "");
|
|
303
|
+
if (expectedBotUsername && expectedBotUsername !== actualBotUsername) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
197
309
|
function uniqueNormalizedSelectors(values) {
|
|
198
310
|
return Array.from(new Set(
|
|
199
311
|
ensureArray(values)
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
buildRunnerRouteStateFromComment,
|
|
8
8
|
compareArchiveCommentRecords,
|
|
9
9
|
dedupeProcessableArchiveComments,
|
|
10
|
+
findRecentTelegramMessageEnvelope,
|
|
11
|
+
isTelegramLocalInboundEnvelopeForRoute,
|
|
10
12
|
isInboundArchiveKind,
|
|
11
13
|
normalizeTelegramMessageEnvelope,
|
|
12
14
|
selectPendingArchiveComments,
|
|
@@ -376,11 +378,59 @@ function normalizeBoundaryViolations(rawViolations) {
|
|
|
376
378
|
.filter(Boolean);
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
function normalizeMentionSelector(value) {
|
|
380
|
-
return String(value || "").trim().replace(/^@+/, "").toLowerCase();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function
|
|
381
|
+
function normalizeMentionSelector(value) {
|
|
382
|
+
return String(value || "").trim().replace(/^@+/, "").toLowerCase();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resolveRunnerDeliverySourceMessageEnvelope({
|
|
386
|
+
routeState,
|
|
387
|
+
persistedRequest,
|
|
388
|
+
selectedRecord,
|
|
389
|
+
routeKey,
|
|
390
|
+
currentBotSelector,
|
|
391
|
+
}) {
|
|
392
|
+
const archiveEnvelope = buildTelegramMessageEnvelopeFromParsedArchive(selectedRecord?.parsedArchive, {
|
|
393
|
+
source_origin: "archive_reconstructed",
|
|
394
|
+
source_route_key: String(routeKey || "").trim(),
|
|
395
|
+
source_bot_username: currentBotSelector,
|
|
396
|
+
});
|
|
397
|
+
const archiveChatID = String(archiveEnvelope.chat_id || "").trim();
|
|
398
|
+
const archiveMessageID = intFromRawAllowZero(archiveEnvelope.message_id, 0);
|
|
399
|
+
const routeLocalEnvelope = findRecentTelegramMessageEnvelope(
|
|
400
|
+
safeObject(routeState).recent_local_inbound_envelopes,
|
|
401
|
+
{
|
|
402
|
+
chatID: archiveChatID,
|
|
403
|
+
messageID: archiveMessageID,
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
if (isTelegramLocalInboundEnvelopeForRoute(routeLocalEnvelope, {
|
|
407
|
+
routeKey,
|
|
408
|
+
botUsername: currentBotSelector,
|
|
409
|
+
})) {
|
|
410
|
+
return routeLocalEnvelope;
|
|
411
|
+
}
|
|
412
|
+
const persistedSourceEnvelope = normalizeTelegramMessageEnvelope(
|
|
413
|
+
safeObject(persistedRequest).source_message_envelope
|
|
414
|
+
|| safeObject(persistedRequest).sourceMessageEnvelope,
|
|
415
|
+
);
|
|
416
|
+
const persistedChatID = String(persistedSourceEnvelope.chat_id || "").trim();
|
|
417
|
+
const persistedMessageID = intFromRawAllowZero(persistedSourceEnvelope.message_id, 0);
|
|
418
|
+
if (
|
|
419
|
+
isTelegramLocalInboundEnvelopeForRoute(persistedSourceEnvelope, {
|
|
420
|
+
routeKey,
|
|
421
|
+
botUsername: currentBotSelector,
|
|
422
|
+
})
|
|
423
|
+
&& archiveChatID
|
|
424
|
+
&& archiveChatID === persistedChatID
|
|
425
|
+
&& archiveMessageID > 0
|
|
426
|
+
&& archiveMessageID === persistedMessageID
|
|
427
|
+
) {
|
|
428
|
+
return persistedSourceEnvelope;
|
|
429
|
+
}
|
|
430
|
+
return archiveEnvelope;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function escapeRegExp(text) {
|
|
384
434
|
return String(text || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
385
435
|
}
|
|
386
436
|
|
|
@@ -3338,21 +3388,22 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
3338
3388
|
};
|
|
3339
3389
|
}
|
|
3340
3390
|
|
|
3341
|
-
function buildConversationPeerMap(bot, normalizedRoute, deps) {
|
|
3342
|
-
const peers = typeof deps?.resolveConversationPeerBots === "function"
|
|
3343
|
-
? ensureArray(deps.resolveConversationPeerBots(normalizedRoute))
|
|
3344
|
-
: [];
|
|
3345
|
-
const output = new Map();
|
|
3346
|
-
const register = (peerRaw) => {
|
|
3347
|
-
const peer = safeObject(peerRaw);
|
|
3348
|
-
const displayName = String(peer.name || peer.username || peer.id || "").trim();
|
|
3349
|
-
const
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3391
|
+
function buildConversationPeerMap(bot, normalizedRoute, deps) {
|
|
3392
|
+
const peers = typeof deps?.resolveConversationPeerBots === "function"
|
|
3393
|
+
? ensureArray(deps.resolveConversationPeerBots(normalizedRoute))
|
|
3394
|
+
: [];
|
|
3395
|
+
const output = new Map();
|
|
3396
|
+
const register = (peerRaw) => {
|
|
3397
|
+
const peer = safeObject(peerRaw);
|
|
3398
|
+
const displayName = String(peer.name || peer.username || peer.id || "").trim();
|
|
3399
|
+
const explicitUsername = normalizeMentionSelector(peer.username);
|
|
3400
|
+
const selectors = uniqueOrdered([
|
|
3401
|
+
explicitUsername,
|
|
3402
|
+
explicitUsername ? "" : normalizeMentionSelector(peer.name),
|
|
3403
|
+
]);
|
|
3404
|
+
for (const selector of selectors) {
|
|
3405
|
+
output.set(selector, {
|
|
3406
|
+
selector,
|
|
3356
3407
|
displayName: displayName || selector,
|
|
3357
3408
|
id: String(peer.id || "").trim(),
|
|
3358
3409
|
});
|
|
@@ -4720,10 +4771,18 @@ export async function processRunnerSelectedRecord({
|
|
|
4720
4771
|
safeObject(persistedHumanIntentRequest).reply_chain_context
|
|
4721
4772
|
|| safeObject(persistedHumanIntentRequest).replyChainContext,
|
|
4722
4773
|
);
|
|
4723
|
-
const
|
|
4774
|
+
const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
|
|
4775
|
+
const sourceMessageEnvelope = resolveRunnerDeliverySourceMessageEnvelope({
|
|
4776
|
+
routeState,
|
|
4777
|
+
persistedRequest: persistedHumanIntentRequest,
|
|
4778
|
+
selectedRecord,
|
|
4779
|
+
routeKey,
|
|
4780
|
+
currentBotSelector,
|
|
4781
|
+
});
|
|
4724
4782
|
const replyMessageThreadID = intFromRawAllowZero(sourceMessageEnvelope.message_thread_id, 0);
|
|
4725
4783
|
const replyToMessageID = intFromRawAllowZero(sourceMessageEnvelope.message_id, 0);
|
|
4726
|
-
const replyAnchorSource =
|
|
4784
|
+
const replyAnchorSource = String(sourceMessageEnvelope.source_origin || "").trim()
|
|
4785
|
+
|| (replyToMessageID > 0 ? "source_message_envelope" : "");
|
|
4727
4786
|
const normalizedPrecomputedHumanIntentContext = safeObject(precomputedHumanIntentContext);
|
|
4728
4787
|
const validateWorkspaceArtifacts = typeof executionDeps.validateWorkspaceArtifacts === "function"
|
|
4729
4788
|
? executionDeps.validateWorkspaceArtifacts
|
|
@@ -4734,7 +4793,6 @@ export async function processRunnerSelectedRecord({
|
|
|
4734
4793
|
const resolveInformationalQueryReply = typeof executionDeps.resolveInformationalQueryReply === "function"
|
|
4735
4794
|
? executionDeps.resolveInformationalQueryReply
|
|
4736
4795
|
: null;
|
|
4737
|
-
const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
|
|
4738
4796
|
const triggerDecision = safeObject(precomputedTriggerDecision);
|
|
4739
4797
|
const effectiveTriggerDecision = typeof triggerDecision.shouldRespond === "boolean"
|
|
4740
4798
|
? triggerDecision
|
|
@@ -4884,7 +4942,10 @@ export async function processRunnerSelectedRecord({
|
|
|
4884
4942
|
&& !precomputedConversationResponderSelection;
|
|
4885
4943
|
if (!currentBotSelected && !shouldDeferBotReplyConversationAuthorization) {
|
|
4886
4944
|
const adjudicationDecision = String(responderAdjudication.decision || "").trim() || "no_responder";
|
|
4887
|
-
const
|
|
4945
|
+
const rawAdjudicationReason = String(responderAdjudication.reason_code || "").trim() || "not_selected_by_adjudicator";
|
|
4946
|
+
const adjudicationReason = rawAdjudicationReason === "precomputed_human_intent_contract" && selectedResponderSelectors.length > 0
|
|
4947
|
+
? "precomputed_human_intent_contract_selected_other_responder"
|
|
4948
|
+
: rawAdjudicationReason;
|
|
4888
4949
|
saveRunnerRouteState(
|
|
4889
4950
|
routeKey,
|
|
4890
4951
|
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
@@ -5592,6 +5653,7 @@ export async function processRunnerSelectedRecord({
|
|
|
5592
5653
|
disableWebPagePreview: true,
|
|
5593
5654
|
messageThreadID: replyMessageThreadID,
|
|
5594
5655
|
replyToMessageID,
|
|
5656
|
+
sourceMessageEnvelope: sourceMessageEnvelope,
|
|
5595
5657
|
archiveReplies: normalizedRoute.archivePolicy.mirrorReplies,
|
|
5596
5658
|
archiveDedupeOutbound: normalizedRoute.archivePolicy.dedupeOutbound,
|
|
5597
5659
|
archiveThreadID: archiveThread.threadID,
|
package/lib/runner-runtime.mjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeRecentTelegramMessageEnvelopes,
|
|
3
|
+
normalizeTelegramMessageEnvelope,
|
|
4
|
+
} from "./runner-helpers.mjs";
|
|
5
|
+
|
|
1
6
|
function safeObject(value) {
|
|
2
7
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
8
|
return {};
|
|
@@ -198,6 +203,40 @@ function mergeRunnerSharedInboxUpdates(existingUpdates, incomingUpdates) {
|
|
|
198
203
|
.slice(-500);
|
|
199
204
|
}
|
|
200
205
|
|
|
206
|
+
const RUNNER_RECENT_LOCAL_INBOUND_ENVELOPE_LIMIT = 200;
|
|
207
|
+
|
|
208
|
+
function buildRunnerRecentLocalInboundEnvelopes(routeStateRaw, updates, routeKey, bot, destination) {
|
|
209
|
+
const routeState = safeObject(routeStateRaw);
|
|
210
|
+
const currentBotSelector = normalizeMentionSelector(
|
|
211
|
+
bot?.username
|
|
212
|
+
|| bot?.name
|
|
213
|
+
|| "",
|
|
214
|
+
);
|
|
215
|
+
const relevantEnvelopes = ensureArray(updates)
|
|
216
|
+
.filter((update) => String(update.chatID || "").trim() === String(destination?.chatID || "").trim())
|
|
217
|
+
.filter((update) => String(update.text || "").trim())
|
|
218
|
+
.map((update) => normalizeTelegramMessageEnvelope({
|
|
219
|
+
chat_id: update.chatID,
|
|
220
|
+
message_id: update.messageID,
|
|
221
|
+
message_thread_id: update.messageThreadID,
|
|
222
|
+
reply_to_message_id: update.replyToMessageID,
|
|
223
|
+
kind: update.fromIsBot ? "bot_reply" : "telegram_message",
|
|
224
|
+
sender: update.fromName,
|
|
225
|
+
sender_username: update.fromUsername,
|
|
226
|
+
sender_is_bot: update.fromIsBot === true,
|
|
227
|
+
body: update.text,
|
|
228
|
+
source_origin: "local_telegram_inbound",
|
|
229
|
+
source_route_key: String(routeKey || "").trim(),
|
|
230
|
+
source_bot_username: currentBotSelector,
|
|
231
|
+
}))
|
|
232
|
+
.filter((envelope) => Object.keys(envelope).length > 0);
|
|
233
|
+
return mergeRecentTelegramMessageEnvelopes(
|
|
234
|
+
routeState.recent_local_inbound_envelopes,
|
|
235
|
+
relevantEnvelopes,
|
|
236
|
+
RUNNER_RECENT_LOCAL_INBOUND_ENVELOPE_LIMIT,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
201
240
|
const RUNNER_INBOUND_ARCHIVE_RESERVATION_TTL_MS = 10 * 60 * 1000;
|
|
202
241
|
const runnerInboundArchiveReservations = new Map();
|
|
203
242
|
|
|
@@ -470,6 +509,13 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
470
509
|
.sort((left, right) => intFromRawAllowZero(left.updateID, 0) - intFromRawAllowZero(right.updateID, 0));
|
|
471
510
|
let handledUpdateID = lastUpdateID;
|
|
472
511
|
const mergedSharedUpdates = mergeRunnerSharedInboxUpdates(sharedInbox.updates, updates);
|
|
512
|
+
const recentLocalInboundEnvelopes = buildRunnerRecentLocalInboundEnvelopes(
|
|
513
|
+
routeState,
|
|
514
|
+
mergedSharedUpdates,
|
|
515
|
+
routeKey,
|
|
516
|
+
bot,
|
|
517
|
+
destination,
|
|
518
|
+
);
|
|
473
519
|
|
|
474
520
|
const persistPollingProgress = (remainingSharedUpdates = []) => {
|
|
475
521
|
if (sharedInboxKey && saveBotRunnerState) {
|
|
@@ -489,6 +535,7 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
489
535
|
local_telegram_polling_ready: true,
|
|
490
536
|
last_provider_update_id: handledUpdateID,
|
|
491
537
|
last_local_poll_at: new Date().toISOString(),
|
|
538
|
+
recent_local_inbound_envelopes: recentLocalInboundEnvelopes,
|
|
492
539
|
});
|
|
493
540
|
};
|
|
494
541
|
|
|
@@ -564,7 +611,12 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
564
611
|
timeoutSeconds: runtime.timeoutSeconds,
|
|
565
612
|
threadID: archiveThread.threadID,
|
|
566
613
|
actorUserID: runtime.actor.user_id,
|
|
567
|
-
body: formatTelegramInboundArchiveComment(
|
|
614
|
+
body: formatTelegramInboundArchiveComment({
|
|
615
|
+
...update,
|
|
616
|
+
sourceOrigin: "local_telegram_inbound",
|
|
617
|
+
sourceRouteKey: String(routeKey || "").trim(),
|
|
618
|
+
sourceBotUsername: normalizeMentionSelector(bot?.username || bot?.name),
|
|
619
|
+
}),
|
|
568
620
|
});
|
|
569
621
|
} catch (err) {
|
|
570
622
|
releaseRunnerInboundArchiveMessageReservation(reservation.reservationKey);
|
package/lib/runner-trigger.mjs
CHANGED
|
@@ -35,19 +35,11 @@ function normalizeTelegramMentionUsername(rawValue) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function listTelegramMentionUsernames(parsedArchive) {
|
|
38
|
-
|
|
38
|
+
return uniqueOrdered(
|
|
39
39
|
ensureArray(parsedArchive?.mentionUsernames)
|
|
40
40
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
41
41
|
.filter(Boolean),
|
|
42
42
|
);
|
|
43
|
-
const text = String(parsedArchive?.body || "").trim().toLowerCase();
|
|
44
|
-
for (const match of text.matchAll(/@([a-z0-9_]{3,})/gi)) {
|
|
45
|
-
const normalized = normalizeTelegramMentionUsername(match?.[1]);
|
|
46
|
-
if (normalized) {
|
|
47
|
-
usernames.add(normalized);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return Array.from(usernames);
|
|
51
43
|
}
|
|
52
44
|
|
|
53
45
|
function inferTelegramArchiveChatType(parsedArchive) {
|
|
@@ -60,10 +52,13 @@ function inferTelegramArchiveChatType(parsedArchive) {
|
|
|
60
52
|
|
|
61
53
|
function buildTelegramBotUsernameCandidates(bot, route) {
|
|
62
54
|
const set = new Set();
|
|
55
|
+
const explicitUsername = normalizeTelegramMentionUsername(bot?.username);
|
|
56
|
+
const fallbackDisplayCandidates = explicitUsername
|
|
57
|
+
? []
|
|
58
|
+
: [bot?.name, route?.botName];
|
|
63
59
|
for (const candidate of [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
route?.botName,
|
|
60
|
+
explicitUsername,
|
|
61
|
+
...fallbackDisplayCandidates,
|
|
67
62
|
]) {
|
|
68
63
|
const normalized = normalizeTelegramMentionUsername(candidate);
|
|
69
64
|
if (normalized) {
|
|
@@ -74,13 +69,11 @@ function buildTelegramBotUsernameCandidates(bot, route) {
|
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
function doesTelegramArchiveMentionBot(parsedArchive, bot, route) {
|
|
77
|
-
const text = String(parsedArchive?.body || "").trim().toLowerCase();
|
|
78
72
|
const mentions = new Set(listTelegramMentionUsernames(parsedArchive));
|
|
79
73
|
const candidates = buildTelegramBotUsernameCandidates(bot, route);
|
|
80
74
|
for (const username of candidates) {
|
|
81
75
|
if (!username) continue;
|
|
82
76
|
if (mentions.has(username)) return true;
|
|
83
|
-
if (text.includes(`@${username}`)) return true;
|
|
84
77
|
}
|
|
85
78
|
return false;
|
|
86
79
|
}
|
|
@@ -117,7 +117,8 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
117
117
|
const tryJsonParse = requireDependency(deps, "tryJsonParse");
|
|
118
118
|
const safeObject = requireDependency(deps, "safeObject");
|
|
119
119
|
const normalizeRunnerTriggerPolicy = requireDependency(deps, "normalizeRunnerTriggerPolicy");
|
|
120
|
-
const evaluateTelegramRunnerTrigger = requireDependency(deps, "evaluateTelegramRunnerTrigger");
|
|
120
|
+
const evaluateTelegramRunnerTrigger = requireDependency(deps, "evaluateTelegramRunnerTrigger");
|
|
121
|
+
const resolveHumanIntentContext = requireDependency(deps, "resolveHumanIntentContext");
|
|
121
122
|
const resolveRunnerResponderAdjudication = requireDependency(deps, "resolveRunnerResponderAdjudication");
|
|
122
123
|
const selectPendingArchiveComments = requireDependency(deps, "selectPendingArchiveComments");
|
|
123
124
|
const selectRunnerPendingWork = requireDependency(deps, "selectRunnerPendingWork");
|
|
@@ -2464,13 +2465,80 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2464
2465
|
mentionOnlyRoute,
|
|
2465
2466
|
{ name: "ServerProtocolMonitorBot", role: "monitor" },
|
|
2466
2467
|
);
|
|
2467
|
-
push(
|
|
2468
|
-
"telegram_trigger_mentions_only_accepts_bot_mention",
|
|
2469
|
-
mentionOnlyMatched.shouldRespond === true && mentionOnlyMatched.trigger === "mention",
|
|
2470
|
-
`shouldRespond=${mentionOnlyMatched.shouldRespond} trigger=${mentionOnlyMatched.trigger}`,
|
|
2471
|
-
);
|
|
2472
|
-
|
|
2473
|
-
const
|
|
2468
|
+
push(
|
|
2469
|
+
"telegram_trigger_mentions_only_accepts_bot_mention",
|
|
2470
|
+
mentionOnlyMatched.shouldRespond === true && mentionOnlyMatched.trigger === "mention",
|
|
2471
|
+
`shouldRespond=${mentionOnlyMatched.shouldRespond} trigger=${mentionOnlyMatched.trigger}`,
|
|
2472
|
+
);
|
|
2473
|
+
|
|
2474
|
+
const mentionOnlyAliasCandidate = evaluateTelegramRunnerTrigger(
|
|
2475
|
+
{
|
|
2476
|
+
id: "comment-2a",
|
|
2477
|
+
parsedArchive: {
|
|
2478
|
+
kind: "telegram_message",
|
|
2479
|
+
chatID: "-100123",
|
|
2480
|
+
chatType: "supergroup",
|
|
2481
|
+
body: "hello @RyoAI_bot2",
|
|
2482
|
+
mentionUsernames: [],
|
|
2483
|
+
replyToSenderIsBot: false,
|
|
2484
|
+
},
|
|
2485
|
+
},
|
|
2486
|
+
mentionOnlyRoute,
|
|
2487
|
+
{ username: "ryoai2_bot", name: "RyoAI_bot2", role: "monitor" },
|
|
2488
|
+
);
|
|
2489
|
+
push(
|
|
2490
|
+
"telegram_trigger_does_not_treat_raw_alias_text_as_authoritative_mention",
|
|
2491
|
+
mentionOnlyAliasCandidate.shouldRespond === true
|
|
2492
|
+
&& mentionOnlyAliasCandidate.trigger === "mentions_only_unaddressed_candidate",
|
|
2493
|
+
`shouldRespond=${mentionOnlyAliasCandidate.shouldRespond} trigger=${mentionOnlyAliasCandidate.trigger}`,
|
|
2494
|
+
);
|
|
2495
|
+
|
|
2496
|
+
try {
|
|
2497
|
+
const aliasHumanIntentContext = await resolveHumanIntentContext({
|
|
2498
|
+
selectedRecord: {
|
|
2499
|
+
id: "comment-alias-managed-mention",
|
|
2500
|
+
parsedArchive: {
|
|
2501
|
+
kind: "telegram_message",
|
|
2502
|
+
body: "@RyoAI_bot2 hi",
|
|
2503
|
+
senderIsBot: false,
|
|
2504
|
+
},
|
|
2505
|
+
},
|
|
2506
|
+
normalizedRoute: {
|
|
2507
|
+
name: "telegram-monitor-ryoai2-bot-2",
|
|
2508
|
+
},
|
|
2509
|
+
bot: {
|
|
2510
|
+
username: "ryoai2_bot",
|
|
2511
|
+
name: "RyoAI_bot2",
|
|
2512
|
+
},
|
|
2513
|
+
executionPlan: {},
|
|
2514
|
+
deps: {
|
|
2515
|
+
resolveConversationPeerBots: () => [],
|
|
2516
|
+
},
|
|
2517
|
+
persistedRequest: {
|
|
2518
|
+
conversation_intent_mode: "single_bot",
|
|
2519
|
+
conversation_reply_expectation: "informational",
|
|
2520
|
+
conversation_initial_responders: ["ryoai2_bot"],
|
|
2521
|
+
conversation_allowed_responders: ["ryoai2_bot"],
|
|
2522
|
+
conversation_participants: ["ryoai2_bot"],
|
|
2523
|
+
conversation_lead_bot: "ryoai2_bot",
|
|
2524
|
+
conversation_summary_bot: "ryoai2_bot",
|
|
2525
|
+
},
|
|
2526
|
+
});
|
|
2527
|
+
push(
|
|
2528
|
+
"runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
|
|
2529
|
+
ensureArray(aliasHumanIntentContext?.managedMentions).length === 0
|
|
2530
|
+
&& aliasHumanIntentContext?.reusedPersistedContract === true,
|
|
2531
|
+
`managedMentions=${JSON.stringify(ensureArray(aliasHumanIntentContext?.managedMentions))} reused=${String(aliasHumanIntentContext?.reusedPersistedContract)}`,
|
|
2532
|
+
);
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
push(
|
|
2535
|
+
"runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
|
|
2536
|
+
false,
|
|
2537
|
+
String(err?.message || err),
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
|
|
2474
2542
|
{
|
|
2475
2543
|
id: "comment-2b",
|
|
2476
2544
|
parsedArchive: {
|
|
@@ -12829,6 +12897,298 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
12829
12897
|
push("runner_delivery_failure_after_generation_records_ai_state_without_execution_error", false, String(err?.message || err));
|
|
12830
12898
|
}
|
|
12831
12899
|
|
|
12900
|
+
try {
|
|
12901
|
+
let capturedReplyToMessageID = 0;
|
|
12902
|
+
let capturedMessageThreadID = 0;
|
|
12903
|
+
let capturedSourceMessageEnvelope = {};
|
|
12904
|
+
const processed = await processRunnerSelectedRecord({
|
|
12905
|
+
routeKey: "delivery-prefers-route-local-inbound-envelope-key",
|
|
12906
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
12907
|
+
name: "telegram-monitor-delivery-prefers-route-local-inbound-envelope",
|
|
12908
|
+
project_id: selftestProjectID,
|
|
12909
|
+
provider: "telegram",
|
|
12910
|
+
role: "monitor",
|
|
12911
|
+
role_profile: "monitor",
|
|
12912
|
+
destination_id: "dest-1",
|
|
12913
|
+
destination_label: "Main Room",
|
|
12914
|
+
server_bot_name: "RyoAI_bot",
|
|
12915
|
+
server_bot_id: "bot-1",
|
|
12916
|
+
trigger_policy: {
|
|
12917
|
+
mentions_only: true,
|
|
12918
|
+
direct_messages: true,
|
|
12919
|
+
reply_to_bot_messages: true,
|
|
12920
|
+
},
|
|
12921
|
+
archive_policy: {
|
|
12922
|
+
mirror_replies: true,
|
|
12923
|
+
dedupe_inbound: true,
|
|
12924
|
+
dedupe_outbound: true,
|
|
12925
|
+
skip_bot_messages: true,
|
|
12926
|
+
},
|
|
12927
|
+
dry_run_delivery: false,
|
|
12928
|
+
}),
|
|
12929
|
+
routeState: {
|
|
12930
|
+
recent_local_inbound_envelopes: {
|
|
12931
|
+
"-100123:128": {
|
|
12932
|
+
chat_id: "-100123",
|
|
12933
|
+
message_id: 128,
|
|
12934
|
+
message_thread_id: 912,
|
|
12935
|
+
reply_to_message_id: 127,
|
|
12936
|
+
kind: "telegram_message",
|
|
12937
|
+
sender: "human",
|
|
12938
|
+
sender_is_bot: false,
|
|
12939
|
+
body: "@RyoAI_bot hi",
|
|
12940
|
+
source_origin: "local_telegram_inbound",
|
|
12941
|
+
source_route_key: "delivery-prefers-route-local-inbound-envelope-key",
|
|
12942
|
+
source_bot_username: "ryoai_bot",
|
|
12943
|
+
},
|
|
12944
|
+
},
|
|
12945
|
+
},
|
|
12946
|
+
selectedRecord: {
|
|
12947
|
+
id: "comment-delivery-prefers-route-local-inbound-envelope",
|
|
12948
|
+
createdAt: "2026-03-27T00:00:00.000Z",
|
|
12949
|
+
parsedArchive: {
|
|
12950
|
+
kind: "telegram_message",
|
|
12951
|
+
chatID: "-100123",
|
|
12952
|
+
chatType: "supergroup",
|
|
12953
|
+
body: "@RyoAI_bot hi",
|
|
12954
|
+
messageID: 128,
|
|
12955
|
+
messageThreadID: 912,
|
|
12956
|
+
replyToMessageID: 127,
|
|
12957
|
+
sender: "human",
|
|
12958
|
+
senderIsBot: false,
|
|
12959
|
+
mentionUsernames: ["ryoai_bot"],
|
|
12960
|
+
},
|
|
12961
|
+
},
|
|
12962
|
+
pendingOrdered: [],
|
|
12963
|
+
bot: {
|
|
12964
|
+
id: "bot-1",
|
|
12965
|
+
name: "RyoAI_bot",
|
|
12966
|
+
username: "RyoAI_bot",
|
|
12967
|
+
role: "monitor",
|
|
12968
|
+
provider: "telegram",
|
|
12969
|
+
},
|
|
12970
|
+
destination: {
|
|
12971
|
+
id: "dest-1",
|
|
12972
|
+
label: "Main Room",
|
|
12973
|
+
provider: "telegram",
|
|
12974
|
+
chatID: "-100123",
|
|
12975
|
+
},
|
|
12976
|
+
archiveThread: {
|
|
12977
|
+
threadID: "thread-1",
|
|
12978
|
+
workItemID: "work-item-1",
|
|
12979
|
+
},
|
|
12980
|
+
executionPlan: {
|
|
12981
|
+
mode: "role_profile",
|
|
12982
|
+
roleProfileName: "monitor",
|
|
12983
|
+
roleProfile: {
|
|
12984
|
+
client: "sample",
|
|
12985
|
+
model: "",
|
|
12986
|
+
permissionMode: "read_only",
|
|
12987
|
+
reasoningEffort: "low",
|
|
12988
|
+
},
|
|
12989
|
+
workspaceDir: "",
|
|
12990
|
+
workspaceSource: "selftest",
|
|
12991
|
+
usedCommandFallback: false,
|
|
12992
|
+
},
|
|
12993
|
+
runtime: {
|
|
12994
|
+
baseURL: "https://example.test",
|
|
12995
|
+
token: "selftest-token",
|
|
12996
|
+
timeoutSeconds: 30,
|
|
12997
|
+
actor: { user_id: "user-1" },
|
|
12998
|
+
},
|
|
12999
|
+
deps: {
|
|
13000
|
+
saveRunnerRouteState: () => {},
|
|
13001
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
13002
|
+
runRunnerAIExecution: async () => ({
|
|
13003
|
+
skip: false,
|
|
13004
|
+
reply: "Hello from RyoAI_bot.",
|
|
13005
|
+
}),
|
|
13006
|
+
performLocalBotDelivery: async ({ replyToMessageID, messageThreadID, sourceMessageEnvelope }) => {
|
|
13007
|
+
capturedReplyToMessageID = Number(replyToMessageID || 0);
|
|
13008
|
+
capturedMessageThreadID = Number(messageThreadID || 0);
|
|
13009
|
+
capturedSourceMessageEnvelope = safeObject(sourceMessageEnvelope);
|
|
13010
|
+
return {
|
|
13011
|
+
delivery: {
|
|
13012
|
+
dryRun: false,
|
|
13013
|
+
body: {
|
|
13014
|
+
result: {
|
|
13015
|
+
message_id: 9001,
|
|
13016
|
+
message_thread_id: Number(messageThreadID || 0) || undefined,
|
|
13017
|
+
},
|
|
13018
|
+
},
|
|
13019
|
+
},
|
|
13020
|
+
archive: {},
|
|
13021
|
+
};
|
|
13022
|
+
},
|
|
13023
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
13024
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
13025
|
+
buildRunnerExecutionDeps: () => ({
|
|
13026
|
+
validateWorkspaceArtifacts,
|
|
13027
|
+
analyzeHumanConversationIntentWithAI: async () => ({
|
|
13028
|
+
mode: "single_bot",
|
|
13029
|
+
lead_bot: "ryoai_bot",
|
|
13030
|
+
participants: ["ryoai_bot"],
|
|
13031
|
+
initial_responders: ["ryoai_bot"],
|
|
13032
|
+
allowed_responders: ["ryoai_bot"],
|
|
13033
|
+
summary_bot: "",
|
|
13034
|
+
allow_bot_to_bot: false,
|
|
13035
|
+
reply_expectation: "informational",
|
|
13036
|
+
intent_type: "small_talk",
|
|
13037
|
+
}),
|
|
13038
|
+
}),
|
|
13039
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
13040
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
13041
|
+
resolveConversationPeerBots: () => [],
|
|
13042
|
+
},
|
|
13043
|
+
});
|
|
13044
|
+
push(
|
|
13045
|
+
"runner_delivery_prefers_route_local_inbound_provenance_envelope",
|
|
13046
|
+
processed.kind === "replied"
|
|
13047
|
+
&& capturedReplyToMessageID === 128
|
|
13048
|
+
&& capturedMessageThreadID === 912
|
|
13049
|
+
&& String(capturedSourceMessageEnvelope.source_origin || "") === "local_telegram_inbound"
|
|
13050
|
+
&& Number(capturedSourceMessageEnvelope.message_id || 0) === 128,
|
|
13051
|
+
`kind=${String(processed.kind || "(none)")} reply_to=${String(capturedReplyToMessageID || 0)} thread=${String(capturedMessageThreadID || 0)} origin=${String(capturedSourceMessageEnvelope.source_origin || "(none)")} message=${String(capturedSourceMessageEnvelope.message_id || "(none)")}`,
|
|
13052
|
+
);
|
|
13053
|
+
} catch (err) {
|
|
13054
|
+
push("runner_delivery_prefers_route_local_inbound_provenance_envelope", false, String(err?.message || err));
|
|
13055
|
+
}
|
|
13056
|
+
|
|
13057
|
+
try {
|
|
13058
|
+
let capturedSourceMessageEnvelope = {};
|
|
13059
|
+
const processed = await processRunnerSelectedRecord({
|
|
13060
|
+
routeKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
13061
|
+
normalizedRoute: {
|
|
13062
|
+
name: "telegram-monitor-ryoai2-bot-2",
|
|
13063
|
+
provider: "telegram",
|
|
13064
|
+
role: "monitor",
|
|
13065
|
+
roleProfile: "monitor",
|
|
13066
|
+
botName: "RyoAI2_bot",
|
|
13067
|
+
archivePolicy: { mirrorReplies: true, dedupeOutbound: true },
|
|
13068
|
+
triggerPolicy: {
|
|
13069
|
+
directMessages: true,
|
|
13070
|
+
mentionsOnly: false,
|
|
13071
|
+
replyToBotMessages: true,
|
|
13072
|
+
ignoreEditedMessages: true,
|
|
13073
|
+
},
|
|
13074
|
+
},
|
|
13075
|
+
routeState: {
|
|
13076
|
+
recent_local_inbound_envelopes: {},
|
|
13077
|
+
},
|
|
13078
|
+
selectedRecord: {
|
|
13079
|
+
id: "comment-foreign-provenance",
|
|
13080
|
+
threadID: "thread-1",
|
|
13081
|
+
parsedArchive: {
|
|
13082
|
+
kind: "telegram_message",
|
|
13083
|
+
chatID: "-100123",
|
|
13084
|
+
chatType: "supergroup",
|
|
13085
|
+
body: "@RyoAI2_bot hi",
|
|
13086
|
+
messageID: 228,
|
|
13087
|
+
sender: "human",
|
|
13088
|
+
senderIsBot: false,
|
|
13089
|
+
mentionUsernames: ["ryoai2_bot"],
|
|
13090
|
+
},
|
|
13091
|
+
},
|
|
13092
|
+
persistedHumanIntentRequest: {
|
|
13093
|
+
source_message_envelope: {
|
|
13094
|
+
chat_id: "-100123",
|
|
13095
|
+
message_id: 228,
|
|
13096
|
+
source_origin: "local_telegram_inbound",
|
|
13097
|
+
source_route_key: "telegram-monitor-ryoai-bot-2::project::telegram::monitor::dest::actor",
|
|
13098
|
+
source_bot_username: "ryoai_bot",
|
|
13099
|
+
},
|
|
13100
|
+
},
|
|
13101
|
+
pendingOrdered: [],
|
|
13102
|
+
bot: {
|
|
13103
|
+
id: "bot-2",
|
|
13104
|
+
name: "RyoAI2_bot",
|
|
13105
|
+
username: "RyoAI2_bot",
|
|
13106
|
+
role: "monitor",
|
|
13107
|
+
provider: "telegram",
|
|
13108
|
+
},
|
|
13109
|
+
destination: {
|
|
13110
|
+
id: "dest-1",
|
|
13111
|
+
label: "Main Room",
|
|
13112
|
+
provider: "telegram",
|
|
13113
|
+
chatID: "-100123",
|
|
13114
|
+
},
|
|
13115
|
+
archiveThread: {
|
|
13116
|
+
threadID: "thread-1",
|
|
13117
|
+
workItemID: "work-item-1",
|
|
13118
|
+
},
|
|
13119
|
+
executionPlan: {
|
|
13120
|
+
mode: "role_profile",
|
|
13121
|
+
roleProfileName: "monitor",
|
|
13122
|
+
roleProfile: {
|
|
13123
|
+
client: "sample",
|
|
13124
|
+
model: "",
|
|
13125
|
+
permissionMode: "read_only",
|
|
13126
|
+
reasoningEffort: "low",
|
|
13127
|
+
},
|
|
13128
|
+
workspaceDir: "",
|
|
13129
|
+
workspaceSource: "selftest",
|
|
13130
|
+
usedCommandFallback: false,
|
|
13131
|
+
},
|
|
13132
|
+
runtime: {
|
|
13133
|
+
baseURL: "https://example.test",
|
|
13134
|
+
token: "selftest-token",
|
|
13135
|
+
timeoutSeconds: 30,
|
|
13136
|
+
actor: { user_id: "user-1" },
|
|
13137
|
+
},
|
|
13138
|
+
deps: {
|
|
13139
|
+
saveRunnerRouteState: () => {},
|
|
13140
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
13141
|
+
runRunnerAIExecution: async () => ({
|
|
13142
|
+
skip: false,
|
|
13143
|
+
reply: "Hello from RyoAI2_bot.",
|
|
13144
|
+
}),
|
|
13145
|
+
performLocalBotDelivery: async ({ sourceMessageEnvelope }) => {
|
|
13146
|
+
capturedSourceMessageEnvelope = safeObject(sourceMessageEnvelope);
|
|
13147
|
+
return {
|
|
13148
|
+
delivery: {
|
|
13149
|
+
dryRun: false,
|
|
13150
|
+
body: {
|
|
13151
|
+
result: {
|
|
13152
|
+
message_id: 9002,
|
|
13153
|
+
},
|
|
13154
|
+
},
|
|
13155
|
+
},
|
|
13156
|
+
archive: {},
|
|
13157
|
+
};
|
|
13158
|
+
},
|
|
13159
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
13160
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
13161
|
+
buildRunnerExecutionDeps: () => ({
|
|
13162
|
+
validateWorkspaceArtifacts,
|
|
13163
|
+
analyzeHumanConversationIntentWithAI: async () => ({
|
|
13164
|
+
mode: "single_bot",
|
|
13165
|
+
lead_bot: "ryoai2_bot",
|
|
13166
|
+
participants: ["ryoai2_bot"],
|
|
13167
|
+
initial_responders: ["ryoai2_bot"],
|
|
13168
|
+
allowed_responders: ["ryoai2_bot"],
|
|
13169
|
+
summary_bot: "",
|
|
13170
|
+
allow_bot_to_bot: false,
|
|
13171
|
+
reply_expectation: "informational",
|
|
13172
|
+
intent_type: "small_talk",
|
|
13173
|
+
}),
|
|
13174
|
+
}),
|
|
13175
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
13176
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
13177
|
+
resolveConversationPeerBots: () => [],
|
|
13178
|
+
},
|
|
13179
|
+
});
|
|
13180
|
+
push(
|
|
13181
|
+
"runner_delivery_rejects_foreign_route_local_provenance",
|
|
13182
|
+
processed.kind === "replied"
|
|
13183
|
+
&& String(capturedSourceMessageEnvelope.source_origin || "") === "archive_reconstructed"
|
|
13184
|
+
&& String(capturedSourceMessageEnvelope.source_route_key || "").includes("telegram-monitor-ryoai2-bot-2")
|
|
13185
|
+
&& String(capturedSourceMessageEnvelope.source_bot_username || "") === "ryoai2_bot",
|
|
13186
|
+
`kind=${String(processed.kind || "(none)")} origin=${String(capturedSourceMessageEnvelope.source_origin || "(none)")} route=${String(capturedSourceMessageEnvelope.source_route_key || "(none)")} bot=${String(capturedSourceMessageEnvelope.source_bot_username || "(none)")}`,
|
|
13187
|
+
);
|
|
13188
|
+
} catch (err) {
|
|
13189
|
+
push("runner_delivery_rejects_foreign_route_local_provenance", false, String(err?.message || err));
|
|
13190
|
+
}
|
|
13191
|
+
|
|
12832
13192
|
try {
|
|
12833
13193
|
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-observed-artifacts-"));
|
|
12834
13194
|
const scriptDir = path.join(workspaceDir, ".metheus", "runner-runtime", "local-ai-scratch");
|
|
@@ -503,17 +503,51 @@ export async function runSelftestTelegramE2E(push, deps) {
|
|
|
503
503
|
dryRun: true,
|
|
504
504
|
deps: buildRunnerDeliveryDeps(),
|
|
505
505
|
});
|
|
506
|
-
push(
|
|
507
|
-
"telegram_delivery_dry_run_skips_send_and_archive",
|
|
508
|
-
Boolean(dryRunResult.delivery?.dryRun)
|
|
509
|
-
&& String(dryRunResult.archive?.reason || "") === "dry_run_delivery"
|
|
510
|
-
&& telegramE2EServer.state.sentMessages.length === sentCountBeforeDryRun
|
|
511
|
-
&& telegramE2EServer.state.comments.length === commentCountBeforeDryRun,
|
|
512
|
-
`dry_run=${String(dryRunResult.delivery?.dryRun || false)} sent=${telegramE2EServer.state.sentMessages.length} comments=${telegramE2EServer.state.comments.length}`,
|
|
513
|
-
);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
506
|
+
push(
|
|
507
|
+
"telegram_delivery_dry_run_skips_send_and_archive",
|
|
508
|
+
Boolean(dryRunResult.delivery?.dryRun)
|
|
509
|
+
&& String(dryRunResult.archive?.reason || "") === "dry_run_delivery"
|
|
510
|
+
&& telegramE2EServer.state.sentMessages.length === sentCountBeforeDryRun
|
|
511
|
+
&& telegramE2EServer.state.comments.length === commentCountBeforeDryRun,
|
|
512
|
+
`dry_run=${String(dryRunResult.delivery?.dryRun || false)} sent=${telegramE2EServer.state.sentMessages.length} comments=${telegramE2EServer.state.comments.length}`,
|
|
513
|
+
);
|
|
514
|
+
const sentCountBeforeUnsafeAnchor = telegramE2EServer.state.sentMessages.length;
|
|
515
|
+
let unsafeAnchorError = "";
|
|
516
|
+
try {
|
|
517
|
+
await performLocalBotDelivery({
|
|
518
|
+
siteBaseURL: telegramE2EServer.baseURL,
|
|
519
|
+
token: e2eToken,
|
|
520
|
+
timeoutSeconds: 10,
|
|
521
|
+
actorUserID: e2eActorUserID,
|
|
522
|
+
bot: e2eBot,
|
|
523
|
+
projectID: selftestProjectID,
|
|
524
|
+
provider: "telegram",
|
|
525
|
+
destinationSelectors: {
|
|
526
|
+
destinationLabel: e2eDestination.label,
|
|
527
|
+
},
|
|
528
|
+
text: "unsafe archive anchor",
|
|
529
|
+
replyToMessageID: 41,
|
|
530
|
+
sourceMessageEnvelope: {
|
|
531
|
+
chat_id: e2eDestination.chat_id,
|
|
532
|
+
message_id: 41,
|
|
533
|
+
source_origin: "archive_reconstructed",
|
|
534
|
+
},
|
|
535
|
+
archiveReplies: false,
|
|
536
|
+
dryRun: false,
|
|
537
|
+
deps: buildRunnerDeliveryDeps(),
|
|
538
|
+
});
|
|
539
|
+
} catch (err) {
|
|
540
|
+
unsafeAnchorError = String(err?.message || err);
|
|
541
|
+
}
|
|
542
|
+
push(
|
|
543
|
+
"telegram_delivery_rejects_archive_reconstructed_reply_anchor",
|
|
544
|
+
/telegram reply anchor is not local to this route/i.test(unsafeAnchorError)
|
|
545
|
+
&& telegramE2EServer.state.sentMessages.length === sentCountBeforeUnsafeAnchor,
|
|
546
|
+
`error=${unsafeAnchorError || "(none)"} sent=${telegramE2EServer.state.sentMessages.length}`,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
telegramE2EServer.state.comments = [];
|
|
550
|
+
telegramE2EServer.state.updates = [
|
|
517
551
|
{
|
|
518
552
|
update_id: 201,
|
|
519
553
|
message: {
|