metheus-governance-mcp-cli 0.2.293 → 0.2.295
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 +38 -94
- package/lib/local-ai-adapters.mjs +5 -0
- package/lib/runner-local-inbound-receipts.mjs +212 -0
- package/lib/runner-orchestration-decision-bundle.mjs +30 -0
- package/lib/runner-orchestration-entrypoints.mjs +325 -36
- package/lib/runner-orchestration-intent-contracts.mjs +0 -30
- package/lib/runner-orchestration-visibility.mjs +4 -100
- package/lib/runner-orchestration.mjs +0 -51
- package/lib/runner-runtime.mjs +15 -119
- package/lib/selftest-runner-scenarios.mjs +385 -6
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -151,6 +151,9 @@ import {
|
|
|
151
151
|
selectPendingArchiveComments,
|
|
152
152
|
printRunnerResult,
|
|
153
153
|
} from "./lib/runner-helpers.mjs";
|
|
154
|
+
import {
|
|
155
|
+
normalizeRunnerRecentLocalInboundReceiptMap,
|
|
156
|
+
} from "./lib/runner-local-inbound-receipts.mjs";
|
|
154
157
|
import {
|
|
155
158
|
normalizeRunnerConversationDecisionBundle,
|
|
156
159
|
validateRunnerConversationDecisionBundle,
|
|
@@ -1966,88 +1969,6 @@ function prefersRunnerStateRecord(candidate, current) {
|
|
|
1966
1969
|
return false;
|
|
1967
1970
|
}
|
|
1968
1971
|
|
|
1969
|
-
function normalizeRunnerRecentLocalInboundReceiptRecord(rawReceipt, fallbackKey = "") {
|
|
1970
|
-
const receipt = safeObject(rawReceipt);
|
|
1971
|
-
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
1972
|
-
const messageID = intFromRawAllowZero(receipt.message_id ?? receipt.messageID, 0);
|
|
1973
|
-
const receiptKey = String(fallbackKey || `${chatID}:${messageID}`).trim();
|
|
1974
|
-
if (!receiptKey || !chatID || !(messageID > 0)) {
|
|
1975
|
-
return null;
|
|
1976
|
-
}
|
|
1977
|
-
const normalized = {
|
|
1978
|
-
chat_id: chatID,
|
|
1979
|
-
message_id: messageID,
|
|
1980
|
-
receipt_origin: firstNonEmptyString([
|
|
1981
|
-
receipt.receipt_origin,
|
|
1982
|
-
receipt.receiptOrigin,
|
|
1983
|
-
receipt.source_origin,
|
|
1984
|
-
receipt.sourceOrigin,
|
|
1985
|
-
"local_telegram_inbound",
|
|
1986
|
-
]),
|
|
1987
|
-
receipt_route_key: firstNonEmptyString([
|
|
1988
|
-
receipt.receipt_route_key,
|
|
1989
|
-
receipt.receiptRouteKey,
|
|
1990
|
-
receipt.source_route_key,
|
|
1991
|
-
receipt.sourceRouteKey,
|
|
1992
|
-
]),
|
|
1993
|
-
receipt_bot_username: normalizeTelegramMentionUsername(
|
|
1994
|
-
firstNonEmptyString([
|
|
1995
|
-
receipt.receipt_bot_username,
|
|
1996
|
-
receipt.receiptBotUsername,
|
|
1997
|
-
receipt.source_bot_username,
|
|
1998
|
-
receipt.sourceBotUsername,
|
|
1999
|
-
]),
|
|
2000
|
-
),
|
|
2001
|
-
kind: firstNonEmptyString([receipt.kind, "telegram_message"]),
|
|
2002
|
-
sender: firstNonEmptyString([receipt.sender, receipt.from_name, receipt.fromName]),
|
|
2003
|
-
sender_username: firstNonEmptyString([
|
|
2004
|
-
receipt.sender_username,
|
|
2005
|
-
receipt.senderUsername,
|
|
2006
|
-
receipt.from_username,
|
|
2007
|
-
receipt.fromUsername,
|
|
2008
|
-
]),
|
|
2009
|
-
sender_is_bot: Boolean(receipt.sender_is_bot ?? receipt.senderIsBot ?? false),
|
|
2010
|
-
body: firstNonEmptyString([receipt.body, receipt.text]),
|
|
2011
|
-
occurred_at: firstNonEmptyString([receipt.occurred_at, receipt.occurredAt]),
|
|
2012
|
-
received_at: firstNonEmptyString([receipt.received_at, receipt.receivedAt, new Date().toISOString()]),
|
|
2013
|
-
};
|
|
2014
|
-
const senderID = firstNonEmptyString([receipt.sender_id, receipt.senderID, receipt.from_id, receipt.fromID]);
|
|
2015
|
-
if (senderID) {
|
|
2016
|
-
normalized.sender_id = senderID;
|
|
2017
|
-
}
|
|
2018
|
-
const updateID = intFromRawAllowZero(receipt.update_id ?? receipt.updateID, 0);
|
|
2019
|
-
if (updateID > 0) {
|
|
2020
|
-
normalized.update_id = updateID;
|
|
2021
|
-
}
|
|
2022
|
-
const messageThreadID = intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0);
|
|
2023
|
-
if (messageThreadID > 0) {
|
|
2024
|
-
normalized.message_thread_id = messageThreadID;
|
|
2025
|
-
}
|
|
2026
|
-
const replyToMessageID = intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0);
|
|
2027
|
-
if (replyToMessageID > 0) {
|
|
2028
|
-
normalized.reply_to_message_id = replyToMessageID;
|
|
2029
|
-
}
|
|
2030
|
-
const chatType = firstNonEmptyString([receipt.chat_type, receipt.chatType]);
|
|
2031
|
-
if (chatType) {
|
|
2032
|
-
normalized.chat_type = chatType;
|
|
2033
|
-
}
|
|
2034
|
-
const chatTitle = firstNonEmptyString([receipt.chat_title, receipt.chatTitle]);
|
|
2035
|
-
if (chatTitle) {
|
|
2036
|
-
normalized.chat_title = chatTitle;
|
|
2037
|
-
}
|
|
2038
|
-
return [receiptKey, normalized];
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
function normalizeRunnerRecentLocalInboundReceiptMap(rawReceipts) {
|
|
2042
|
-
const normalizedEntries = [];
|
|
2043
|
-
for (const [key, value] of Object.entries(safeObject(rawReceipts))) {
|
|
2044
|
-
const normalizedEntry = normalizeRunnerRecentLocalInboundReceiptRecord(value, key);
|
|
2045
|
-
if (!normalizedEntry) continue;
|
|
2046
|
-
normalizedEntries.push(normalizedEntry);
|
|
2047
|
-
}
|
|
2048
|
-
return Object.fromEntries(normalizedEntries);
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
1972
|
function mergeRunnerStateRecords(preferred, fallback) {
|
|
2052
1973
|
const primary = safeObject(preferred);
|
|
2053
1974
|
const secondary = safeObject(fallback);
|
|
@@ -10711,10 +10632,10 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
10711
10632
|
latestRunnerState.consumedComments || latestRunnerState.consumed_comments,
|
|
10712
10633
|
);
|
|
10713
10634
|
const requests = normalizeBotRunnerRequests(latestRunnerState.requests);
|
|
10714
|
-
const commentsForPending = ensureArray(comments).filter((comment) => {
|
|
10715
|
-
const normalizedComment = normalizeArchiveCommentRecord(comment, parseArchivedChatComment);
|
|
10716
|
-
const commentID = String(normalizedComment.id || "").trim();
|
|
10717
|
-
const parsed = safeObject(normalizedComment.parsedArchive);
|
|
10635
|
+
const commentsForPending = ensureArray(comments).filter((comment) => {
|
|
10636
|
+
const normalizedComment = normalizeArchiveCommentRecord(comment, parseArchivedChatComment);
|
|
10637
|
+
const commentID = String(normalizedComment.id || "").trim();
|
|
10638
|
+
const parsed = safeObject(normalizedComment.parsedArchive);
|
|
10718
10639
|
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
10719
10640
|
if (commentID && safeObject(excludedComments)[commentID]) {
|
|
10720
10641
|
return false;
|
|
@@ -10742,22 +10663,45 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
10742
10663
|
}
|
|
10743
10664
|
const sessionMatch = findScopedConversationSessionState(latestRunnerState, normalizedRoute, conversationID);
|
|
10744
10665
|
return sessionAllowsConversationResponder(sessionMatch.session, currentBotSelector);
|
|
10745
|
-
}
|
|
10746
|
-
return true;
|
|
10747
|
-
});
|
|
10666
|
+
}
|
|
10667
|
+
return true;
|
|
10668
|
+
});
|
|
10669
|
+
const followupRequestsForPending = Object.values(requests).filter((entryRaw) => {
|
|
10670
|
+
const entry = safeObject(entryRaw);
|
|
10671
|
+
const nextExpectedResponders = ensureArray(entry.next_expected_responders)
|
|
10672
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
10673
|
+
.filter(Boolean);
|
|
10674
|
+
if (!nextExpectedResponders.includes(currentBotSelector)) {
|
|
10675
|
+
return false;
|
|
10676
|
+
}
|
|
10677
|
+
if (!isActiveRunnerRequestStatus(entry.status)) {
|
|
10678
|
+
return false;
|
|
10679
|
+
}
|
|
10680
|
+
if (
|
|
10681
|
+
String(entry.project_id || "").trim() !== String(normalizedRoute.projectID || "").trim()
|
|
10682
|
+
|| String(entry.provider || "").trim() !== String(normalizedRoute.provider || "").trim()
|
|
10683
|
+
|| String(entry.chat_id || "").trim() !== String(destination.chatID || "").trim()
|
|
10684
|
+
) {
|
|
10685
|
+
return false;
|
|
10686
|
+
}
|
|
10687
|
+
const lastReplyEnvelope = safeObject(entry.last_reply_message_envelope);
|
|
10688
|
+
return intFromRawAllowZero(lastReplyEnvelope.message_id || lastReplyEnvelope.messageID, 0) > 0;
|
|
10689
|
+
});
|
|
10748
10690
|
const pendingWork = selectRunnerPendingWork({
|
|
10749
10691
|
comments: commentsForPending,
|
|
10750
10692
|
importOutcome,
|
|
10751
10693
|
refreshedState,
|
|
10752
|
-
mode,
|
|
10694
|
+
mode,
|
|
10753
10695
|
parseArchivedChatComment,
|
|
10754
10696
|
pendingSelectionOptions: {
|
|
10755
10697
|
maxPendingAgeMs: BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS,
|
|
10756
10698
|
},
|
|
10757
|
-
deps: {
|
|
10758
|
-
applyPendingAgeSelection,
|
|
10759
|
-
normalizeArchiveCommentRecord,
|
|
10760
|
-
},
|
|
10699
|
+
deps: {
|
|
10700
|
+
applyPendingAgeSelection,
|
|
10701
|
+
normalizeArchiveCommentRecord,
|
|
10702
|
+
},
|
|
10703
|
+
followupRequests: followupRequestsForPending,
|
|
10704
|
+
currentBotSelector,
|
|
10761
10705
|
});
|
|
10762
10706
|
const pending = pendingWork.pending;
|
|
10763
10707
|
if (pending.staleSkippedLatest) {
|
|
@@ -2143,6 +2143,11 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
2143
2143
|
"Do not mention another managed bot unless the contract explicitly names that bot in assignments or next_responders.",
|
|
2144
2144
|
"Without a matching contract, mentioned bots will not act.",
|
|
2145
2145
|
"When delegating to another managed bot, use contract.type=\"delegation\" with actionable=true, assignments, and next_responders.",
|
|
2146
|
+
"Decision bundle consistency rules:",
|
|
2147
|
+
"- If execution_contract_type is \"delegation\", conversation_intent_mode must be \"delegated_single_lead\".",
|
|
2148
|
+
"- If execution_contract_type is \"delegation\", initial_responders must contain only the lead bot. Delegated target bots belong in next_expected_responders, not initial_responders.",
|
|
2149
|
+
"- If multiple bots should each answer the opening human message, do not set should_close_after_reply=true on the first reply. Keep should_close_after_reply=false and list the remaining bots in next_expected_responders until the multi-bot opening is complete.",
|
|
2150
|
+
"- Never combine conversation_intent_mode=\"multi_bot_direct\" with execution_contract_type=\"delegation\".",
|
|
2146
2151
|
"Each assignment must declare whether it is a conversational contribution or a real execution task.",
|
|
2147
2152
|
"Use assignment.mode=\"conversation_contribution\" for opinions, discussion, review, comparison, synthesis, greetings, or other room-visible contributions that do not require workspace artifacts.",
|
|
2148
2153
|
"Use assignment.mode=\"execution_task\" only when the delegated bot must change workspace files, create artifacts, update ctxpack, or produce other concrete project outputs. If it is an execution task, also set artifacts_required=true.",
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCanonicalHumanInboundKey,
|
|
3
|
+
} from "./runner-helpers.mjs";
|
|
4
|
+
|
|
5
|
+
function safeObject(value) {
|
|
6
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function ensureArray(value) {
|
|
13
|
+
return Array.isArray(value) ? value : [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function intFromRawAllowZero(raw, fallback = 0) {
|
|
17
|
+
if (raw === null || raw === undefined || raw === "") {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
const parsed = Number.parseInt(String(raw).trim(), 10);
|
|
21
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function firstNonEmptyString(values) {
|
|
25
|
+
for (const value of ensureArray(values)) {
|
|
26
|
+
const normalized = String(value ?? "").trim();
|
|
27
|
+
if (normalized) return normalized;
|
|
28
|
+
}
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeMentionSelector(value) {
|
|
33
|
+
return String(value || "").trim().replace(/^@+/, "").toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildRunnerInboundTargetSelectorSuffix(selector) {
|
|
37
|
+
const normalizedSelector = normalizeMentionSelector(selector);
|
|
38
|
+
return normalizedSelector ? `:${normalizedSelector}` : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function buildRunnerRecentLocalInboundReceiptKey(chatID, messageID) {
|
|
42
|
+
return `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function normalizeRunnerRecentLocalInboundReceiptEntry(rawReceipt, fallbackKey = "") {
|
|
46
|
+
const receipt = safeObject(rawReceipt);
|
|
47
|
+
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
48
|
+
const messageID = intFromRawAllowZero(receipt.message_id ?? receipt.messageID, 0);
|
|
49
|
+
const receiptKey = String(fallbackKey || buildRunnerRecentLocalInboundReceiptKey(chatID, messageID)).trim();
|
|
50
|
+
if (!receiptKey || !chatID || !(messageID > 0)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const normalized = {
|
|
54
|
+
chat_id: chatID,
|
|
55
|
+
message_id: messageID,
|
|
56
|
+
receipt_origin: firstNonEmptyString([
|
|
57
|
+
receipt.receipt_origin,
|
|
58
|
+
receipt.receiptOrigin,
|
|
59
|
+
receipt.source_origin,
|
|
60
|
+
receipt.sourceOrigin,
|
|
61
|
+
"local_telegram_inbound",
|
|
62
|
+
]),
|
|
63
|
+
receipt_route_key: firstNonEmptyString([
|
|
64
|
+
receipt.receipt_route_key,
|
|
65
|
+
receipt.receiptRouteKey,
|
|
66
|
+
receipt.source_route_key,
|
|
67
|
+
receipt.sourceRouteKey,
|
|
68
|
+
]),
|
|
69
|
+
receipt_bot_username: normalizeMentionSelector(
|
|
70
|
+
firstNonEmptyString([
|
|
71
|
+
receipt.receipt_bot_username,
|
|
72
|
+
receipt.receiptBotUsername,
|
|
73
|
+
receipt.source_bot_username,
|
|
74
|
+
receipt.sourceBotUsername,
|
|
75
|
+
]),
|
|
76
|
+
),
|
|
77
|
+
kind: firstNonEmptyString([receipt.kind, "telegram_message"]),
|
|
78
|
+
sender: firstNonEmptyString([receipt.sender, receipt.from_name, receipt.fromName]),
|
|
79
|
+
sender_username: firstNonEmptyString([
|
|
80
|
+
receipt.sender_username,
|
|
81
|
+
receipt.senderUsername,
|
|
82
|
+
receipt.from_username,
|
|
83
|
+
receipt.fromUsername,
|
|
84
|
+
]),
|
|
85
|
+
sender_is_bot: Boolean(receipt.sender_is_bot ?? receipt.senderIsBot ?? false),
|
|
86
|
+
body: firstNonEmptyString([receipt.body, receipt.text]),
|
|
87
|
+
occurred_at: firstNonEmptyString([receipt.occurred_at, receipt.occurredAt]),
|
|
88
|
+
received_at: firstNonEmptyString([receipt.received_at, receipt.receivedAt, new Date().toISOString()]),
|
|
89
|
+
};
|
|
90
|
+
const senderID = firstNonEmptyString([receipt.sender_id, receipt.senderID, receipt.from_id, receipt.fromID]);
|
|
91
|
+
if (senderID) {
|
|
92
|
+
normalized.sender_id = senderID;
|
|
93
|
+
}
|
|
94
|
+
const updateID = intFromRawAllowZero(receipt.update_id ?? receipt.updateID, 0);
|
|
95
|
+
if (updateID > 0) {
|
|
96
|
+
normalized.update_id = updateID;
|
|
97
|
+
}
|
|
98
|
+
const messageThreadID = intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0);
|
|
99
|
+
if (messageThreadID > 0) {
|
|
100
|
+
normalized.message_thread_id = messageThreadID;
|
|
101
|
+
}
|
|
102
|
+
const replyToMessageID = intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0);
|
|
103
|
+
if (replyToMessageID > 0) {
|
|
104
|
+
normalized.reply_to_message_id = replyToMessageID;
|
|
105
|
+
}
|
|
106
|
+
const replyToFromUsername = firstNonEmptyString([
|
|
107
|
+
receipt.reply_to_from_username,
|
|
108
|
+
receipt.replyToFromUsername,
|
|
109
|
+
receipt.reply_to_username,
|
|
110
|
+
receipt.replyToUsername,
|
|
111
|
+
]);
|
|
112
|
+
if (replyToFromUsername) {
|
|
113
|
+
normalized.reply_to_from_username = replyToFromUsername;
|
|
114
|
+
}
|
|
115
|
+
if (receipt.reply_to_from_is_bot === true || receipt.replyToFromIsBot === true) {
|
|
116
|
+
normalized.reply_to_from_is_bot = true;
|
|
117
|
+
}
|
|
118
|
+
const chatType = firstNonEmptyString([receipt.chat_type, receipt.chatType]);
|
|
119
|
+
if (chatType) {
|
|
120
|
+
normalized.chat_type = chatType;
|
|
121
|
+
}
|
|
122
|
+
const chatTitle = firstNonEmptyString([receipt.chat_title, receipt.chatTitle]);
|
|
123
|
+
if (chatTitle) {
|
|
124
|
+
normalized.chat_title = chatTitle;
|
|
125
|
+
}
|
|
126
|
+
const canonicalHumanMessageKey = String(buildCanonicalHumanInboundKey({
|
|
127
|
+
chat_id: normalized.chat_id,
|
|
128
|
+
message_id: normalized.message_id,
|
|
129
|
+
message_thread_id: normalized.message_thread_id,
|
|
130
|
+
reply_to_message_id: normalized.reply_to_message_id,
|
|
131
|
+
kind: normalized.kind,
|
|
132
|
+
sender_id: normalized.sender_id,
|
|
133
|
+
sender_username: normalized.sender_username,
|
|
134
|
+
sender_is_bot: normalized.sender_is_bot === true,
|
|
135
|
+
body: normalized.body,
|
|
136
|
+
occurred_at: normalized.occurred_at,
|
|
137
|
+
canonical_human_message_key: firstNonEmptyString([
|
|
138
|
+
receipt.canonical_human_message_key,
|
|
139
|
+
receipt.canonicalHumanMessageKey,
|
|
140
|
+
]),
|
|
141
|
+
}) || "").trim();
|
|
142
|
+
if (canonicalHumanMessageKey) {
|
|
143
|
+
normalized.canonical_human_message_key = canonicalHumanMessageKey;
|
|
144
|
+
}
|
|
145
|
+
return [receiptKey, normalized];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function normalizeRunnerRecentLocalInboundReceipt(rawReceipt, fallbackKey = "") {
|
|
149
|
+
const normalizedEntry = normalizeRunnerRecentLocalInboundReceiptEntry(rawReceipt, fallbackKey);
|
|
150
|
+
return normalizedEntry ? safeObject(normalizedEntry[1]) : {};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function normalizeRunnerRecentLocalInboundReceiptMap(rawReceipts) {
|
|
154
|
+
const normalizedEntries = [];
|
|
155
|
+
for (const [key, value] of Object.entries(safeObject(rawReceipts))) {
|
|
156
|
+
const normalizedEntry = normalizeRunnerRecentLocalInboundReceiptEntry(value, key);
|
|
157
|
+
if (!normalizedEntry) continue;
|
|
158
|
+
normalizedEntries.push(normalizedEntry);
|
|
159
|
+
}
|
|
160
|
+
return Object.fromEntries(normalizedEntries);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function buildRunnerLocalInboundReceiptKey(receiptRaw, { resolveTargetSelector = null } = {}) {
|
|
164
|
+
const receipt = normalizeRunnerRecentLocalInboundReceipt(receiptRaw);
|
|
165
|
+
const canonicalHumanKey = String(receipt.canonical_human_message_key || "").trim();
|
|
166
|
+
if (canonicalHumanKey) {
|
|
167
|
+
return `human:${canonicalHumanKey}`;
|
|
168
|
+
}
|
|
169
|
+
const chatID = String(receipt.chat_id || "").trim();
|
|
170
|
+
const messageID = intFromRawAllowZero(receipt.message_id, 0);
|
|
171
|
+
if (!chatID || !(messageID > 0)) {
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
const targetSelector = typeof resolveTargetSelector === "function"
|
|
175
|
+
? normalizeMentionSelector(resolveTargetSelector(receiptRaw))
|
|
176
|
+
: normalizeMentionSelector(receipt.receipt_bot_username || "");
|
|
177
|
+
return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(targetSelector)}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function findRecentTelegramInboundReceipt(rawMap, {
|
|
181
|
+
chatID = "",
|
|
182
|
+
messageID = 0,
|
|
183
|
+
canonicalHumanMessageKey = "",
|
|
184
|
+
} = {}) {
|
|
185
|
+
const normalizedChatID = String(chatID || "").trim();
|
|
186
|
+
const normalizedMessageID = intFromRawAllowZero(messageID, 0);
|
|
187
|
+
const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
|
|
188
|
+
if (!normalizedChatID || (!(normalizedMessageID > 0) && !normalizedCanonicalHumanMessageKey)) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
if (normalizedMessageID > 0) {
|
|
192
|
+
const exactReceipt = normalizeRunnerRecentLocalInboundReceipt(
|
|
193
|
+
safeObject(safeObject(rawMap)[buildRunnerRecentLocalInboundReceiptKey(normalizedChatID, normalizedMessageID)]),
|
|
194
|
+
);
|
|
195
|
+
if (Object.keys(exactReceipt).length > 0) {
|
|
196
|
+
return exactReceipt;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!normalizedCanonicalHumanMessageKey) {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
for (const value of Object.values(safeObject(rawMap))) {
|
|
203
|
+
const normalizedReceipt = normalizeRunnerRecentLocalInboundReceipt(value);
|
|
204
|
+
if (
|
|
205
|
+
String(normalizedReceipt.chat_id || "").trim() === normalizedChatID
|
|
206
|
+
&& String(normalizedReceipt.canonical_human_message_key || "").trim() === normalizedCanonicalHumanMessageKey
|
|
207
|
+
) {
|
|
208
|
+
return normalizedReceipt;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return {};
|
|
212
|
+
}
|
|
@@ -132,6 +132,14 @@ export function validateRunnerConversationDecisionBundle(bundleRaw) {
|
|
|
132
132
|
bundle,
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
|
+
if (bundle.should_close_after_reply === true && bundle.initial_responders.length > 1) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
status: "contradictory_multi_initial_close_state",
|
|
139
|
+
reason: "should_close_after_reply cannot be true when multiple initial_responders are expected to answer the opening turn",
|
|
140
|
+
bundle,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
135
143
|
if (
|
|
136
144
|
bundle.conversation_intent_mode === "single_bot"
|
|
137
145
|
&& bundle.allowed_responders.length > 1
|
|
@@ -154,6 +162,28 @@ export function validateRunnerConversationDecisionBundle(bundleRaw) {
|
|
|
154
162
|
bundle,
|
|
155
163
|
};
|
|
156
164
|
}
|
|
165
|
+
if (
|
|
166
|
+
bundle.conversation_intent_mode === "delegated_single_lead"
|
|
167
|
+
&& bundle.initial_responders.length !== 1
|
|
168
|
+
) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
status: "invalid_delegated_single_lead_initial_responders",
|
|
172
|
+
reason: "delegated_single_lead requires exactly one initial responder",
|
|
173
|
+
bundle,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
bundle.execution_contract_type === "delegation"
|
|
178
|
+
&& bundle.conversation_intent_mode === "multi_bot_direct"
|
|
179
|
+
) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
status: "contradictory_delegation_intent_mode",
|
|
183
|
+
reason: "multi_bot_direct cannot be combined with a delegation contract",
|
|
184
|
+
bundle,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
157
187
|
if (bundle.visible_handoff_required === true && bundle.visible_handoff_targets.length === 0) {
|
|
158
188
|
return {
|
|
159
189
|
ok: false,
|