metheus-governance-mcp-cli 0.2.273 → 0.2.275
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 +45 -34
- package/lib/runner-orchestration-entrypoints.mjs +159 -5
- package/lib/runner-runtime.mjs +78 -7
- package/lib/selftest-runner-scenarios.mjs +127 -4
- package/lib/selftest-telegram-e2e.mjs +90 -3
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -3333,37 +3333,46 @@ function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
|
3333
3333
|
return normalized;
|
|
3334
3334
|
}
|
|
3335
3335
|
|
|
3336
|
-
function buildRunnerConsumedCommentLedgerKey(commentIDRaw, routeKeyRaw = "", commentKindRaw = "") {
|
|
3337
|
-
const commentID = String(commentIDRaw || "").trim();
|
|
3338
|
-
if (!commentID) return "";
|
|
3339
|
-
const routeKey = String(routeKeyRaw || "").trim();
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
const
|
|
3351
|
-
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID,
|
|
3352
|
-
const directEntry = safeObject(consumedComments[ledgerKey]);
|
|
3353
|
-
if (Object.keys(directEntry).length) {
|
|
3354
|
-
return directEntry;
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3336
|
+
function buildRunnerConsumedCommentLedgerKey(commentIDRaw, routeKeyRaw = "", commentKindRaw = "") {
|
|
3337
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
3338
|
+
if (!commentID) return "";
|
|
3339
|
+
const routeKey = String(routeKeyRaw || "").trim();
|
|
3340
|
+
if (routeKey) {
|
|
3341
|
+
return `${commentID}::${routeKey}`;
|
|
3342
|
+
}
|
|
3343
|
+
return commentID;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function findRunnerConsumedCommentEntry(rawConsumed, commentIDRaw, { routeKey = "", commentKind = "" } = {}) {
|
|
3347
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
3348
|
+
if (!commentID) return {};
|
|
3349
|
+
const consumedComments = normalizeBotRunnerConsumedComments(rawConsumed);
|
|
3350
|
+
const normalizedRouteKey = String(routeKey || "").trim();
|
|
3351
|
+
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID, normalizedRouteKey, commentKind);
|
|
3352
|
+
const directEntry = safeObject(consumedComments[ledgerKey]);
|
|
3353
|
+
if (Object.keys(directEntry).length) {
|
|
3354
|
+
return directEntry;
|
|
3355
|
+
}
|
|
3356
|
+
if (normalizedRouteKey) {
|
|
3357
|
+
const routeEntry = Object.values(consumedComments).find((entryRaw) => {
|
|
3358
|
+
const entry = safeObject(entryRaw);
|
|
3359
|
+
return String(entry.comment_id || "").trim() === commentID
|
|
3360
|
+
&& String(entry.route_key || "").trim() === normalizedRouteKey;
|
|
3361
|
+
});
|
|
3362
|
+
if (Object.keys(safeObject(routeEntry)).length > 0) {
|
|
3363
|
+
return safeObject(routeEntry);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
const legacyGlobalEntry = safeObject(consumedComments[commentID]);
|
|
3367
|
+
if (!Object.keys(legacyGlobalEntry).length) {
|
|
3368
|
+
return {};
|
|
3369
|
+
}
|
|
3370
|
+
const legacyRouteKey = String(legacyGlobalEntry.route_key || "").trim();
|
|
3371
|
+
if (normalizedRouteKey && legacyRouteKey && legacyRouteKey !== normalizedRouteKey) {
|
|
3372
|
+
return {};
|
|
3373
|
+
}
|
|
3374
|
+
return legacyGlobalEntry;
|
|
3375
|
+
}
|
|
3367
3376
|
|
|
3368
3377
|
function runnerRouteMatchesProjectConversationScope(candidateRouteRaw, normalizedRouteRaw) {
|
|
3369
3378
|
const candidateRoute = normalizeRunnerRoute(candidateRouteRaw);
|
|
@@ -8582,9 +8591,11 @@ function normalizeLocalTelegramUpdate(rawUpdate) {
|
|
|
8582
8591
|
};
|
|
8583
8592
|
}
|
|
8584
8593
|
|
|
8585
|
-
function buildArchivedInboundMessageKey(chatID, messageID) {
|
|
8586
|
-
|
|
8587
|
-
|
|
8594
|
+
function buildArchivedInboundMessageKey(chatID, messageID, sourceBotUsername = "") {
|
|
8595
|
+
const baseKey = `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
|
|
8596
|
+
const normalizedBotUsername = normalizeTelegramMentionUsername(sourceBotUsername);
|
|
8597
|
+
return normalizedBotUsername ? `${baseKey}::${normalizedBotUsername}` : baseKey;
|
|
8598
|
+
}
|
|
8588
8599
|
|
|
8589
8600
|
function formatTelegramInboundArchiveComment(normalized) {
|
|
8590
8601
|
const archiveSourceOrigin = String(normalized.archiveSourceOrigin || normalized.sourceOrigin || "").trim();
|
|
@@ -51,6 +51,150 @@ function intFromRawAllowZero(raw, fallback = 0) {
|
|
|
51
51
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function escapeRegex(text) {
|
|
55
|
+
return String(text || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeMentionSelector(value) {
|
|
59
|
+
return String(value || "").trim().replace(/^@+/, "").toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildRunnerInboundTargetSelectorSuffix(selector) {
|
|
63
|
+
const normalizedSelector = normalizeMentionSelector(selector);
|
|
64
|
+
return normalizedSelector ? `::${normalizedSelector}` : "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveRunnerArchiveSourceTargetSelector(recordRaw) {
|
|
68
|
+
const parsed = safeObject(safeObject(recordRaw).parsedArchive);
|
|
69
|
+
return normalizeMentionSelector(parsed.sourceBotUsername || parsed.source_bot_username);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveRunnerLocalInboundReceiptTargetSelector(receiptRaw) {
|
|
73
|
+
const receipt = safeObject(receiptRaw);
|
|
74
|
+
const receiptBotSelector = normalizeMentionSelector(
|
|
75
|
+
receipt.receipt_bot_username
|
|
76
|
+
|| receipt.receiptBotUsername
|
|
77
|
+
|| receipt.source_bot_username
|
|
78
|
+
|| receipt.sourceBotUsername,
|
|
79
|
+
);
|
|
80
|
+
if (!receiptBotSelector) {
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
const replyTargetSelector = normalizeMentionSelector(
|
|
84
|
+
receipt.reply_to_from_username
|
|
85
|
+
|| receipt.replyToFromUsername
|
|
86
|
+
|| receipt.reply_to_username
|
|
87
|
+
|| receipt.replyToUsername,
|
|
88
|
+
);
|
|
89
|
+
const replyTargetIsBot = receipt.reply_to_from_is_bot === true
|
|
90
|
+
|| receipt.replyToFromIsBot === true
|
|
91
|
+
|| receipt.reply_to_sender_is_bot === true
|
|
92
|
+
|| receipt.replyToSenderIsBot === true;
|
|
93
|
+
if (replyTargetIsBot && replyTargetSelector && replyTargetSelector === receiptBotSelector) {
|
|
94
|
+
return receiptBotSelector;
|
|
95
|
+
}
|
|
96
|
+
const body = String(receipt.body || receipt.text || "").trim();
|
|
97
|
+
if (!body) {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
const mentionPattern = new RegExp(`(^|\\s)@${escapeRegex(receiptBotSelector)}(?=\\b|\\s|$)`, "i");
|
|
101
|
+
return mentionPattern.test(body) ? receiptBotSelector : "";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildRunnerArchiveSourceMessageKey(recordRaw) {
|
|
105
|
+
const parsed = safeObject(safeObject(recordRaw).parsedArchive);
|
|
106
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
107
|
+
const messageID = intFromRawAllowZero(parsed.messageID || parsed.messageId, 0);
|
|
108
|
+
if (!chatID || !(messageID > 0)) {
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(resolveRunnerArchiveSourceTargetSelector(recordRaw))}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildRunnerLocalInboundReceiptKey(receiptRaw) {
|
|
115
|
+
const receipt = safeObject(receiptRaw);
|
|
116
|
+
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
117
|
+
const messageID = intFromRawAllowZero(receipt.message_id || receipt.messageID, 0);
|
|
118
|
+
if (!chatID || !(messageID > 0)) {
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
return `${chatID}:${messageID}${buildRunnerInboundTargetSelectorSuffix(resolveRunnerLocalInboundReceiptTargetSelector(receiptRaw))}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function buildRunnerReceiptReplaySortTime(receiptRaw) {
|
|
125
|
+
const receipt = safeObject(receiptRaw);
|
|
126
|
+
const receiptTime = Date.parse(firstNonEmptyString([
|
|
127
|
+
receipt.occurred_at,
|
|
128
|
+
receipt.occurredAt,
|
|
129
|
+
receipt.received_at,
|
|
130
|
+
receipt.receivedAt,
|
|
131
|
+
]));
|
|
132
|
+
return Number.isFinite(receiptTime) ? receiptTime : 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildRunnerReceiptBackedReplayRecord(recordRaw, receiptRaw, pendingSelectionOptions = {}) {
|
|
136
|
+
const record = safeObject(recordRaw);
|
|
137
|
+
const receipt = safeObject(receiptRaw);
|
|
138
|
+
const replayOccurredAt = firstNonEmptyString([
|
|
139
|
+
receipt.occurred_at,
|
|
140
|
+
receipt.occurredAt,
|
|
141
|
+
receipt.received_at,
|
|
142
|
+
receipt.receivedAt,
|
|
143
|
+
record.sourceOccurredAt,
|
|
144
|
+
safeObject(record.parsedArchive).occurredAt,
|
|
145
|
+
safeObject(record.parsedArchive).occurred_at,
|
|
146
|
+
record.createdAt,
|
|
147
|
+
record.updatedAt,
|
|
148
|
+
]);
|
|
149
|
+
let staleAfterAt = String(record.staleAfterAt || "").trim();
|
|
150
|
+
const maxPendingAgeMs = intFromRawAllowZero(safeObject(pendingSelectionOptions).maxPendingAgeMs, 0);
|
|
151
|
+
const replayOccurredAtMs = Date.parse(replayOccurredAt);
|
|
152
|
+
if (!staleAfterAt && maxPendingAgeMs > 0 && Number.isFinite(replayOccurredAtMs)) {
|
|
153
|
+
staleAfterAt = new Date(replayOccurredAtMs + maxPendingAgeMs).toISOString();
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...record,
|
|
157
|
+
sourceOccurredAt: replayOccurredAt,
|
|
158
|
+
staleAfterAt,
|
|
159
|
+
replayTriggeredByLocalReceipt: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildRunnerReceiptBackedPendingArchiveComments({
|
|
164
|
+
orderedComments,
|
|
165
|
+
currentPollLocalInboundReceipts,
|
|
166
|
+
existingPendingIDs,
|
|
167
|
+
pendingSelectionOptions,
|
|
168
|
+
}) {
|
|
169
|
+
const pendingIDs = new Set(ensureArray(existingPendingIDs).map((value) => String(value || "").trim()).filter(Boolean));
|
|
170
|
+
const latestReceiptsByKey = new Map();
|
|
171
|
+
for (const receiptRaw of ensureArray(currentPollLocalInboundReceipts)) {
|
|
172
|
+
const receipt = safeObject(receiptRaw);
|
|
173
|
+
const receiptKey = buildRunnerLocalInboundReceiptKey(receipt);
|
|
174
|
+
if (!receiptKey) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const previous = safeObject(latestReceiptsByKey.get(receiptKey));
|
|
178
|
+
if (buildRunnerReceiptReplaySortTime(receipt) >= buildRunnerReceiptReplaySortTime(previous)) {
|
|
179
|
+
latestReceiptsByKey.set(receiptKey, receipt);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const replayCandidates = [];
|
|
183
|
+
for (const [receiptKey, receipt] of latestReceiptsByKey.entries()) {
|
|
184
|
+
const matchedRecord = orderedComments.find((record) => buildRunnerArchiveSourceMessageKey(record) === receiptKey);
|
|
185
|
+
const matchedID = String(safeObject(matchedRecord).id || "").trim();
|
|
186
|
+
if (!matchedID || pendingIDs.has(matchedID)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
replayCandidates.push(buildRunnerReceiptBackedReplayRecord(
|
|
190
|
+
matchedRecord,
|
|
191
|
+
receipt,
|
|
192
|
+
pendingSelectionOptions,
|
|
193
|
+
));
|
|
194
|
+
}
|
|
195
|
+
return replayCandidates.sort(compareArchiveCommentRecords);
|
|
196
|
+
}
|
|
197
|
+
|
|
54
198
|
function buildContextSpeakerType(parsedArchiveRaw) {
|
|
55
199
|
const parsed = safeObject(parsedArchiveRaw);
|
|
56
200
|
const kind = String(parsed.kind || "").trim();
|
|
@@ -63,10 +207,6 @@ function buildContextSpeakerType(parsedArchiveRaw) {
|
|
|
63
207
|
return parsed.senderIsBot === true ? "bot" : "system";
|
|
64
208
|
}
|
|
65
209
|
|
|
66
|
-
function normalizeMentionSelector(value) {
|
|
67
|
-
return String(value || "").trim().replace(/^@+/, "").toLowerCase();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
210
|
function uniqueOrdered(values) {
|
|
71
211
|
const seen = new Set();
|
|
72
212
|
const output = [];
|
|
@@ -862,10 +1002,24 @@ export function selectRunnerPendingWork({
|
|
|
862
1002
|
pending: importedPendingAfterCursor,
|
|
863
1003
|
}, pendingSelectionOptions)
|
|
864
1004
|
: fullPending;
|
|
1005
|
+
const receiptBackedPending = buildRunnerReceiptBackedPendingArchiveComments({
|
|
1006
|
+
orderedComments,
|
|
1007
|
+
currentPollLocalInboundReceipts: ensureArray(importOutcome?.currentPollLocalInboundReceipts),
|
|
1008
|
+
existingPendingIDs: ensureArray(safeObject(pending).pending).map((record) => String(safeObject(record).id || "").trim()),
|
|
1009
|
+
pendingSelectionOptions,
|
|
1010
|
+
});
|
|
1011
|
+
const finalPending = ensureArray(safeObject(pending).pending).length === 0 && receiptBackedPending.length > 0
|
|
1012
|
+
? applyPendingAgeSelection({
|
|
1013
|
+
...safeObject(pending),
|
|
1014
|
+
shouldPrime: false,
|
|
1015
|
+
pending: receiptBackedPending,
|
|
1016
|
+
}, pendingSelectionOptions)
|
|
1017
|
+
: pending;
|
|
865
1018
|
return {
|
|
866
1019
|
orderedComments,
|
|
867
1020
|
inboundComments,
|
|
868
1021
|
importedRecords,
|
|
869
|
-
|
|
1022
|
+
receiptBackedPending,
|
|
1023
|
+
pending: finalPending,
|
|
870
1024
|
};
|
|
871
1025
|
}
|
package/lib/runner-runtime.mjs
CHANGED
|
@@ -280,6 +280,34 @@ function currentRouteOwnsRunnerInboundUpdate({
|
|
|
280
280
|
}).some((owner) => String(safeObject(owner).routeKey || "").trim() === normalizedRouteKey);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
function resolveRunnerInboundArchiveSourceBotUsername({
|
|
284
|
+
update,
|
|
285
|
+
route,
|
|
286
|
+
bot,
|
|
287
|
+
}) {
|
|
288
|
+
const normalizedUpdate = safeObject(update);
|
|
289
|
+
const currentBotSelectors = buildRouteBotUsernameCandidates(bot, route);
|
|
290
|
+
const currentBotSelector = ensureArray(currentBotSelectors)[0] || "";
|
|
291
|
+
if (!currentBotSelector) {
|
|
292
|
+
return "";
|
|
293
|
+
}
|
|
294
|
+
const explicitMentions = ensureArray(normalizedUpdate.mentionUsernames)
|
|
295
|
+
.map((value) => normalizeMentionSelector(value))
|
|
296
|
+
.filter(Boolean);
|
|
297
|
+
if (explicitMentions.includes(currentBotSelector)) {
|
|
298
|
+
return currentBotSelector;
|
|
299
|
+
}
|
|
300
|
+
const replyTargetSelector = normalizeMentionSelector(normalizedUpdate.replyToFromUsername);
|
|
301
|
+
if (
|
|
302
|
+
normalizedUpdate.replyToFromIsBot === true
|
|
303
|
+
&& replyTargetSelector
|
|
304
|
+
&& currentBotSelectors.includes(replyTargetSelector)
|
|
305
|
+
) {
|
|
306
|
+
return replyTargetSelector;
|
|
307
|
+
}
|
|
308
|
+
return "";
|
|
309
|
+
}
|
|
310
|
+
|
|
283
311
|
function managedConversationBotTargetsCurrentRoute({
|
|
284
312
|
update,
|
|
285
313
|
bot,
|
|
@@ -480,6 +508,18 @@ function normalizeRunnerRecentLocalInboundReceipt(rawReceipt, fallbackKey = "")
|
|
|
480
508
|
if (replyToMessageID > 0) {
|
|
481
509
|
normalized.reply_to_message_id = replyToMessageID;
|
|
482
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
|
+
}
|
|
483
523
|
const chatType = firstNonEmptyString([receipt.chat_type, receipt.chatType]);
|
|
484
524
|
if (chatType) {
|
|
485
525
|
normalized.chat_type = chatType;
|
|
@@ -535,6 +575,8 @@ function buildRunnerLocalInboundArtifacts(updates, routeKey, route, bot, destina
|
|
|
535
575
|
message_id: messageID,
|
|
536
576
|
message_thread_id: intFromRawAllowZero(update.messageThreadID, 0),
|
|
537
577
|
reply_to_message_id: intFromRawAllowZero(update.replyToMessageID, 0),
|
|
578
|
+
reply_to_from_username: update.replyToFromUsername,
|
|
579
|
+
reply_to_from_is_bot: update.replyToFromIsBot === true,
|
|
538
580
|
kind: update.fromIsBot ? "bot_reply" : "telegram_message",
|
|
539
581
|
sender_id: update.fromID,
|
|
540
582
|
sender: update.fromName,
|
|
@@ -575,6 +617,14 @@ function groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts) {
|
|
|
575
617
|
return Object.fromEntries(grouped.entries());
|
|
576
618
|
}
|
|
577
619
|
|
|
620
|
+
function buildRunnerCurrentPollLocalInboundReceipts(localInboundArtifactsByRoute, routeKey) {
|
|
621
|
+
return ensureArray(safeObject(localInboundArtifactsByRoute)[String(routeKey || "").trim()])
|
|
622
|
+
.map((artifactRaw) => ensureArray(safeObject(artifactRaw).receiptEntry))
|
|
623
|
+
.filter((entry) => entry.length === 2)
|
|
624
|
+
.map((entry) => safeObject(entry[1]))
|
|
625
|
+
.filter((receipt) => Object.keys(receipt).length > 0);
|
|
626
|
+
}
|
|
627
|
+
|
|
578
628
|
function buildRunnerRecentLocalInboundEnvelopes(routeStateRaw, recentLocalInboundReceipts) {
|
|
579
629
|
const routeState = safeObject(routeStateRaw);
|
|
580
630
|
const relevantEnvelopes = Object.values(safeObject(recentLocalInboundReceipts))
|
|
@@ -620,14 +670,16 @@ function buildRunnerRecentLocalInboundReceipts(routeStateRaw, localInboundArtifa
|
|
|
620
670
|
const RUNNER_INBOUND_ARCHIVE_RESERVATION_TTL_MS = 10 * 60 * 1000;
|
|
621
671
|
const runnerInboundArchiveReservations = new Map();
|
|
622
672
|
|
|
623
|
-
function buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID) {
|
|
673
|
+
function buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID, sourceBotUsername = "") {
|
|
624
674
|
const normalizedThreadID = String(threadID || "").trim();
|
|
625
675
|
const normalizedChatID = String(chatID || "").trim();
|
|
626
676
|
const normalizedMessageID = intFromRawAllowZero(messageID, 0);
|
|
677
|
+
const normalizedSourceBotUsername = normalizeMentionSelector(sourceBotUsername);
|
|
627
678
|
if (!normalizedThreadID || !normalizedChatID || !(normalizedMessageID > 0)) {
|
|
628
679
|
return "";
|
|
629
680
|
}
|
|
630
|
-
|
|
681
|
+
const baseKey = `${normalizedThreadID}::${normalizedChatID}:${normalizedMessageID}`;
|
|
682
|
+
return normalizedSourceBotUsername ? `${baseKey}::${normalizedSourceBotUsername}` : baseKey;
|
|
631
683
|
}
|
|
632
684
|
|
|
633
685
|
function cleanupRunnerInboundArchiveReservations(nowMs = Date.now()) {
|
|
@@ -638,8 +690,8 @@ function cleanupRunnerInboundArchiveReservations(nowMs = Date.now()) {
|
|
|
638
690
|
}
|
|
639
691
|
}
|
|
640
692
|
|
|
641
|
-
function reserveRunnerInboundArchiveMessage(threadID, chatID, messageID) {
|
|
642
|
-
const reservationKey = buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID);
|
|
693
|
+
function reserveRunnerInboundArchiveMessage(threadID, chatID, messageID, sourceBotUsername = "") {
|
|
694
|
+
const reservationKey = buildRunnerInboundArchiveReservationKey(threadID, chatID, messageID, sourceBotUsername);
|
|
643
695
|
if (!reservationKey) {
|
|
644
696
|
return {
|
|
645
697
|
ok: false,
|
|
@@ -769,7 +821,7 @@ async function loadRunnerExistingInboundArchiveKeys({
|
|
|
769
821
|
.map((record) => normalizeArchiveCommentRecord(record, parseArchivedChatComment))
|
|
770
822
|
.map((record) => record.parsedArchive)
|
|
771
823
|
.filter((parsed) => parsed && isInboundArchiveKind(parsed.kind) && parsed.chatID)
|
|
772
|
-
.map((parsed) => buildArchivedInboundMessageKey(parsed.chatID, parsed.messageID)),
|
|
824
|
+
.map((parsed) => buildArchivedInboundMessageKey(parsed.chatID, parsed.messageID, parsed.sourceBotUsername)),
|
|
773
825
|
);
|
|
774
826
|
}
|
|
775
827
|
|
|
@@ -827,12 +879,22 @@ async function archiveRunnerTelegramInboundUpdates({
|
|
|
827
879
|
) {
|
|
828
880
|
continue;
|
|
829
881
|
}
|
|
830
|
-
const
|
|
882
|
+
const archiveSourceBotUsername = resolveRunnerInboundArchiveSourceBotUsername({
|
|
883
|
+
update,
|
|
884
|
+
route,
|
|
885
|
+
bot,
|
|
886
|
+
});
|
|
887
|
+
const dedupeKey = buildArchivedInboundMessageKey(update.chatID, update.messageID, archiveSourceBotUsername);
|
|
831
888
|
if (boolFromRaw(archivePolicy.dedupeInbound, true) && existingKeys.has(dedupeKey)) {
|
|
832
889
|
continue;
|
|
833
890
|
}
|
|
834
891
|
const reservation = boolFromRaw(archivePolicy.dedupeInbound, true)
|
|
835
|
-
? reserveRunnerInboundArchiveMessage(
|
|
892
|
+
? reserveRunnerInboundArchiveMessage(
|
|
893
|
+
archiveThread?.threadID,
|
|
894
|
+
update.chatID,
|
|
895
|
+
update.messageID,
|
|
896
|
+
archiveSourceBotUsername,
|
|
897
|
+
)
|
|
836
898
|
: { ok: true, reservationKey: "" };
|
|
837
899
|
if (!reservation.ok) {
|
|
838
900
|
continue;
|
|
@@ -843,6 +905,8 @@ async function archiveRunnerTelegramInboundUpdates({
|
|
|
843
905
|
archiveBody = formatTelegramInboundArchiveComment({
|
|
844
906
|
...update,
|
|
845
907
|
archiveSourceOrigin: "telegram_archive_context",
|
|
908
|
+
archiveSourceRouteKey: archiveSourceBotUsername ? String(routeKey || "").trim() : "",
|
|
909
|
+
archiveSourceBotUsername,
|
|
846
910
|
});
|
|
847
911
|
createdComment = await createThreadComment({
|
|
848
912
|
siteBaseURL: runtime.baseURL,
|
|
@@ -1144,6 +1208,10 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1144
1208
|
managedConversationBots,
|
|
1145
1209
|
);
|
|
1146
1210
|
const localInboundArtifactsByRoute = groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts);
|
|
1211
|
+
const currentPollLocalInboundReceipts = buildRunnerCurrentPollLocalInboundReceipts(
|
|
1212
|
+
localInboundArtifactsByRoute,
|
|
1213
|
+
routeKey,
|
|
1214
|
+
);
|
|
1147
1215
|
const recentLocalInboundReceipts = buildRunnerRecentLocalInboundReceipts(
|
|
1148
1216
|
routeState,
|
|
1149
1217
|
ensureArray(localInboundArtifactsByRoute[routeKey]),
|
|
@@ -1205,7 +1273,9 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1205
1273
|
persistPollingProgress([], lastUpdateID);
|
|
1206
1274
|
return {
|
|
1207
1275
|
importedCommentIDs: [],
|
|
1276
|
+
importedComments: [],
|
|
1208
1277
|
importedCount: 0,
|
|
1278
|
+
currentPollLocalInboundReceipts,
|
|
1209
1279
|
lastUpdateID,
|
|
1210
1280
|
};
|
|
1211
1281
|
}
|
|
@@ -1249,6 +1319,7 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1249
1319
|
importedCommentIDs,
|
|
1250
1320
|
importedComments,
|
|
1251
1321
|
importedCount: importedCommentIDs.length,
|
|
1322
|
+
currentPollLocalInboundReceipts,
|
|
1252
1323
|
lastUpdateID: Math.max(lastUpdateID, handledUpdateID),
|
|
1253
1324
|
};
|
|
1254
1325
|
}
|
|
@@ -1007,6 +1007,63 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
1007
1007
|
);
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
|
+
try {
|
|
1011
|
+
const pendingWork = selectRunnerPendingWork({
|
|
1012
|
+
comments: [
|
|
1013
|
+
{
|
|
1014
|
+
id: "archived-current-message",
|
|
1015
|
+
createdAt: "2026-03-18T00:00:01.000Z",
|
|
1016
|
+
updatedAt: "2026-03-18T00:00:01.000Z",
|
|
1017
|
+
parsedArchive: { kind: "telegram_message", chatID: "-1001", messageID: 280, body: "@RyoAI3_bot 하이" },
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
id: "cursor-comment",
|
|
1021
|
+
createdAt: "2026-03-18T00:04:00.000Z",
|
|
1022
|
+
updatedAt: "2026-03-18T00:04:00.000Z",
|
|
1023
|
+
parsedArchive: { kind: "telegram_message", chatID: "-1001", messageID: 281, body: "@RyoAI_bot already processed" },
|
|
1024
|
+
},
|
|
1025
|
+
],
|
|
1026
|
+
importOutcome: {
|
|
1027
|
+
importedCommentIDs: [],
|
|
1028
|
+
currentPollLocalInboundReceipts: [
|
|
1029
|
+
{
|
|
1030
|
+
chat_id: "-1001",
|
|
1031
|
+
message_id: 280,
|
|
1032
|
+
occurred_at: "2026-03-18T00:05:00.000Z",
|
|
1033
|
+
received_at: "2026-03-18T00:05:00.000Z",
|
|
1034
|
+
update_id: 2001,
|
|
1035
|
+
},
|
|
1036
|
+
],
|
|
1037
|
+
},
|
|
1038
|
+
refreshedState: {
|
|
1039
|
+
last_processed_comment_id: "cursor-comment",
|
|
1040
|
+
last_processed_created_at: "2026-03-18T00:04:00.000Z",
|
|
1041
|
+
},
|
|
1042
|
+
mode: "start",
|
|
1043
|
+
parseArchivedChatComment: () => null,
|
|
1044
|
+
deps: {
|
|
1045
|
+
normalizeArchiveCommentRecord: (record) => record,
|
|
1046
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
1047
|
+
},
|
|
1048
|
+
pendingSelectionOptions: {
|
|
1049
|
+
maxPendingAgeMs: 15 * 60 * 1000,
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
push(
|
|
1053
|
+
"runner_pending_selection_replays_current_poll_local_receipt_against_existing_archive_comment",
|
|
1054
|
+
pendingWork.pending.pending.length === 1
|
|
1055
|
+
&& String(pendingWork.pending.pending[0]?.id || "").trim() === "archived-current-message"
|
|
1056
|
+
&& pendingWork.pending.pending[0]?.replayTriggeredByLocalReceipt === true,
|
|
1057
|
+
`pending=${pendingWork.pending.pending.map((item) => `${item.id}:${String(item.replayTriggeredByLocalReceipt === true)}`).join(",") || "(none)"}`,
|
|
1058
|
+
);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
push(
|
|
1061
|
+
"runner_pending_selection_replays_current_poll_local_receipt_against_existing_archive_comment",
|
|
1062
|
+
false,
|
|
1063
|
+
String(err?.message || err),
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1010
1067
|
try {
|
|
1011
1068
|
const selected = selectProjectChatDestination(
|
|
1012
1069
|
[
|
|
@@ -2042,10 +2099,16 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2042
2099
|
selectedRecord: humanRecord,
|
|
2043
2100
|
selectedBotUsernames: ["ryoai_bot"],
|
|
2044
2101
|
normalizedIntent: "coordination_request",
|
|
2045
|
-
});
|
|
2046
|
-
const claimedState = loadBotRunnerState();
|
|
2047
|
-
const claimedRequest = safeObject(safeObject(claimedState.requests)[claimed.requestKey]);
|
|
2048
|
-
const claimedConsumed = safeObject(
|
|
2102
|
+
});
|
|
2103
|
+
const claimedState = loadBotRunnerState();
|
|
2104
|
+
const claimedRequest = safeObject(safeObject(claimedState.requests)[claimed.requestKey]);
|
|
2105
|
+
const claimedConsumed = safeObject(
|
|
2106
|
+
Object.values(safeObject(claimedState.consumedComments)).find((entryRaw) => {
|
|
2107
|
+
const entry = safeObject(entryRaw);
|
|
2108
|
+
return String(entry.comment_id || "").trim() === humanRecord.id
|
|
2109
|
+
&& String(entry.route_key || "").trim() === requestRouteKey;
|
|
2110
|
+
}),
|
|
2111
|
+
);
|
|
2049
2112
|
push(
|
|
2050
2113
|
"runner_request_claim_persists_request_and_consumed_comment",
|
|
2051
2114
|
claimed.ok === true
|
|
@@ -19628,6 +19691,66 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
19628
19691
|
push("runner_entrypoint_pending_selection_stays_within_module_4_7_boundary", false, String(err?.message || err));
|
|
19629
19692
|
}
|
|
19630
19693
|
|
|
19694
|
+
try {
|
|
19695
|
+
const foreignArchiveComment = {
|
|
19696
|
+
id: "comment-foreign-owner-1",
|
|
19697
|
+
createdAt: "2026-03-30T00:00:00.000Z",
|
|
19698
|
+
updatedAt: "2026-03-30T00:00:00.000Z",
|
|
19699
|
+
body: "@woobn_bot hi",
|
|
19700
|
+
parsedArchive: {
|
|
19701
|
+
kind: "telegram_message",
|
|
19702
|
+
messageID: 280,
|
|
19703
|
+
chatID: "-100999",
|
|
19704
|
+
chatType: "supergroup",
|
|
19705
|
+
sender: "tester",
|
|
19706
|
+
senderIsBot: false,
|
|
19707
|
+
body: "@woobn_bot hi",
|
|
19708
|
+
sourceBotUsername: "woobn_bot",
|
|
19709
|
+
},
|
|
19710
|
+
};
|
|
19711
|
+
const pendingWork = selectRunnerPendingWorkEntrypoint({
|
|
19712
|
+
comments: [foreignArchiveComment],
|
|
19713
|
+
importOutcome: {
|
|
19714
|
+
importedCommentIDs: [],
|
|
19715
|
+
importedComments: [],
|
|
19716
|
+
currentPollLocalInboundReceipts: [
|
|
19717
|
+
{
|
|
19718
|
+
chat_id: "-100999",
|
|
19719
|
+
message_id: 280,
|
|
19720
|
+
body: "@RyoAI3_bot 하이",
|
|
19721
|
+
receipt_bot_username: "ryoai3_bot",
|
|
19722
|
+
occurred_at: "2026-03-31T00:10:00.000Z",
|
|
19723
|
+
},
|
|
19724
|
+
],
|
|
19725
|
+
},
|
|
19726
|
+
refreshedState: {
|
|
19727
|
+
last_processed_comment_id: "comment-foreign-owner-1",
|
|
19728
|
+
last_processed_created_at: "2026-03-30T00:00:00.000Z",
|
|
19729
|
+
},
|
|
19730
|
+
mode: "continue",
|
|
19731
|
+
parseArchivedChatComment,
|
|
19732
|
+
deps: {
|
|
19733
|
+
normalizeArchiveCommentRecord: (record) => ({
|
|
19734
|
+
id: String(record?.id || "").trim(),
|
|
19735
|
+
body: String(record?.body || "").trim(),
|
|
19736
|
+
createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
|
|
19737
|
+
updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
|
|
19738
|
+
parsedArchive: safeObject(record?.parsedArchive),
|
|
19739
|
+
}),
|
|
19740
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
19741
|
+
},
|
|
19742
|
+
pendingSelectionOptions: {},
|
|
19743
|
+
});
|
|
19744
|
+
push(
|
|
19745
|
+
"runner_entrypoint_receipt_replay_skips_foreign_bot_archive_collision",
|
|
19746
|
+
ensureArray(pendingWork.receiptBackedPending).length === 0
|
|
19747
|
+
&& ensureArray(safeObject(pendingWork.pending).pending).length === 0,
|
|
19748
|
+
`replay=${String(ensureArray(pendingWork.receiptBackedPending).length)} pending=${String(ensureArray(safeObject(pendingWork.pending).pending).length)}`,
|
|
19749
|
+
);
|
|
19750
|
+
} catch (err) {
|
|
19751
|
+
push("runner_entrypoint_receipt_replay_skips_foreign_bot_archive_collision", false, String(err?.message || err));
|
|
19752
|
+
}
|
|
19753
|
+
|
|
19631
19754
|
try {
|
|
19632
19755
|
const deliveryContext = await prepareLocalBotDeliveryContext({
|
|
19633
19756
|
siteBaseURL: "https://example.test",
|
|
@@ -923,10 +923,10 @@ export async function runSelftestTelegramE2E(push, deps) {
|
|
|
923
923
|
`envelope=${JSON.stringify(progressEnvelope)}`,
|
|
924
924
|
);
|
|
925
925
|
push(
|
|
926
|
-
"
|
|
926
|
+
"telegram_archive_comment_preserves_route_ownership_fields_for_explicit_targeting",
|
|
927
927
|
progressCommentBody.includes("archive_source_origin: telegram_archive_context")
|
|
928
|
-
&&
|
|
929
|
-
&&
|
|
928
|
+
&& progressCommentBody.includes("archive_source_route_key:")
|
|
929
|
+
&& progressCommentBody.includes("archive_source_bot_username:"),
|
|
930
930
|
`body=${progressCommentBody}`,
|
|
931
931
|
);
|
|
932
932
|
const progressParsedArchive = parseArchivedChatComment(progressCommentBody);
|
|
@@ -1657,6 +1657,93 @@ export async function runSelftestTelegramE2E(push, deps) {
|
|
|
1657
1657
|
&& ownershipBodies.length === 0,
|
|
1658
1658
|
`owner=${JSON.stringify(ryoai2Receipt)} foreign=${JSON.stringify(ryoai3Receipt)} comments=${ownershipBodies.join(" || ")}`,
|
|
1659
1659
|
);
|
|
1660
|
+
|
|
1661
|
+
telegramE2EServer.state.comments = [
|
|
1662
|
+
{
|
|
1663
|
+
id: "foreign-owner-archive-280",
|
|
1664
|
+
body: buildRunnerRuntimeDeps().formatTelegramInboundArchiveComment({
|
|
1665
|
+
eventName: "telegram.message.created",
|
|
1666
|
+
chatID: e2eDestination.chat_id,
|
|
1667
|
+
chatType: "supergroup",
|
|
1668
|
+
messageID: 280,
|
|
1669
|
+
fromID: "7001",
|
|
1670
|
+
fromName: "Operator",
|
|
1671
|
+
fromUsername: "operator_user",
|
|
1672
|
+
fromIsBot: false,
|
|
1673
|
+
mentionUsernames: ["woobn_bot"],
|
|
1674
|
+
text: "@WooBN_bot 하이",
|
|
1675
|
+
occurredAt: "2026-03-31T00:00:00.000Z",
|
|
1676
|
+
archiveSourceOrigin: "telegram_archive_context",
|
|
1677
|
+
archiveSourceRouteKey: "telegram-monitor-woobn-bot::project::telegram::monitor::dest::actor",
|
|
1678
|
+
archiveSourceBotUsername: "woobn_bot",
|
|
1679
|
+
}),
|
|
1680
|
+
created_at: "2026-03-31T00:00:01.000Z",
|
|
1681
|
+
updated_at: "2026-03-31T00:00:01.000Z",
|
|
1682
|
+
author_user_id: e2eActorUserID,
|
|
1683
|
+
},
|
|
1684
|
+
];
|
|
1685
|
+
telegramE2EServer.state.updates = [
|
|
1686
|
+
{
|
|
1687
|
+
update_id: 404,
|
|
1688
|
+
message: {
|
|
1689
|
+
message_id: 280,
|
|
1690
|
+
date: Math.floor(Date.now() / 1000),
|
|
1691
|
+
chat: {
|
|
1692
|
+
id: Number(e2eDestination.chat_id),
|
|
1693
|
+
type: "supergroup",
|
|
1694
|
+
title: e2eDestination.label,
|
|
1695
|
+
},
|
|
1696
|
+
from: {
|
|
1697
|
+
id: 7002,
|
|
1698
|
+
is_bot: false,
|
|
1699
|
+
first_name: "Operator",
|
|
1700
|
+
username: "operator_user",
|
|
1701
|
+
},
|
|
1702
|
+
text: "@RyoAI3_bot 하이",
|
|
1703
|
+
entities: buildTelegramMentionEntities("@RyoAI3_bot 하이"),
|
|
1704
|
+
},
|
|
1705
|
+
},
|
|
1706
|
+
];
|
|
1707
|
+
await archiveLocalTelegramMessagesForRoute({
|
|
1708
|
+
routeKey: routeRyoai3Key,
|
|
1709
|
+
route: routeRyoai3,
|
|
1710
|
+
routeState: {},
|
|
1711
|
+
runtime: {
|
|
1712
|
+
baseURL: telegramE2EServer.baseURL,
|
|
1713
|
+
timeoutSeconds: 10,
|
|
1714
|
+
token: e2eToken,
|
|
1715
|
+
actor: {
|
|
1716
|
+
user_id: e2eActorUserID,
|
|
1717
|
+
},
|
|
1718
|
+
},
|
|
1719
|
+
bot: {
|
|
1720
|
+
id: "88888888-8888-4888-8888-888888888883",
|
|
1721
|
+
name: "RyoAI3_bot",
|
|
1722
|
+
username: "RyoAI3_bot",
|
|
1723
|
+
role: "monitor",
|
|
1724
|
+
},
|
|
1725
|
+
destination: {
|
|
1726
|
+
chatID: e2eDestination.chat_id,
|
|
1727
|
+
},
|
|
1728
|
+
archiveThread: {
|
|
1729
|
+
threadID: e2eThreadID,
|
|
1730
|
+
},
|
|
1731
|
+
managedConversationBots: [
|
|
1732
|
+
{
|
|
1733
|
+
username: "RyoAI3_bot",
|
|
1734
|
+
route: routeRyoai3,
|
|
1735
|
+
bot: { username: "RyoAI3_bot", name: "RyoAI3_bot" },
|
|
1736
|
+
},
|
|
1737
|
+
],
|
|
1738
|
+
deps: buildRunnerRuntimeDeps(),
|
|
1739
|
+
});
|
|
1740
|
+
const foreignCollisionBodies = telegramE2EServer.state.comments.map((item) => String(item.body || ""));
|
|
1741
|
+
push(
|
|
1742
|
+
"telegram_explicit_self_mention_reimports_when_same_message_id_exists_for_foreign_bot_archive",
|
|
1743
|
+
foreignCollisionBodies.filter((item) => item.includes("message_id: 280")).length === 2
|
|
1744
|
+
&& foreignCollisionBodies.some((item) => item.includes("archive_source_bot_username: @ryoai3_bot")),
|
|
1745
|
+
`count=${foreignCollisionBodies.filter((item) => item.includes("message_id: 280")).length} bodies=${foreignCollisionBodies.join(" || ")}`,
|
|
1746
|
+
);
|
|
1660
1747
|
} catch (err) {
|
|
1661
1748
|
push("telegram_runner_e2e_local_mock", false, String(err?.message || err));
|
|
1662
1749
|
} finally {
|