metheus-governance-mcp-cli 0.2.294 → 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 +35 -12
- package/lib/runner-orchestration-entrypoints.mjs +319 -10
- package/lib/selftest-runner-scenarios.mjs +248 -0
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -10632,10 +10632,10 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
10632
10632
|
latestRunnerState.consumedComments || latestRunnerState.consumed_comments,
|
|
10633
10633
|
);
|
|
10634
10634
|
const requests = normalizeBotRunnerRequests(latestRunnerState.requests);
|
|
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);
|
|
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);
|
|
10639
10639
|
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
10640
10640
|
if (commentID && safeObject(excludedComments)[commentID]) {
|
|
10641
10641
|
return false;
|
|
@@ -10663,22 +10663,45 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
10663
10663
|
}
|
|
10664
10664
|
const sessionMatch = findScopedConversationSessionState(latestRunnerState, normalizedRoute, conversationID);
|
|
10665
10665
|
return sessionAllowsConversationResponder(sessionMatch.session, currentBotSelector);
|
|
10666
|
-
}
|
|
10667
|
-
return true;
|
|
10668
|
-
});
|
|
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
|
+
});
|
|
10669
10690
|
const pendingWork = selectRunnerPendingWork({
|
|
10670
10691
|
comments: commentsForPending,
|
|
10671
10692
|
importOutcome,
|
|
10672
10693
|
refreshedState,
|
|
10673
|
-
mode,
|
|
10694
|
+
mode,
|
|
10674
10695
|
parseArchivedChatComment,
|
|
10675
10696
|
pendingSelectionOptions: {
|
|
10676
10697
|
maxPendingAgeMs: BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS,
|
|
10677
10698
|
},
|
|
10678
|
-
deps: {
|
|
10679
|
-
applyPendingAgeSelection,
|
|
10680
|
-
normalizeArchiveCommentRecord,
|
|
10681
|
-
},
|
|
10699
|
+
deps: {
|
|
10700
|
+
applyPendingAgeSelection,
|
|
10701
|
+
normalizeArchiveCommentRecord,
|
|
10702
|
+
},
|
|
10703
|
+
followupRequests: followupRequestsForPending,
|
|
10704
|
+
currentBotSelector,
|
|
10682
10705
|
});
|
|
10683
10706
|
const pending = pendingWork.pending;
|
|
10684
10707
|
if (pending.staleSkippedLatest) {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
dedupeProcessableArchiveComments,
|
|
6
6
|
isArchiveRecordAfterState,
|
|
7
7
|
isInboundArchiveKind,
|
|
8
|
+
normalizeTelegramMessageEnvelope,
|
|
8
9
|
selectPendingArchiveComments,
|
|
9
10
|
} from "./runner-helpers.mjs";
|
|
10
11
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
} from "./runner-trigger.mjs";
|
|
14
15
|
import {
|
|
15
16
|
buildRunnerLocalInboundReceiptKey,
|
|
17
|
+
normalizeRunnerRecentLocalInboundReceiptMap,
|
|
16
18
|
} from "./runner-local-inbound-receipts.mjs";
|
|
17
19
|
|
|
18
20
|
function safeObject(value) {
|
|
@@ -130,6 +132,101 @@ function buildRunnerReceiptReplaySortTime(receiptRaw) {
|
|
|
130
132
|
return Number.isFinite(receiptTime) ? receiptTime : 0;
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
function extractReceiptMentionSelectors(text) {
|
|
136
|
+
const matches = Array.from(String(text || "").matchAll(/@([A-Za-z0-9_]+)/g));
|
|
137
|
+
return Array.from(new Set(matches.map((match) => normalizeMentionSelector(match[1] || "")).filter(Boolean)));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildRunnerReceiptReplayRouteState(routeStateRaw) {
|
|
141
|
+
const routeState = safeObject(routeStateRaw);
|
|
142
|
+
return {
|
|
143
|
+
...routeState,
|
|
144
|
+
last_processed_comment_id: String(routeState.active_comment_id || "").trim()
|
|
145
|
+
|| String(routeState.last_processed_comment_id || "").trim(),
|
|
146
|
+
last_processed_created_at: String(routeState.active_comment_created_at || "").trim()
|
|
147
|
+
|| String(routeState.last_processed_created_at || "").trim(),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildRunnerReceiptBackedSyntheticRecord(receiptRaw, receiptKey) {
|
|
152
|
+
const receipt = safeObject(receiptRaw);
|
|
153
|
+
const chatID = String(receipt.chat_id || "").trim();
|
|
154
|
+
const messageID = intFromRawAllowZero(receipt.message_id, 0);
|
|
155
|
+
const occurredAt = firstNonEmptyString([
|
|
156
|
+
receipt.occurred_at,
|
|
157
|
+
receipt.occurredAt,
|
|
158
|
+
receipt.received_at,
|
|
159
|
+
receipt.receivedAt,
|
|
160
|
+
]);
|
|
161
|
+
if (!chatID || !(messageID > 0) || !occurredAt) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const body = String(receipt.body || receipt.text || "").trim();
|
|
165
|
+
const parsedArchive = {
|
|
166
|
+
kind: firstNonEmptyString([receipt.kind, "telegram_message"]),
|
|
167
|
+
chatID,
|
|
168
|
+
messageID,
|
|
169
|
+
sender: String(receipt.sender || "").trim(),
|
|
170
|
+
senderID: String(receipt.sender_id || "").trim(),
|
|
171
|
+
senderIsBot: receipt.sender_is_bot === true,
|
|
172
|
+
username: String(receipt.sender_username || "").trim(),
|
|
173
|
+
mentionUsernames: extractReceiptMentionSelectors(body),
|
|
174
|
+
occurredAt,
|
|
175
|
+
canonicalHumanMessageKey: String(receipt.canonical_human_message_key || "").trim(),
|
|
176
|
+
sourceOrigin: firstNonEmptyString([receipt.receipt_origin, "local_telegram_inbound"]).toLowerCase(),
|
|
177
|
+
sourceRouteKey: String(receipt.receipt_route_key || "").trim(),
|
|
178
|
+
sourceBotUsername: normalizeMentionSelector(receipt.receipt_bot_username || ""),
|
|
179
|
+
body,
|
|
180
|
+
};
|
|
181
|
+
const messageThreadID = intFromRawAllowZero(receipt.message_thread_id, 0);
|
|
182
|
+
if (messageThreadID > 0) {
|
|
183
|
+
parsedArchive.messageThreadID = messageThreadID;
|
|
184
|
+
}
|
|
185
|
+
const replyToMessageID = intFromRawAllowZero(receipt.reply_to_message_id, 0);
|
|
186
|
+
if (replyToMessageID > 0) {
|
|
187
|
+
parsedArchive.replyToMessageID = replyToMessageID;
|
|
188
|
+
}
|
|
189
|
+
const replyToUsername = String(receipt.reply_to_from_username || "").trim();
|
|
190
|
+
if (replyToUsername) {
|
|
191
|
+
parsedArchive.replyToUsername = replyToUsername;
|
|
192
|
+
}
|
|
193
|
+
if (receipt.reply_to_from_is_bot === true) {
|
|
194
|
+
parsedArchive.replyToSenderIsBot = true;
|
|
195
|
+
}
|
|
196
|
+
const chatType = String(receipt.chat_type || "").trim();
|
|
197
|
+
if (chatType) {
|
|
198
|
+
parsedArchive.chatType = chatType;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
id: `receipt-replay:${receiptKey}`,
|
|
202
|
+
body,
|
|
203
|
+
createdAt: occurredAt,
|
|
204
|
+
updatedAt: occurredAt,
|
|
205
|
+
sourceOccurredAt: occurredAt,
|
|
206
|
+
parsedArchive,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function findRunnerReceiptArchiveCollisionRecord(orderedComments, receiptRaw, expectedReceiptKey = "") {
|
|
211
|
+
const receipt = safeObject(receiptRaw);
|
|
212
|
+
const chatID = String(receipt.chat_id || "").trim();
|
|
213
|
+
const messageID = intFromRawAllowZero(receipt.message_id, 0);
|
|
214
|
+
if (!chatID || !(messageID > 0)) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
return ensureArray(orderedComments).find((recordRaw) => {
|
|
218
|
+
const record = safeObject(recordRaw);
|
|
219
|
+
const parsed = safeObject(record.parsedArchive);
|
|
220
|
+
if (
|
|
221
|
+
firstNonEmptyString([parsed.chatID, parsed.chatId]) !== chatID
|
|
222
|
+
|| intFromRawAllowZero(parsed.messageID, 0) !== messageID
|
|
223
|
+
) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return buildRunnerArchiveSourceMessageKey(record) !== String(expectedReceiptKey || "").trim();
|
|
227
|
+
}) || null;
|
|
228
|
+
}
|
|
229
|
+
|
|
133
230
|
function buildRunnerReceiptBackedReplayRecord(recordRaw, receiptRaw, pendingSelectionOptions = {}) {
|
|
134
231
|
const record = safeObject(recordRaw);
|
|
135
232
|
const receipt = safeObject(receiptRaw);
|
|
@@ -161,33 +258,75 @@ function buildRunnerReceiptBackedReplayRecord(recordRaw, receiptRaw, pendingSele
|
|
|
161
258
|
function buildRunnerReceiptBackedPendingArchiveComments({
|
|
162
259
|
orderedComments,
|
|
163
260
|
currentPollLocalInboundReceipts,
|
|
261
|
+
routeStateLocalInboundReceipts,
|
|
262
|
+
routeState,
|
|
164
263
|
existingPendingIDs,
|
|
165
264
|
pendingSelectionOptions,
|
|
166
265
|
}) {
|
|
167
266
|
const pendingIDs = new Set(ensureArray(existingPendingIDs).map((value) => String(value || "").trim()).filter(Boolean));
|
|
168
267
|
const latestReceiptsByKey = new Map();
|
|
169
|
-
|
|
268
|
+
const registerReceipt = (receiptRaw, source) => {
|
|
170
269
|
const receipt = safeObject(receiptRaw);
|
|
171
270
|
const receiptKey = buildRunnerLocalInboundReceiptKey(receipt, {
|
|
172
271
|
resolveTargetSelector: resolveRunnerLocalInboundReceiptTargetSelector,
|
|
173
272
|
});
|
|
174
273
|
if (!receiptKey) {
|
|
175
|
-
|
|
274
|
+
return;
|
|
176
275
|
}
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
276
|
+
const previousEntry = safeObject(latestReceiptsByKey.get(receiptKey));
|
|
277
|
+
const previousReceipt = safeObject(previousEntry.receipt);
|
|
278
|
+
const previousSource = String(previousEntry.source || "").trim();
|
|
279
|
+
const nextSortTime = buildRunnerReceiptReplaySortTime(receipt);
|
|
280
|
+
const previousSortTime = buildRunnerReceiptReplaySortTime(previousReceipt);
|
|
281
|
+
if (
|
|
282
|
+
nextSortTime > previousSortTime
|
|
283
|
+
|| (
|
|
284
|
+
nextSortTime === previousSortTime
|
|
285
|
+
&& source === "current_poll"
|
|
286
|
+
&& previousSource !== "current_poll"
|
|
287
|
+
)
|
|
288
|
+
) {
|
|
289
|
+
latestReceiptsByKey.set(receiptKey, { receipt, source });
|
|
180
290
|
}
|
|
291
|
+
};
|
|
292
|
+
for (const receiptRaw of ensureArray(currentPollLocalInboundReceipts)) {
|
|
293
|
+
registerReceipt(receiptRaw, "current_poll");
|
|
181
294
|
}
|
|
295
|
+
for (const receiptRaw of Object.values(normalizeRunnerRecentLocalInboundReceiptMap(routeStateLocalInboundReceipts))) {
|
|
296
|
+
registerReceipt(receiptRaw, "route_state");
|
|
297
|
+
}
|
|
298
|
+
const replayRouteState = buildRunnerReceiptReplayRouteState(routeState);
|
|
182
299
|
const replayCandidates = [];
|
|
183
|
-
for (const [receiptKey,
|
|
300
|
+
for (const [receiptKey, receiptEntryRaw] of latestReceiptsByKey.entries()) {
|
|
301
|
+
const receiptEntry = safeObject(receiptEntryRaw);
|
|
302
|
+
const receipt = safeObject(receiptEntry.receipt);
|
|
303
|
+
const receiptSource = String(receiptEntry.source || "").trim();
|
|
184
304
|
const matchedRecord = orderedComments.find((record) => buildRunnerArchiveSourceMessageKey(record) === receiptKey);
|
|
185
|
-
const
|
|
305
|
+
const collisionRecord = matchedRecord
|
|
306
|
+
? null
|
|
307
|
+
: findRunnerReceiptArchiveCollisionRecord(orderedComments, receipt, receiptKey);
|
|
308
|
+
const selectedRecord = matchedRecord || buildRunnerReceiptBackedSyntheticRecord(receipt, receiptKey);
|
|
309
|
+
if (
|
|
310
|
+
collisionRecord
|
|
311
|
+
|| !selectedRecord
|
|
312
|
+
|| (
|
|
313
|
+
!matchedRecord
|
|
314
|
+
&& !String(safeObject(selectedRecord.parsedArchive).canonicalHumanMessageKey || "").trim()
|
|
315
|
+
&& !String(safeObject(selectedRecord.parsedArchive).canonical_human_message_key || "").trim()
|
|
316
|
+
)
|
|
317
|
+
|| (
|
|
318
|
+
receiptSource !== "current_poll"
|
|
319
|
+
&& !isArchiveRecordAfterState(selectedRecord, replayRouteState)
|
|
320
|
+
)
|
|
321
|
+
) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const matchedID = String(safeObject(selectedRecord).id || "").trim();
|
|
186
325
|
if (!matchedID || pendingIDs.has(matchedID)) {
|
|
187
326
|
continue;
|
|
188
327
|
}
|
|
189
328
|
replayCandidates.push(buildRunnerReceiptBackedReplayRecord(
|
|
190
|
-
|
|
329
|
+
selectedRecord,
|
|
191
330
|
receipt,
|
|
192
331
|
pendingSelectionOptions,
|
|
193
332
|
));
|
|
@@ -195,6 +334,156 @@ function buildRunnerReceiptBackedPendingArchiveComments({
|
|
|
195
334
|
return replayCandidates.sort(compareArchiveCommentRecords);
|
|
196
335
|
}
|
|
197
336
|
|
|
337
|
+
function buildRunnerRequestFollowupSyntheticRecord(requestRaw, currentBotSelector, pendingSelectionOptions = {}) {
|
|
338
|
+
const request = safeObject(requestRaw);
|
|
339
|
+
const replyEnvelope = safeObject(normalizeTelegramMessageEnvelope(request.last_reply_message_envelope));
|
|
340
|
+
const chatID = String(request.chat_id || replyEnvelope.chat_id || "").trim();
|
|
341
|
+
const messageID = intFromRawAllowZero(replyEnvelope.message_id, 0);
|
|
342
|
+
const occurredAt = firstNonEmptyString([
|
|
343
|
+
replyEnvelope.occurred_at,
|
|
344
|
+
request.updated_at,
|
|
345
|
+
request.claimed_at,
|
|
346
|
+
request.created_at,
|
|
347
|
+
]);
|
|
348
|
+
if (!chatID || !(messageID > 0) || !occurredAt) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const normalizedCurrentBotSelector = normalizeMentionSelector(currentBotSelector);
|
|
352
|
+
const senderSelector = normalizeMentionSelector(
|
|
353
|
+
replyEnvelope.sender_username
|
|
354
|
+
|| replyEnvelope.bot_username
|
|
355
|
+
|| replyEnvelope.username
|
|
356
|
+
|| replyEnvelope.sender,
|
|
357
|
+
);
|
|
358
|
+
if (normalizedCurrentBotSelector && senderSelector && normalizedCurrentBotSelector === senderSelector) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const participants = Array.from(new Set(
|
|
362
|
+
ensureArray(request.conversation_participants)
|
|
363
|
+
.map((value) => normalizeMentionSelector(value))
|
|
364
|
+
.filter(Boolean),
|
|
365
|
+
));
|
|
366
|
+
const initialResponders = Array.from(new Set(
|
|
367
|
+
ensureArray(request.conversation_initial_responders)
|
|
368
|
+
.map((value) => normalizeMentionSelector(value))
|
|
369
|
+
.filter(Boolean),
|
|
370
|
+
));
|
|
371
|
+
const allowedResponders = Array.from(new Set(
|
|
372
|
+
ensureArray(request.conversation_allowed_responders)
|
|
373
|
+
.map((value) => normalizeMentionSelector(value))
|
|
374
|
+
.filter(Boolean),
|
|
375
|
+
));
|
|
376
|
+
const nextResponders = Array.from(new Set(
|
|
377
|
+
ensureArray(request.next_expected_responders)
|
|
378
|
+
.map((value) => normalizeMentionSelector(value))
|
|
379
|
+
.filter(Boolean),
|
|
380
|
+
));
|
|
381
|
+
const executionTargets = Array.from(new Set(
|
|
382
|
+
ensureArray(request.execution_contract_targets)
|
|
383
|
+
.map((value) => normalizeMentionSelector(value))
|
|
384
|
+
.filter(Boolean),
|
|
385
|
+
));
|
|
386
|
+
const replyOccurredAtMs = Date.parse(occurredAt);
|
|
387
|
+
const maxPendingAgeMs = intFromRawAllowZero(safeObject(pendingSelectionOptions).maxPendingAgeMs, 0);
|
|
388
|
+
const staleAfterAt = maxPendingAgeMs > 0 && Number.isFinite(replyOccurredAtMs)
|
|
389
|
+
? new Date(replyOccurredAtMs + maxPendingAgeMs).toISOString()
|
|
390
|
+
: "";
|
|
391
|
+
const syntheticID = `request-followup:${String(request.request_key || "").trim()}:${messageID}:${normalizedCurrentBotSelector || "route"}`;
|
|
392
|
+
return {
|
|
393
|
+
id: syntheticID,
|
|
394
|
+
body: String(replyEnvelope.body || "").trim(),
|
|
395
|
+
createdAt: occurredAt,
|
|
396
|
+
updatedAt: occurredAt,
|
|
397
|
+
sourceOccurredAt: occurredAt,
|
|
398
|
+
...(staleAfterAt ? { staleAfterAt } : {}),
|
|
399
|
+
replayTriggeredByRequestFollowup: true,
|
|
400
|
+
parsedArchive: {
|
|
401
|
+
kind: "bot_reply",
|
|
402
|
+
chatID,
|
|
403
|
+
messageID,
|
|
404
|
+
...(intFromRawAllowZero(replyEnvelope.message_thread_id, 0) > 0
|
|
405
|
+
? { messageThreadID: intFromRawAllowZero(replyEnvelope.message_thread_id, 0) }
|
|
406
|
+
: {}),
|
|
407
|
+
...(intFromRawAllowZero(replyEnvelope.reply_to_message_id, 0) > 0
|
|
408
|
+
? { replyToMessageID: intFromRawAllowZero(replyEnvelope.reply_to_message_id, 0) }
|
|
409
|
+
: {}),
|
|
410
|
+
sender: String(replyEnvelope.sender || (senderSelector ? `@${senderSelector}` : "@bot")).trim(),
|
|
411
|
+
senderID: String(replyEnvelope.sender_id || "").trim(),
|
|
412
|
+
senderIsBot: true,
|
|
413
|
+
username: senderSelector,
|
|
414
|
+
botUsername: senderSelector,
|
|
415
|
+
mentionUsernames: ensureArray(replyEnvelope.mention_usernames),
|
|
416
|
+
occurredAt,
|
|
417
|
+
body: String(replyEnvelope.body || "").trim(),
|
|
418
|
+
conversationID: String(request.conversation_id || "").trim(),
|
|
419
|
+
conversationMode: String(request.conversation_intent_mode || "").trim(),
|
|
420
|
+
conversationStage: "bot_reply",
|
|
421
|
+
conversationAllowBotToBot: request.conversation_allow_bot_to_bot === true,
|
|
422
|
+
conversationLeadBotUsername: normalizeMentionSelector(request.conversation_lead_bot),
|
|
423
|
+
conversationSummaryBotUsername: normalizeMentionSelector(request.conversation_summary_bot),
|
|
424
|
+
conversationTargetBotUsername: nextResponders.length === 1 ? nextResponders[0] : "",
|
|
425
|
+
conversationParticipants: participants,
|
|
426
|
+
conversationInitialResponders: initialResponders,
|
|
427
|
+
conversationAllowedResponders: allowedResponders,
|
|
428
|
+
conversationReplyExpectation: String(request.conversation_reply_expectation || "").trim().toLowerCase(),
|
|
429
|
+
executionContractType: String(
|
|
430
|
+
request.execution_contract_type || request.normalized_execution_contract_type || "",
|
|
431
|
+
).trim().toLowerCase(),
|
|
432
|
+
executionContractActionable: request.execution_contract_actionable === true,
|
|
433
|
+
executionContractAssignments: executionTargets.map((targetBot) => ({
|
|
434
|
+
targetBot,
|
|
435
|
+
})),
|
|
436
|
+
executionContractNextResponders: nextResponders,
|
|
437
|
+
sourceOrigin: "request_followup_replay",
|
|
438
|
+
sourceRouteKey: String(request.claimed_by_route || "").trim(),
|
|
439
|
+
sourceBotUsername: senderSelector,
|
|
440
|
+
canonicalHumanMessageKey: String(request.canonical_human_message_key || "").trim(),
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function buildRunnerRequestFollowupPendingArchiveComments({
|
|
446
|
+
orderedComments,
|
|
447
|
+
routeState,
|
|
448
|
+
existingPendingIDs,
|
|
449
|
+
followupRequests,
|
|
450
|
+
currentBotSelector,
|
|
451
|
+
pendingSelectionOptions,
|
|
452
|
+
}) {
|
|
453
|
+
const pendingIDs = new Set(ensureArray(existingPendingIDs).map((value) => String(value || "").trim()).filter(Boolean));
|
|
454
|
+
const replayCandidates = [];
|
|
455
|
+
for (const requestRaw of ensureArray(followupRequests)) {
|
|
456
|
+
const request = safeObject(requestRaw);
|
|
457
|
+
const syntheticRecord = buildRunnerRequestFollowupSyntheticRecord(
|
|
458
|
+
request,
|
|
459
|
+
currentBotSelector,
|
|
460
|
+
pendingSelectionOptions,
|
|
461
|
+
);
|
|
462
|
+
if (!syntheticRecord) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (pendingIDs.has(String(syntheticRecord.id || "").trim())) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const parsedSynthetic = safeObject(syntheticRecord.parsedArchive);
|
|
469
|
+
const archiveCollision = ensureArray(orderedComments).some((recordRaw) => {
|
|
470
|
+
const parsed = safeObject(safeObject(recordRaw).parsedArchive);
|
|
471
|
+
return String(parsed.kind || "").trim().toLowerCase() === "bot_reply"
|
|
472
|
+
&& String(parsed.chatID || parsed.chatId || "").trim() === String(parsedSynthetic.chatID || "").trim()
|
|
473
|
+
&& intFromRawAllowZero(parsed.messageID || parsed.messageId, 0) === intFromRawAllowZero(parsedSynthetic.messageID, 0);
|
|
474
|
+
});
|
|
475
|
+
if (archiveCollision) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (!isArchiveRecordAfterState(syntheticRecord, routeState)) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
pendingIDs.add(String(syntheticRecord.id || "").trim());
|
|
482
|
+
replayCandidates.push(syntheticRecord);
|
|
483
|
+
}
|
|
484
|
+
return replayCandidates.sort(compareArchiveCommentRecords);
|
|
485
|
+
}
|
|
486
|
+
|
|
198
487
|
function buildContextSpeakerType(parsedArchiveRaw) {
|
|
199
488
|
const parsed = safeObject(parsedArchiveRaw);
|
|
200
489
|
const kind = String(parsed.kind || "").trim();
|
|
@@ -944,6 +1233,8 @@ export function selectRunnerPendingWork({
|
|
|
944
1233
|
parseArchivedChatComment,
|
|
945
1234
|
deps,
|
|
946
1235
|
pendingSelectionOptions,
|
|
1236
|
+
followupRequests = [],
|
|
1237
|
+
currentBotSelector = "",
|
|
947
1238
|
}) {
|
|
948
1239
|
const normalizeArchiveCommentRecord = requireDependency(deps, "normalizeArchiveCommentRecord");
|
|
949
1240
|
const applyPendingAgeSelection = requireDependency(deps, "applyPendingAgeSelection");
|
|
@@ -1007,14 +1298,31 @@ export function selectRunnerPendingWork({
|
|
|
1007
1298
|
const receiptBackedPending = buildRunnerReceiptBackedPendingArchiveComments({
|
|
1008
1299
|
orderedComments,
|
|
1009
1300
|
currentPollLocalInboundReceipts: ensureArray(importOutcome?.currentPollLocalInboundReceipts),
|
|
1301
|
+
routeStateLocalInboundReceipts: safeObject(refreshedState).recent_local_inbound_receipts,
|
|
1302
|
+
routeState: refreshedState,
|
|
1010
1303
|
existingPendingIDs: ensureArray(safeObject(pending).pending).map((record) => String(safeObject(record).id || "").trim()),
|
|
1011
1304
|
pendingSelectionOptions,
|
|
1012
1305
|
});
|
|
1013
|
-
const
|
|
1306
|
+
const requestFollowupPending = buildRunnerRequestFollowupPendingArchiveComments({
|
|
1307
|
+
orderedComments,
|
|
1308
|
+
routeState: refreshedState,
|
|
1309
|
+
existingPendingIDs: [
|
|
1310
|
+
...ensureArray(safeObject(pending).pending).map((record) => String(safeObject(record).id || "").trim()),
|
|
1311
|
+
...ensureArray(receiptBackedPending).map((record) => String(safeObject(record).id || "").trim()),
|
|
1312
|
+
],
|
|
1313
|
+
followupRequests,
|
|
1314
|
+
currentBotSelector,
|
|
1315
|
+
pendingSelectionOptions,
|
|
1316
|
+
});
|
|
1317
|
+
const replayFallbackPending = [
|
|
1318
|
+
...ensureArray(receiptBackedPending),
|
|
1319
|
+
...ensureArray(requestFollowupPending),
|
|
1320
|
+
].sort(compareArchiveCommentRecords);
|
|
1321
|
+
const finalPending = ensureArray(safeObject(pending).pending).length === 0 && replayFallbackPending.length > 0
|
|
1014
1322
|
? applyPendingAgeSelection({
|
|
1015
1323
|
...safeObject(pending),
|
|
1016
1324
|
shouldPrime: false,
|
|
1017
|
-
pending:
|
|
1325
|
+
pending: replayFallbackPending,
|
|
1018
1326
|
}, pendingSelectionOptions)
|
|
1019
1327
|
: pending;
|
|
1020
1328
|
return {
|
|
@@ -1022,6 +1330,7 @@ export function selectRunnerPendingWork({
|
|
|
1022
1330
|
inboundComments,
|
|
1023
1331
|
importedRecords,
|
|
1024
1332
|
receiptBackedPending,
|
|
1333
|
+
requestFollowupPending,
|
|
1025
1334
|
pending: finalPending,
|
|
1026
1335
|
};
|
|
1027
1336
|
}
|
|
@@ -22062,6 +22062,254 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
22062
22062
|
push("runner_entrypoint_receipt_replay_skips_foreign_bot_archive_collision", false, String(err?.message || err));
|
|
22063
22063
|
}
|
|
22064
22064
|
|
|
22065
|
+
try {
|
|
22066
|
+
const pendingWork = selectRunnerPendingWorkEntrypoint({
|
|
22067
|
+
comments: [
|
|
22068
|
+
{
|
|
22069
|
+
id: "comment-earlier-cursor-1",
|
|
22070
|
+
createdAt: "2026-04-06T00:00:00.000Z",
|
|
22071
|
+
updatedAt: "2026-04-06T00:00:00.000Z",
|
|
22072
|
+
body: "@RyoAI_bot earlier",
|
|
22073
|
+
parsedArchive: {
|
|
22074
|
+
kind: "telegram_message",
|
|
22075
|
+
messageID: 485,
|
|
22076
|
+
chatID: "-100999",
|
|
22077
|
+
chatType: "supergroup",
|
|
22078
|
+
sender: "tester",
|
|
22079
|
+
senderIsBot: false,
|
|
22080
|
+
body: "@RyoAI_bot earlier",
|
|
22081
|
+
},
|
|
22082
|
+
},
|
|
22083
|
+
],
|
|
22084
|
+
importOutcome: {
|
|
22085
|
+
importedCommentIDs: [],
|
|
22086
|
+
importedComments: [],
|
|
22087
|
+
currentPollLocalInboundReceipts: [],
|
|
22088
|
+
},
|
|
22089
|
+
refreshedState: {
|
|
22090
|
+
last_processed_comment_id: "comment-earlier-cursor-1",
|
|
22091
|
+
last_processed_created_at: "2026-04-06T00:00:00.000Z",
|
|
22092
|
+
recent_local_inbound_receipts: {
|
|
22093
|
+
"-100999:486": {
|
|
22094
|
+
chat_id: "-100999",
|
|
22095
|
+
message_id: 486,
|
|
22096
|
+
body: "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 하이",
|
|
22097
|
+
receipt_bot_username: "ryoai_bot",
|
|
22098
|
+
occurred_at: "2026-04-06T00:00:06.000Z",
|
|
22099
|
+
canonical_human_message_key: "shared-human-key-486",
|
|
22100
|
+
},
|
|
22101
|
+
},
|
|
22102
|
+
},
|
|
22103
|
+
mode: "continue",
|
|
22104
|
+
parseArchivedChatComment,
|
|
22105
|
+
deps: {
|
|
22106
|
+
normalizeArchiveCommentRecord: (record) => ({
|
|
22107
|
+
id: String(record?.id || "").trim(),
|
|
22108
|
+
body: String(record?.body || "").trim(),
|
|
22109
|
+
createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
|
|
22110
|
+
updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
|
|
22111
|
+
parsedArchive: safeObject(record?.parsedArchive),
|
|
22112
|
+
}),
|
|
22113
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
22114
|
+
},
|
|
22115
|
+
pendingSelectionOptions: {},
|
|
22116
|
+
});
|
|
22117
|
+
push(
|
|
22118
|
+
"runner_entrypoint_replays_route_state_local_receipt_after_shared_human_fanout",
|
|
22119
|
+
ensureArray(pendingWork.receiptBackedPending).length === 1
|
|
22120
|
+
&& String(safeObject(ensureArray(pendingWork.receiptBackedPending)[0]).id || "").startsWith("receipt-replay:human:shared-human-key-486")
|
|
22121
|
+
&& safeObject(safeObject(ensureArray(pendingWork.receiptBackedPending)[0]).parsedArchive).messageID === 486
|
|
22122
|
+
&& ensureArray(safeObject(pendingWork.pending).pending).length === 1
|
|
22123
|
+
&& String(safeObject(ensureArray(safeObject(pendingWork.pending).pending)[0]).id || "").startsWith("receipt-replay:human:shared-human-key-486"),
|
|
22124
|
+
`replay=${ensureArray(pendingWork.receiptBackedPending).map((item) => String(safeObject(item).id || "")).join(",") || "(none)"} pending=${ensureArray(safeObject(pendingWork.pending).pending).map((item) => String(safeObject(item).id || "")).join(",") || "(none)"}`,
|
|
22125
|
+
);
|
|
22126
|
+
} catch (err) {
|
|
22127
|
+
push("runner_entrypoint_replays_route_state_local_receipt_after_shared_human_fanout", false, String(err?.message || err));
|
|
22128
|
+
}
|
|
22129
|
+
|
|
22130
|
+
try {
|
|
22131
|
+
const pendingWork = selectRunnerPendingWorkEntrypoint({
|
|
22132
|
+
comments: [],
|
|
22133
|
+
importOutcome: {
|
|
22134
|
+
importedCommentIDs: [],
|
|
22135
|
+
importedComments: [],
|
|
22136
|
+
currentPollLocalInboundReceipts: [],
|
|
22137
|
+
},
|
|
22138
|
+
refreshedState: {
|
|
22139
|
+
last_processed_comment_id: "comment-shared-human-opening-processed",
|
|
22140
|
+
last_processed_created_at: "2026-04-06T00:00:07.000Z",
|
|
22141
|
+
recent_local_inbound_receipts: {
|
|
22142
|
+
"-100999:487": {
|
|
22143
|
+
chat_id: "-100999",
|
|
22144
|
+
message_id: 487,
|
|
22145
|
+
body: "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 하이",
|
|
22146
|
+
receipt_bot_username: "ryoai3_bot",
|
|
22147
|
+
occurred_at: "2026-04-06T00:00:06.000Z",
|
|
22148
|
+
canonical_human_message_key: "shared-human-key-487",
|
|
22149
|
+
},
|
|
22150
|
+
},
|
|
22151
|
+
},
|
|
22152
|
+
mode: "continue",
|
|
22153
|
+
parseArchivedChatComment,
|
|
22154
|
+
deps: {
|
|
22155
|
+
normalizeArchiveCommentRecord: (record) => ({
|
|
22156
|
+
id: String(record?.id || "").trim(),
|
|
22157
|
+
body: String(record?.body || "").trim(),
|
|
22158
|
+
createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
|
|
22159
|
+
updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
|
|
22160
|
+
parsedArchive: safeObject(record?.parsedArchive),
|
|
22161
|
+
}),
|
|
22162
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
22163
|
+
},
|
|
22164
|
+
pendingSelectionOptions: {},
|
|
22165
|
+
});
|
|
22166
|
+
push(
|
|
22167
|
+
"runner_entrypoint_does_not_replay_route_state_local_receipt_after_route_cursor",
|
|
22168
|
+
ensureArray(pendingWork.receiptBackedPending).length === 0
|
|
22169
|
+
&& ensureArray(safeObject(pendingWork.pending).pending).length === 0,
|
|
22170
|
+
`replay=${ensureArray(pendingWork.receiptBackedPending).length} pending=${ensureArray(safeObject(pendingWork.pending).pending).length}`,
|
|
22171
|
+
);
|
|
22172
|
+
} catch (err) {
|
|
22173
|
+
push("runner_entrypoint_does_not_replay_route_state_local_receipt_after_route_cursor", false, String(err?.message || err));
|
|
22174
|
+
}
|
|
22175
|
+
|
|
22176
|
+
try {
|
|
22177
|
+
const pendingWork = selectRunnerPendingWorkEntrypoint({
|
|
22178
|
+
comments: [],
|
|
22179
|
+
importOutcome: {
|
|
22180
|
+
importedCommentIDs: [],
|
|
22181
|
+
importedComments: [],
|
|
22182
|
+
currentPollLocalInboundReceipts: [],
|
|
22183
|
+
},
|
|
22184
|
+
refreshedState: {
|
|
22185
|
+
last_processed_comment_id: "comment-before-followup-1",
|
|
22186
|
+
last_processed_created_at: "2026-04-06T00:00:00.000Z",
|
|
22187
|
+
},
|
|
22188
|
+
mode: "continue",
|
|
22189
|
+
parseArchivedChatComment,
|
|
22190
|
+
deps: {
|
|
22191
|
+
normalizeArchiveCommentRecord: (record) => ({
|
|
22192
|
+
id: String(record?.id || "").trim(),
|
|
22193
|
+
body: String(record?.body || "").trim(),
|
|
22194
|
+
createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
|
|
22195
|
+
updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
|
|
22196
|
+
parsedArchive: safeObject(record?.parsedArchive),
|
|
22197
|
+
}),
|
|
22198
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
22199
|
+
},
|
|
22200
|
+
pendingSelectionOptions: {},
|
|
22201
|
+
followupRequests: [
|
|
22202
|
+
{
|
|
22203
|
+
request_key: "request-followup-1",
|
|
22204
|
+
status: "running",
|
|
22205
|
+
project_id: "project-followup-1",
|
|
22206
|
+
provider: "telegram",
|
|
22207
|
+
chat_id: "-100999",
|
|
22208
|
+
conversation_id: "conversation-followup-1",
|
|
22209
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
22210
|
+
conversation_participants: ["ryoai_bot", "ryoai2_bot"],
|
|
22211
|
+
conversation_initial_responders: ["ryoai_bot", "ryoai2_bot"],
|
|
22212
|
+
conversation_allowed_responders: ["ryoai_bot", "ryoai2_bot"],
|
|
22213
|
+
execution_contract_type: "direct_result",
|
|
22214
|
+
execution_contract_actionable: true,
|
|
22215
|
+
execution_contract_targets: ["ryoai2_bot"],
|
|
22216
|
+
next_expected_responders: ["ryoai_bot"],
|
|
22217
|
+
updated_at: "2026-04-06T00:10:00.000Z",
|
|
22218
|
+
last_reply_message_envelope: {
|
|
22219
|
+
chat_id: "-100999",
|
|
22220
|
+
message_id: 498,
|
|
22221
|
+
reply_to_message_id: 497,
|
|
22222
|
+
body: "하이!",
|
|
22223
|
+
sender: "@RyoAI2_bot",
|
|
22224
|
+
sender_username: "ryoai2_bot",
|
|
22225
|
+
sender_is_bot: true,
|
|
22226
|
+
occurred_at: "2026-04-06T00:10:00.000Z",
|
|
22227
|
+
canonical_human_message_key: "shared-human-key-497",
|
|
22228
|
+
},
|
|
22229
|
+
},
|
|
22230
|
+
],
|
|
22231
|
+
currentBotSelector: "ryoai_bot",
|
|
22232
|
+
});
|
|
22233
|
+
push(
|
|
22234
|
+
"runner_entrypoint_replays_request_followup_for_next_expected_responder",
|
|
22235
|
+
ensureArray(pendingWork.requestFollowupPending).length === 1
|
|
22236
|
+
&& ensureArray(safeObject(pendingWork.pending).pending).length === 1
|
|
22237
|
+
&& String(safeObject(ensureArray(pendingWork.requestFollowupPending)[0]).id || "").startsWith("request-followup:request-followup-1:498:ryoai_bot")
|
|
22238
|
+
&& safeObject(safeObject(ensureArray(pendingWork.requestFollowupPending)[0]).parsedArchive).kind === "bot_reply"
|
|
22239
|
+
&& ensureArray(safeObject(safeObject(ensureArray(pendingWork.requestFollowupPending)[0]).parsedArchive).executionContractNextResponders).includes("ryoai_bot"),
|
|
22240
|
+
`replay=${ensureArray(pendingWork.requestFollowupPending).map((item) => String(safeObject(item).id || "")).join(",") || "(none)"} pending=${ensureArray(safeObject(pendingWork.pending).pending).map((item) => String(safeObject(item).id || "")).join(",") || "(none)"}`,
|
|
22241
|
+
);
|
|
22242
|
+
} catch (err) {
|
|
22243
|
+
push("runner_entrypoint_replays_request_followup_for_next_expected_responder", false, String(err?.message || err));
|
|
22244
|
+
}
|
|
22245
|
+
|
|
22246
|
+
try {
|
|
22247
|
+
const pendingWork = selectRunnerPendingWorkEntrypoint({
|
|
22248
|
+
comments: [],
|
|
22249
|
+
importOutcome: {
|
|
22250
|
+
importedCommentIDs: [],
|
|
22251
|
+
importedComments: [],
|
|
22252
|
+
currentPollLocalInboundReceipts: [],
|
|
22253
|
+
},
|
|
22254
|
+
refreshedState: {
|
|
22255
|
+
last_processed_comment_id: "request-followup:request-followup-2:499:ryoai_bot",
|
|
22256
|
+
last_processed_created_at: "2026-04-06T00:11:00.000Z",
|
|
22257
|
+
},
|
|
22258
|
+
mode: "continue",
|
|
22259
|
+
parseArchivedChatComment,
|
|
22260
|
+
deps: {
|
|
22261
|
+
normalizeArchiveCommentRecord: (record) => ({
|
|
22262
|
+
id: String(record?.id || "").trim(),
|
|
22263
|
+
body: String(record?.body || "").trim(),
|
|
22264
|
+
createdAt: String(record?.created_at || record?.createdAt || record?.updated_at || record?.updatedAt || "").trim(),
|
|
22265
|
+
updatedAt: String(record?.updated_at || record?.updatedAt || "").trim(),
|
|
22266
|
+
parsedArchive: safeObject(record?.parsedArchive),
|
|
22267
|
+
}),
|
|
22268
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
22269
|
+
},
|
|
22270
|
+
pendingSelectionOptions: {},
|
|
22271
|
+
followupRequests: [
|
|
22272
|
+
{
|
|
22273
|
+
request_key: "request-followup-2",
|
|
22274
|
+
status: "running",
|
|
22275
|
+
project_id: "project-followup-1",
|
|
22276
|
+
provider: "telegram",
|
|
22277
|
+
chat_id: "-100999",
|
|
22278
|
+
conversation_id: "conversation-followup-2",
|
|
22279
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
22280
|
+
conversation_participants: ["ryoai_bot", "ryoai2_bot"],
|
|
22281
|
+
conversation_initial_responders: ["ryoai_bot", "ryoai2_bot"],
|
|
22282
|
+
conversation_allowed_responders: ["ryoai_bot", "ryoai2_bot"],
|
|
22283
|
+
execution_contract_type: "direct_result",
|
|
22284
|
+
execution_contract_actionable: true,
|
|
22285
|
+
execution_contract_targets: ["ryoai2_bot"],
|
|
22286
|
+
next_expected_responders: ["ryoai_bot"],
|
|
22287
|
+
updated_at: "2026-04-06T00:11:00.000Z",
|
|
22288
|
+
last_reply_message_envelope: {
|
|
22289
|
+
chat_id: "-100999",
|
|
22290
|
+
message_id: 499,
|
|
22291
|
+
reply_to_message_id: 497,
|
|
22292
|
+
body: "하이!",
|
|
22293
|
+
sender: "@RyoAI2_bot",
|
|
22294
|
+
sender_username: "ryoai2_bot",
|
|
22295
|
+
sender_is_bot: true,
|
|
22296
|
+
occurred_at: "2026-04-06T00:11:00.000Z",
|
|
22297
|
+
canonical_human_message_key: "shared-human-key-499",
|
|
22298
|
+
},
|
|
22299
|
+
},
|
|
22300
|
+
],
|
|
22301
|
+
currentBotSelector: "ryoai_bot",
|
|
22302
|
+
});
|
|
22303
|
+
push(
|
|
22304
|
+
"runner_entrypoint_does_not_replay_request_followup_after_route_cursor",
|
|
22305
|
+
ensureArray(pendingWork.requestFollowupPending).length === 0
|
|
22306
|
+
&& ensureArray(safeObject(pendingWork.pending).pending).length === 0,
|
|
22307
|
+
`replay=${ensureArray(pendingWork.requestFollowupPending).length} pending=${ensureArray(safeObject(pendingWork.pending).pending).length}`,
|
|
22308
|
+
);
|
|
22309
|
+
} catch (err) {
|
|
22310
|
+
push("runner_entrypoint_does_not_replay_request_followup_after_route_cursor", false, String(err?.message || err));
|
|
22311
|
+
}
|
|
22312
|
+
|
|
22065
22313
|
try {
|
|
22066
22314
|
const deliveryContext = await prepareLocalBotDeliveryContext({
|
|
22067
22315
|
siteBaseURL: "https://example.test",
|