metheus-governance-mcp-cli 0.2.293 → 0.2.294
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 +3 -82
- 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 +6 -26
- 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 +137 -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);
|
|
@@ -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,
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
resolveRunnerAuthoritativeTriggerDecision,
|
|
12
12
|
shouldRunnerTriggerProceed,
|
|
13
13
|
} from "./runner-trigger.mjs";
|
|
14
|
+
import {
|
|
15
|
+
buildRunnerLocalInboundReceiptKey,
|
|
16
|
+
} from "./runner-local-inbound-receipts.mjs";
|
|
14
17
|
|
|
15
18
|
function safeObject(value) {
|
|
16
19
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -116,31 +119,6 @@ function buildRunnerArchiveSourceMessageKey(recordRaw) {
|
|
|
116
119
|
return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(resolveRunnerArchiveSourceTargetSelector(recordRaw))}`;
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
function buildRunnerLocalInboundReceiptKey(receiptRaw) {
|
|
120
|
-
const receipt = safeObject(receiptRaw);
|
|
121
|
-
const canonicalHumanKey = buildCanonicalHumanInboundKey({
|
|
122
|
-
chat_id: receipt.chat_id,
|
|
123
|
-
message_id: receipt.message_id,
|
|
124
|
-
message_thread_id: receipt.message_thread_id,
|
|
125
|
-
reply_to_message_id: receipt.reply_to_message_id,
|
|
126
|
-
kind: receipt.kind,
|
|
127
|
-
sender_id: receipt.sender_id,
|
|
128
|
-
sender_username: receipt.sender_username,
|
|
129
|
-
sender_is_bot: receipt.sender_is_bot === true,
|
|
130
|
-
body: receipt.body,
|
|
131
|
-
occurred_at: receipt.occurred_at,
|
|
132
|
-
});
|
|
133
|
-
if (canonicalHumanKey) {
|
|
134
|
-
return `human:${canonicalHumanKey}`;
|
|
135
|
-
}
|
|
136
|
-
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
137
|
-
const messageID = intFromRawAllowZero(receipt.message_id || receipt.messageID, 0);
|
|
138
|
-
if (!chatID || !(messageID > 0)) {
|
|
139
|
-
return "";
|
|
140
|
-
}
|
|
141
|
-
return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(resolveRunnerLocalInboundReceiptTargetSelector(receiptRaw))}`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
122
|
function buildRunnerReceiptReplaySortTime(receiptRaw) {
|
|
145
123
|
const receipt = safeObject(receiptRaw);
|
|
146
124
|
const receiptTime = Date.parse(firstNonEmptyString([
|
|
@@ -190,7 +168,9 @@ function buildRunnerReceiptBackedPendingArchiveComments({
|
|
|
190
168
|
const latestReceiptsByKey = new Map();
|
|
191
169
|
for (const receiptRaw of ensureArray(currentPollLocalInboundReceipts)) {
|
|
192
170
|
const receipt = safeObject(receiptRaw);
|
|
193
|
-
const receiptKey = buildRunnerLocalInboundReceiptKey(receipt
|
|
171
|
+
const receiptKey = buildRunnerLocalInboundReceiptKey(receipt, {
|
|
172
|
+
resolveTargetSelector: resolveRunnerLocalInboundReceiptTargetSelector,
|
|
173
|
+
});
|
|
194
174
|
if (!receiptKey) {
|
|
195
175
|
continue;
|
|
196
176
|
}
|
|
@@ -21,36 +21,6 @@ function escapeRegexText(value) {
|
|
|
21
21
|
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function detectDirectedManagedReplyTarget({
|
|
25
|
-
text,
|
|
26
|
-
currentBotSelector = "",
|
|
27
|
-
managedMentions = [],
|
|
28
|
-
}) {
|
|
29
|
-
const normalizedText = String(text || "").trim();
|
|
30
|
-
const currentSelector = String(currentBotSelector || "").trim().toLowerCase();
|
|
31
|
-
if (!normalizedText || !currentSelector) {
|
|
32
|
-
return "";
|
|
33
|
-
}
|
|
34
|
-
const instructionPattern = /(?:\b(?:say|tell|greet|reply|answer|introduce|mention)\b|인사(?:해|하)|말(?:해|하)|얘기(?:해|하)|전달(?:해|하)|전해|알려|소개(?:해|하)|답(?:해|하)|응답(?:해|하)|말씀(?:해|하))/i;
|
|
35
|
-
if (!instructionPattern.test(normalizedText)) {
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
const candidates = ensureArray(managedMentions)
|
|
39
|
-
.map((item) => String(item || "").trim().toLowerCase())
|
|
40
|
-
.filter((item) => item && item !== currentSelector);
|
|
41
|
-
for (const selector of candidates) {
|
|
42
|
-
const escapedSelector = escapeRegexText(String(selector || "").replace(/^@+/, ""));
|
|
43
|
-
const targetPattern = new RegExp(
|
|
44
|
-
`(?:@${escapedSelector}\\s*(?:에게|한테|께|보고)?|(?:to|for)\\s+@${escapedSelector}\\b)`,
|
|
45
|
-
"i",
|
|
46
|
-
);
|
|
47
|
-
if (targetPattern.test(normalizedText)) {
|
|
48
|
-
return selector;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return "";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
24
|
const DIRECTED_MANAGED_REPLY_ENGLISH_ACTION_PATTERN = "\\b(?:say|tell|greet|reply|answer|introduce|mention)\\b";
|
|
55
25
|
const DIRECTED_MANAGED_REPLY_KOREAN_ACTION_PATTERN = "(?:\\uC778\\uC0AC|\\uB9D0|\\uC804\\uB2EC|\\uB2F5|\\uC18C\\uAC1C|\\uC5B8\\uAE09)";
|
|
56
26
|
const DIRECTED_MANAGED_REPLY_ACTION_PATTERN = `(?:${DIRECTED_MANAGED_REPLY_ENGLISH_ACTION_PATTERN}|${DIRECTED_MANAGED_REPLY_KOREAN_ACTION_PATTERN})`;
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
resolveRunnerAuthoritativeTriggerDecision,
|
|
10
10
|
} from "./runner-trigger.mjs";
|
|
11
|
+
import {
|
|
12
|
+
findRecentTelegramInboundReceipt,
|
|
13
|
+
normalizeRunnerRecentLocalInboundReceipt,
|
|
14
|
+
} from "./runner-local-inbound-receipts.mjs";
|
|
11
15
|
|
|
12
16
|
function safeObject(value) {
|
|
13
17
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -106,106 +110,6 @@ function doesTelegramEnvelopeMatchCanonicalHumanMessage(rawEnvelope, canonicalHu
|
|
|
106
110
|
return String(envelope.canonical_human_message_key || "").trim() === normalizedCanonicalHumanMessageKey;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
function normalizeRunnerRecentLocalInboundReceipt(rawReceipt) {
|
|
110
|
-
const receipt = safeObject(rawReceipt);
|
|
111
|
-
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
112
|
-
const messageID = intFromRawAllowZero(receipt.message_id ?? receipt.messageID, 0);
|
|
113
|
-
if (!chatID || !(messageID > 0)) {
|
|
114
|
-
return {};
|
|
115
|
-
}
|
|
116
|
-
const normalized = {
|
|
117
|
-
chat_id: chatID,
|
|
118
|
-
message_id: messageID,
|
|
119
|
-
...(intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0) > 0
|
|
120
|
-
? { message_thread_id: intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0) }
|
|
121
|
-
: {}),
|
|
122
|
-
...(intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0) > 0
|
|
123
|
-
? { reply_to_message_id: intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0) }
|
|
124
|
-
: {}),
|
|
125
|
-
...(String(receipt.kind || "").trim()
|
|
126
|
-
? { kind: String(receipt.kind || "").trim().toLowerCase() }
|
|
127
|
-
: {}),
|
|
128
|
-
...(String(receipt.sender || "").trim()
|
|
129
|
-
? { sender: String(receipt.sender || "").trim() }
|
|
130
|
-
: {}),
|
|
131
|
-
...(normalizeMentionSelector(receipt.sender_username || receipt.senderUsername || "")
|
|
132
|
-
? { sender_username: normalizeMentionSelector(receipt.sender_username || receipt.senderUsername || "") }
|
|
133
|
-
: {}),
|
|
134
|
-
...(String(receipt.sender_id || receipt.senderID || "").trim()
|
|
135
|
-
? { sender_id: String(receipt.sender_id || receipt.senderID || "").trim() }
|
|
136
|
-
: {}),
|
|
137
|
-
sender_is_bot: receipt.sender_is_bot === true || receipt.senderIsBot === true,
|
|
138
|
-
...(String(receipt.body || "").trim()
|
|
139
|
-
? { body: String(receipt.body || "").trim() }
|
|
140
|
-
: {}),
|
|
141
|
-
...(String(receipt.occurred_at || receipt.occurredAt || "").trim()
|
|
142
|
-
? { occurred_at: String(receipt.occurred_at || receipt.occurredAt || "").trim() }
|
|
143
|
-
: {}),
|
|
144
|
-
...(String(receipt.receipt_origin || receipt.receiptOrigin || "").trim()
|
|
145
|
-
? { receipt_origin: String(receipt.receipt_origin || receipt.receiptOrigin || "").trim().toLowerCase() }
|
|
146
|
-
: {}),
|
|
147
|
-
...(String(receipt.receipt_route_key || receipt.receiptRouteKey || "").trim()
|
|
148
|
-
? { receipt_route_key: String(receipt.receipt_route_key || receipt.receiptRouteKey || "").trim() }
|
|
149
|
-
: {}),
|
|
150
|
-
...(normalizeMentionSelector(receipt.receipt_bot_username || receipt.receiptBotUsername || "")
|
|
151
|
-
? { receipt_bot_username: normalizeMentionSelector(receipt.receipt_bot_username || receipt.receiptBotUsername || "") }
|
|
152
|
-
: {}),
|
|
153
|
-
};
|
|
154
|
-
const canonicalHumanMessageKey = buildCanonicalHumanInboundKey({
|
|
155
|
-
chat_id: normalized.chat_id,
|
|
156
|
-
message_id: normalized.message_id,
|
|
157
|
-
message_thread_id: normalized.message_thread_id,
|
|
158
|
-
reply_to_message_id: normalized.reply_to_message_id,
|
|
159
|
-
kind: normalized.kind,
|
|
160
|
-
sender_id: normalized.sender_id,
|
|
161
|
-
sender_username: normalized.sender_username,
|
|
162
|
-
sender_is_bot: normalized.sender_is_bot === true,
|
|
163
|
-
body: normalized.body,
|
|
164
|
-
occurred_at: normalized.occurred_at,
|
|
165
|
-
canonical_human_message_key: firstNonEmptyString([
|
|
166
|
-
receipt.canonical_human_message_key,
|
|
167
|
-
receipt.canonicalHumanMessageKey,
|
|
168
|
-
]),
|
|
169
|
-
});
|
|
170
|
-
if (canonicalHumanMessageKey) {
|
|
171
|
-
normalized.canonical_human_message_key = canonicalHumanMessageKey;
|
|
172
|
-
}
|
|
173
|
-
return normalized;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function findRecentTelegramInboundReceipt(rawMap, {
|
|
177
|
-
chatID = "",
|
|
178
|
-
messageID = 0,
|
|
179
|
-
canonicalHumanMessageKey = "",
|
|
180
|
-
} = {}) {
|
|
181
|
-
const normalizedChatID = String(chatID || "").trim();
|
|
182
|
-
const normalizedMessageID = intFromRawAllowZero(messageID, 0);
|
|
183
|
-
const normalizedCanonicalHumanMessageKey = normalizeCanonicalHumanMessageKey(canonicalHumanMessageKey);
|
|
184
|
-
if (!normalizedChatID || (!(normalizedMessageID > 0) && !normalizedCanonicalHumanMessageKey)) {
|
|
185
|
-
return {};
|
|
186
|
-
}
|
|
187
|
-
if (normalizedMessageID > 0) {
|
|
188
|
-
const key = `${normalizedChatID}:${normalizedMessageID}`;
|
|
189
|
-
const exactReceipt = normalizeRunnerRecentLocalInboundReceipt(safeObject(safeObject(rawMap)[key]));
|
|
190
|
-
if (Object.keys(exactReceipt).length > 0) {
|
|
191
|
-
return exactReceipt;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (!normalizedCanonicalHumanMessageKey) {
|
|
195
|
-
return {};
|
|
196
|
-
}
|
|
197
|
-
for (const value of Object.values(safeObject(rawMap))) {
|
|
198
|
-
const normalizedReceipt = normalizeRunnerRecentLocalInboundReceipt(value);
|
|
199
|
-
if (
|
|
200
|
-
String(normalizedReceipt.chat_id || "").trim() === normalizedChatID
|
|
201
|
-
&& String(normalizedReceipt.canonical_human_message_key || "").trim() === normalizedCanonicalHumanMessageKey
|
|
202
|
-
) {
|
|
203
|
-
return normalizedReceipt;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return {};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
113
|
function buildTelegramMessageEnvelopeFromRecentReceipt(rawReceipt) {
|
|
210
114
|
const receipt = normalizeRunnerRecentLocalInboundReceipt(rawReceipt);
|
|
211
115
|
if (!Object.keys(receipt).length) {
|
|
@@ -896,57 +896,6 @@ function doesTelegramEnvelopeMatchMessage(rawEnvelope, {
|
|
|
896
896
|
&& intFromRawAllowZero(envelope.message_id, 0) === normalizedMessageID;
|
|
897
897
|
}
|
|
898
898
|
|
|
899
|
-
function normalizeRunnerRecentLocalInboundReceipt(rawReceipt) {
|
|
900
|
-
const receipt = safeObject(rawReceipt);
|
|
901
|
-
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
902
|
-
const messageID = intFromRawAllowZero(receipt.message_id ?? receipt.messageID, 0);
|
|
903
|
-
if (!chatID || !(messageID > 0)) {
|
|
904
|
-
return {};
|
|
905
|
-
}
|
|
906
|
-
return {
|
|
907
|
-
chat_id: chatID,
|
|
908
|
-
message_id: messageID,
|
|
909
|
-
...(intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0) > 0
|
|
910
|
-
? { message_thread_id: intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0) }
|
|
911
|
-
: {}),
|
|
912
|
-
...(intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0) > 0
|
|
913
|
-
? { reply_to_message_id: intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0) }
|
|
914
|
-
: {}),
|
|
915
|
-
...(String(receipt.kind || "").trim()
|
|
916
|
-
? { kind: String(receipt.kind || "").trim().toLowerCase() }
|
|
917
|
-
: {}),
|
|
918
|
-
...(String(receipt.sender || "").trim()
|
|
919
|
-
? { sender: String(receipt.sender || "").trim() }
|
|
920
|
-
: {}),
|
|
921
|
-
...(normalizeMentionSelector(receipt.sender_username || receipt.senderUsername || "")
|
|
922
|
-
? { sender_username: normalizeMentionSelector(receipt.sender_username || receipt.senderUsername || "") }
|
|
923
|
-
: {}),
|
|
924
|
-
sender_is_bot: receipt.sender_is_bot === true || receipt.senderIsBot === true,
|
|
925
|
-
...(String(receipt.body || "").trim()
|
|
926
|
-
? { body: String(receipt.body || "").trim() }
|
|
927
|
-
: {}),
|
|
928
|
-
...(String(receipt.receipt_origin || receipt.receiptOrigin || "").trim()
|
|
929
|
-
? { receipt_origin: String(receipt.receipt_origin || receipt.receiptOrigin || "").trim().toLowerCase() }
|
|
930
|
-
: {}),
|
|
931
|
-
...(String(receipt.receipt_route_key || receipt.receiptRouteKey || "").trim()
|
|
932
|
-
? { receipt_route_key: String(receipt.receipt_route_key || receipt.receiptRouteKey || "").trim() }
|
|
933
|
-
: {}),
|
|
934
|
-
...(normalizeMentionSelector(receipt.receipt_bot_username || receipt.receiptBotUsername || "")
|
|
935
|
-
? { receipt_bot_username: normalizeMentionSelector(receipt.receipt_bot_username || receipt.receiptBotUsername || "") }
|
|
936
|
-
: {}),
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function findRecentTelegramInboundReceipt(rawMap, { chatID = "", messageID = 0 } = {}) {
|
|
941
|
-
const normalizedChatID = String(chatID || "").trim();
|
|
942
|
-
const normalizedMessageID = intFromRawAllowZero(messageID, 0);
|
|
943
|
-
if (!normalizedChatID || !(normalizedMessageID > 0)) {
|
|
944
|
-
return {};
|
|
945
|
-
}
|
|
946
|
-
const key = `${normalizedChatID}:${normalizedMessageID}`;
|
|
947
|
-
return normalizeRunnerRecentLocalInboundReceipt(safeObject(safeObject(rawMap)[key]));
|
|
948
|
-
}
|
|
949
|
-
|
|
950
899
|
export function resolveRunnerHumanInboundVisibility(args) {
|
|
951
900
|
return resolveRunnerHumanInboundVisibilityImpl(args);
|
|
952
901
|
}
|
package/lib/runner-runtime.mjs
CHANGED
|
@@ -2,6 +2,11 @@ import {
|
|
|
2
2
|
mergeRecentTelegramMessageEnvelopes,
|
|
3
3
|
normalizeTelegramMessageEnvelope,
|
|
4
4
|
} from "./runner-helpers.mjs";
|
|
5
|
+
import {
|
|
6
|
+
buildRunnerRecentLocalInboundReceiptKey,
|
|
7
|
+
normalizeRunnerRecentLocalInboundReceipt,
|
|
8
|
+
normalizeRunnerRecentLocalInboundReceiptEntry,
|
|
9
|
+
} from "./runner-local-inbound-receipts.mjs";
|
|
5
10
|
|
|
6
11
|
function safeObject(value) {
|
|
7
12
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -203,7 +208,7 @@ function buildRunnerLocalInboundOwnerMap({
|
|
|
203
208
|
return owners;
|
|
204
209
|
}
|
|
205
210
|
|
|
206
|
-
function resolveRunnerLocalInboundArtifactOwners({
|
|
211
|
+
export function resolveRunnerLocalInboundArtifactOwners({
|
|
207
212
|
update,
|
|
208
213
|
routeKey,
|
|
209
214
|
route,
|
|
@@ -233,15 +238,15 @@ function resolveRunnerLocalInboundArtifactOwners({
|
|
|
233
238
|
.filter(Boolean),
|
|
234
239
|
));
|
|
235
240
|
if (explicitMentions.length > 0) {
|
|
241
|
+
const explicitOwners = explicitMentions
|
|
242
|
+
.map((selector) => ownersBySelector.get(selector))
|
|
243
|
+
.filter((owner) => owner && owner.routeKey);
|
|
236
244
|
if (normalizedUpdate.fromIsBot !== true) {
|
|
237
|
-
if (
|
|
238
|
-
return
|
|
245
|
+
if (explicitOwners.length > 0) {
|
|
246
|
+
return explicitOwners;
|
|
239
247
|
}
|
|
240
248
|
return [];
|
|
241
249
|
}
|
|
242
|
-
const explicitOwners = explicitMentions
|
|
243
|
-
.map((selector) => ownersBySelector.get(selector))
|
|
244
|
-
.filter((owner) => owner && owner.routeKey);
|
|
245
250
|
if (explicitOwners.length > 0) {
|
|
246
251
|
return explicitOwners;
|
|
247
252
|
}
|
|
@@ -443,115 +448,6 @@ const RUNNER_RECENT_LOCAL_INBOUND_ENVELOPE_LIMIT = 200;
|
|
|
443
448
|
const RUNNER_RECENT_LOCAL_INBOUND_RECEIPT_LIMIT = 200;
|
|
444
449
|
const RUNNER_TELEGRAM_BOT_CAPABILITY_REFRESH_MS = 6 * 60 * 60 * 1000;
|
|
445
450
|
|
|
446
|
-
function buildRunnerRecentLocalInboundReceiptKey(chatID, messageID) {
|
|
447
|
-
return `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function normalizeRunnerRecentLocalInboundReceipt(rawReceipt, fallbackKey = "") {
|
|
451
|
-
const receipt = safeObject(rawReceipt);
|
|
452
|
-
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
453
|
-
const messageID = intFromRawAllowZero(receipt.message_id ?? receipt.messageID, 0);
|
|
454
|
-
const receiptKey = String(fallbackKey || buildRunnerRecentLocalInboundReceiptKey(chatID, messageID)).trim();
|
|
455
|
-
if (!receiptKey || !chatID || !(messageID > 0)) {
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
const normalized = {
|
|
459
|
-
chat_id: chatID,
|
|
460
|
-
message_id: messageID,
|
|
461
|
-
receipt_origin: firstNonEmptyString([
|
|
462
|
-
receipt.receipt_origin,
|
|
463
|
-
receipt.receiptOrigin,
|
|
464
|
-
receipt.source_origin,
|
|
465
|
-
receipt.sourceOrigin,
|
|
466
|
-
"local_telegram_inbound",
|
|
467
|
-
]),
|
|
468
|
-
receipt_route_key: firstNonEmptyString([
|
|
469
|
-
receipt.receipt_route_key,
|
|
470
|
-
receipt.receiptRouteKey,
|
|
471
|
-
receipt.source_route_key,
|
|
472
|
-
receipt.sourceRouteKey,
|
|
473
|
-
]),
|
|
474
|
-
receipt_bot_username: normalizeMentionSelector(
|
|
475
|
-
firstNonEmptyString([
|
|
476
|
-
receipt.receipt_bot_username,
|
|
477
|
-
receipt.receiptBotUsername,
|
|
478
|
-
receipt.source_bot_username,
|
|
479
|
-
receipt.sourceBotUsername,
|
|
480
|
-
]),
|
|
481
|
-
),
|
|
482
|
-
kind: firstNonEmptyString([receipt.kind, "telegram_message"]),
|
|
483
|
-
sender: firstNonEmptyString([receipt.sender, receipt.from_name, receipt.fromName]),
|
|
484
|
-
sender_username: firstNonEmptyString([
|
|
485
|
-
receipt.sender_username,
|
|
486
|
-
receipt.senderUsername,
|
|
487
|
-
receipt.from_username,
|
|
488
|
-
receipt.fromUsername,
|
|
489
|
-
]),
|
|
490
|
-
sender_is_bot: Boolean(receipt.sender_is_bot ?? receipt.senderIsBot ?? false),
|
|
491
|
-
body: firstNonEmptyString([receipt.body, receipt.text]),
|
|
492
|
-
occurred_at: firstNonEmptyString([receipt.occurred_at, receipt.occurredAt]),
|
|
493
|
-
received_at: firstNonEmptyString([receipt.received_at, receipt.receivedAt, new Date().toISOString()]),
|
|
494
|
-
};
|
|
495
|
-
const senderID = firstNonEmptyString([receipt.sender_id, receipt.senderID, receipt.from_id, receipt.fromID]);
|
|
496
|
-
if (senderID) {
|
|
497
|
-
normalized.sender_id = senderID;
|
|
498
|
-
}
|
|
499
|
-
const updateID = intFromRawAllowZero(receipt.update_id ?? receipt.updateID, 0);
|
|
500
|
-
if (updateID > 0) {
|
|
501
|
-
normalized.update_id = updateID;
|
|
502
|
-
}
|
|
503
|
-
const messageThreadID = intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0);
|
|
504
|
-
if (messageThreadID > 0) {
|
|
505
|
-
normalized.message_thread_id = messageThreadID;
|
|
506
|
-
}
|
|
507
|
-
const replyToMessageID = intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0);
|
|
508
|
-
if (replyToMessageID > 0) {
|
|
509
|
-
normalized.reply_to_message_id = replyToMessageID;
|
|
510
|
-
}
|
|
511
|
-
const replyToFromUsername = firstNonEmptyString([
|
|
512
|
-
receipt.reply_to_from_username,
|
|
513
|
-
receipt.replyToFromUsername,
|
|
514
|
-
receipt.reply_to_username,
|
|
515
|
-
receipt.replyToUsername,
|
|
516
|
-
]);
|
|
517
|
-
if (replyToFromUsername) {
|
|
518
|
-
normalized.reply_to_from_username = replyToFromUsername;
|
|
519
|
-
}
|
|
520
|
-
if (receipt.reply_to_from_is_bot === true || receipt.replyToFromIsBot === true) {
|
|
521
|
-
normalized.reply_to_from_is_bot = true;
|
|
522
|
-
}
|
|
523
|
-
const chatType = firstNonEmptyString([receipt.chat_type, receipt.chatType]);
|
|
524
|
-
if (chatType) {
|
|
525
|
-
normalized.chat_type = chatType;
|
|
526
|
-
}
|
|
527
|
-
const chatTitle = firstNonEmptyString([receipt.chat_title, receipt.chatTitle]);
|
|
528
|
-
if (chatTitle) {
|
|
529
|
-
normalized.chat_title = chatTitle;
|
|
530
|
-
}
|
|
531
|
-
const canonicalHumanMessageKey = String(
|
|
532
|
-
safeObject(normalizeTelegramMessageEnvelope({
|
|
533
|
-
chat_id: normalized.chat_id,
|
|
534
|
-
message_id: normalized.message_id,
|
|
535
|
-
message_thread_id: normalized.message_thread_id,
|
|
536
|
-
reply_to_message_id: normalized.reply_to_message_id,
|
|
537
|
-
kind: normalized.kind,
|
|
538
|
-
sender_id: normalized.sender_id,
|
|
539
|
-
sender_username: normalized.sender_username,
|
|
540
|
-
sender_is_bot: normalized.sender_is_bot === true,
|
|
541
|
-
body: normalized.body,
|
|
542
|
-
occurred_at: normalized.occurred_at,
|
|
543
|
-
canonical_human_message_key: firstNonEmptyString([
|
|
544
|
-
receipt.canonical_human_message_key,
|
|
545
|
-
receipt.canonicalHumanMessageKey,
|
|
546
|
-
]),
|
|
547
|
-
})).canonical_human_message_key || "",
|
|
548
|
-
).trim();
|
|
549
|
-
if (canonicalHumanMessageKey) {
|
|
550
|
-
normalized.canonical_human_message_key = canonicalHumanMessageKey;
|
|
551
|
-
}
|
|
552
|
-
return [receiptKey, normalized];
|
|
553
|
-
}
|
|
554
|
-
|
|
555
451
|
function buildRunnerLocalInboundEnvelopeFromReceipt(rawReceipt) {
|
|
556
452
|
const receipt = safeObject(rawReceipt);
|
|
557
453
|
return normalizeTelegramMessageEnvelope({
|
|
@@ -590,7 +486,7 @@ function buildRunnerLocalInboundArtifacts(updates, routeKey, route, bot, destina
|
|
|
590
486
|
const chatID = String(update.chatID || "").trim();
|
|
591
487
|
const messageID = intFromRawAllowZero(update.messageID, 0);
|
|
592
488
|
const receiptKey = buildRunnerRecentLocalInboundReceiptKey(chatID, messageID);
|
|
593
|
-
const receiptEntry =
|
|
489
|
+
const receiptEntry = normalizeRunnerRecentLocalInboundReceiptEntry({
|
|
594
490
|
update_id: intFromRawAllowZero(update.updateID, 0),
|
|
595
491
|
chat_id: chatID,
|
|
596
492
|
chat_type: update.chatType,
|
|
@@ -664,9 +560,9 @@ function buildRunnerRecentLocalInboundReceipts(routeStateRaw, localInboundArtifa
|
|
|
664
560
|
const routeState = safeObject(routeStateRaw);
|
|
665
561
|
const merged = new Map();
|
|
666
562
|
for (const [key, value] of Object.entries(safeObject(routeState.recent_local_inbound_receipts))) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
563
|
+
const normalizedEntry = normalizeRunnerRecentLocalInboundReceiptEntry(value, key);
|
|
564
|
+
if (!normalizedEntry) continue;
|
|
565
|
+
merged.set(normalizedEntry[0], normalizedEntry[1]);
|
|
670
566
|
}
|
|
671
567
|
for (const artifact of ensureArray(localInboundArtifacts)) {
|
|
672
568
|
const normalizedEntry = ensureArray(safeObject(artifact).receiptEntry);
|
|
@@ -93,6 +93,18 @@ import {
|
|
|
93
93
|
buildRunnerRouteDuplicateStateFromComment,
|
|
94
94
|
buildRunnerRouteStateFromComment,
|
|
95
95
|
} from "./runner-helpers.mjs";
|
|
96
|
+
import {
|
|
97
|
+
validateRunnerConversationDecisionBundle,
|
|
98
|
+
} from "./runner-orchestration-decision-bundle.mjs";
|
|
99
|
+
import {
|
|
100
|
+
resolveRunnerLocalInboundArtifactOwners,
|
|
101
|
+
} from "./runner-runtime.mjs";
|
|
102
|
+
import {
|
|
103
|
+
buildRunnerLocalInboundReceiptKey,
|
|
104
|
+
findRecentTelegramInboundReceipt,
|
|
105
|
+
normalizeRunnerRecentLocalInboundReceiptEntry,
|
|
106
|
+
normalizeRunnerRecentLocalInboundReceiptMap,
|
|
107
|
+
} from "./runner-local-inbound-receipts.mjs";
|
|
96
108
|
import {
|
|
97
109
|
buildRunnerInheritedRootReferenceRecorderResult,
|
|
98
110
|
buildRunnerRootThreadRecorderFailure,
|
|
@@ -22509,9 +22521,128 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
22509
22521
|
[{ id: "comment-3", body: botReplyArchiveComment }],
|
|
22510
22522
|
{ chatID: "-100123", messageID: 4321, parseArchivedChatComment },
|
|
22511
22523
|
);
|
|
22512
|
-
push(
|
|
22513
|
-
"archive_bot_reply_dedupe_by_chat_and_message_id",
|
|
22514
|
-
String(existingBotReplyRecord?.id || "") === "comment-3",
|
|
22515
|
-
`comment_id=${String(existingBotReplyRecord?.id || "(none)")}`,
|
|
22516
|
-
);
|
|
22517
|
-
|
|
22524
|
+
push(
|
|
22525
|
+
"archive_bot_reply_dedupe_by_chat_and_message_id",
|
|
22526
|
+
String(existingBotReplyRecord?.id || "") === "comment-3",
|
|
22527
|
+
`comment_id=${String(existingBotReplyRecord?.id || "(none)")}`,
|
|
22528
|
+
);
|
|
22529
|
+
|
|
22530
|
+
try {
|
|
22531
|
+
const owners = resolveRunnerLocalInboundArtifactOwners({
|
|
22532
|
+
update: {
|
|
22533
|
+
fromIsBot: false,
|
|
22534
|
+
mentionUsernames: ["RyoAI_bot", "RyoAI2_bot", "RyoAI3_bot"],
|
|
22535
|
+
},
|
|
22536
|
+
routeKey: "telegram-monitor-ryoai-bot-2::project::telegram::monitor::dest::actor",
|
|
22537
|
+
route: {
|
|
22538
|
+
botName: "RyoAI_bot",
|
|
22539
|
+
},
|
|
22540
|
+
bot: {
|
|
22541
|
+
username: "RyoAI_bot",
|
|
22542
|
+
},
|
|
22543
|
+
managedConversationBots: [
|
|
22544
|
+
{
|
|
22545
|
+
username: "RyoAI2_bot",
|
|
22546
|
+
routeKey: "telegram-monitor-ryoai2-bot-2::project::telegram::monitor::dest::actor",
|
|
22547
|
+
},
|
|
22548
|
+
{
|
|
22549
|
+
username: "RyoAI3_bot",
|
|
22550
|
+
routeKey: "telegram-monitor-ryoai3-bot::project::telegram::monitor::dest::actor",
|
|
22551
|
+
},
|
|
22552
|
+
],
|
|
22553
|
+
});
|
|
22554
|
+
push(
|
|
22555
|
+
"runner_runtime_fans_out_human_multi_mention_receipts_to_all_explicit_managed_routes",
|
|
22556
|
+
ensureArray(owners).length === 3
|
|
22557
|
+
&& ensureArray(owners).map((item) => String(safeSelftestObject(item).botUsername || "").trim().toLowerCase()).join(",") === "ryoai_bot,ryoai2_bot,ryoai3_bot",
|
|
22558
|
+
`owners=${ensureArray(owners).map((item) => `${String(safeSelftestObject(item).botUsername || "(none)")}:${String(safeSelftestObject(item).routeKey || "(none)")}`).join("|")}`,
|
|
22559
|
+
);
|
|
22560
|
+
} catch (err) {
|
|
22561
|
+
push("runner_runtime_fans_out_human_multi_mention_receipts_to_all_explicit_managed_routes", false, String(err?.message || err));
|
|
22562
|
+
}
|
|
22563
|
+
|
|
22564
|
+
try {
|
|
22565
|
+
const normalizedEntry = normalizeRunnerRecentLocalInboundReceiptEntry({
|
|
22566
|
+
chat_id: "-100123",
|
|
22567
|
+
message_id: 321,
|
|
22568
|
+
kind: "telegram_message",
|
|
22569
|
+
sender_username: "tester",
|
|
22570
|
+
sender_is_bot: false,
|
|
22571
|
+
body: "@RyoAI_bot @RyoAI2_bot hi",
|
|
22572
|
+
occurred_at: "2026-04-06T00:00:00.000Z",
|
|
22573
|
+
receipt_bot_username: "RyoAI_bot",
|
|
22574
|
+
});
|
|
22575
|
+
const normalizedReceipt = safeSelftestObject(ensureArray(normalizedEntry)[1]);
|
|
22576
|
+
const canonicalKey = String(normalizedReceipt.canonical_human_message_key || "").trim();
|
|
22577
|
+
const receiptMap = normalizeRunnerRecentLocalInboundReceiptMap({
|
|
22578
|
+
"-100123:321": {
|
|
22579
|
+
chat_id: "-100123",
|
|
22580
|
+
message_id: 321,
|
|
22581
|
+
kind: "telegram_message",
|
|
22582
|
+
sender_username: "tester",
|
|
22583
|
+
sender_is_bot: false,
|
|
22584
|
+
body: "@RyoAI_bot @RyoAI2_bot hi",
|
|
22585
|
+
occurred_at: "2026-04-06T00:00:00.000Z",
|
|
22586
|
+
receipt_bot_username: "RyoAI_bot",
|
|
22587
|
+
},
|
|
22588
|
+
});
|
|
22589
|
+
const foundByCanonical = findRecentTelegramInboundReceipt(receiptMap, {
|
|
22590
|
+
chatID: "-100123",
|
|
22591
|
+
canonicalHumanMessageKey: canonicalKey,
|
|
22592
|
+
});
|
|
22593
|
+
const archiveReceiptKey = buildRunnerLocalInboundReceiptKey(normalizedReceipt, {
|
|
22594
|
+
resolveTargetSelector: () => "RyoAI_bot",
|
|
22595
|
+
});
|
|
22596
|
+
push(
|
|
22597
|
+
"runner_local_inbound_receipt_helpers_share_canonical_normalization",
|
|
22598
|
+
Boolean(normalizedEntry)
|
|
22599
|
+
&& Boolean(canonicalKey)
|
|
22600
|
+
&& String(safeSelftestObject(foundByCanonical).canonical_human_message_key || "") === canonicalKey
|
|
22601
|
+
&& archiveReceiptKey === `human:${canonicalKey}`,
|
|
22602
|
+
`canonical=${canonicalKey || "(none)"} archive_key=${archiveReceiptKey || "(none)"} found=${String(safeSelftestObject(foundByCanonical).canonical_human_message_key || "(none)")}`,
|
|
22603
|
+
);
|
|
22604
|
+
} catch (err) {
|
|
22605
|
+
push("runner_local_inbound_receipt_helpers_share_canonical_normalization", false, String(err?.message || err));
|
|
22606
|
+
}
|
|
22607
|
+
|
|
22608
|
+
try {
|
|
22609
|
+
const contradictoryDirectValidation = validateRunnerConversationDecisionBundle({
|
|
22610
|
+
schema_version: "runner_conversation_decision.v1",
|
|
22611
|
+
decision_type: "reply_outcome",
|
|
22612
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
22613
|
+
allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
22614
|
+
initial_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
22615
|
+
selected_bot_usernames: ["ryoai_bot"],
|
|
22616
|
+
execution_contract_type: "direct_result",
|
|
22617
|
+
execution_contract_targets: ["ryoai_bot"],
|
|
22618
|
+
next_expected_responders: [],
|
|
22619
|
+
should_close_after_reply: true,
|
|
22620
|
+
turn_kind: "direct_reply",
|
|
22621
|
+
actionable_for_current_route: false,
|
|
22622
|
+
});
|
|
22623
|
+
const contradictoryDelegationValidation = validateRunnerConversationDecisionBundle({
|
|
22624
|
+
schema_version: "runner_conversation_decision.v1",
|
|
22625
|
+
decision_type: "reply_outcome",
|
|
22626
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
22627
|
+
allowed_responders: ["ryoai_bot", "ryoai2_bot"],
|
|
22628
|
+
initial_responders: ["ryoai_bot", "ryoai2_bot"],
|
|
22629
|
+
selected_bot_usernames: ["ryoai_bot", "ryoai2_bot"],
|
|
22630
|
+
execution_contract_type: "delegation",
|
|
22631
|
+
execution_contract_targets: ["ryoai2_bot"],
|
|
22632
|
+
next_expected_responders: ["ryoai2_bot"],
|
|
22633
|
+
should_close_after_reply: false,
|
|
22634
|
+
turn_kind: "delegation_kickoff",
|
|
22635
|
+
actionable_for_current_route: true,
|
|
22636
|
+
});
|
|
22637
|
+
push(
|
|
22638
|
+
"runner_decision_bundle_validation_rejects_contradictory_multi_bot_opening_contracts",
|
|
22639
|
+
contradictoryDirectValidation.ok !== true
|
|
22640
|
+
&& String(contradictoryDirectValidation.status || "") === "contradictory_multi_initial_close_state"
|
|
22641
|
+
&& contradictoryDelegationValidation.ok !== true
|
|
22642
|
+
&& String(contradictoryDelegationValidation.status || "") === "contradictory_delegation_intent_mode",
|
|
22643
|
+
`direct=${String(contradictoryDirectValidation.status || "(none)")}:${String(contradictoryDirectValidation.reason || "(none)")} delegation=${String(contradictoryDelegationValidation.status || "(none)")}:${String(contradictoryDelegationValidation.reason || "(none)")}`,
|
|
22644
|
+
);
|
|
22645
|
+
} catch (err) {
|
|
22646
|
+
push("runner_decision_bundle_validation_rejects_contradictory_multi_bot_opening_contracts", false, String(err?.message || err));
|
|
22647
|
+
}
|
|
22648
|
+
}
|